@glob-ui/toast 0.0.0-stage → 0.2.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 +21 -0
- package/README.md +16 -2
- package/dist/index.d.mts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +8 -0
- package/dist/index.mjs.map +1 -0
- package/dist/store.d.mts +71 -0
- package/dist/store.d.ts +71 -0
- package/dist/store.js +115 -0
- package/dist/store.js.map +1 -0
- package/dist/store.mjs +88 -0
- package/dist/store.mjs.map +1 -0
- package/dist/toaster.js +276 -0
- package/dist/toaster.js.map +1 -0
- package/dist/toaster.mjs +252 -0
- package/dist/toaster.mjs.map +1 -0
- package/package.json +66 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kevin Martínez E
|
|
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
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @glob-ui/toast
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Short, timed messages. Call `toast()` from anywhere, and mount `<Toaster />` once.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @glob-ui/toast
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Documentation
|
|
12
|
+
|
|
13
|
+
Full docs, examples and the accessibility table: [Toast](https://github.com/kevin-martineze/GlobUI/tree/main/apps/docs/content/docs/components/toast.mdx).
|
|
14
|
+
|
|
15
|
+
## License
|
|
16
|
+
|
|
17
|
+
MIT. See [LICENSE](./LICENSE).
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { ToastAction, ToastContent, ToastData, ToastExtraOptions, ToastOptions, ToastSettings, ToastVariant, toast } from './store.mjs';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
|
|
4
|
+
type ToasterPosition = "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right";
|
|
5
|
+
interface ToasterProps {
|
|
6
|
+
position?: ToasterPosition;
|
|
7
|
+
/** Toasts on screen at once. The rest wait in a queue. */
|
|
8
|
+
max?: number;
|
|
9
|
+
/** Accessible name of each toast's close button. */
|
|
10
|
+
closeLabel?: string;
|
|
11
|
+
/** Accessible name of the notification list. */
|
|
12
|
+
label?: string;
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
declare function Toaster({ position, max, closeLabel, label, className, }: ToasterProps): react.ReactPortal | null;
|
|
16
|
+
declare namespace Toaster {
|
|
17
|
+
var displayName: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { Toaster, type ToasterPosition, type ToasterProps };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export { ToastAction, ToastContent, ToastData, ToastExtraOptions, ToastOptions, ToastSettings, ToastVariant, toast } from './store.js';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
|
|
4
|
+
type ToasterPosition = "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right";
|
|
5
|
+
interface ToasterProps {
|
|
6
|
+
position?: ToasterPosition;
|
|
7
|
+
/** Toasts on screen at once. The rest wait in a queue. */
|
|
8
|
+
max?: number;
|
|
9
|
+
/** Accessible name of each toast's close button. */
|
|
10
|
+
closeLabel?: string;
|
|
11
|
+
/** Accessible name of the notification list. */
|
|
12
|
+
label?: string;
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
declare function Toaster({ position, max, closeLabel, label, className, }: ToasterProps): react.ReactPortal | null;
|
|
16
|
+
declare namespace Toaster {
|
|
17
|
+
var displayName: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { Toaster, type ToasterPosition, type ToasterProps };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
Toaster: () => import_toaster.Toaster,
|
|
24
|
+
toast: () => import_store.toast
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(src_exports);
|
|
27
|
+
var import_store = require("./store.js");
|
|
28
|
+
var import_toaster = require("./toaster.js");
|
|
29
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
30
|
+
0 && (module.exports = {
|
|
31
|
+
Toaster,
|
|
32
|
+
toast
|
|
33
|
+
});
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// No \"use client\" here: `toast()` stays callable from server code (as a no-op).\n// The Toaster is a separate client module; see tsup.config.ts.\nexport { toast } from \"./store\"\nexport type { ToastOptions, ToastExtraOptions, ToastSettings, ToastContent, ToastAction, ToastVariant, ToastData } from \"./store\"\nexport { Toaster } from \"./toaster\"\nexport type { ToasterProps, ToasterPosition } from \"./toaster\"\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,mBAAsB;AAEtB,qBAAwB;","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// No \"use client\" here: `toast()` stays callable from server code (as a no-op).\n// The Toaster is a separate client module; see tsup.config.ts.\nexport { toast } from \"./store\"\nexport type { ToastOptions, ToastExtraOptions, ToastSettings, ToastContent, ToastAction, ToastVariant, ToastData } from \"./store\"\nexport { Toaster } from \"./toaster\"\nexport type { ToasterProps, ToasterPosition } from \"./toaster\"\n"],"mappings":";AAEA,SAAS,aAAa;AAEtB,SAAS,eAAe;","names":[]}
|
package/dist/store.d.mts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
type ToastVariant = "default" | "success" | "warning" | "danger" | "info";
|
|
2
|
+
interface ToastAction {
|
|
3
|
+
label: React.ReactNode;
|
|
4
|
+
/** Call `event.preventDefault()` to keep the toast open after the click. */
|
|
5
|
+
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
|
6
|
+
}
|
|
7
|
+
interface ToastSettings {
|
|
8
|
+
/** Reuse an id to update a toast in place instead of adding a new one. */
|
|
9
|
+
id?: string;
|
|
10
|
+
variant?: ToastVariant;
|
|
11
|
+
/** Time on screen in ms. `Infinity` keeps it until dismissed. Default 5000. */
|
|
12
|
+
duration?: number;
|
|
13
|
+
action?: ToastAction;
|
|
14
|
+
}
|
|
15
|
+
/** A toast needs a title, a description, or both. */
|
|
16
|
+
type ToastContent = {
|
|
17
|
+
title: React.ReactNode;
|
|
18
|
+
description?: React.ReactNode;
|
|
19
|
+
} | {
|
|
20
|
+
title?: React.ReactNode;
|
|
21
|
+
description: React.ReactNode;
|
|
22
|
+
};
|
|
23
|
+
/** Options for `toast(options)`. */
|
|
24
|
+
type ToastOptions = ToastSettings & ToastContent;
|
|
25
|
+
/** Options for `toast(title, options)`: everything except the title. */
|
|
26
|
+
type ToastExtraOptions = ToastSettings & {
|
|
27
|
+
description?: React.ReactNode;
|
|
28
|
+
};
|
|
29
|
+
interface ToastData extends ToastSettings {
|
|
30
|
+
id: string;
|
|
31
|
+
title?: React.ReactNode;
|
|
32
|
+
description?: React.ReactNode;
|
|
33
|
+
variant: ToastVariant;
|
|
34
|
+
duration: number;
|
|
35
|
+
/** Increments on every update, so timers restart. */
|
|
36
|
+
version: number;
|
|
37
|
+
/** Playing the exit animation; removed shortly after. */
|
|
38
|
+
leaving: boolean;
|
|
39
|
+
}
|
|
40
|
+
/** Matches the exit transition in the Toaster. */
|
|
41
|
+
declare const EXIT_MS = 200;
|
|
42
|
+
declare function subscribe(listener: () => void): () => void;
|
|
43
|
+
declare function getSnapshot(): ToastData[];
|
|
44
|
+
declare function getServerSnapshot(): ToastData[];
|
|
45
|
+
declare function dismiss(id?: string): void;
|
|
46
|
+
declare function show(title: string, opts?: ToastExtraOptions): string;
|
|
47
|
+
declare function show(opts: ToastOptions): string;
|
|
48
|
+
interface VariantShortcut {
|
|
49
|
+
(title: string, opts?: Omit<ToastExtraOptions, "variant">): string;
|
|
50
|
+
(opts: Omit<ToastSettings, "variant"> & ToastContent): string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Shows a toast and returns its id. Needs a mounted `<Toaster />` to be visible.
|
|
54
|
+
* Safe to call anywhere; on the server it does nothing.
|
|
55
|
+
*/
|
|
56
|
+
declare const toast: typeof show & {
|
|
57
|
+
success: VariantShortcut;
|
|
58
|
+
error: VariantShortcut;
|
|
59
|
+
warning: VariantShortcut;
|
|
60
|
+
info: VariantShortcut;
|
|
61
|
+
/** Dismisses one toast, or all of them without an id. */
|
|
62
|
+
dismiss: typeof dismiss;
|
|
63
|
+
};
|
|
64
|
+
/** @internal Used by the Toaster. */
|
|
65
|
+
declare const toastStore: {
|
|
66
|
+
subscribe: typeof subscribe;
|
|
67
|
+
getSnapshot: typeof getSnapshot;
|
|
68
|
+
getServerSnapshot: typeof getServerSnapshot;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export { EXIT_MS, type ToastAction, type ToastContent, type ToastData, type ToastExtraOptions, type ToastOptions, type ToastSettings, type ToastVariant, getServerSnapshot, getSnapshot, subscribe, toast, toastStore };
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
type ToastVariant = "default" | "success" | "warning" | "danger" | "info";
|
|
2
|
+
interface ToastAction {
|
|
3
|
+
label: React.ReactNode;
|
|
4
|
+
/** Call `event.preventDefault()` to keep the toast open after the click. */
|
|
5
|
+
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
|
6
|
+
}
|
|
7
|
+
interface ToastSettings {
|
|
8
|
+
/** Reuse an id to update a toast in place instead of adding a new one. */
|
|
9
|
+
id?: string;
|
|
10
|
+
variant?: ToastVariant;
|
|
11
|
+
/** Time on screen in ms. `Infinity` keeps it until dismissed. Default 5000. */
|
|
12
|
+
duration?: number;
|
|
13
|
+
action?: ToastAction;
|
|
14
|
+
}
|
|
15
|
+
/** A toast needs a title, a description, or both. */
|
|
16
|
+
type ToastContent = {
|
|
17
|
+
title: React.ReactNode;
|
|
18
|
+
description?: React.ReactNode;
|
|
19
|
+
} | {
|
|
20
|
+
title?: React.ReactNode;
|
|
21
|
+
description: React.ReactNode;
|
|
22
|
+
};
|
|
23
|
+
/** Options for `toast(options)`. */
|
|
24
|
+
type ToastOptions = ToastSettings & ToastContent;
|
|
25
|
+
/** Options for `toast(title, options)`: everything except the title. */
|
|
26
|
+
type ToastExtraOptions = ToastSettings & {
|
|
27
|
+
description?: React.ReactNode;
|
|
28
|
+
};
|
|
29
|
+
interface ToastData extends ToastSettings {
|
|
30
|
+
id: string;
|
|
31
|
+
title?: React.ReactNode;
|
|
32
|
+
description?: React.ReactNode;
|
|
33
|
+
variant: ToastVariant;
|
|
34
|
+
duration: number;
|
|
35
|
+
/** Increments on every update, so timers restart. */
|
|
36
|
+
version: number;
|
|
37
|
+
/** Playing the exit animation; removed shortly after. */
|
|
38
|
+
leaving: boolean;
|
|
39
|
+
}
|
|
40
|
+
/** Matches the exit transition in the Toaster. */
|
|
41
|
+
declare const EXIT_MS = 200;
|
|
42
|
+
declare function subscribe(listener: () => void): () => void;
|
|
43
|
+
declare function getSnapshot(): ToastData[];
|
|
44
|
+
declare function getServerSnapshot(): ToastData[];
|
|
45
|
+
declare function dismiss(id?: string): void;
|
|
46
|
+
declare function show(title: string, opts?: ToastExtraOptions): string;
|
|
47
|
+
declare function show(opts: ToastOptions): string;
|
|
48
|
+
interface VariantShortcut {
|
|
49
|
+
(title: string, opts?: Omit<ToastExtraOptions, "variant">): string;
|
|
50
|
+
(opts: Omit<ToastSettings, "variant"> & ToastContent): string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Shows a toast and returns its id. Needs a mounted `<Toaster />` to be visible.
|
|
54
|
+
* Safe to call anywhere; on the server it does nothing.
|
|
55
|
+
*/
|
|
56
|
+
declare const toast: typeof show & {
|
|
57
|
+
success: VariantShortcut;
|
|
58
|
+
error: VariantShortcut;
|
|
59
|
+
warning: VariantShortcut;
|
|
60
|
+
info: VariantShortcut;
|
|
61
|
+
/** Dismisses one toast, or all of them without an id. */
|
|
62
|
+
dismiss: typeof dismiss;
|
|
63
|
+
};
|
|
64
|
+
/** @internal Used by the Toaster. */
|
|
65
|
+
declare const toastStore: {
|
|
66
|
+
subscribe: typeof subscribe;
|
|
67
|
+
getSnapshot: typeof getSnapshot;
|
|
68
|
+
getServerSnapshot: typeof getServerSnapshot;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export { EXIT_MS, type ToastAction, type ToastContent, type ToastData, type ToastExtraOptions, type ToastOptions, type ToastSettings, type ToastVariant, getServerSnapshot, getSnapshot, subscribe, toast, toastStore };
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defProps = Object.defineProperties;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
10
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
11
|
+
var __spreadValues = (a, b) => {
|
|
12
|
+
for (var prop in b || (b = {}))
|
|
13
|
+
if (__hasOwnProp.call(b, prop))
|
|
14
|
+
__defNormalProp(a, prop, b[prop]);
|
|
15
|
+
if (__getOwnPropSymbols)
|
|
16
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
17
|
+
if (__propIsEnum.call(b, prop))
|
|
18
|
+
__defNormalProp(a, prop, b[prop]);
|
|
19
|
+
}
|
|
20
|
+
return a;
|
|
21
|
+
};
|
|
22
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
23
|
+
var __export = (target, all) => {
|
|
24
|
+
for (var name in all)
|
|
25
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
26
|
+
};
|
|
27
|
+
var __copyProps = (to, from, except, desc) => {
|
|
28
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
29
|
+
for (let key of __getOwnPropNames(from))
|
|
30
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
31
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
32
|
+
}
|
|
33
|
+
return to;
|
|
34
|
+
};
|
|
35
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
36
|
+
|
|
37
|
+
// src/store.ts
|
|
38
|
+
var store_exports = {};
|
|
39
|
+
__export(store_exports, {
|
|
40
|
+
EXIT_MS: () => EXIT_MS,
|
|
41
|
+
getServerSnapshot: () => getServerSnapshot,
|
|
42
|
+
getSnapshot: () => getSnapshot,
|
|
43
|
+
subscribe: () => subscribe,
|
|
44
|
+
toast: () => toast,
|
|
45
|
+
toastStore: () => toastStore
|
|
46
|
+
});
|
|
47
|
+
module.exports = __toCommonJS(store_exports);
|
|
48
|
+
var DEFAULT_DURATION = 5e3;
|
|
49
|
+
var EXIT_MS = 200;
|
|
50
|
+
var toasts = [];
|
|
51
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
52
|
+
var EMPTY = [];
|
|
53
|
+
var counter = 0;
|
|
54
|
+
function setToasts(next) {
|
|
55
|
+
toasts = next;
|
|
56
|
+
for (const listener of listeners) listener();
|
|
57
|
+
}
|
|
58
|
+
function subscribe(listener) {
|
|
59
|
+
listeners.add(listener);
|
|
60
|
+
return () => {
|
|
61
|
+
listeners.delete(listener);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function getSnapshot() {
|
|
65
|
+
return toasts;
|
|
66
|
+
}
|
|
67
|
+
function getServerSnapshot() {
|
|
68
|
+
return EMPTY;
|
|
69
|
+
}
|
|
70
|
+
function dismiss(id) {
|
|
71
|
+
if (typeof window === "undefined") return;
|
|
72
|
+
const targets = toasts.filter((t) => !t.leaving && (id === void 0 || t.id === id));
|
|
73
|
+
if (targets.length === 0) return;
|
|
74
|
+
const ids = new Set(targets.map((t) => t.id));
|
|
75
|
+
setToasts(toasts.map((t) => ids.has(t.id) ? __spreadProps(__spreadValues({}, t), { leaving: true }) : t));
|
|
76
|
+
setTimeout(() => {
|
|
77
|
+
setToasts(toasts.filter((t) => !(ids.has(t.id) && t.leaving)));
|
|
78
|
+
}, EXIT_MS);
|
|
79
|
+
}
|
|
80
|
+
function show(titleOrOpts, extra) {
|
|
81
|
+
var _a, _b, _c, _d, _e, _f;
|
|
82
|
+
const opts = typeof titleOrOpts === "string" ? __spreadProps(__spreadValues({}, extra), { title: titleOrOpts }) : titleOrOpts;
|
|
83
|
+
const id = (_a = opts.id) != null ? _a : `glob-toast-${++counter}`;
|
|
84
|
+
if (typeof window === "undefined") return id;
|
|
85
|
+
const existing = toasts.find((t) => t.id === id);
|
|
86
|
+
const data = __spreadProps(__spreadValues(__spreadValues({}, existing), opts), {
|
|
87
|
+
id,
|
|
88
|
+
variant: (_c = (_b = opts.variant) != null ? _b : existing == null ? void 0 : existing.variant) != null ? _c : "default",
|
|
89
|
+
duration: (_e = (_d = opts.duration) != null ? _d : existing == null ? void 0 : existing.duration) != null ? _e : DEFAULT_DURATION,
|
|
90
|
+
version: ((_f = existing == null ? void 0 : existing.version) != null ? _f : 0) + 1,
|
|
91
|
+
leaving: false
|
|
92
|
+
});
|
|
93
|
+
setToasts(existing ? toasts.map((t) => t.id === id ? data : t) : [...toasts, data]);
|
|
94
|
+
return id;
|
|
95
|
+
}
|
|
96
|
+
var variantShortcut = (variant) => ((titleOrOpts, extra) => typeof titleOrOpts === "string" ? show(titleOrOpts, __spreadProps(__spreadValues({}, extra), { variant })) : show(__spreadProps(__spreadValues({}, titleOrOpts), { variant })));
|
|
97
|
+
var toast = Object.assign(show, {
|
|
98
|
+
success: variantShortcut("success"),
|
|
99
|
+
error: variantShortcut("danger"),
|
|
100
|
+
warning: variantShortcut("warning"),
|
|
101
|
+
info: variantShortcut("info"),
|
|
102
|
+
/** Dismisses one toast, or all of them without an id. */
|
|
103
|
+
dismiss
|
|
104
|
+
});
|
|
105
|
+
var toastStore = { subscribe, getSnapshot, getServerSnapshot };
|
|
106
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
107
|
+
0 && (module.exports = {
|
|
108
|
+
EXIT_MS,
|
|
109
|
+
getServerSnapshot,
|
|
110
|
+
getSnapshot,
|
|
111
|
+
subscribe,
|
|
112
|
+
toast,
|
|
113
|
+
toastStore
|
|
114
|
+
});
|
|
115
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/store.ts"],"sourcesContent":["// Module-level toast store. No React state and no \"use client\": `toast()` can be imported anywhere.\n// On the server it is a no-op, because module state there is shared by every request.\n\nexport type ToastVariant = \"default\" | \"success\" | \"warning\" | \"danger\" | \"info\"\n\nexport interface ToastAction {\n label: React.ReactNode\n /** Call `event.preventDefault()` to keep the toast open after the click. */\n onClick: (event: React.MouseEvent<HTMLButtonElement>) => void\n}\n\nexport interface ToastSettings {\n /** Reuse an id to update a toast in place instead of adding a new one. */\n id?: string\n variant?: ToastVariant\n /** Time on screen in ms. `Infinity` keeps it until dismissed. Default 5000. */\n duration?: number\n action?: ToastAction\n}\n\n/** A toast needs a title, a description, or both. */\nexport type ToastContent =\n | { title: React.ReactNode; description?: React.ReactNode }\n | { title?: React.ReactNode; description: React.ReactNode }\n\n/** Options for `toast(options)`. */\nexport type ToastOptions = ToastSettings & ToastContent\n\n/** Options for `toast(title, options)`: everything except the title. */\nexport type ToastExtraOptions = ToastSettings & { description?: React.ReactNode }\n\nexport interface ToastData extends ToastSettings {\n id: string\n title?: React.ReactNode\n description?: React.ReactNode\n variant: ToastVariant\n duration: number\n /** Increments on every update, so timers restart. */\n version: number\n /** Playing the exit animation; removed shortly after. */\n leaving: boolean\n}\n\nconst DEFAULT_DURATION = 5000\n/** Matches the exit transition in the Toaster. */\nexport const EXIT_MS = 200\n\nlet toasts: ToastData[] = []\nconst listeners = new Set<() => void>()\nconst EMPTY: ToastData[] = []\nlet counter = 0\n\nfunction setToasts(next: ToastData[]) {\n toasts = next\n for (const listener of listeners) listener()\n}\n\nexport function subscribe(listener: () => void) {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n}\n\nexport function getSnapshot() {\n return toasts\n}\n\nexport function getServerSnapshot() {\n return EMPTY\n}\n\nfunction dismiss(id?: string) {\n if (typeof window === \"undefined\") return\n const targets = toasts.filter((t) => !t.leaving && (id === undefined || t.id === id))\n if (targets.length === 0) return\n const ids = new Set(targets.map((t) => t.id))\n setToasts(toasts.map((t) => (ids.has(t.id) ? { ...t, leaving: true } : t)))\n setTimeout(() => {\n // Skip toasts that were revived by an update with the same id meanwhile.\n setToasts(toasts.filter((t) => !(ids.has(t.id) && t.leaving)))\n }, EXIT_MS)\n}\n\nfunction show(title: string, opts?: ToastExtraOptions): string\nfunction show(opts: ToastOptions): string\nfunction show(titleOrOpts: string | ToastOptions, extra?: ToastExtraOptions): string {\n const opts: ToastSettings & { title?: React.ReactNode; description?: React.ReactNode } =\n typeof titleOrOpts === \"string\" ? { ...extra, title: titleOrOpts } : titleOrOpts\n const id = opts.id ?? `glob-toast-${++counter}`\n if (typeof window === \"undefined\") return id\n\n const existing = toasts.find((t) => t.id === id)\n const data: ToastData = {\n ...existing,\n ...opts,\n id,\n variant: opts.variant ?? existing?.variant ?? \"default\",\n duration: opts.duration ?? existing?.duration ?? DEFAULT_DURATION,\n version: (existing?.version ?? 0) + 1,\n leaving: false,\n }\n setToasts(existing ? toasts.map((t) => (t.id === id ? data : t)) : [...toasts, data])\n return id\n}\n\ninterface VariantShortcut {\n (title: string, opts?: Omit<ToastExtraOptions, \"variant\">): string\n (opts: Omit<ToastSettings, \"variant\"> & ToastContent): string\n}\n\nconst variantShortcut = (variant: ToastVariant): VariantShortcut =>\n ((titleOrOpts: string | ToastOptions, extra?: ToastExtraOptions) =>\n typeof titleOrOpts === \"string\"\n ? show(titleOrOpts, { ...extra, variant })\n : show({ ...titleOrOpts, variant })) as VariantShortcut\n\n/**\n * Shows a toast and returns its id. Needs a mounted `<Toaster />` to be visible.\n * Safe to call anywhere; on the server it does nothing.\n */\nexport const toast = Object.assign(show, {\n success: variantShortcut(\"success\"),\n error: variantShortcut(\"danger\"),\n warning: variantShortcut(\"warning\"),\n info: variantShortcut(\"info\"),\n /** Dismisses one toast, or all of them without an id. */\n dismiss,\n})\n\n/** @internal Used by the Toaster. */\nexport const toastStore = { subscribe, getSnapshot, getServerSnapshot }\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA2CA,IAAM,mBAAmB;AAElB,IAAM,UAAU;AAEvB,IAAI,SAAsB,CAAC;AAC3B,IAAM,YAAY,oBAAI,IAAgB;AACtC,IAAM,QAAqB,CAAC;AAC5B,IAAI,UAAU;AAEd,SAAS,UAAU,MAAmB;AACpC,WAAS;AACT,aAAW,YAAY,UAAW,UAAS;AAC7C;AAEO,SAAS,UAAU,UAAsB;AAC9C,YAAU,IAAI,QAAQ;AACtB,SAAO,MAAM;AACX,cAAU,OAAO,QAAQ;AAAA,EAC3B;AACF;AAEO,SAAS,cAAc;AAC5B,SAAO;AACT;AAEO,SAAS,oBAAoB;AAClC,SAAO;AACT;AAEA,SAAS,QAAQ,IAAa;AAC5B,MAAI,OAAO,WAAW,YAAa;AACnC,QAAM,UAAU,OAAO,OAAO,CAAC,MAAM,CAAC,EAAE,YAAY,OAAO,UAAa,EAAE,OAAO,GAAG;AACpF,MAAI,QAAQ,WAAW,EAAG;AAC1B,QAAM,MAAM,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAC5C,YAAU,OAAO,IAAI,CAAC,MAAO,IAAI,IAAI,EAAE,EAAE,IAAI,iCAAK,IAAL,EAAQ,SAAS,KAAK,KAAI,CAAE,CAAC;AAC1E,aAAW,MAAM;AAEf,cAAU,OAAO,OAAO,CAAC,MAAM,EAAE,IAAI,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC;AAAA,EAC/D,GAAG,OAAO;AACZ;AAIA,SAAS,KAAK,aAAoC,OAAmC;AAtFrF;AAuFE,QAAM,OACJ,OAAO,gBAAgB,WAAW,iCAAK,QAAL,EAAY,OAAO,YAAY,KAAI;AACvE,QAAM,MAAK,UAAK,OAAL,YAAW,cAAc,EAAE,OAAO;AAC7C,MAAI,OAAO,WAAW,YAAa,QAAO;AAE1C,QAAM,WAAW,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AAC/C,QAAM,OAAkB,gDACnB,WACA,OAFmB;AAAA,IAGtB;AAAA,IACA,UAAS,gBAAK,YAAL,YAAgB,qCAAU,YAA1B,YAAqC;AAAA,IAC9C,WAAU,gBAAK,aAAL,YAAiB,qCAAU,aAA3B,YAAuC;AAAA,IACjD,WAAU,0CAAU,YAAV,YAAqB,KAAK;AAAA,IACpC,SAAS;AAAA,EACX;AACA,YAAU,WAAW,OAAO,IAAI,CAAC,MAAO,EAAE,OAAO,KAAK,OAAO,CAAE,IAAI,CAAC,GAAG,QAAQ,IAAI,CAAC;AACpF,SAAO;AACT;AAOA,IAAM,kBAAkB,CAAC,aACtB,CAAC,aAAoC,UACpC,OAAO,gBAAgB,WACnB,KAAK,aAAa,iCAAK,QAAL,EAAY,QAAQ,EAAC,IACvC,KAAK,iCAAK,cAAL,EAAkB,QAAQ,EAAC;AAMjC,IAAM,QAAQ,OAAO,OAAO,MAAM;AAAA,EACvC,SAAS,gBAAgB,SAAS;AAAA,EAClC,OAAO,gBAAgB,QAAQ;AAAA,EAC/B,SAAS,gBAAgB,SAAS;AAAA,EAClC,MAAM,gBAAgB,MAAM;AAAA;AAAA,EAE5B;AACF,CAAC;AAGM,IAAM,aAAa,EAAE,WAAW,aAAa,kBAAkB;","names":[]}
|
package/dist/store.mjs
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __spreadValues = (a, b) => {
|
|
9
|
+
for (var prop in b || (b = {}))
|
|
10
|
+
if (__hasOwnProp.call(b, prop))
|
|
11
|
+
__defNormalProp(a, prop, b[prop]);
|
|
12
|
+
if (__getOwnPropSymbols)
|
|
13
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
+
if (__propIsEnum.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
}
|
|
17
|
+
return a;
|
|
18
|
+
};
|
|
19
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
|
|
21
|
+
// src/store.ts
|
|
22
|
+
var DEFAULT_DURATION = 5e3;
|
|
23
|
+
var EXIT_MS = 200;
|
|
24
|
+
var toasts = [];
|
|
25
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
26
|
+
var EMPTY = [];
|
|
27
|
+
var counter = 0;
|
|
28
|
+
function setToasts(next) {
|
|
29
|
+
toasts = next;
|
|
30
|
+
for (const listener of listeners) listener();
|
|
31
|
+
}
|
|
32
|
+
function subscribe(listener) {
|
|
33
|
+
listeners.add(listener);
|
|
34
|
+
return () => {
|
|
35
|
+
listeners.delete(listener);
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function getSnapshot() {
|
|
39
|
+
return toasts;
|
|
40
|
+
}
|
|
41
|
+
function getServerSnapshot() {
|
|
42
|
+
return EMPTY;
|
|
43
|
+
}
|
|
44
|
+
function dismiss(id) {
|
|
45
|
+
if (typeof window === "undefined") return;
|
|
46
|
+
const targets = toasts.filter((t) => !t.leaving && (id === void 0 || t.id === id));
|
|
47
|
+
if (targets.length === 0) return;
|
|
48
|
+
const ids = new Set(targets.map((t) => t.id));
|
|
49
|
+
setToasts(toasts.map((t) => ids.has(t.id) ? __spreadProps(__spreadValues({}, t), { leaving: true }) : t));
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
setToasts(toasts.filter((t) => !(ids.has(t.id) && t.leaving)));
|
|
52
|
+
}, EXIT_MS);
|
|
53
|
+
}
|
|
54
|
+
function show(titleOrOpts, extra) {
|
|
55
|
+
var _a, _b, _c, _d, _e, _f;
|
|
56
|
+
const opts = typeof titleOrOpts === "string" ? __spreadProps(__spreadValues({}, extra), { title: titleOrOpts }) : titleOrOpts;
|
|
57
|
+
const id = (_a = opts.id) != null ? _a : `glob-toast-${++counter}`;
|
|
58
|
+
if (typeof window === "undefined") return id;
|
|
59
|
+
const existing = toasts.find((t) => t.id === id);
|
|
60
|
+
const data = __spreadProps(__spreadValues(__spreadValues({}, existing), opts), {
|
|
61
|
+
id,
|
|
62
|
+
variant: (_c = (_b = opts.variant) != null ? _b : existing == null ? void 0 : existing.variant) != null ? _c : "default",
|
|
63
|
+
duration: (_e = (_d = opts.duration) != null ? _d : existing == null ? void 0 : existing.duration) != null ? _e : DEFAULT_DURATION,
|
|
64
|
+
version: ((_f = existing == null ? void 0 : existing.version) != null ? _f : 0) + 1,
|
|
65
|
+
leaving: false
|
|
66
|
+
});
|
|
67
|
+
setToasts(existing ? toasts.map((t) => t.id === id ? data : t) : [...toasts, data]);
|
|
68
|
+
return id;
|
|
69
|
+
}
|
|
70
|
+
var variantShortcut = (variant) => ((titleOrOpts, extra) => typeof titleOrOpts === "string" ? show(titleOrOpts, __spreadProps(__spreadValues({}, extra), { variant })) : show(__spreadProps(__spreadValues({}, titleOrOpts), { variant })));
|
|
71
|
+
var toast = Object.assign(show, {
|
|
72
|
+
success: variantShortcut("success"),
|
|
73
|
+
error: variantShortcut("danger"),
|
|
74
|
+
warning: variantShortcut("warning"),
|
|
75
|
+
info: variantShortcut("info"),
|
|
76
|
+
/** Dismisses one toast, or all of them without an id. */
|
|
77
|
+
dismiss
|
|
78
|
+
});
|
|
79
|
+
var toastStore = { subscribe, getSnapshot, getServerSnapshot };
|
|
80
|
+
export {
|
|
81
|
+
EXIT_MS,
|
|
82
|
+
getServerSnapshot,
|
|
83
|
+
getSnapshot,
|
|
84
|
+
subscribe,
|
|
85
|
+
toast,
|
|
86
|
+
toastStore
|
|
87
|
+
};
|
|
88
|
+
//# sourceMappingURL=store.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/store.ts"],"sourcesContent":["// Module-level toast store. No React state and no \"use client\": `toast()` can be imported anywhere.\n// On the server it is a no-op, because module state there is shared by every request.\n\nexport type ToastVariant = \"default\" | \"success\" | \"warning\" | \"danger\" | \"info\"\n\nexport interface ToastAction {\n label: React.ReactNode\n /** Call `event.preventDefault()` to keep the toast open after the click. */\n onClick: (event: React.MouseEvent<HTMLButtonElement>) => void\n}\n\nexport interface ToastSettings {\n /** Reuse an id to update a toast in place instead of adding a new one. */\n id?: string\n variant?: ToastVariant\n /** Time on screen in ms. `Infinity` keeps it until dismissed. Default 5000. */\n duration?: number\n action?: ToastAction\n}\n\n/** A toast needs a title, a description, or both. */\nexport type ToastContent =\n | { title: React.ReactNode; description?: React.ReactNode }\n | { title?: React.ReactNode; description: React.ReactNode }\n\n/** Options for `toast(options)`. */\nexport type ToastOptions = ToastSettings & ToastContent\n\n/** Options for `toast(title, options)`: everything except the title. */\nexport type ToastExtraOptions = ToastSettings & { description?: React.ReactNode }\n\nexport interface ToastData extends ToastSettings {\n id: string\n title?: React.ReactNode\n description?: React.ReactNode\n variant: ToastVariant\n duration: number\n /** Increments on every update, so timers restart. */\n version: number\n /** Playing the exit animation; removed shortly after. */\n leaving: boolean\n}\n\nconst DEFAULT_DURATION = 5000\n/** Matches the exit transition in the Toaster. */\nexport const EXIT_MS = 200\n\nlet toasts: ToastData[] = []\nconst listeners = new Set<() => void>()\nconst EMPTY: ToastData[] = []\nlet counter = 0\n\nfunction setToasts(next: ToastData[]) {\n toasts = next\n for (const listener of listeners) listener()\n}\n\nexport function subscribe(listener: () => void) {\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n }\n}\n\nexport function getSnapshot() {\n return toasts\n}\n\nexport function getServerSnapshot() {\n return EMPTY\n}\n\nfunction dismiss(id?: string) {\n if (typeof window === \"undefined\") return\n const targets = toasts.filter((t) => !t.leaving && (id === undefined || t.id === id))\n if (targets.length === 0) return\n const ids = new Set(targets.map((t) => t.id))\n setToasts(toasts.map((t) => (ids.has(t.id) ? { ...t, leaving: true } : t)))\n setTimeout(() => {\n // Skip toasts that were revived by an update with the same id meanwhile.\n setToasts(toasts.filter((t) => !(ids.has(t.id) && t.leaving)))\n }, EXIT_MS)\n}\n\nfunction show(title: string, opts?: ToastExtraOptions): string\nfunction show(opts: ToastOptions): string\nfunction show(titleOrOpts: string | ToastOptions, extra?: ToastExtraOptions): string {\n const opts: ToastSettings & { title?: React.ReactNode; description?: React.ReactNode } =\n typeof titleOrOpts === \"string\" ? { ...extra, title: titleOrOpts } : titleOrOpts\n const id = opts.id ?? `glob-toast-${++counter}`\n if (typeof window === \"undefined\") return id\n\n const existing = toasts.find((t) => t.id === id)\n const data: ToastData = {\n ...existing,\n ...opts,\n id,\n variant: opts.variant ?? existing?.variant ?? \"default\",\n duration: opts.duration ?? existing?.duration ?? DEFAULT_DURATION,\n version: (existing?.version ?? 0) + 1,\n leaving: false,\n }\n setToasts(existing ? toasts.map((t) => (t.id === id ? data : t)) : [...toasts, data])\n return id\n}\n\ninterface VariantShortcut {\n (title: string, opts?: Omit<ToastExtraOptions, \"variant\">): string\n (opts: Omit<ToastSettings, \"variant\"> & ToastContent): string\n}\n\nconst variantShortcut = (variant: ToastVariant): VariantShortcut =>\n ((titleOrOpts: string | ToastOptions, extra?: ToastExtraOptions) =>\n typeof titleOrOpts === \"string\"\n ? show(titleOrOpts, { ...extra, variant })\n : show({ ...titleOrOpts, variant })) as VariantShortcut\n\n/**\n * Shows a toast and returns its id. Needs a mounted `<Toaster />` to be visible.\n * Safe to call anywhere; on the server it does nothing.\n */\nexport const toast = Object.assign(show, {\n success: variantShortcut(\"success\"),\n error: variantShortcut(\"danger\"),\n warning: variantShortcut(\"warning\"),\n info: variantShortcut(\"info\"),\n /** Dismisses one toast, or all of them without an id. */\n dismiss,\n})\n\n/** @internal Used by the Toaster. */\nexport const toastStore = { subscribe, getSnapshot, getServerSnapshot }\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA2CA,IAAM,mBAAmB;AAElB,IAAM,UAAU;AAEvB,IAAI,SAAsB,CAAC;AAC3B,IAAM,YAAY,oBAAI,IAAgB;AACtC,IAAM,QAAqB,CAAC;AAC5B,IAAI,UAAU;AAEd,SAAS,UAAU,MAAmB;AACpC,WAAS;AACT,aAAW,YAAY,UAAW,UAAS;AAC7C;AAEO,SAAS,UAAU,UAAsB;AAC9C,YAAU,IAAI,QAAQ;AACtB,SAAO,MAAM;AACX,cAAU,OAAO,QAAQ;AAAA,EAC3B;AACF;AAEO,SAAS,cAAc;AAC5B,SAAO;AACT;AAEO,SAAS,oBAAoB;AAClC,SAAO;AACT;AAEA,SAAS,QAAQ,IAAa;AAC5B,MAAI,OAAO,WAAW,YAAa;AACnC,QAAM,UAAU,OAAO,OAAO,CAAC,MAAM,CAAC,EAAE,YAAY,OAAO,UAAa,EAAE,OAAO,GAAG;AACpF,MAAI,QAAQ,WAAW,EAAG;AAC1B,QAAM,MAAM,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAC5C,YAAU,OAAO,IAAI,CAAC,MAAO,IAAI,IAAI,EAAE,EAAE,IAAI,iCAAK,IAAL,EAAQ,SAAS,KAAK,KAAI,CAAE,CAAC;AAC1E,aAAW,MAAM;AAEf,cAAU,OAAO,OAAO,CAAC,MAAM,EAAE,IAAI,IAAI,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC;AAAA,EAC/D,GAAG,OAAO;AACZ;AAIA,SAAS,KAAK,aAAoC,OAAmC;AAtFrF;AAuFE,QAAM,OACJ,OAAO,gBAAgB,WAAW,iCAAK,QAAL,EAAY,OAAO,YAAY,KAAI;AACvE,QAAM,MAAK,UAAK,OAAL,YAAW,cAAc,EAAE,OAAO;AAC7C,MAAI,OAAO,WAAW,YAAa,QAAO;AAE1C,QAAM,WAAW,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AAC/C,QAAM,OAAkB,gDACnB,WACA,OAFmB;AAAA,IAGtB;AAAA,IACA,UAAS,gBAAK,YAAL,YAAgB,qCAAU,YAA1B,YAAqC;AAAA,IAC9C,WAAU,gBAAK,aAAL,YAAiB,qCAAU,aAA3B,YAAuC;AAAA,IACjD,WAAU,0CAAU,YAAV,YAAqB,KAAK;AAAA,IACpC,SAAS;AAAA,EACX;AACA,YAAU,WAAW,OAAO,IAAI,CAAC,MAAO,EAAE,OAAO,KAAK,OAAO,CAAE,IAAI,CAAC,GAAG,QAAQ,IAAI,CAAC;AACpF,SAAO;AACT;AAOA,IAAM,kBAAkB,CAAC,aACtB,CAAC,aAAoC,UACpC,OAAO,gBAAgB,WACnB,KAAK,aAAa,iCAAK,QAAL,EAAY,QAAQ,EAAC,IACvC,KAAK,iCAAK,cAAL,EAAkB,QAAQ,EAAC;AAMjC,IAAM,QAAQ,OAAO,OAAO,MAAM;AAAA,EACvC,SAAS,gBAAgB,SAAS;AAAA,EAClC,OAAO,gBAAgB,QAAQ;AAAA,EAC/B,SAAS,gBAAgB,SAAS;AAAA,EAClC,MAAM,gBAAgB,MAAM;AAAA;AAAA,EAE5B;AACF,CAAC;AAGM,IAAM,aAAa,EAAE,WAAW,aAAa,kBAAkB;","names":[]}
|
package/dist/toaster.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/toaster.tsx
|
|
22
|
+
var toaster_exports = {};
|
|
23
|
+
__export(toaster_exports, {
|
|
24
|
+
Toaster: () => Toaster
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(toaster_exports);
|
|
27
|
+
var import_react = require("react");
|
|
28
|
+
var import_react_dom = require("react-dom");
|
|
29
|
+
var import_utils = require("@glob-ui/utils");
|
|
30
|
+
var import_alert = require("@glob-ui/alert");
|
|
31
|
+
var import_store = require("./store.js");
|
|
32
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
33
|
+
var positionClasses = {
|
|
34
|
+
"top-left": "top-0 left-0",
|
|
35
|
+
"top-center": "top-0 left-1/2 -translate-x-1/2",
|
|
36
|
+
"top-right": "top-0 right-0",
|
|
37
|
+
"bottom-left": "bottom-0 left-0",
|
|
38
|
+
"bottom-center": "bottom-0 left-1/2 -translate-x-1/2",
|
|
39
|
+
"bottom-right": "bottom-0 right-0"
|
|
40
|
+
};
|
|
41
|
+
var isUrgent = (t) => t.variant === "warning" || t.variant === "danger";
|
|
42
|
+
function isPopoverOpen(el) {
|
|
43
|
+
try {
|
|
44
|
+
return el.matches(":popover-open");
|
|
45
|
+
} catch (e) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function topModalDialog() {
|
|
50
|
+
var _a;
|
|
51
|
+
const modals = document.querySelectorAll("dialog:modal");
|
|
52
|
+
return (_a = modals[modals.length - 1]) != null ? _a : null;
|
|
53
|
+
}
|
|
54
|
+
var subscribeNothing = () => () => {
|
|
55
|
+
};
|
|
56
|
+
var subscribeVisibility = (cb) => {
|
|
57
|
+
document.addEventListener("visibilitychange", cb);
|
|
58
|
+
return () => document.removeEventListener("visibilitychange", cb);
|
|
59
|
+
};
|
|
60
|
+
function Toaster({
|
|
61
|
+
position = "bottom-right",
|
|
62
|
+
max = 3,
|
|
63
|
+
closeLabel = "Close notification",
|
|
64
|
+
label = "Notifications",
|
|
65
|
+
className
|
|
66
|
+
}) {
|
|
67
|
+
const toasts = (0, import_react.useSyncExternalStore)(import_store.toastStore.subscribe, import_store.toastStore.getSnapshot, import_store.toastStore.getServerSnapshot);
|
|
68
|
+
const hidden = (0, import_react.useSyncExternalStore)(subscribeVisibility, () => document.hidden, () => false);
|
|
69
|
+
const hydrated = (0, import_react.useSyncExternalStore)(subscribeNothing, () => true, () => false);
|
|
70
|
+
const [hovered, setHovered] = (0, import_react.useState)(false);
|
|
71
|
+
const [focusWithin, setFocusWithin] = (0, import_react.useState)(false);
|
|
72
|
+
const [host] = (0, import_react.useState)(() => {
|
|
73
|
+
if (typeof document === "undefined") return null;
|
|
74
|
+
const el = document.createElement("div");
|
|
75
|
+
el.dataset.globToaster = "";
|
|
76
|
+
el.style.display = "contents";
|
|
77
|
+
return el;
|
|
78
|
+
});
|
|
79
|
+
const containerRef = (0, import_react.useRef)(null);
|
|
80
|
+
const shownIds = (0, import_react.useRef)(/* @__PURE__ */ new Set());
|
|
81
|
+
const visible = (0, import_react.useMemo)(() => toasts.slice(0, max), [toasts, max]);
|
|
82
|
+
const active = visible.length > 0;
|
|
83
|
+
const paused = hovered || focusWithin || hidden;
|
|
84
|
+
(0, import_react.useLayoutEffect)(() => {
|
|
85
|
+
if (!host) return;
|
|
86
|
+
document.body.appendChild(host);
|
|
87
|
+
return () => host.remove();
|
|
88
|
+
}, [host]);
|
|
89
|
+
(0, import_react.useLayoutEffect)(() => {
|
|
90
|
+
const container = containerRef.current;
|
|
91
|
+
if (!host || !container) return;
|
|
92
|
+
const newIds = visible.filter((t) => !shownIds.current.has(t.id));
|
|
93
|
+
shownIds.current = new Set(visible.map((t) => t.id));
|
|
94
|
+
placeHost(host, container, visible.length > 0, newIds.length > 0);
|
|
95
|
+
}, [host, hydrated, visible]);
|
|
96
|
+
(0, import_react.useLayoutEffect)(() => {
|
|
97
|
+
const container = containerRef.current;
|
|
98
|
+
if (!host || !container || !active) return;
|
|
99
|
+
const observer = new MutationObserver((records) => {
|
|
100
|
+
if (host.isConnected && !records.some(involvesDialog)) return;
|
|
101
|
+
placeHost(host, container, true, false);
|
|
102
|
+
});
|
|
103
|
+
observer.observe(document.body, { subtree: true, childList: true, attributes: true, attributeFilter: ["open"] });
|
|
104
|
+
return () => observer.disconnect();
|
|
105
|
+
}, [host, hydrated, active]);
|
|
106
|
+
const regionText = (urgent) => visible.filter((t) => !t.leaving && isUrgent(t) === urgent).map((t) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
107
|
+
t.title,
|
|
108
|
+
" ",
|
|
109
|
+
t.description
|
|
110
|
+
] }, `${t.id}:${t.version}`));
|
|
111
|
+
const content = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
112
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "status", "aria-live": "polite", "aria-atomic": "false", className: "sr-only", children: regionText(false) }),
|
|
113
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "alert", "aria-live": "assertive", "aria-atomic": "false", className: "sr-only", children: regionText(true) }),
|
|
114
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
115
|
+
"section",
|
|
116
|
+
{
|
|
117
|
+
ref: containerRef,
|
|
118
|
+
popover: "manual",
|
|
119
|
+
"aria-label": label,
|
|
120
|
+
className: (0, import_utils.cn)(
|
|
121
|
+
// Reset the UA popover box. The container itself ignores the pointer; toasts do not.
|
|
122
|
+
"pointer-events-none fixed inset-auto m-0 w-full max-w-sm overflow-visible border-0 bg-transparent p-4",
|
|
123
|
+
"transition-[display,overlay] transition-discrete duration-200",
|
|
124
|
+
positionClasses[position],
|
|
125
|
+
className
|
|
126
|
+
),
|
|
127
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
128
|
+
"ol",
|
|
129
|
+
{
|
|
130
|
+
className: "pointer-events-auto flex flex-col gap-2",
|
|
131
|
+
onPointerEnter: () => setHovered(true),
|
|
132
|
+
onPointerLeave: () => setHovered(false),
|
|
133
|
+
onFocus: () => setFocusWithin(true),
|
|
134
|
+
onBlur: (e) => {
|
|
135
|
+
if (!e.currentTarget.contains(e.relatedTarget)) setFocusWithin(false);
|
|
136
|
+
},
|
|
137
|
+
children: visible.map((t) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
138
|
+
ToastItem,
|
|
139
|
+
{
|
|
140
|
+
toast: t,
|
|
141
|
+
paused,
|
|
142
|
+
closeLabel,
|
|
143
|
+
fromTop: position.startsWith("top"),
|
|
144
|
+
onFocusLost: () => setFocusWithin(false)
|
|
145
|
+
},
|
|
146
|
+
t.id
|
|
147
|
+
))
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
] });
|
|
153
|
+
return host && hydrated ? (0, import_react_dom.createPortal)(content, host) : null;
|
|
154
|
+
}
|
|
155
|
+
Toaster.displayName = "Toaster";
|
|
156
|
+
function placeHost(host, container, hasToasts, promote) {
|
|
157
|
+
var _a;
|
|
158
|
+
const target = (_a = topModalDialog()) != null ? _a : document.body;
|
|
159
|
+
let moved = false;
|
|
160
|
+
if (host.parentElement !== target) {
|
|
161
|
+
target.appendChild(host);
|
|
162
|
+
moved = true;
|
|
163
|
+
}
|
|
164
|
+
if (!hasToasts) {
|
|
165
|
+
if (isPopoverOpen(container)) container.hidePopover();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (!isPopoverOpen(container)) {
|
|
169
|
+
container.showPopover();
|
|
170
|
+
} else if (promote && !moved && hasOtherTopLayer(container)) {
|
|
171
|
+
container.hidePopover();
|
|
172
|
+
container.showPopover();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
var containsDialog = (node) => node.nodeName === "DIALOG" || node instanceof Element && node.querySelector("dialog") !== null;
|
|
176
|
+
function involvesDialog(record) {
|
|
177
|
+
if (record.type === "attributes") return record.target.nodeName === "DIALOG";
|
|
178
|
+
return Array.from(record.addedNodes).some(containsDialog) || Array.from(record.removedNodes).some(containsDialog);
|
|
179
|
+
}
|
|
180
|
+
function hasOtherTopLayer(container) {
|
|
181
|
+
if (document.querySelector("dialog:modal")) return true;
|
|
182
|
+
try {
|
|
183
|
+
return Array.from(document.querySelectorAll(":popover-open")).some((el) => el !== container);
|
|
184
|
+
} catch (e) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function ToastItem({ toast: t, paused, closeLabel, fromTop, onFocusLost }) {
|
|
189
|
+
const liRef = (0, import_react.useRef)(null);
|
|
190
|
+
const remaining = (0, import_react.useRef)(t.duration);
|
|
191
|
+
const onFocusLostRef = (0, import_react.useRef)(onFocusLost);
|
|
192
|
+
(0, import_react.useEffect)(() => {
|
|
193
|
+
onFocusLostRef.current = onFocusLost;
|
|
194
|
+
});
|
|
195
|
+
(0, import_react.useLayoutEffect)(() => {
|
|
196
|
+
const li = liRef.current;
|
|
197
|
+
return () => {
|
|
198
|
+
var _a, _b, _c;
|
|
199
|
+
if (!li || !li.contains(document.activeElement)) return;
|
|
200
|
+
const keyboard = li.querySelector(":focus-visible") !== null;
|
|
201
|
+
const next = Array.from((_b = (_a = li.parentElement) == null ? void 0 : _a.children) != null ? _b : []).find((el) => el !== li && !el.hasAttribute("data-leaving"));
|
|
202
|
+
const target = next == null ? void 0 : next.querySelector("button[aria-label]");
|
|
203
|
+
if (keyboard && target) {
|
|
204
|
+
target.focus();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
;
|
|
208
|
+
(_c = document.activeElement) == null ? void 0 : _c.blur();
|
|
209
|
+
onFocusLostRef.current();
|
|
210
|
+
};
|
|
211
|
+
}, []);
|
|
212
|
+
const version = (0, import_react.useRef)(t.version);
|
|
213
|
+
(0, import_react.useEffect)(() => {
|
|
214
|
+
if (version.current !== t.version) {
|
|
215
|
+
version.current = t.version;
|
|
216
|
+
remaining.current = t.duration;
|
|
217
|
+
}
|
|
218
|
+
if (paused || t.leaving || !Number.isFinite(t.duration)) return;
|
|
219
|
+
const start = Date.now();
|
|
220
|
+
const timer = setTimeout(() => import_store.toast.dismiss(t.id), remaining.current);
|
|
221
|
+
return () => {
|
|
222
|
+
clearTimeout(timer);
|
|
223
|
+
remaining.current -= Date.now() - start;
|
|
224
|
+
};
|
|
225
|
+
}, [paused, t.id, t.version, t.duration, t.leaving]);
|
|
226
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
227
|
+
"li",
|
|
228
|
+
{
|
|
229
|
+
ref: liRef,
|
|
230
|
+
"data-leaving": t.leaving || void 0,
|
|
231
|
+
className: (0, import_utils.cn)(
|
|
232
|
+
// Same colours as Alert; no role here, the live regions above announce it.
|
|
233
|
+
(0, import_alert.alertVariants)({ variant: t.variant }),
|
|
234
|
+
"items-start pr-10 shadow-glob-md",
|
|
235
|
+
"transition-[opacity,translate] transition-discrete duration-200 ease-out",
|
|
236
|
+
fromTop ? "starting:-translate-y-2" : "starting:translate-y-2",
|
|
237
|
+
"starting:opacity-0 data-leaving:opacity-0 motion-reduce:transition-none"
|
|
238
|
+
),
|
|
239
|
+
children: [
|
|
240
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_alert.AlertIcon, { variant: t.variant }),
|
|
241
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
|
|
242
|
+
t.title != null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "font-medium leading-5", children: t.title }),
|
|
243
|
+
t.description != null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "leading-5 text-glob-text-secondary", children: t.description }),
|
|
244
|
+
t.action && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
245
|
+
"button",
|
|
246
|
+
{
|
|
247
|
+
type: "button",
|
|
248
|
+
onClick: (e) => {
|
|
249
|
+
var _a;
|
|
250
|
+
(_a = t.action) == null ? void 0 : _a.onClick(e);
|
|
251
|
+
if (!e.defaultPrevented) import_store.toast.dismiss(t.id);
|
|
252
|
+
},
|
|
253
|
+
className: "mt-1 self-start rounded-glob-sm border border-glob-border bg-glob-surface px-2 py-1 text-xs font-medium text-glob-text hover:bg-glob-surface-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-glob-ring",
|
|
254
|
+
children: t.action.label
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
] }),
|
|
258
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
259
|
+
"button",
|
|
260
|
+
{
|
|
261
|
+
type: "button",
|
|
262
|
+
"aria-label": closeLabel,
|
|
263
|
+
onClick: () => import_store.toast.dismiss(t.id),
|
|
264
|
+
className: "absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-glob-sm text-glob-text-secondary hover:bg-glob-surface-hover hover:text-glob-text focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-glob-ring",
|
|
265
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", className: "h-3.5 w-3.5", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M18 6L6 18M6 6l12 12" }) })
|
|
266
|
+
}
|
|
267
|
+
)
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
273
|
+
0 && (module.exports = {
|
|
274
|
+
Toaster
|
|
275
|
+
});
|
|
276
|
+
//# sourceMappingURL=toaster.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/toaster.tsx"],"sourcesContent":["\"use client\"\n\nimport { useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from \"react\"\nimport { createPortal } from \"react-dom\"\nimport { cn } from \"@glob-ui/utils\"\nimport { AlertIcon, alertVariants } from \"@glob-ui/alert\"\nimport { toast, toastStore, type ToastData } from \"./store\"\n\nexport type ToasterPosition = \"top-left\" | \"top-center\" | \"top-right\" | \"bottom-left\" | \"bottom-center\" | \"bottom-right\"\n\nexport interface ToasterProps {\n position?: ToasterPosition\n /** Toasts on screen at once. The rest wait in a queue. */\n max?: number\n /** Accessible name of each toast's close button. */\n closeLabel?: string\n /** Accessible name of the notification list. */\n label?: string\n className?: string\n}\n\nconst positionClasses: Record<ToasterPosition, string> = {\n \"top-left\": \"top-0 left-0\",\n \"top-center\": \"top-0 left-1/2 -translate-x-1/2\",\n \"top-right\": \"top-0 right-0\",\n \"bottom-left\": \"bottom-0 left-0\",\n \"bottom-center\": \"bottom-0 left-1/2 -translate-x-1/2\",\n \"bottom-right\": \"bottom-0 right-0\",\n}\n\nconst isUrgent = (t: ToastData) => t.variant === \"warning\" || t.variant === \"danger\"\n\nfunction isPopoverOpen(el: HTMLElement) {\n try {\n return el.matches(\":popover-open\")\n } catch {\n return false\n }\n}\n\n/** Topmost open modal dialog. Everything outside it is inert, so toasts must live inside it. */\nfunction topModalDialog() {\n const modals = document.querySelectorAll<HTMLDialogElement>(\"dialog:modal\")\n return modals[modals.length - 1] ?? null\n}\n\nconst subscribeNothing = () => () => {}\n\nconst subscribeVisibility = (cb: () => void) => {\n document.addEventListener(\"visibilitychange\", cb)\n return () => document.removeEventListener(\"visibilitychange\", cb)\n}\n\nfunction Toaster({\n position = \"bottom-right\",\n max = 3,\n closeLabel = \"Close notification\",\n label = \"Notifications\",\n className,\n}: ToasterProps) {\n const toasts = useSyncExternalStore(toastStore.subscribe, toastStore.getSnapshot, toastStore.getServerSnapshot)\n const hidden = useSyncExternalStore(subscribeVisibility, () => document.hidden, () => false)\n // False during SSR and hydration, true after: the server HTML has no toaster markup to match.\n const hydrated = useSyncExternalStore(subscribeNothing, () => true, () => false)\n const [hovered, setHovered] = useState(false)\n const [focusWithin, setFocusWithin] = useState(false)\n // A host node we move between <body> and the topmost modal dialog. React only owns its children.\n // Null on the server. The portal is rendered only after hydration (see `hydrated`).\n const [host] = useState(() => {\n if (typeof document === \"undefined\") return null\n const el = document.createElement(\"div\")\n el.dataset.globToaster = \"\"\n el.style.display = \"contents\"\n return el\n })\n const containerRef = useRef<HTMLElement>(null)\n const shownIds = useRef(new Set<string>())\n\n const visible = useMemo(() => toasts.slice(0, max), [toasts, max])\n const active = visible.length > 0\n const paused = hovered || focusWithin || hidden\n\n useLayoutEffect(() => {\n if (!host) return\n document.body.appendChild(host)\n return () => host.remove()\n }, [host])\n\n // Keep the host where it is not inert, and the container above other top-layer elements.\n // Runs when the visible toasts change; promotes the container when new ones arrive.\n useLayoutEffect(() => {\n const container = containerRef.current\n if (!host || !container) return\n const newIds = visible.filter((t) => !shownIds.current.has(t.id))\n shownIds.current = new Set(visible.map((t) => t.id))\n placeHost(host, container, visible.length > 0, newIds.length > 0)\n }, [host, hydrated, visible])\n\n // Dialogs opening, closing or unmounting change where the host must be. Only watched while\n // toasts are on screen; with none, nothing observes the document.\n useLayoutEffect(() => {\n const container = containerRef.current\n if (!host || !container || !active) return\n const observer = new MutationObserver((records) => {\n if (host.isConnected && !records.some(involvesDialog)) return\n placeHost(host, container, true, false)\n })\n observer.observe(document.body, { subtree: true, childList: true, attributes: true, attributeFilter: [\"open\"] })\n return () => observer.disconnect()\n }, [host, hydrated, active])\n\n const regionText = (urgent: boolean) =>\n visible\n .filter((t) => !t.leaving && isUrgent(t) === urgent)\n .map((t) => (\n <div key={`${t.id}:${t.version}`}>\n {t.title} {t.description}\n </div>\n ))\n\n const content = (\n <>\n {/* Always mounted so screen readers track them before the first toast arrives. */}\n <div role=\"status\" aria-live=\"polite\" aria-atomic=\"false\" className=\"sr-only\">\n {regionText(false)}\n </div>\n <div role=\"alert\" aria-live=\"assertive\" aria-atomic=\"false\" className=\"sr-only\">\n {regionText(true)}\n </div>\n <section\n ref={containerRef}\n popover=\"manual\"\n aria-label={label}\n className={cn(\n // Reset the UA popover box. The container itself ignores the pointer; toasts do not.\n \"pointer-events-none fixed inset-auto m-0 w-full max-w-sm overflow-visible border-0 bg-transparent p-4\",\n \"transition-[display,overlay] transition-discrete duration-200\",\n positionClasses[position],\n className\n )}\n >\n <ol\n className=\"pointer-events-auto flex flex-col gap-2\"\n onPointerEnter={() => setHovered(true)}\n onPointerLeave={() => setHovered(false)}\n onFocus={() => setFocusWithin(true)}\n onBlur={(e) => {\n if (!e.currentTarget.contains(e.relatedTarget as Node | null)) setFocusWithin(false)\n }}\n >\n {visible.map((t) => (\n <ToastItem\n key={t.id}\n toast={t}\n paused={paused}\n closeLabel={closeLabel}\n fromTop={position.startsWith(\"top\")}\n onFocusLost={() => setFocusWithin(false)}\n />\n ))}\n </ol>\n </section>\n </>\n )\n\n return host && hydrated ? createPortal(content, host) : null\n}\nToaster.displayName = \"Toaster\"\n\n/** Moves the host next to the topmost modal dialog (or body) and shows or hides the container. */\nfunction placeHost(host: HTMLElement, container: HTMLElement, hasToasts: boolean, promote: boolean) {\n const target = topModalDialog() ?? document.body\n let moved = false\n if (host.parentElement !== target) {\n // Moving a shown popover out of the document hides it; it is shown again below.\n target.appendChild(host)\n moved = true\n }\n if (!hasToasts) {\n if (isPopoverOpen(container)) container.hidePopover()\n return\n }\n if (!isPopoverOpen(container)) {\n container.showPopover()\n } else if (promote && !moved && hasOtherTopLayer(container)) {\n // Showing again puts the container last in the top layer, so it paints above menus and dialogs.\n container.hidePopover()\n container.showPopover()\n }\n}\n\nconst containsDialog = (node: Node) =>\n node.nodeName === \"DIALOG\" || (node instanceof Element && node.querySelector(\"dialog\") !== null)\n\n/** True when a mutation record opened/closed, added or removed a dialog. */\nfunction involvesDialog(record: MutationRecord) {\n if (record.type === \"attributes\") return record.target.nodeName === \"DIALOG\"\n return Array.from(record.addedNodes).some(containsDialog) || Array.from(record.removedNodes).some(containsDialog)\n}\n\nfunction hasOtherTopLayer(container: HTMLElement) {\n if (document.querySelector(\"dialog:modal\")) return true\n try {\n return Array.from(document.querySelectorAll(\":popover-open\")).some((el) => el !== container)\n } catch {\n return false\n }\n}\n\ninterface ToastItemProps {\n toast: ToastData\n paused: boolean\n closeLabel: string\n fromTop: boolean\n /** The focused toast was removed and no other toast could take focus. */\n onFocusLost: () => void\n}\n\nfunction ToastItem({ toast: t, paused, closeLabel, fromTop, onFocusLost }: ToastItemProps) {\n const liRef = useRef<HTMLLIElement>(null)\n const remaining = useRef(t.duration)\n const onFocusLostRef = useRef(onFocusLost)\n useEffect(() => {\n onFocusLostRef.current = onFocusLost\n })\n\n // Removing a focused element fires no blur, so the list would stay \"focused\" (and paused) forever.\n // Runs before React detaches the node. Keyboard users get focus on the next toast; after a\n // mouse click focus is simply released, so the remaining toasts keep their timers.\n useLayoutEffect(() => {\n const li = liRef.current\n return () => {\n if (!li || !li.contains(document.activeElement)) return\n const keyboard = li.querySelector(\":focus-visible\") !== null\n const next = Array.from(li.parentElement?.children ?? []).find((el) => el !== li && !el.hasAttribute(\"data-leaving\"))\n const target = next?.querySelector<HTMLElement>(\"button[aria-label]\")\n if (keyboard && target) {\n target.focus()\n return\n }\n ;(document.activeElement as HTMLElement | null)?.blur()\n onFocusLostRef.current()\n }\n }, [])\n const version = useRef(t.version)\n\n // Auto-dismiss. Pausing keeps the time left; an update restarts it.\n useEffect(() => {\n if (version.current !== t.version) {\n version.current = t.version\n remaining.current = t.duration\n }\n if (paused || t.leaving || !Number.isFinite(t.duration)) return\n const start = Date.now()\n const timer = setTimeout(() => toast.dismiss(t.id), remaining.current)\n return () => {\n clearTimeout(timer)\n remaining.current -= Date.now() - start\n }\n }, [paused, t.id, t.version, t.duration, t.leaving])\n\n return (\n <li\n ref={liRef}\n data-leaving={t.leaving || undefined}\n className={cn(\n // Same colours as Alert; no role here, the live regions above announce it.\n alertVariants({ variant: t.variant }),\n \"items-start pr-10 shadow-glob-md\",\n \"transition-[opacity,translate] transition-discrete duration-200 ease-out\",\n fromTop ? \"starting:-translate-y-2\" : \"starting:translate-y-2\",\n \"starting:opacity-0 data-leaving:opacity-0 motion-reduce:transition-none\"\n )}\n >\n <AlertIcon variant={t.variant} />\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n {t.title != null && <div className=\"font-medium leading-5\">{t.title}</div>}\n {t.description != null && <div className=\"leading-5 text-glob-text-secondary\">{t.description}</div>}\n {t.action && (\n <button\n type=\"button\"\n onClick={(e) => {\n t.action?.onClick(e)\n if (!e.defaultPrevented) toast.dismiss(t.id)\n }}\n className=\"mt-1 self-start rounded-glob-sm border border-glob-border bg-glob-surface px-2 py-1 text-xs font-medium text-glob-text hover:bg-glob-surface-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-glob-ring\"\n >\n {t.action.label}\n </button>\n )}\n </div>\n <button\n type=\"button\"\n aria-label={closeLabel}\n onClick={() => toast.dismiss(t.id)}\n className=\"absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-glob-sm text-glob-text-secondary hover:bg-glob-surface-hover hover:text-glob-text focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-glob-ring\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" className=\"h-3.5 w-3.5\" aria-hidden=\"true\">\n <path d=\"M18 6L6 18M6 6l12 12\" />\n </svg>\n </button>\n </li>\n )\n}\n\nexport { Toaster }\n"],"mappings":"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","names":[]}
|
package/dist/toaster.mjs
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/toaster.tsx
|
|
4
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
5
|
+
import { createPortal } from "react-dom";
|
|
6
|
+
import { cn } from "@glob-ui/utils";
|
|
7
|
+
import { AlertIcon, alertVariants } from "@glob-ui/alert";
|
|
8
|
+
import { toast, toastStore } from "./store.mjs";
|
|
9
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
var positionClasses = {
|
|
11
|
+
"top-left": "top-0 left-0",
|
|
12
|
+
"top-center": "top-0 left-1/2 -translate-x-1/2",
|
|
13
|
+
"top-right": "top-0 right-0",
|
|
14
|
+
"bottom-left": "bottom-0 left-0",
|
|
15
|
+
"bottom-center": "bottom-0 left-1/2 -translate-x-1/2",
|
|
16
|
+
"bottom-right": "bottom-0 right-0"
|
|
17
|
+
};
|
|
18
|
+
var isUrgent = (t) => t.variant === "warning" || t.variant === "danger";
|
|
19
|
+
function isPopoverOpen(el) {
|
|
20
|
+
try {
|
|
21
|
+
return el.matches(":popover-open");
|
|
22
|
+
} catch (e) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function topModalDialog() {
|
|
27
|
+
var _a;
|
|
28
|
+
const modals = document.querySelectorAll("dialog:modal");
|
|
29
|
+
return (_a = modals[modals.length - 1]) != null ? _a : null;
|
|
30
|
+
}
|
|
31
|
+
var subscribeNothing = () => () => {
|
|
32
|
+
};
|
|
33
|
+
var subscribeVisibility = (cb) => {
|
|
34
|
+
document.addEventListener("visibilitychange", cb);
|
|
35
|
+
return () => document.removeEventListener("visibilitychange", cb);
|
|
36
|
+
};
|
|
37
|
+
function Toaster({
|
|
38
|
+
position = "bottom-right",
|
|
39
|
+
max = 3,
|
|
40
|
+
closeLabel = "Close notification",
|
|
41
|
+
label = "Notifications",
|
|
42
|
+
className
|
|
43
|
+
}) {
|
|
44
|
+
const toasts = useSyncExternalStore(toastStore.subscribe, toastStore.getSnapshot, toastStore.getServerSnapshot);
|
|
45
|
+
const hidden = useSyncExternalStore(subscribeVisibility, () => document.hidden, () => false);
|
|
46
|
+
const hydrated = useSyncExternalStore(subscribeNothing, () => true, () => false);
|
|
47
|
+
const [hovered, setHovered] = useState(false);
|
|
48
|
+
const [focusWithin, setFocusWithin] = useState(false);
|
|
49
|
+
const [host] = useState(() => {
|
|
50
|
+
if (typeof document === "undefined") return null;
|
|
51
|
+
const el = document.createElement("div");
|
|
52
|
+
el.dataset.globToaster = "";
|
|
53
|
+
el.style.display = "contents";
|
|
54
|
+
return el;
|
|
55
|
+
});
|
|
56
|
+
const containerRef = useRef(null);
|
|
57
|
+
const shownIds = useRef(/* @__PURE__ */ new Set());
|
|
58
|
+
const visible = useMemo(() => toasts.slice(0, max), [toasts, max]);
|
|
59
|
+
const active = visible.length > 0;
|
|
60
|
+
const paused = hovered || focusWithin || hidden;
|
|
61
|
+
useLayoutEffect(() => {
|
|
62
|
+
if (!host) return;
|
|
63
|
+
document.body.appendChild(host);
|
|
64
|
+
return () => host.remove();
|
|
65
|
+
}, [host]);
|
|
66
|
+
useLayoutEffect(() => {
|
|
67
|
+
const container = containerRef.current;
|
|
68
|
+
if (!host || !container) return;
|
|
69
|
+
const newIds = visible.filter((t) => !shownIds.current.has(t.id));
|
|
70
|
+
shownIds.current = new Set(visible.map((t) => t.id));
|
|
71
|
+
placeHost(host, container, visible.length > 0, newIds.length > 0);
|
|
72
|
+
}, [host, hydrated, visible]);
|
|
73
|
+
useLayoutEffect(() => {
|
|
74
|
+
const container = containerRef.current;
|
|
75
|
+
if (!host || !container || !active) return;
|
|
76
|
+
const observer = new MutationObserver((records) => {
|
|
77
|
+
if (host.isConnected && !records.some(involvesDialog)) return;
|
|
78
|
+
placeHost(host, container, true, false);
|
|
79
|
+
});
|
|
80
|
+
observer.observe(document.body, { subtree: true, childList: true, attributes: true, attributeFilter: ["open"] });
|
|
81
|
+
return () => observer.disconnect();
|
|
82
|
+
}, [host, hydrated, active]);
|
|
83
|
+
const regionText = (urgent) => visible.filter((t) => !t.leaving && isUrgent(t) === urgent).map((t) => /* @__PURE__ */ jsxs("div", { children: [
|
|
84
|
+
t.title,
|
|
85
|
+
" ",
|
|
86
|
+
t.description
|
|
87
|
+
] }, `${t.id}:${t.version}`));
|
|
88
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
89
|
+
/* @__PURE__ */ jsx("div", { role: "status", "aria-live": "polite", "aria-atomic": "false", className: "sr-only", children: regionText(false) }),
|
|
90
|
+
/* @__PURE__ */ jsx("div", { role: "alert", "aria-live": "assertive", "aria-atomic": "false", className: "sr-only", children: regionText(true) }),
|
|
91
|
+
/* @__PURE__ */ jsx(
|
|
92
|
+
"section",
|
|
93
|
+
{
|
|
94
|
+
ref: containerRef,
|
|
95
|
+
popover: "manual",
|
|
96
|
+
"aria-label": label,
|
|
97
|
+
className: cn(
|
|
98
|
+
// Reset the UA popover box. The container itself ignores the pointer; toasts do not.
|
|
99
|
+
"pointer-events-none fixed inset-auto m-0 w-full max-w-sm overflow-visible border-0 bg-transparent p-4",
|
|
100
|
+
"transition-[display,overlay] transition-discrete duration-200",
|
|
101
|
+
positionClasses[position],
|
|
102
|
+
className
|
|
103
|
+
),
|
|
104
|
+
children: /* @__PURE__ */ jsx(
|
|
105
|
+
"ol",
|
|
106
|
+
{
|
|
107
|
+
className: "pointer-events-auto flex flex-col gap-2",
|
|
108
|
+
onPointerEnter: () => setHovered(true),
|
|
109
|
+
onPointerLeave: () => setHovered(false),
|
|
110
|
+
onFocus: () => setFocusWithin(true),
|
|
111
|
+
onBlur: (e) => {
|
|
112
|
+
if (!e.currentTarget.contains(e.relatedTarget)) setFocusWithin(false);
|
|
113
|
+
},
|
|
114
|
+
children: visible.map((t) => /* @__PURE__ */ jsx(
|
|
115
|
+
ToastItem,
|
|
116
|
+
{
|
|
117
|
+
toast: t,
|
|
118
|
+
paused,
|
|
119
|
+
closeLabel,
|
|
120
|
+
fromTop: position.startsWith("top"),
|
|
121
|
+
onFocusLost: () => setFocusWithin(false)
|
|
122
|
+
},
|
|
123
|
+
t.id
|
|
124
|
+
))
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
] });
|
|
130
|
+
return host && hydrated ? createPortal(content, host) : null;
|
|
131
|
+
}
|
|
132
|
+
Toaster.displayName = "Toaster";
|
|
133
|
+
function placeHost(host, container, hasToasts, promote) {
|
|
134
|
+
var _a;
|
|
135
|
+
const target = (_a = topModalDialog()) != null ? _a : document.body;
|
|
136
|
+
let moved = false;
|
|
137
|
+
if (host.parentElement !== target) {
|
|
138
|
+
target.appendChild(host);
|
|
139
|
+
moved = true;
|
|
140
|
+
}
|
|
141
|
+
if (!hasToasts) {
|
|
142
|
+
if (isPopoverOpen(container)) container.hidePopover();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (!isPopoverOpen(container)) {
|
|
146
|
+
container.showPopover();
|
|
147
|
+
} else if (promote && !moved && hasOtherTopLayer(container)) {
|
|
148
|
+
container.hidePopover();
|
|
149
|
+
container.showPopover();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
var containsDialog = (node) => node.nodeName === "DIALOG" || node instanceof Element && node.querySelector("dialog") !== null;
|
|
153
|
+
function involvesDialog(record) {
|
|
154
|
+
if (record.type === "attributes") return record.target.nodeName === "DIALOG";
|
|
155
|
+
return Array.from(record.addedNodes).some(containsDialog) || Array.from(record.removedNodes).some(containsDialog);
|
|
156
|
+
}
|
|
157
|
+
function hasOtherTopLayer(container) {
|
|
158
|
+
if (document.querySelector("dialog:modal")) return true;
|
|
159
|
+
try {
|
|
160
|
+
return Array.from(document.querySelectorAll(":popover-open")).some((el) => el !== container);
|
|
161
|
+
} catch (e) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function ToastItem({ toast: t, paused, closeLabel, fromTop, onFocusLost }) {
|
|
166
|
+
const liRef = useRef(null);
|
|
167
|
+
const remaining = useRef(t.duration);
|
|
168
|
+
const onFocusLostRef = useRef(onFocusLost);
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
onFocusLostRef.current = onFocusLost;
|
|
171
|
+
});
|
|
172
|
+
useLayoutEffect(() => {
|
|
173
|
+
const li = liRef.current;
|
|
174
|
+
return () => {
|
|
175
|
+
var _a, _b, _c;
|
|
176
|
+
if (!li || !li.contains(document.activeElement)) return;
|
|
177
|
+
const keyboard = li.querySelector(":focus-visible") !== null;
|
|
178
|
+
const next = Array.from((_b = (_a = li.parentElement) == null ? void 0 : _a.children) != null ? _b : []).find((el) => el !== li && !el.hasAttribute("data-leaving"));
|
|
179
|
+
const target = next == null ? void 0 : next.querySelector("button[aria-label]");
|
|
180
|
+
if (keyboard && target) {
|
|
181
|
+
target.focus();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
;
|
|
185
|
+
(_c = document.activeElement) == null ? void 0 : _c.blur();
|
|
186
|
+
onFocusLostRef.current();
|
|
187
|
+
};
|
|
188
|
+
}, []);
|
|
189
|
+
const version = useRef(t.version);
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
if (version.current !== t.version) {
|
|
192
|
+
version.current = t.version;
|
|
193
|
+
remaining.current = t.duration;
|
|
194
|
+
}
|
|
195
|
+
if (paused || t.leaving || !Number.isFinite(t.duration)) return;
|
|
196
|
+
const start = Date.now();
|
|
197
|
+
const timer = setTimeout(() => toast.dismiss(t.id), remaining.current);
|
|
198
|
+
return () => {
|
|
199
|
+
clearTimeout(timer);
|
|
200
|
+
remaining.current -= Date.now() - start;
|
|
201
|
+
};
|
|
202
|
+
}, [paused, t.id, t.version, t.duration, t.leaving]);
|
|
203
|
+
return /* @__PURE__ */ jsxs(
|
|
204
|
+
"li",
|
|
205
|
+
{
|
|
206
|
+
ref: liRef,
|
|
207
|
+
"data-leaving": t.leaving || void 0,
|
|
208
|
+
className: cn(
|
|
209
|
+
// Same colours as Alert; no role here, the live regions above announce it.
|
|
210
|
+
alertVariants({ variant: t.variant }),
|
|
211
|
+
"items-start pr-10 shadow-glob-md",
|
|
212
|
+
"transition-[opacity,translate] transition-discrete duration-200 ease-out",
|
|
213
|
+
fromTop ? "starting:-translate-y-2" : "starting:translate-y-2",
|
|
214
|
+
"starting:opacity-0 data-leaving:opacity-0 motion-reduce:transition-none"
|
|
215
|
+
),
|
|
216
|
+
children: [
|
|
217
|
+
/* @__PURE__ */ jsx(AlertIcon, { variant: t.variant }),
|
|
218
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
|
|
219
|
+
t.title != null && /* @__PURE__ */ jsx("div", { className: "font-medium leading-5", children: t.title }),
|
|
220
|
+
t.description != null && /* @__PURE__ */ jsx("div", { className: "leading-5 text-glob-text-secondary", children: t.description }),
|
|
221
|
+
t.action && /* @__PURE__ */ jsx(
|
|
222
|
+
"button",
|
|
223
|
+
{
|
|
224
|
+
type: "button",
|
|
225
|
+
onClick: (e) => {
|
|
226
|
+
var _a;
|
|
227
|
+
(_a = t.action) == null ? void 0 : _a.onClick(e);
|
|
228
|
+
if (!e.defaultPrevented) toast.dismiss(t.id);
|
|
229
|
+
},
|
|
230
|
+
className: "mt-1 self-start rounded-glob-sm border border-glob-border bg-glob-surface px-2 py-1 text-xs font-medium text-glob-text hover:bg-glob-surface-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-glob-ring",
|
|
231
|
+
children: t.action.label
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
] }),
|
|
235
|
+
/* @__PURE__ */ jsx(
|
|
236
|
+
"button",
|
|
237
|
+
{
|
|
238
|
+
type: "button",
|
|
239
|
+
"aria-label": closeLabel,
|
|
240
|
+
onClick: () => toast.dismiss(t.id),
|
|
241
|
+
className: "absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-glob-sm text-glob-text-secondary hover:bg-glob-surface-hover hover:text-glob-text focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-glob-ring",
|
|
242
|
+
children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", className: "h-3.5 w-3.5", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M18 6L6 18M6 6l12 12" }) })
|
|
243
|
+
}
|
|
244
|
+
)
|
|
245
|
+
]
|
|
246
|
+
}
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
export {
|
|
250
|
+
Toaster
|
|
251
|
+
};
|
|
252
|
+
//# sourceMappingURL=toaster.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/toaster.tsx"],"sourcesContent":["\"use client\"\n\nimport { useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from \"react\"\nimport { createPortal } from \"react-dom\"\nimport { cn } from \"@glob-ui/utils\"\nimport { AlertIcon, alertVariants } from \"@glob-ui/alert\"\nimport { toast, toastStore, type ToastData } from \"./store\"\n\nexport type ToasterPosition = \"top-left\" | \"top-center\" | \"top-right\" | \"bottom-left\" | \"bottom-center\" | \"bottom-right\"\n\nexport interface ToasterProps {\n position?: ToasterPosition\n /** Toasts on screen at once. The rest wait in a queue. */\n max?: number\n /** Accessible name of each toast's close button. */\n closeLabel?: string\n /** Accessible name of the notification list. */\n label?: string\n className?: string\n}\n\nconst positionClasses: Record<ToasterPosition, string> = {\n \"top-left\": \"top-0 left-0\",\n \"top-center\": \"top-0 left-1/2 -translate-x-1/2\",\n \"top-right\": \"top-0 right-0\",\n \"bottom-left\": \"bottom-0 left-0\",\n \"bottom-center\": \"bottom-0 left-1/2 -translate-x-1/2\",\n \"bottom-right\": \"bottom-0 right-0\",\n}\n\nconst isUrgent = (t: ToastData) => t.variant === \"warning\" || t.variant === \"danger\"\n\nfunction isPopoverOpen(el: HTMLElement) {\n try {\n return el.matches(\":popover-open\")\n } catch {\n return false\n }\n}\n\n/** Topmost open modal dialog. Everything outside it is inert, so toasts must live inside it. */\nfunction topModalDialog() {\n const modals = document.querySelectorAll<HTMLDialogElement>(\"dialog:modal\")\n return modals[modals.length - 1] ?? null\n}\n\nconst subscribeNothing = () => () => {}\n\nconst subscribeVisibility = (cb: () => void) => {\n document.addEventListener(\"visibilitychange\", cb)\n return () => document.removeEventListener(\"visibilitychange\", cb)\n}\n\nfunction Toaster({\n position = \"bottom-right\",\n max = 3,\n closeLabel = \"Close notification\",\n label = \"Notifications\",\n className,\n}: ToasterProps) {\n const toasts = useSyncExternalStore(toastStore.subscribe, toastStore.getSnapshot, toastStore.getServerSnapshot)\n const hidden = useSyncExternalStore(subscribeVisibility, () => document.hidden, () => false)\n // False during SSR and hydration, true after: the server HTML has no toaster markup to match.\n const hydrated = useSyncExternalStore(subscribeNothing, () => true, () => false)\n const [hovered, setHovered] = useState(false)\n const [focusWithin, setFocusWithin] = useState(false)\n // A host node we move between <body> and the topmost modal dialog. React only owns its children.\n // Null on the server. The portal is rendered only after hydration (see `hydrated`).\n const [host] = useState(() => {\n if (typeof document === \"undefined\") return null\n const el = document.createElement(\"div\")\n el.dataset.globToaster = \"\"\n el.style.display = \"contents\"\n return el\n })\n const containerRef = useRef<HTMLElement>(null)\n const shownIds = useRef(new Set<string>())\n\n const visible = useMemo(() => toasts.slice(0, max), [toasts, max])\n const active = visible.length > 0\n const paused = hovered || focusWithin || hidden\n\n useLayoutEffect(() => {\n if (!host) return\n document.body.appendChild(host)\n return () => host.remove()\n }, [host])\n\n // Keep the host where it is not inert, and the container above other top-layer elements.\n // Runs when the visible toasts change; promotes the container when new ones arrive.\n useLayoutEffect(() => {\n const container = containerRef.current\n if (!host || !container) return\n const newIds = visible.filter((t) => !shownIds.current.has(t.id))\n shownIds.current = new Set(visible.map((t) => t.id))\n placeHost(host, container, visible.length > 0, newIds.length > 0)\n }, [host, hydrated, visible])\n\n // Dialogs opening, closing or unmounting change where the host must be. Only watched while\n // toasts are on screen; with none, nothing observes the document.\n useLayoutEffect(() => {\n const container = containerRef.current\n if (!host || !container || !active) return\n const observer = new MutationObserver((records) => {\n if (host.isConnected && !records.some(involvesDialog)) return\n placeHost(host, container, true, false)\n })\n observer.observe(document.body, { subtree: true, childList: true, attributes: true, attributeFilter: [\"open\"] })\n return () => observer.disconnect()\n }, [host, hydrated, active])\n\n const regionText = (urgent: boolean) =>\n visible\n .filter((t) => !t.leaving && isUrgent(t) === urgent)\n .map((t) => (\n <div key={`${t.id}:${t.version}`}>\n {t.title} {t.description}\n </div>\n ))\n\n const content = (\n <>\n {/* Always mounted so screen readers track them before the first toast arrives. */}\n <div role=\"status\" aria-live=\"polite\" aria-atomic=\"false\" className=\"sr-only\">\n {regionText(false)}\n </div>\n <div role=\"alert\" aria-live=\"assertive\" aria-atomic=\"false\" className=\"sr-only\">\n {regionText(true)}\n </div>\n <section\n ref={containerRef}\n popover=\"manual\"\n aria-label={label}\n className={cn(\n // Reset the UA popover box. The container itself ignores the pointer; toasts do not.\n \"pointer-events-none fixed inset-auto m-0 w-full max-w-sm overflow-visible border-0 bg-transparent p-4\",\n \"transition-[display,overlay] transition-discrete duration-200\",\n positionClasses[position],\n className\n )}\n >\n <ol\n className=\"pointer-events-auto flex flex-col gap-2\"\n onPointerEnter={() => setHovered(true)}\n onPointerLeave={() => setHovered(false)}\n onFocus={() => setFocusWithin(true)}\n onBlur={(e) => {\n if (!e.currentTarget.contains(e.relatedTarget as Node | null)) setFocusWithin(false)\n }}\n >\n {visible.map((t) => (\n <ToastItem\n key={t.id}\n toast={t}\n paused={paused}\n closeLabel={closeLabel}\n fromTop={position.startsWith(\"top\")}\n onFocusLost={() => setFocusWithin(false)}\n />\n ))}\n </ol>\n </section>\n </>\n )\n\n return host && hydrated ? createPortal(content, host) : null\n}\nToaster.displayName = \"Toaster\"\n\n/** Moves the host next to the topmost modal dialog (or body) and shows or hides the container. */\nfunction placeHost(host: HTMLElement, container: HTMLElement, hasToasts: boolean, promote: boolean) {\n const target = topModalDialog() ?? document.body\n let moved = false\n if (host.parentElement !== target) {\n // Moving a shown popover out of the document hides it; it is shown again below.\n target.appendChild(host)\n moved = true\n }\n if (!hasToasts) {\n if (isPopoverOpen(container)) container.hidePopover()\n return\n }\n if (!isPopoverOpen(container)) {\n container.showPopover()\n } else if (promote && !moved && hasOtherTopLayer(container)) {\n // Showing again puts the container last in the top layer, so it paints above menus and dialogs.\n container.hidePopover()\n container.showPopover()\n }\n}\n\nconst containsDialog = (node: Node) =>\n node.nodeName === \"DIALOG\" || (node instanceof Element && node.querySelector(\"dialog\") !== null)\n\n/** True when a mutation record opened/closed, added or removed a dialog. */\nfunction involvesDialog(record: MutationRecord) {\n if (record.type === \"attributes\") return record.target.nodeName === \"DIALOG\"\n return Array.from(record.addedNodes).some(containsDialog) || Array.from(record.removedNodes).some(containsDialog)\n}\n\nfunction hasOtherTopLayer(container: HTMLElement) {\n if (document.querySelector(\"dialog:modal\")) return true\n try {\n return Array.from(document.querySelectorAll(\":popover-open\")).some((el) => el !== container)\n } catch {\n return false\n }\n}\n\ninterface ToastItemProps {\n toast: ToastData\n paused: boolean\n closeLabel: string\n fromTop: boolean\n /** The focused toast was removed and no other toast could take focus. */\n onFocusLost: () => void\n}\n\nfunction ToastItem({ toast: t, paused, closeLabel, fromTop, onFocusLost }: ToastItemProps) {\n const liRef = useRef<HTMLLIElement>(null)\n const remaining = useRef(t.duration)\n const onFocusLostRef = useRef(onFocusLost)\n useEffect(() => {\n onFocusLostRef.current = onFocusLost\n })\n\n // Removing a focused element fires no blur, so the list would stay \"focused\" (and paused) forever.\n // Runs before React detaches the node. Keyboard users get focus on the next toast; after a\n // mouse click focus is simply released, so the remaining toasts keep their timers.\n useLayoutEffect(() => {\n const li = liRef.current\n return () => {\n if (!li || !li.contains(document.activeElement)) return\n const keyboard = li.querySelector(\":focus-visible\") !== null\n const next = Array.from(li.parentElement?.children ?? []).find((el) => el !== li && !el.hasAttribute(\"data-leaving\"))\n const target = next?.querySelector<HTMLElement>(\"button[aria-label]\")\n if (keyboard && target) {\n target.focus()\n return\n }\n ;(document.activeElement as HTMLElement | null)?.blur()\n onFocusLostRef.current()\n }\n }, [])\n const version = useRef(t.version)\n\n // Auto-dismiss. Pausing keeps the time left; an update restarts it.\n useEffect(() => {\n if (version.current !== t.version) {\n version.current = t.version\n remaining.current = t.duration\n }\n if (paused || t.leaving || !Number.isFinite(t.duration)) return\n const start = Date.now()\n const timer = setTimeout(() => toast.dismiss(t.id), remaining.current)\n return () => {\n clearTimeout(timer)\n remaining.current -= Date.now() - start\n }\n }, [paused, t.id, t.version, t.duration, t.leaving])\n\n return (\n <li\n ref={liRef}\n data-leaving={t.leaving || undefined}\n className={cn(\n // Same colours as Alert; no role here, the live regions above announce it.\n alertVariants({ variant: t.variant }),\n \"items-start pr-10 shadow-glob-md\",\n \"transition-[opacity,translate] transition-discrete duration-200 ease-out\",\n fromTop ? \"starting:-translate-y-2\" : \"starting:translate-y-2\",\n \"starting:opacity-0 data-leaving:opacity-0 motion-reduce:transition-none\"\n )}\n >\n <AlertIcon variant={t.variant} />\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n {t.title != null && <div className=\"font-medium leading-5\">{t.title}</div>}\n {t.description != null && <div className=\"leading-5 text-glob-text-secondary\">{t.description}</div>}\n {t.action && (\n <button\n type=\"button\"\n onClick={(e) => {\n t.action?.onClick(e)\n if (!e.defaultPrevented) toast.dismiss(t.id)\n }}\n className=\"mt-1 self-start rounded-glob-sm border border-glob-border bg-glob-surface px-2 py-1 text-xs font-medium text-glob-text hover:bg-glob-surface-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-glob-ring\"\n >\n {t.action.label}\n </button>\n )}\n </div>\n <button\n type=\"button\"\n aria-label={closeLabel}\n onClick={() => toast.dismiss(t.id)}\n className=\"absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-glob-sm text-glob-text-secondary hover:bg-glob-surface-hover hover:text-glob-text focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-glob-ring\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" className=\"h-3.5 w-3.5\" aria-hidden=\"true\">\n <path d=\"M18 6L6 18M6 6l12 12\" />\n </svg>\n </button>\n </li>\n )\n}\n\nexport { Toaster }\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,69 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glob-ui/toast",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"main": "./dist/index.js",
|
|
5
|
+
"module": "./dist/index.mjs",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": {
|
|
10
|
+
"types": "./dist/index.d.mts",
|
|
11
|
+
"default": "./dist/index.mjs"
|
|
12
|
+
},
|
|
13
|
+
"require": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist/",
|
|
22
|
+
"LICENSE",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"react": "^19",
|
|
27
|
+
"react-dom": "^19"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@glob-ui/alert": "0.2.0",
|
|
31
|
+
"@glob-ui/utils": "0.2.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"tsup": "^8.5.1"
|
|
35
|
+
},
|
|
36
|
+
"description": "Short, timed messages. Call `toast()` from anywhere, and mount `<Toaster />` once.",
|
|
37
|
+
"keywords": [
|
|
38
|
+
"react",
|
|
39
|
+
"glob-ui",
|
|
40
|
+
"components",
|
|
41
|
+
"accessible",
|
|
42
|
+
"tailwind",
|
|
43
|
+
"toast"
|
|
44
|
+
],
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"author": "Kevin Martínez E",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/kevin-martineze/GlobUI.git",
|
|
50
|
+
"directory": "packages/toast"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://github.com/kevin-martineze/GlobUI#readme",
|
|
53
|
+
"bugs": {
|
|
54
|
+
"url": "https://github.com/kevin-martineze/GlobUI/issues"
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public",
|
|
58
|
+
"provenance": true
|
|
59
|
+
},
|
|
60
|
+
"sideEffects": false,
|
|
61
|
+
"type": "commonjs",
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">=18.18"
|
|
64
|
+
},
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build": "tsup",
|
|
67
|
+
"dev": "tsup --watch --no-clean"
|
|
68
|
+
}
|
|
6
69
|
}
|