@choice-ui/react 1.6.7 → 1.6.9
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/code-block/dist/index.d.ts +11 -14
- package/dist/components/code-block/dist/index.js +120 -93
- package/dist/components/code-block/src/code-block.d.ts +1 -2
- package/dist/components/code-block/src/code-block.js +11 -21
- package/dist/components/code-block/src/components/code-block-code.js +25 -13
- package/dist/components/code-block/src/components/code-block-content.d.ts +1 -1
- package/dist/components/code-block/src/components/code-block-content.js +14 -11
- package/dist/components/code-block/src/components/code-block-footer.js +4 -3
- package/dist/components/code-block/src/components/code-block-header.js +5 -6
- package/dist/components/code-block/src/hooks/index.d.ts +0 -1
- package/dist/components/code-block/src/hooks/use-code-block.js +26 -5
- package/dist/components/code-block/src/hooks/use-scroll-detection.d.ts +1 -2
- package/dist/components/code-block/src/hooks/use-scroll-detection.js +18 -12
- package/dist/components/code-block/src/types.d.ts +3 -5
- package/dist/components/code-block/src/utils/extract-code.js +23 -0
- package/dist/components/command/src/components/command-list.js +15 -3
- package/dist/components/description/dist/index.d.ts +8 -0
- package/dist/components/description/dist/index.js +29 -0
- package/dist/components/description/src/description.d.ts +6 -0
- package/dist/components/description/src/description.js +18 -0
- package/dist/components/description/src/index.d.ts +2 -0
- package/dist/components/description/src/tv.d.ts +13 -0
- package/dist/components/description/src/tv.js +15 -0
- package/dist/components/description/tsup.config.d.ts +2 -0
- package/dist/components/emoji-picker/dist/index.d.ts +1 -0
- package/dist/components/emoji-picker/dist/index.js +4 -2
- package/dist/components/emoji-picker/src/emoji-picker.d.ts +1 -0
- package/dist/components/emoji-picker/src/emoji-picker.js +4 -2
- package/dist/components/error-message/dist/index.d.ts +8 -0
- package/dist/components/error-message/dist/index.js +30 -0
- package/dist/components/error-message/src/error-message.d.ts +6 -0
- package/dist/components/error-message/src/error-message.js +19 -0
- package/dist/components/error-message/src/index.d.ts +2 -0
- package/dist/components/error-message/src/tv.d.ts +13 -0
- package/dist/components/error-message/src/tv.js +15 -0
- package/dist/components/error-message/tsup.config.d.ts +2 -0
- package/dist/components/form/src/adapters/base-adapter.js +4 -2
- package/dist/components/form/src/tv.d.ts +0 -12
- package/dist/components/form/src/tv.js +1 -13
- package/dist/components/index.d.ts +3 -0
- package/dist/components/md-render/dist/index.d.ts +2 -1
- package/dist/components/md-render/dist/index.js +5 -9
- package/dist/components/md-render/src/components/markdown-components.js +1 -7
- package/dist/components/md-render/src/md-render.js +4 -2
- package/dist/components/md-render/src/types.d.ts +2 -1
- package/dist/components/notifications/dist/index.d.ts +1 -5
- package/dist/components/notifications/src/notifications.d.ts +0 -1
- package/dist/components/notifications/src/notifications.js +0 -1
- package/dist/components/numeric-input/dist/index.d.ts +23 -9
- package/dist/components/numeric-input/dist/index.js +26 -3
- package/dist/components/numeric-input/src/components/numeric-input-menu-trigger.js +4 -1
- package/dist/components/numeric-input/src/hooks/index.d.ts +1 -0
- package/dist/components/numeric-input/src/hooks/use-numeric-long-press.d.ts +13 -0
- package/dist/components/numeric-input/src/hooks/use-numeric-long-press.js +27 -0
- package/dist/components/numeric-input/src/index.d.ts +1 -0
- package/dist/components/numeric-input/src/tv.js +22 -2
- package/dist/components/picture-preview/dist/index.d.ts +5 -0
- package/dist/components/picture-preview/dist/index.js +287 -140
- package/dist/components/picture-preview/src/hooks/useWheelHandler.d.ts +6 -1
- package/dist/components/picture-preview/src/hooks/useWheelHandler.js +25 -7
- package/dist/components/picture-preview/src/picture-preview.d.ts +5 -0
- package/dist/components/picture-preview/src/picture-preview.js +214 -123
- package/dist/components/picture-preview/src/tv.d.ts +93 -3
- package/dist/components/picture-preview/src/tv.js +48 -10
- package/dist/components/separator/dist/index.d.ts +1 -8
- package/dist/components/separator/src/separator.d.ts +1 -8
- package/dist/components/separator/src/separator.js +33 -5
- package/dist/components/separator/src/tv.d.ts +39 -18
- package/dist/components/separator/src/tv.js +37 -7
- package/dist/components/text-field/dist/index.d.ts +2 -3
- package/dist/components/text-field/dist/index.js +4 -19
- package/dist/components/text-field/src/components/index.d.ts +0 -1
- package/dist/components/text-field/src/text-field.d.ts +3 -2
- package/dist/components/text-field/src/text-field.js +2 -2
- package/dist/components/text-field/src/tv.d.ts +3 -3
- package/dist/components/text-field/src/tv.js +1 -6
- package/dist/components/toast/dist/index.d.ts +248 -0
- package/dist/components/toast/src/components/index.d.ts +3 -0
- package/dist/components/toast/src/components/toast-progress-bar.d.ts +7 -0
- package/dist/components/toast/src/components/toast-progress-bar.js +53 -0
- package/dist/components/toast/src/components/toaster-item.d.ts +26 -0
- package/dist/components/toast/src/components/toaster-item.js +416 -0
- package/dist/components/toast/src/components/toaster-slots.d.ts +87 -0
- package/dist/components/toast/src/components/toaster-slots.js +38 -0
- package/dist/components/toast/src/index.d.ts +5 -0
- package/dist/components/toast/src/store.d.ts +101 -0
- package/dist/components/toast/src/store.js +205 -0
- package/dist/components/toast/src/toaster.d.ts +87 -0
- package/dist/components/toast/src/toaster.js +271 -0
- package/dist/components/toast/src/tv.d.ts +365 -0
- package/dist/components/toast/src/tv.js +412 -0
- package/dist/components/toast/src/types.d.ts +79 -0
- package/dist/components/toast/tsup.config.d.ts +2 -0
- package/dist/index.js +11 -2
- package/dist/styles/components.css +2 -0
- package/package.json +1 -1
- package/dist/components/code-block/src/hooks/use-line-count.d.ts +0 -2
- package/dist/components/code-block/src/hooks/use-line-count.js +0 -27
- package/dist/components/text-field/src/components/field-description.d.ts +0 -2
- package/dist/components/text-field/src/components/field-description.js +0 -16
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
const stores = /* @__PURE__ */ new Map();
|
|
2
|
+
const subscribers = /* @__PURE__ */ new Map();
|
|
3
|
+
const DEFAULT_TOASTER_ID = "default";
|
|
4
|
+
const MAX_TOASTS_PER_TOASTER = 100;
|
|
5
|
+
let toastCounter = 0;
|
|
6
|
+
const MAX_COUNTER = Number.MAX_SAFE_INTEGER - 1;
|
|
7
|
+
function generateId() {
|
|
8
|
+
if (toastCounter >= MAX_COUNTER) {
|
|
9
|
+
toastCounter = 0;
|
|
10
|
+
}
|
|
11
|
+
return `toast-${++toastCounter}-${Date.now()}`;
|
|
12
|
+
}
|
|
13
|
+
function validateToasterId(toasterId) {
|
|
14
|
+
if (typeof toasterId !== "string" || toasterId.length === 0) {
|
|
15
|
+
return DEFAULT_TOASTER_ID;
|
|
16
|
+
}
|
|
17
|
+
return toasterId.slice(0, 64);
|
|
18
|
+
}
|
|
19
|
+
function getStore(toasterId = DEFAULT_TOASTER_ID) {
|
|
20
|
+
if (!stores.has(toasterId)) {
|
|
21
|
+
stores.set(toasterId, {
|
|
22
|
+
toasts: [],
|
|
23
|
+
expanded: false,
|
|
24
|
+
position: "bottom-right"
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return stores.get(toasterId);
|
|
28
|
+
}
|
|
29
|
+
function updateStore(toasterId, updates) {
|
|
30
|
+
const current = getStore(toasterId);
|
|
31
|
+
const newState = { ...current, ...updates };
|
|
32
|
+
stores.set(toasterId, newState);
|
|
33
|
+
return newState;
|
|
34
|
+
}
|
|
35
|
+
function notify(toasterId = DEFAULT_TOASTER_ID) {
|
|
36
|
+
const subs = subscribers.get(toasterId);
|
|
37
|
+
if (subs) {
|
|
38
|
+
subs.forEach((callback) => callback(getStore(toasterId)));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function subscribe(callback, toasterId = DEFAULT_TOASTER_ID) {
|
|
42
|
+
const validatedId = validateToasterId(toasterId);
|
|
43
|
+
if (!subscribers.has(validatedId)) {
|
|
44
|
+
subscribers.set(validatedId, /* @__PURE__ */ new Set());
|
|
45
|
+
}
|
|
46
|
+
subscribers.get(validatedId).add(callback);
|
|
47
|
+
return () => {
|
|
48
|
+
const subs = subscribers.get(validatedId);
|
|
49
|
+
subs == null ? void 0 : subs.delete(callback);
|
|
50
|
+
if ((subs == null ? void 0 : subs.size) === 0) {
|
|
51
|
+
subscribers.delete(validatedId);
|
|
52
|
+
const store = stores.get(validatedId);
|
|
53
|
+
if ((store == null ? void 0 : store.toasts.length) === 0) {
|
|
54
|
+
stores.delete(validatedId);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function getSnapshot(toasterId = DEFAULT_TOASTER_ID) {
|
|
60
|
+
return getStore(toasterId);
|
|
61
|
+
}
|
|
62
|
+
function setToasterConfig(config, toasterId = DEFAULT_TOASTER_ID) {
|
|
63
|
+
updateStore(toasterId, config);
|
|
64
|
+
notify(toasterId);
|
|
65
|
+
}
|
|
66
|
+
function addToast(title, type, options = {}, toasterId = DEFAULT_TOASTER_ID) {
|
|
67
|
+
const validatedId = validateToasterId(toasterId);
|
|
68
|
+
const store = getStore(validatedId);
|
|
69
|
+
const id = options.id ?? generateId();
|
|
70
|
+
const existingIndex = store.toasts.findIndex((t) => t.id === id);
|
|
71
|
+
const toastData = {
|
|
72
|
+
id,
|
|
73
|
+
type,
|
|
74
|
+
variant: options.variant,
|
|
75
|
+
title,
|
|
76
|
+
description: options.description,
|
|
77
|
+
descriptionHtml: options.descriptionHtml,
|
|
78
|
+
duration: options.duration,
|
|
79
|
+
icon: options.icon,
|
|
80
|
+
action: options.action,
|
|
81
|
+
cancel: options.cancel,
|
|
82
|
+
onClose: options.onClose,
|
|
83
|
+
onAutoClose: options.onAutoClose,
|
|
84
|
+
dismissible: options.dismissible ?? true,
|
|
85
|
+
createdAt: Date.now()
|
|
86
|
+
};
|
|
87
|
+
let newToasts;
|
|
88
|
+
if (existingIndex !== -1) {
|
|
89
|
+
newToasts = [...store.toasts];
|
|
90
|
+
newToasts[existingIndex] = toastData;
|
|
91
|
+
} else {
|
|
92
|
+
newToasts = [toastData, ...store.toasts];
|
|
93
|
+
if (newToasts.length > MAX_TOASTS_PER_TOASTER) {
|
|
94
|
+
const removed = newToasts.splice(MAX_TOASTS_PER_TOASTER);
|
|
95
|
+
removed.forEach((t) => {
|
|
96
|
+
var _a;
|
|
97
|
+
return (_a = t.onClose) == null ? void 0 : _a.call(t);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
updateStore(validatedId, { toasts: newToasts });
|
|
102
|
+
notify(validatedId);
|
|
103
|
+
return id;
|
|
104
|
+
}
|
|
105
|
+
function dismiss(id, toasterId = DEFAULT_TOASTER_ID) {
|
|
106
|
+
var _a;
|
|
107
|
+
const store = getStore(toasterId);
|
|
108
|
+
const toast2 = store.toasts.find((t) => t.id === id);
|
|
109
|
+
if (toast2) {
|
|
110
|
+
(_a = toast2.onClose) == null ? void 0 : _a.call(toast2);
|
|
111
|
+
const newToasts = store.toasts.filter((t) => t.id !== id);
|
|
112
|
+
updateStore(toasterId, { toasts: newToasts });
|
|
113
|
+
notify(toasterId);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function dismissAll(toasterId) {
|
|
117
|
+
if (toasterId) {
|
|
118
|
+
const store = getStore(toasterId);
|
|
119
|
+
store.toasts.forEach((t) => {
|
|
120
|
+
var _a;
|
|
121
|
+
return (_a = t.onClose) == null ? void 0 : _a.call(t);
|
|
122
|
+
});
|
|
123
|
+
updateStore(toasterId, { toasts: [] });
|
|
124
|
+
notify(toasterId);
|
|
125
|
+
} else {
|
|
126
|
+
stores.forEach((store, id) => {
|
|
127
|
+
store.toasts.forEach((t) => {
|
|
128
|
+
var _a;
|
|
129
|
+
return (_a = t.onClose) == null ? void 0 : _a.call(t);
|
|
130
|
+
});
|
|
131
|
+
updateStore(id, { toasts: [] });
|
|
132
|
+
notify(id);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function updateHeight(id, height, toasterId = DEFAULT_TOASTER_ID) {
|
|
137
|
+
const store = getStore(toasterId);
|
|
138
|
+
const toastIndex = store.toasts.findIndex((t) => t.id === id);
|
|
139
|
+
if (toastIndex !== -1 && store.toasts[toastIndex].height !== height) {
|
|
140
|
+
const newToasts = [...store.toasts];
|
|
141
|
+
newToasts[toastIndex] = { ...newToasts[toastIndex], height };
|
|
142
|
+
updateStore(toasterId, { toasts: newToasts });
|
|
143
|
+
notify(toasterId);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function setExpanded(expanded, toasterId = DEFAULT_TOASTER_ID) {
|
|
147
|
+
updateStore(toasterId, { expanded });
|
|
148
|
+
notify(toasterId);
|
|
149
|
+
}
|
|
150
|
+
function promiseToast(promise, options, toasterId = DEFAULT_TOASTER_ID) {
|
|
151
|
+
const loadingOpts = typeof options.loading === "string" ? { title: options.loading } : options.loading;
|
|
152
|
+
const id = addToast(
|
|
153
|
+
loadingOpts.title,
|
|
154
|
+
"loading",
|
|
155
|
+
{ ...loadingOpts, duration: Infinity },
|
|
156
|
+
toasterId
|
|
157
|
+
);
|
|
158
|
+
promise.then((data) => {
|
|
159
|
+
const successOpts = typeof options.success === "function" ? options.success(data) : options.success;
|
|
160
|
+
const opts = typeof successOpts === "string" ? { title: successOpts } : successOpts;
|
|
161
|
+
addToast(opts.title, "success", { ...opts, id }, toasterId);
|
|
162
|
+
}).catch((err) => {
|
|
163
|
+
const errorOpts = typeof options.error === "function" ? options.error(err) : options.error;
|
|
164
|
+
const opts = typeof errorOpts === "string" ? { title: errorOpts } : errorOpts;
|
|
165
|
+
addToast(opts.title, "error", { ...opts, id }, toasterId);
|
|
166
|
+
});
|
|
167
|
+
return promise;
|
|
168
|
+
}
|
|
169
|
+
function createToastApi(toasterId = DEFAULT_TOASTER_ID) {
|
|
170
|
+
const validatedId = validateToasterId(toasterId);
|
|
171
|
+
const api = (title, options) => addToast(title, "default", options, validatedId);
|
|
172
|
+
api.success = (title, options) => addToast(title, "success", options, validatedId);
|
|
173
|
+
api.error = (title, options) => addToast(title, "error", options, validatedId);
|
|
174
|
+
api.warning = (title, options) => addToast(title, "warning", options, validatedId);
|
|
175
|
+
api.info = (title, options) => addToast(title, "info", options, validatedId);
|
|
176
|
+
api.loading = (title, options) => addToast(title, "loading", options, validatedId);
|
|
177
|
+
api.promise = (promise, options) => promiseToast(promise, options, validatedId);
|
|
178
|
+
api.dismiss = (id) => dismiss(id, validatedId);
|
|
179
|
+
api.dismissAll = () => dismissAll(validatedId);
|
|
180
|
+
return api;
|
|
181
|
+
}
|
|
182
|
+
const toastApiCache = /* @__PURE__ */ new Map();
|
|
183
|
+
function getOrCreateToastApi(toasterId) {
|
|
184
|
+
const validatedId = validateToasterId(toasterId);
|
|
185
|
+
let api = toastApiCache.get(validatedId);
|
|
186
|
+
if (!api) {
|
|
187
|
+
api = createToastApi(validatedId);
|
|
188
|
+
toastApiCache.set(validatedId, api);
|
|
189
|
+
}
|
|
190
|
+
return api;
|
|
191
|
+
}
|
|
192
|
+
const baseToast = createToastApi();
|
|
193
|
+
const toast = Object.assign(baseToast, {
|
|
194
|
+
use: (toasterId) => getOrCreateToastApi(toasterId)
|
|
195
|
+
});
|
|
196
|
+
export {
|
|
197
|
+
dismiss,
|
|
198
|
+
dismissAll,
|
|
199
|
+
getSnapshot,
|
|
200
|
+
setExpanded,
|
|
201
|
+
setToasterConfig,
|
|
202
|
+
subscribe,
|
|
203
|
+
toast,
|
|
204
|
+
updateHeight
|
|
205
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { ToasterActionsSlot, ToasterDescriptionSlot, ToasterIconSlot, ToasterItemSlot, ToasterTitleSlot } from './components';
|
|
3
|
+
import { ToastPosition } from './types';
|
|
4
|
+
export interface ToasterProps {
|
|
5
|
+
/**
|
|
6
|
+
* Unique ID for this Toaster instance
|
|
7
|
+
* Use this to have multiple Toaster instances in the same app
|
|
8
|
+
* @default "default"
|
|
9
|
+
*/
|
|
10
|
+
id?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Position of the toaster
|
|
13
|
+
* @default "bottom-right"
|
|
14
|
+
*/
|
|
15
|
+
position?: ToastPosition;
|
|
16
|
+
/**
|
|
17
|
+
* Render toasts into a custom container
|
|
18
|
+
*/
|
|
19
|
+
container?: HTMLElement | null;
|
|
20
|
+
/**
|
|
21
|
+
* Label for accessibility
|
|
22
|
+
* @default "Notifications"
|
|
23
|
+
*/
|
|
24
|
+
label?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Whether to use a portal for rendering
|
|
27
|
+
* @default true
|
|
28
|
+
*/
|
|
29
|
+
portal?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Offset from viewport edges in pixels
|
|
32
|
+
* @default 16
|
|
33
|
+
*/
|
|
34
|
+
offset?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Default duration for toasts in ms
|
|
37
|
+
* @default 5000
|
|
38
|
+
*/
|
|
39
|
+
duration?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Maximum number of visible toasts
|
|
42
|
+
* @default 3
|
|
43
|
+
*/
|
|
44
|
+
visibleToasts?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Additional class name
|
|
47
|
+
*/
|
|
48
|
+
className?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Children - use Toaster.Item to customize toast rendering
|
|
51
|
+
*/
|
|
52
|
+
children?: ReactNode;
|
|
53
|
+
/**
|
|
54
|
+
* Show countdown progress bar at the bottom of each toast
|
|
55
|
+
* @default false
|
|
56
|
+
*/
|
|
57
|
+
showProgress?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Layout mode for toasts
|
|
60
|
+
* @default "default"
|
|
61
|
+
*/
|
|
62
|
+
layout?: "default" | "compact";
|
|
63
|
+
}
|
|
64
|
+
interface ToasterComponent extends React.MemoExoticComponent<React.ForwardRefExoticComponent<ToasterProps & React.RefAttributes<HTMLDivElement>>> {
|
|
65
|
+
Item: typeof ToasterItemSlot;
|
|
66
|
+
Icon: typeof ToasterIconSlot;
|
|
67
|
+
Title: typeof ToasterTitleSlot;
|
|
68
|
+
Description: typeof ToasterDescriptionSlot;
|
|
69
|
+
Actions: typeof ToasterActionsSlot;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Toaster component with compound pattern
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```tsx
|
|
76
|
+
* <Toaster id="my-toaster">
|
|
77
|
+
* <Toaster.Item className="custom-class">
|
|
78
|
+
* <Toaster.Icon>{(type, defaultIcon) => <CustomIcon type={type} />}</Toaster.Icon>
|
|
79
|
+
* <Toaster.Title className="font-bold uppercase" />
|
|
80
|
+
* <Toaster.Description className="text-sm" />
|
|
81
|
+
* <Toaster.Actions>{(action, cancel, close) => <CustomActions />}</Toaster.Actions>
|
|
82
|
+
* </Toaster.Item>
|
|
83
|
+
* </Toaster>
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export declare const Toaster: ToasterComponent;
|
|
87
|
+
export {};
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { AnimatePresence } from "framer-motion";
|
|
3
|
+
import { memo, forwardRef, useMemo, Children, isValidElement, useRef, useState, useCallback, useSyncExternalStore, useEffect } from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
|
+
import { useEventCallback } from "usehooks-ts";
|
|
6
|
+
import { subscribe, getSnapshot, setToasterConfig, dismiss, setExpanded } from "./store.js";
|
|
7
|
+
import { toastViewportTv } from "./tv.js";
|
|
8
|
+
import { ToasterActionsSlot, ToasterDescriptionSlot, ToasterTitleSlot, ToasterIconSlot, ToasterItemSlot } from "./components/toaster-slots.js";
|
|
9
|
+
import { DEFAULT_HEIGHT, GAP, ToasterItem } from "./components/toaster-item.js";
|
|
10
|
+
import { tcx } from "../../../shared/utils/tcx/tcx.js";
|
|
11
|
+
import { mergeRefs } from "../../../shared/utils/merge-refs/merge-refs.js";
|
|
12
|
+
let sharedPortalContainer = null;
|
|
13
|
+
let portalRefCount = 0;
|
|
14
|
+
function getSharedPortalContainer() {
|
|
15
|
+
if (!sharedPortalContainer && typeof document !== "undefined") {
|
|
16
|
+
sharedPortalContainer = document.createElement("div");
|
|
17
|
+
sharedPortalContainer.setAttribute("data-toast-portal", "");
|
|
18
|
+
document.body.appendChild(sharedPortalContainer);
|
|
19
|
+
}
|
|
20
|
+
return sharedPortalContainer;
|
|
21
|
+
}
|
|
22
|
+
function releaseSharedPortalContainer() {
|
|
23
|
+
portalRefCount--;
|
|
24
|
+
if (portalRefCount <= 0 && sharedPortalContainer) {
|
|
25
|
+
sharedPortalContainer.remove();
|
|
26
|
+
sharedPortalContainer = null;
|
|
27
|
+
portalRefCount = 0;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const ToasterRoot = memo(
|
|
31
|
+
forwardRef(function Toaster2({
|
|
32
|
+
id: toasterId = "default",
|
|
33
|
+
position = "bottom-right",
|
|
34
|
+
container,
|
|
35
|
+
label = "Notifications",
|
|
36
|
+
portal = true,
|
|
37
|
+
offset = 16,
|
|
38
|
+
duration = 5e3,
|
|
39
|
+
visibleToasts = 3,
|
|
40
|
+
className,
|
|
41
|
+
children,
|
|
42
|
+
showProgress = false,
|
|
43
|
+
layout = "default"
|
|
44
|
+
}, ref) {
|
|
45
|
+
const collectedSlots = useMemo(() => {
|
|
46
|
+
const slots = {};
|
|
47
|
+
Children.forEach(children, (child) => {
|
|
48
|
+
if (isValidElement(child) && child.type === ToasterItemSlot) {
|
|
49
|
+
const itemProps = child.props;
|
|
50
|
+
slots.itemClassName = itemProps.className;
|
|
51
|
+
slots.itemStyle = itemProps.style;
|
|
52
|
+
Children.forEach(itemProps.children, (slotChild) => {
|
|
53
|
+
if (!isValidElement(slotChild)) return;
|
|
54
|
+
if (slotChild.type === ToasterIconSlot) {
|
|
55
|
+
const iconProps = slotChild.props;
|
|
56
|
+
slots.iconClassName = iconProps.className;
|
|
57
|
+
slots.iconStyle = iconProps.style;
|
|
58
|
+
slots.renderIcon = iconProps.children;
|
|
59
|
+
} else if (slotChild.type === ToasterTitleSlot) {
|
|
60
|
+
const titleProps = slotChild.props;
|
|
61
|
+
slots.titleClassName = titleProps.className;
|
|
62
|
+
slots.titleStyle = titleProps.style;
|
|
63
|
+
} else if (slotChild.type === ToasterDescriptionSlot) {
|
|
64
|
+
const descProps = slotChild.props;
|
|
65
|
+
slots.descriptionClassName = descProps.className;
|
|
66
|
+
slots.descriptionStyle = descProps.style;
|
|
67
|
+
} else if (slotChild.type === ToasterActionsSlot) {
|
|
68
|
+
const actionsProps = slotChild.props;
|
|
69
|
+
slots.actionsClassName = actionsProps.className;
|
|
70
|
+
slots.actionsStyle = actionsProps.style;
|
|
71
|
+
slots.renderActions = actionsProps.children;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
return slots;
|
|
77
|
+
}, [children]);
|
|
78
|
+
const internalRef = useRef(null);
|
|
79
|
+
const [hovering, setHovering] = useState(false);
|
|
80
|
+
const subscribeToStore = useCallback(
|
|
81
|
+
(callback) => subscribe(callback, toasterId),
|
|
82
|
+
[toasterId]
|
|
83
|
+
);
|
|
84
|
+
const getStoreSnapshot = useCallback(() => getSnapshot(toasterId), [toasterId]);
|
|
85
|
+
const state = useSyncExternalStore(subscribeToStore, getStoreSnapshot, getStoreSnapshot);
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
setToasterConfig({ position }, toasterId);
|
|
88
|
+
}, [position, toasterId]);
|
|
89
|
+
const timersRef = useRef(/* @__PURE__ */ new Map());
|
|
90
|
+
const remainingRef = useRef(/* @__PURE__ */ new Map());
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
const currentIds = new Set(state.toasts.map((t) => t.id));
|
|
93
|
+
timersRef.current.forEach((timer, id) => {
|
|
94
|
+
if (!currentIds.has(id)) {
|
|
95
|
+
clearTimeout(timer);
|
|
96
|
+
timersRef.current.delete(id);
|
|
97
|
+
remainingRef.current.delete(id);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
state.toasts.forEach((toast) => {
|
|
101
|
+
var _a;
|
|
102
|
+
if (toast.type === "loading") return;
|
|
103
|
+
const toastDuration = toast.duration ?? duration;
|
|
104
|
+
if (toastDuration <= 0) return;
|
|
105
|
+
const hasTimer = timersRef.current.has(toast.id);
|
|
106
|
+
if (hovering) {
|
|
107
|
+
if (hasTimer) {
|
|
108
|
+
clearTimeout(timersRef.current.get(toast.id));
|
|
109
|
+
timersRef.current.delete(toast.id);
|
|
110
|
+
const elapsed = Date.now() - toast.createdAt;
|
|
111
|
+
const remaining = Math.max(0, toastDuration - elapsed);
|
|
112
|
+
remainingRef.current.set(toast.id, remaining);
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
if (!hasTimer) {
|
|
116
|
+
let delay = remainingRef.current.get(toast.id);
|
|
117
|
+
if (delay === void 0) {
|
|
118
|
+
const elapsed = Date.now() - toast.createdAt;
|
|
119
|
+
delay = Math.max(0, toastDuration - elapsed);
|
|
120
|
+
}
|
|
121
|
+
remainingRef.current.delete(toast.id);
|
|
122
|
+
if (delay <= 0) {
|
|
123
|
+
(_a = toast.onAutoClose) == null ? void 0 : _a.call(toast);
|
|
124
|
+
dismiss(toast.id, toasterId);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const timer = setTimeout(() => {
|
|
128
|
+
var _a2;
|
|
129
|
+
(_a2 = toast.onAutoClose) == null ? void 0 : _a2.call(toast);
|
|
130
|
+
dismiss(toast.id, toasterId);
|
|
131
|
+
timersRef.current.delete(toast.id);
|
|
132
|
+
remainingRef.current.delete(toast.id);
|
|
133
|
+
}, delay);
|
|
134
|
+
timersRef.current.set(toast.id, timer);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}, [state.toasts, duration, hovering, toasterId]);
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
return () => {
|
|
141
|
+
timersRef.current.forEach((timer) => clearTimeout(timer));
|
|
142
|
+
timersRef.current.clear();
|
|
143
|
+
remainingRef.current.clear();
|
|
144
|
+
};
|
|
145
|
+
}, []);
|
|
146
|
+
const handleMouseEnter = useEventCallback(() => {
|
|
147
|
+
setHovering(true);
|
|
148
|
+
setExpanded(true, toasterId);
|
|
149
|
+
});
|
|
150
|
+
const handleMouseLeave = useEventCallback(() => {
|
|
151
|
+
setHovering(false);
|
|
152
|
+
setExpanded(false, toasterId);
|
|
153
|
+
});
|
|
154
|
+
const handleKeyDown = useEventCallback((e) => {
|
|
155
|
+
var _a;
|
|
156
|
+
if (e.key === "F6" && state.toasts.length > 0) {
|
|
157
|
+
e.preventDefault();
|
|
158
|
+
const firstToast = (_a = internalRef.current) == null ? void 0 : _a.querySelector(
|
|
159
|
+
'[role="alertdialog"], [role="status"]'
|
|
160
|
+
);
|
|
161
|
+
if (firstToast instanceof HTMLElement) {
|
|
162
|
+
firstToast.focus();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
const visibleToastsList = useMemo(
|
|
167
|
+
() => state.toasts.slice(0, visibleToasts),
|
|
168
|
+
[state.toasts, visibleToasts]
|
|
169
|
+
);
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
if (visibleToastsList.length === 0 && hovering) {
|
|
172
|
+
setHovering(false);
|
|
173
|
+
setExpanded(false, toasterId);
|
|
174
|
+
}
|
|
175
|
+
}, [visibleToastsList.length, hovering, toasterId]);
|
|
176
|
+
const containerHeight = useMemo(() => {
|
|
177
|
+
var _a;
|
|
178
|
+
if (visibleToastsList.length === 0) return void 0;
|
|
179
|
+
if (state.expanded) {
|
|
180
|
+
const totalHeight = visibleToastsList.reduce((acc, toast, index) => {
|
|
181
|
+
const height = toast.height || DEFAULT_HEIGHT;
|
|
182
|
+
const gap = index < visibleToastsList.length - 1 ? GAP : 0;
|
|
183
|
+
return acc + height + gap;
|
|
184
|
+
}, 0);
|
|
185
|
+
return totalHeight;
|
|
186
|
+
} else {
|
|
187
|
+
const frontmostHeight = ((_a = visibleToastsList[0]) == null ? void 0 : _a.height) || DEFAULT_HEIGHT;
|
|
188
|
+
const peekOffset = (visibleToastsList.length - 1) * 8;
|
|
189
|
+
return frontmostHeight + peekOffset;
|
|
190
|
+
}
|
|
191
|
+
}, [state.expanded, visibleToastsList]);
|
|
192
|
+
const toastHeights = useMemo(
|
|
193
|
+
() => visibleToastsList.map((t) => t.height ?? 0),
|
|
194
|
+
[visibleToastsList]
|
|
195
|
+
);
|
|
196
|
+
const tv = toastViewportTv({ position, expanded: state.expanded, layout });
|
|
197
|
+
const viewportStyle = useMemo(() => {
|
|
198
|
+
var _a;
|
|
199
|
+
const frontmostHeight = (_a = visibleToastsList[0]) == null ? void 0 : _a.height;
|
|
200
|
+
return {
|
|
201
|
+
"--toast-frontmost-height": frontmostHeight ? `${frontmostHeight}px` : void 0,
|
|
202
|
+
"--toast-offset": `${offset}px`,
|
|
203
|
+
height: containerHeight ? `${containerHeight}px` : void 0
|
|
204
|
+
};
|
|
205
|
+
}, [visibleToastsList, offset, containerHeight]);
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
if (portal && !container) {
|
|
208
|
+
portalRefCount++;
|
|
209
|
+
return releaseSharedPortalContainer;
|
|
210
|
+
}
|
|
211
|
+
}, [portal, container]);
|
|
212
|
+
if (visibleToastsList.length === 0) return null;
|
|
213
|
+
const content = /* @__PURE__ */ jsx(
|
|
214
|
+
"div",
|
|
215
|
+
{
|
|
216
|
+
ref: mergeRefs(internalRef, ref),
|
|
217
|
+
role: "region",
|
|
218
|
+
"aria-live": "polite",
|
|
219
|
+
"aria-atomic": "false",
|
|
220
|
+
"aria-relevant": "additions text",
|
|
221
|
+
"aria-label": label,
|
|
222
|
+
tabIndex: -1,
|
|
223
|
+
"data-toaster": true,
|
|
224
|
+
"data-toaster-id": toasterId,
|
|
225
|
+
"data-position": position,
|
|
226
|
+
style: viewportStyle,
|
|
227
|
+
className: tcx(tv, className),
|
|
228
|
+
onKeyDown: handleKeyDown,
|
|
229
|
+
onMouseEnter: handleMouseEnter,
|
|
230
|
+
onMouseLeave: handleMouseLeave,
|
|
231
|
+
children: /* @__PURE__ */ jsx(AnimatePresence, { children: visibleToastsList.map((toast, index) => /* @__PURE__ */ jsx(
|
|
232
|
+
ToasterItem,
|
|
233
|
+
{
|
|
234
|
+
toast,
|
|
235
|
+
index,
|
|
236
|
+
total: visibleToastsList.length,
|
|
237
|
+
expanded: state.expanded,
|
|
238
|
+
position,
|
|
239
|
+
toasterId,
|
|
240
|
+
toastHeights,
|
|
241
|
+
slotProps: collectedSlots,
|
|
242
|
+
showProgress,
|
|
243
|
+
defaultDuration: duration,
|
|
244
|
+
isPaused: hovering,
|
|
245
|
+
layout
|
|
246
|
+
},
|
|
247
|
+
toast.id
|
|
248
|
+
)) })
|
|
249
|
+
}
|
|
250
|
+
);
|
|
251
|
+
if (!portal) {
|
|
252
|
+
return content;
|
|
253
|
+
}
|
|
254
|
+
const portalContainer = container ?? getSharedPortalContainer();
|
|
255
|
+
if (!portalContainer) {
|
|
256
|
+
return content;
|
|
257
|
+
}
|
|
258
|
+
return createPortal(content, portalContainer);
|
|
259
|
+
})
|
|
260
|
+
);
|
|
261
|
+
ToasterRoot.displayName = "Toaster";
|
|
262
|
+
const Toaster = Object.assign(ToasterRoot, {
|
|
263
|
+
Item: ToasterItemSlot,
|
|
264
|
+
Icon: ToasterIconSlot,
|
|
265
|
+
Title: ToasterTitleSlot,
|
|
266
|
+
Description: ToasterDescriptionSlot,
|
|
267
|
+
Actions: ToasterActionsSlot
|
|
268
|
+
});
|
|
269
|
+
export {
|
|
270
|
+
Toaster
|
|
271
|
+
};
|