@arun-dev/headless 4.12.0 → 4.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/toast/index.cjs +376 -0
- package/dist/toast/index.d.cts +141 -0
- package/dist/toast/index.d.ts +141 -0
- package/dist/toast/index.js +262 -0
- package/package.json +11 -1
|
@@ -0,0 +1,376 @@
|
|
|
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/toast/index.ts
|
|
21
|
+
var toast_exports = {};
|
|
22
|
+
__export(toast_exports, {
|
|
23
|
+
Toast: () => index_parts_exports,
|
|
24
|
+
useToastManager: () => useToastManager
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(toast_exports);
|
|
27
|
+
|
|
28
|
+
// src/toast/index.parts.ts
|
|
29
|
+
var index_parts_exports = {};
|
|
30
|
+
__export(index_parts_exports, {
|
|
31
|
+
Action: () => ToastAction,
|
|
32
|
+
Close: () => ToastClose,
|
|
33
|
+
Description: () => ToastDescription,
|
|
34
|
+
Provider: () => ToastProvider,
|
|
35
|
+
Root: () => ToastRoot,
|
|
36
|
+
Title: () => ToastTitle,
|
|
37
|
+
Viewport: () => ToastViewport
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// src/toast/ToastProvider.tsx
|
|
41
|
+
var import_react2 = require("react");
|
|
42
|
+
|
|
43
|
+
// src/toast/ToastContext.ts
|
|
44
|
+
var import_react = require("react");
|
|
45
|
+
var ToastContext = (0, import_react.createContext)(null);
|
|
46
|
+
function useToastContext(part) {
|
|
47
|
+
const context = (0, import_react.useContext)(ToastContext);
|
|
48
|
+
if (context === null) {
|
|
49
|
+
throw new Error(`${part} must be used inside <Toast.Provider>.`);
|
|
50
|
+
}
|
|
51
|
+
return context;
|
|
52
|
+
}
|
|
53
|
+
function useToastManager() {
|
|
54
|
+
const { toasts, add, update, close } = useToastContext("useToastManager()");
|
|
55
|
+
return { toasts, add, update, close };
|
|
56
|
+
}
|
|
57
|
+
var ToastRootContext = (0, import_react.createContext)(null);
|
|
58
|
+
function useToastRootContext(part) {
|
|
59
|
+
const toast = (0, import_react.useContext)(ToastRootContext);
|
|
60
|
+
if (toast === null) {
|
|
61
|
+
throw new Error(`<Toast.${part}> must be rendered inside <Toast.Root>.`);
|
|
62
|
+
}
|
|
63
|
+
return toast;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/toast/ToastProvider.tsx
|
|
67
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
68
|
+
function ToastProvider({ limit = 3, timeout = 5e3, children }) {
|
|
69
|
+
const [all, setAll] = (0, import_react2.useState)([]);
|
|
70
|
+
const prefix = (0, import_react2.useId)();
|
|
71
|
+
const counter = (0, import_react2.useRef)(0);
|
|
72
|
+
const closedRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
|
|
73
|
+
const add = (0, import_react2.useCallback)(
|
|
74
|
+
(options) => {
|
|
75
|
+
const id = options.id ?? `${prefix}-${++counter.current}`;
|
|
76
|
+
const toast = {
|
|
77
|
+
...options,
|
|
78
|
+
id,
|
|
79
|
+
timeout: options.timeout ?? timeout,
|
|
80
|
+
priority: options.priority ?? "low"
|
|
81
|
+
};
|
|
82
|
+
closedRef.current.delete(id);
|
|
83
|
+
setAll((current) => [...current.filter((t) => t.id !== id), toast]);
|
|
84
|
+
return id;
|
|
85
|
+
},
|
|
86
|
+
[prefix, timeout]
|
|
87
|
+
);
|
|
88
|
+
const update = (0, import_react2.useCallback)((id, patch) => {
|
|
89
|
+
setAll(
|
|
90
|
+
(current) => current.map((t) => t.id === id ? { ...t, ...patch, id } : t)
|
|
91
|
+
);
|
|
92
|
+
}, []);
|
|
93
|
+
const close = (0, import_react2.useCallback)((id) => {
|
|
94
|
+
setAll((current) => {
|
|
95
|
+
const toast = current.find((t) => t.id === id);
|
|
96
|
+
if (toast && !closedRef.current.has(id)) {
|
|
97
|
+
closedRef.current.add(id);
|
|
98
|
+
queueMicrotask(() => toast.onClose?.());
|
|
99
|
+
}
|
|
100
|
+
return current.filter((t) => t.id !== id);
|
|
101
|
+
});
|
|
102
|
+
}, []);
|
|
103
|
+
const [pointerInside, setPointerInside] = (0, import_react2.useState)(false);
|
|
104
|
+
const [focusInside, setFocusInside] = (0, import_react2.useState)(false);
|
|
105
|
+
const [pageHidden, setPageHidden] = (0, import_react2.useState)(false);
|
|
106
|
+
(0, import_react2.useEffect)(() => {
|
|
107
|
+
const onVisibility = () => setPageHidden(document.visibilityState === "hidden");
|
|
108
|
+
onVisibility();
|
|
109
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
110
|
+
return () => document.removeEventListener("visibilitychange", onVisibility);
|
|
111
|
+
}, []);
|
|
112
|
+
const viewportRef = (0, import_react2.useRef)(null);
|
|
113
|
+
const toasts = (0, import_react2.useMemo)(() => all.slice(-limit), [all, limit]);
|
|
114
|
+
const context = (0, import_react2.useMemo)(
|
|
115
|
+
() => ({
|
|
116
|
+
toasts,
|
|
117
|
+
add,
|
|
118
|
+
update,
|
|
119
|
+
close,
|
|
120
|
+
paused: pointerInside || focusInside || pageHidden,
|
|
121
|
+
setPointerInside,
|
|
122
|
+
setFocusInside,
|
|
123
|
+
viewportRef
|
|
124
|
+
}),
|
|
125
|
+
[toasts, add, update, close, pointerInside, focusInside, pageHidden]
|
|
126
|
+
);
|
|
127
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToastContext.Provider, { value: context, children });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/toast/ToastViewport.tsx
|
|
131
|
+
var import_react4 = require("react");
|
|
132
|
+
|
|
133
|
+
// src/core/useRender.ts
|
|
134
|
+
var import_react3 = require("react");
|
|
135
|
+
|
|
136
|
+
// src/core/mergeProps.ts
|
|
137
|
+
function isEventHandler(key) {
|
|
138
|
+
return key.length > 2 && key.startsWith("on") && key[2] === key[2]?.toUpperCase();
|
|
139
|
+
}
|
|
140
|
+
function mergeRefs(...refs) {
|
|
141
|
+
return (node) => {
|
|
142
|
+
const cleanups = refs.map((ref) => {
|
|
143
|
+
if (typeof ref === "function") {
|
|
144
|
+
const result = ref(node);
|
|
145
|
+
return typeof result === "function" ? result : () => ref(null);
|
|
146
|
+
}
|
|
147
|
+
if (ref && typeof ref === "object") {
|
|
148
|
+
ref.current = node;
|
|
149
|
+
return () => {
|
|
150
|
+
ref.current = null;
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return void 0;
|
|
154
|
+
});
|
|
155
|
+
return () => {
|
|
156
|
+
cleanups.forEach((cleanup) => {
|
|
157
|
+
if (typeof cleanup === "function") cleanup();
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function isSyntheticEvent(event) {
|
|
163
|
+
return typeof event === "object" && event !== null && "nativeEvent" in event;
|
|
164
|
+
}
|
|
165
|
+
function makeEventPreventable(event) {
|
|
166
|
+
if (Object.prototype.hasOwnProperty.call(event, "preventComponentHandler")) return;
|
|
167
|
+
const target = event;
|
|
168
|
+
target.preventComponentHandler = () => {
|
|
169
|
+
target.componentHandlerPrevented = true;
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function isComponentHandlerPrevented(event) {
|
|
173
|
+
return typeof event === "object" && event !== null && event.componentHandlerPrevented === true;
|
|
174
|
+
}
|
|
175
|
+
function chainHandlers(earlier, value) {
|
|
176
|
+
return (...args) => {
|
|
177
|
+
const event = args[0];
|
|
178
|
+
if (isSyntheticEvent(event)) makeEventPreventable(event);
|
|
179
|
+
value(...args);
|
|
180
|
+
if (isComponentHandlerPrevented(event)) return;
|
|
181
|
+
earlier(...args);
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function mergeProps(...objects) {
|
|
185
|
+
const merged = {};
|
|
186
|
+
for (const props of objects) {
|
|
187
|
+
if (!props) continue;
|
|
188
|
+
for (const key of Object.keys(props)) {
|
|
189
|
+
const value = props[key];
|
|
190
|
+
if (value === void 0) continue;
|
|
191
|
+
if (isEventHandler(key) && typeof value === "function") {
|
|
192
|
+
const earlier = merged[key];
|
|
193
|
+
merged[key] = typeof earlier === "function" ? chainHandlers(earlier, value) : value;
|
|
194
|
+
} else if (key === "className") {
|
|
195
|
+
merged.className = merged.className ? `${String(merged.className)} ${String(value)}` : value;
|
|
196
|
+
} else if (key === "style") {
|
|
197
|
+
merged.style = { ...merged.style, ...value };
|
|
198
|
+
} else if (key === "ref") {
|
|
199
|
+
merged.ref = merged.ref ? mergeRefs(merged.ref, value) : value;
|
|
200
|
+
} else {
|
|
201
|
+
merged[key] = value;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return merged;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/core/useRender.ts
|
|
209
|
+
function useRender({
|
|
210
|
+
render,
|
|
211
|
+
defaultTagName,
|
|
212
|
+
props,
|
|
213
|
+
consumerProps
|
|
214
|
+
}) {
|
|
215
|
+
const merged = mergeProps(props, consumerProps);
|
|
216
|
+
if ((0, import_react3.isValidElement)(render)) {
|
|
217
|
+
return (0, import_react3.cloneElement)(render, mergeProps(merged, render.props));
|
|
218
|
+
}
|
|
219
|
+
return (0, import_react3.createElement)(defaultTagName, merged);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/toast/ToastViewport.tsx
|
|
223
|
+
function ToastViewport({ className, children, render, ...rest }) {
|
|
224
|
+
const { toasts, viewportRef, setPointerInside, setFocusInside } = useToastContext("<Toast.Viewport>");
|
|
225
|
+
const elementRef = (0, import_react4.useRef)(null);
|
|
226
|
+
const newest = toasts.at(-1)?.id;
|
|
227
|
+
(0, import_react4.useLayoutEffect)(() => {
|
|
228
|
+
const element = elementRef.current;
|
|
229
|
+
viewportRef.current = element;
|
|
230
|
+
if (!element || typeof element.showPopover !== "function") return;
|
|
231
|
+
if (element.matches(":popover-open")) element.hidePopover();
|
|
232
|
+
element.showPopover();
|
|
233
|
+
}, [newest, viewportRef]);
|
|
234
|
+
return useRender({
|
|
235
|
+
render,
|
|
236
|
+
defaultTagName: "section",
|
|
237
|
+
props: {
|
|
238
|
+
popover: "manual",
|
|
239
|
+
"aria-label": "Notifications",
|
|
240
|
+
"aria-live": "polite",
|
|
241
|
+
tabIndex: -1,
|
|
242
|
+
className,
|
|
243
|
+
ref: elementRef,
|
|
244
|
+
children: typeof children === "function" ? children(toasts) : children,
|
|
245
|
+
onPointerEnter() {
|
|
246
|
+
setPointerInside(true);
|
|
247
|
+
},
|
|
248
|
+
onPointerLeave() {
|
|
249
|
+
setPointerInside(false);
|
|
250
|
+
},
|
|
251
|
+
onFocus() {
|
|
252
|
+
setFocusInside(true);
|
|
253
|
+
},
|
|
254
|
+
onBlur(event) {
|
|
255
|
+
const next = event.relatedTarget;
|
|
256
|
+
if (!next || !event.currentTarget.contains(next)) setFocusInside(false);
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
consumerProps: rest
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// src/toast/ToastRoot.tsx
|
|
264
|
+
var import_react5 = require("react");
|
|
265
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
266
|
+
function ToastRoot({ toast, className, children, render, ...rest }) {
|
|
267
|
+
const { close, paused, viewportRef } = useToastContext("<Toast.Root>");
|
|
268
|
+
const elementRef = (0, import_react5.useRef)(null);
|
|
269
|
+
const { id, timeout } = toast;
|
|
270
|
+
const remaining = (0, import_react5.useRef)(timeout);
|
|
271
|
+
const lastTimeout = (0, import_react5.useRef)(timeout);
|
|
272
|
+
if (lastTimeout.current !== timeout) {
|
|
273
|
+
lastTimeout.current = timeout;
|
|
274
|
+
remaining.current = timeout;
|
|
275
|
+
}
|
|
276
|
+
(0, import_react5.useEffect)(() => {
|
|
277
|
+
if (paused || timeout <= 0) return;
|
|
278
|
+
const started = Date.now();
|
|
279
|
+
const timer = setTimeout(() => close(id), remaining.current);
|
|
280
|
+
return () => {
|
|
281
|
+
clearTimeout(timer);
|
|
282
|
+
remaining.current = Math.max(0, remaining.current - (Date.now() - started));
|
|
283
|
+
};
|
|
284
|
+
}, [paused, timeout, id, close]);
|
|
285
|
+
const focusInside = (0, import_react5.useRef)(false);
|
|
286
|
+
(0, import_react5.useEffect)(
|
|
287
|
+
() => () => {
|
|
288
|
+
if (focusInside.current) viewportRef.current?.focus();
|
|
289
|
+
},
|
|
290
|
+
[viewportRef]
|
|
291
|
+
);
|
|
292
|
+
const element = useRender({
|
|
293
|
+
render,
|
|
294
|
+
defaultTagName: "div",
|
|
295
|
+
props: {
|
|
296
|
+
role: toast.priority === "high" ? "alert" : void 0,
|
|
297
|
+
"aria-atomic": true,
|
|
298
|
+
"data-type": toast.type,
|
|
299
|
+
className,
|
|
300
|
+
children,
|
|
301
|
+
ref: elementRef,
|
|
302
|
+
onFocus() {
|
|
303
|
+
focusInside.current = true;
|
|
304
|
+
},
|
|
305
|
+
onBlur(event) {
|
|
306
|
+
const next = event.relatedTarget;
|
|
307
|
+
if (!next || !event.currentTarget.contains(next)) focusInside.current = false;
|
|
308
|
+
},
|
|
309
|
+
onKeyDown(event) {
|
|
310
|
+
if (event.key === "Escape") {
|
|
311
|
+
event.preventDefault();
|
|
312
|
+
close(id);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
consumerProps: rest
|
|
317
|
+
});
|
|
318
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ToastRootContext.Provider, { value: toast, children: element });
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/toast/ToastTitle.tsx
|
|
322
|
+
function ToastTitle({ className, children, render, ...rest }) {
|
|
323
|
+
const toast = useToastRootContext("Title");
|
|
324
|
+
return useRender({
|
|
325
|
+
render,
|
|
326
|
+
defaultTagName: "p",
|
|
327
|
+
props: { className, children: children ?? toast.title },
|
|
328
|
+
consumerProps: rest
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/toast/ToastDescription.tsx
|
|
333
|
+
function ToastDescription({ className, children, render, ...rest }) {
|
|
334
|
+
const toast = useToastRootContext("Description");
|
|
335
|
+
return useRender({
|
|
336
|
+
render,
|
|
337
|
+
defaultTagName: "div",
|
|
338
|
+
props: { className, children: children ?? toast.description },
|
|
339
|
+
consumerProps: rest
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// src/toast/ToastAction.tsx
|
|
344
|
+
function ToastAction({ className, children, render, ...rest }) {
|
|
345
|
+
useToastRootContext("Action");
|
|
346
|
+
return useRender({
|
|
347
|
+
render,
|
|
348
|
+
defaultTagName: "button",
|
|
349
|
+
props: { type: "button", className, children },
|
|
350
|
+
consumerProps: rest
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/toast/ToastClose.tsx
|
|
355
|
+
function ToastClose({ className, children, render, ...rest }) {
|
|
356
|
+
const { close } = useToastContext("<Toast.Close>");
|
|
357
|
+
const { id } = useToastRootContext("Close");
|
|
358
|
+
return useRender({
|
|
359
|
+
render,
|
|
360
|
+
defaultTagName: "button",
|
|
361
|
+
props: {
|
|
362
|
+
type: "button",
|
|
363
|
+
className,
|
|
364
|
+
children,
|
|
365
|
+
onClick() {
|
|
366
|
+
close(id);
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
consumerProps: rest
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
373
|
+
0 && (module.exports = {
|
|
374
|
+
Toast,
|
|
375
|
+
useToastManager
|
|
376
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, ReactElement, Ref, ComponentPropsWithRef } from 'react';
|
|
3
|
+
|
|
4
|
+
type ToastProviderProps = {
|
|
5
|
+
/** How many show at once. The rest wait, their timers stopped, and show as others close. */
|
|
6
|
+
limit?: number;
|
|
7
|
+
/** Milliseconds a toast stays, unless it sets its own. `0` keeps toasts until closed. */
|
|
8
|
+
timeout?: number;
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Holds the toasts (decision 16). Put it near the root, with one `Toast.Viewport` inside, and
|
|
13
|
+
* call `useToastManager()` anywhere below to show one.
|
|
14
|
+
*/
|
|
15
|
+
declare function ToastProvider({ limit, timeout, children }: ToastProviderProps): react.JSX.Element;
|
|
16
|
+
|
|
17
|
+
/** `'high'` is announced at once (`role="alert"`); `'low'`, the default, when the reader is idle. */
|
|
18
|
+
type ToastPriority = 'low' | 'high';
|
|
19
|
+
/** What `add` takes. Everything is optional but something to show. */
|
|
20
|
+
type ToastOptions<Data = unknown> = {
|
|
21
|
+
/** Reuse an id to replace a toast of the same id. Generated otherwise. */
|
|
22
|
+
id?: string;
|
|
23
|
+
title?: ReactNode;
|
|
24
|
+
description?: ReactNode;
|
|
25
|
+
/** Your kind of toast — `"success"`, `"error"` — emitted as `data-type` for styling. */
|
|
26
|
+
type?: string;
|
|
27
|
+
/** Milliseconds before it closes on its own. `0` keeps it until closed. Defaults to the Provider's. */
|
|
28
|
+
timeout?: number;
|
|
29
|
+
priority?: ToastPriority;
|
|
30
|
+
/** A button on the toast, for the ui's default rendering. The headless `Action` part is yours to place. */
|
|
31
|
+
action?: {
|
|
32
|
+
label: ReactNode;
|
|
33
|
+
onClick: () => void;
|
|
34
|
+
};
|
|
35
|
+
/** Called once it closes, however it closed. */
|
|
36
|
+
onClose?: () => void;
|
|
37
|
+
/** Anything else your rendering needs. */
|
|
38
|
+
data?: Data;
|
|
39
|
+
};
|
|
40
|
+
type ToastObject<Data = unknown> = ToastOptions<Data> & {
|
|
41
|
+
id: string;
|
|
42
|
+
timeout: number;
|
|
43
|
+
priority: ToastPriority;
|
|
44
|
+
};
|
|
45
|
+
type ToastManager = {
|
|
46
|
+
/** The toasts to render: the newest `limit`, oldest first. */
|
|
47
|
+
toasts: readonly ToastObject[];
|
|
48
|
+
/** Shows a toast and returns its id. */
|
|
49
|
+
add: <Data = unknown>(options: ToastOptions<Data>) => string;
|
|
50
|
+
/** Changes a shown toast — its text after a save finishes, say. A new `timeout` restarts it. */
|
|
51
|
+
update: <Data = unknown>(id: string, patch: Partial<ToastOptions<Data>>) => void;
|
|
52
|
+
/** Closes a toast. */
|
|
53
|
+
close: (id: string) => void;
|
|
54
|
+
};
|
|
55
|
+
/** Shows and closes toasts from anywhere under the Provider. */
|
|
56
|
+
declare function useToastManager(): ToastManager;
|
|
57
|
+
|
|
58
|
+
type ToastViewportOwnProps = {
|
|
59
|
+
/** The toasts, rendered: usually `toasts.map((toast) => <Toast.Root key={toast.id} toast={toast}>…)`. */
|
|
60
|
+
children?: ReactNode | ((toasts: readonly ToastObject[]) => ReactNode);
|
|
61
|
+
/** Element to render instead of the default `<section>`. Props and ref are merged onto it. */
|
|
62
|
+
render?: ReactElement;
|
|
63
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
64
|
+
ref?: Ref<HTMLElement>;
|
|
65
|
+
};
|
|
66
|
+
type ToastViewportProps = ToastViewportOwnProps & Omit<ComponentPropsWithRef<'section'>, keyof ToastViewportOwnProps>;
|
|
67
|
+
/**
|
|
68
|
+
* Where toasts show: a `<section aria-label="Notifications">`, `aria-live="polite"`, and a
|
|
69
|
+
* `popover="manual"` kept open in the top layer — above a modal Dialog — with no portal
|
|
70
|
+
* (decisions 12 and 16). It stays open while empty, so the live region already exists when the
|
|
71
|
+
* first toast arrives, and moves back to the top of the top layer as each arrives.
|
|
72
|
+
*
|
|
73
|
+
* Timers pause while the pointer is over it or focus is in it.
|
|
74
|
+
*/
|
|
75
|
+
declare function ToastViewport({ className, children, render, ...rest }: ToastViewportProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
76
|
+
|
|
77
|
+
type ToastRootOwnProps = {
|
|
78
|
+
/** The toast this renders — one of `useToastManager().toasts`. */
|
|
79
|
+
toast: ToastObject;
|
|
80
|
+
/** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
|
|
81
|
+
render?: ReactElement;
|
|
82
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
83
|
+
ref?: Ref<HTMLElement>;
|
|
84
|
+
};
|
|
85
|
+
type ToastRootProps = ToastRootOwnProps & Omit<ComponentPropsWithRef<'div'>, keyof ToastRootOwnProps>;
|
|
86
|
+
/**
|
|
87
|
+
* One toast. Closes itself after its `timeout`, counting only while not paused, and on Esc while
|
|
88
|
+
* focus is in it. A `priority: 'high'` toast is `role="alert"`; the rest are announced politely
|
|
89
|
+
* by the Viewport. `data-type` carries its `type`, for styling.
|
|
90
|
+
*/
|
|
91
|
+
declare function ToastRoot({ toast, className, children, render, ...rest }: ToastRootProps): react.JSX.Element;
|
|
92
|
+
|
|
93
|
+
type ToastTitleOwnProps = {
|
|
94
|
+
/** Element to render instead of the default `<p>`. Props and ref are merged onto it. */
|
|
95
|
+
render?: ReactElement;
|
|
96
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
97
|
+
ref?: Ref<HTMLElement>;
|
|
98
|
+
};
|
|
99
|
+
type ToastTitleProps = ToastTitleOwnProps & Omit<ComponentPropsWithRef<'p'>, keyof ToastTitleOwnProps>;
|
|
100
|
+
/** The toast's title. Its children default to the toast's `title`. */
|
|
101
|
+
declare function ToastTitle({ className, children, render, ...rest }: ToastTitleProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
102
|
+
|
|
103
|
+
type ToastDescriptionOwnProps = {
|
|
104
|
+
/** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
|
|
105
|
+
render?: ReactElement;
|
|
106
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
107
|
+
ref?: Ref<HTMLElement>;
|
|
108
|
+
};
|
|
109
|
+
type ToastDescriptionProps = ToastDescriptionOwnProps & Omit<ComponentPropsWithRef<'div'>, keyof ToastDescriptionOwnProps>;
|
|
110
|
+
/** The toast's description. Its children default to the toast's `description`. */
|
|
111
|
+
declare function ToastDescription({ className, children, render, ...rest }: ToastDescriptionProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
112
|
+
|
|
113
|
+
type ToastActionOwnProps = {
|
|
114
|
+
/** Element to render instead of the default `<button>`. Props and ref are merged onto it. */
|
|
115
|
+
render?: ReactElement;
|
|
116
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
117
|
+
ref?: Ref<HTMLElement>;
|
|
118
|
+
};
|
|
119
|
+
type ToastActionProps = ToastActionOwnProps & Omit<ComponentPropsWithRef<'button'>, keyof ToastActionOwnProps>;
|
|
120
|
+
/**
|
|
121
|
+
* A button that does something about the toast — "Undo". It does not close the toast; call
|
|
122
|
+
* `close` in your handler if it should. Keep it optional: a toast can vanish before anyone
|
|
123
|
+
* reaches it, so the same action must be possible elsewhere.
|
|
124
|
+
*/
|
|
125
|
+
declare function ToastAction({ className, children, render, ...rest }: ToastActionProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
126
|
+
|
|
127
|
+
type ToastCloseOwnProps = {
|
|
128
|
+
/** Element to render instead of the default `<button>`. Props and ref are merged onto it. */
|
|
129
|
+
render?: ReactElement;
|
|
130
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
131
|
+
ref?: Ref<HTMLElement>;
|
|
132
|
+
};
|
|
133
|
+
type ToastCloseProps = ToastCloseOwnProps & Omit<ComponentPropsWithRef<'button'>, keyof ToastCloseOwnProps>;
|
|
134
|
+
/** Closes its toast. Give it an `aria-label` — "Dismiss" — if it shows only an icon. */
|
|
135
|
+
declare function ToastClose({ className, children, render, ...rest }: ToastCloseProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
136
|
+
|
|
137
|
+
declare namespace index_parts {
|
|
138
|
+
export { ToastAction as Action, ToastClose as Close, ToastDescription as Description, ToastProvider as Provider, ToastRoot as Root, ToastTitle as Title, ToastViewport as Viewport };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export { index_parts as Toast, type ToastActionProps, type ToastCloseProps, type ToastDescriptionProps, type ToastManager, type ToastObject, type ToastOptions, type ToastPriority, type ToastProviderProps, type ToastRootProps, type ToastTitleProps, type ToastViewportProps, useToastManager };
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, ReactElement, Ref, ComponentPropsWithRef } from 'react';
|
|
3
|
+
|
|
4
|
+
type ToastProviderProps = {
|
|
5
|
+
/** How many show at once. The rest wait, their timers stopped, and show as others close. */
|
|
6
|
+
limit?: number;
|
|
7
|
+
/** Milliseconds a toast stays, unless it sets its own. `0` keeps toasts until closed. */
|
|
8
|
+
timeout?: number;
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Holds the toasts (decision 16). Put it near the root, with one `Toast.Viewport` inside, and
|
|
13
|
+
* call `useToastManager()` anywhere below to show one.
|
|
14
|
+
*/
|
|
15
|
+
declare function ToastProvider({ limit, timeout, children }: ToastProviderProps): react.JSX.Element;
|
|
16
|
+
|
|
17
|
+
/** `'high'` is announced at once (`role="alert"`); `'low'`, the default, when the reader is idle. */
|
|
18
|
+
type ToastPriority = 'low' | 'high';
|
|
19
|
+
/** What `add` takes. Everything is optional but something to show. */
|
|
20
|
+
type ToastOptions<Data = unknown> = {
|
|
21
|
+
/** Reuse an id to replace a toast of the same id. Generated otherwise. */
|
|
22
|
+
id?: string;
|
|
23
|
+
title?: ReactNode;
|
|
24
|
+
description?: ReactNode;
|
|
25
|
+
/** Your kind of toast — `"success"`, `"error"` — emitted as `data-type` for styling. */
|
|
26
|
+
type?: string;
|
|
27
|
+
/** Milliseconds before it closes on its own. `0` keeps it until closed. Defaults to the Provider's. */
|
|
28
|
+
timeout?: number;
|
|
29
|
+
priority?: ToastPriority;
|
|
30
|
+
/** A button on the toast, for the ui's default rendering. The headless `Action` part is yours to place. */
|
|
31
|
+
action?: {
|
|
32
|
+
label: ReactNode;
|
|
33
|
+
onClick: () => void;
|
|
34
|
+
};
|
|
35
|
+
/** Called once it closes, however it closed. */
|
|
36
|
+
onClose?: () => void;
|
|
37
|
+
/** Anything else your rendering needs. */
|
|
38
|
+
data?: Data;
|
|
39
|
+
};
|
|
40
|
+
type ToastObject<Data = unknown> = ToastOptions<Data> & {
|
|
41
|
+
id: string;
|
|
42
|
+
timeout: number;
|
|
43
|
+
priority: ToastPriority;
|
|
44
|
+
};
|
|
45
|
+
type ToastManager = {
|
|
46
|
+
/** The toasts to render: the newest `limit`, oldest first. */
|
|
47
|
+
toasts: readonly ToastObject[];
|
|
48
|
+
/** Shows a toast and returns its id. */
|
|
49
|
+
add: <Data = unknown>(options: ToastOptions<Data>) => string;
|
|
50
|
+
/** Changes a shown toast — its text after a save finishes, say. A new `timeout` restarts it. */
|
|
51
|
+
update: <Data = unknown>(id: string, patch: Partial<ToastOptions<Data>>) => void;
|
|
52
|
+
/** Closes a toast. */
|
|
53
|
+
close: (id: string) => void;
|
|
54
|
+
};
|
|
55
|
+
/** Shows and closes toasts from anywhere under the Provider. */
|
|
56
|
+
declare function useToastManager(): ToastManager;
|
|
57
|
+
|
|
58
|
+
type ToastViewportOwnProps = {
|
|
59
|
+
/** The toasts, rendered: usually `toasts.map((toast) => <Toast.Root key={toast.id} toast={toast}>…)`. */
|
|
60
|
+
children?: ReactNode | ((toasts: readonly ToastObject[]) => ReactNode);
|
|
61
|
+
/** Element to render instead of the default `<section>`. Props and ref are merged onto it. */
|
|
62
|
+
render?: ReactElement;
|
|
63
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
64
|
+
ref?: Ref<HTMLElement>;
|
|
65
|
+
};
|
|
66
|
+
type ToastViewportProps = ToastViewportOwnProps & Omit<ComponentPropsWithRef<'section'>, keyof ToastViewportOwnProps>;
|
|
67
|
+
/**
|
|
68
|
+
* Where toasts show: a `<section aria-label="Notifications">`, `aria-live="polite"`, and a
|
|
69
|
+
* `popover="manual"` kept open in the top layer — above a modal Dialog — with no portal
|
|
70
|
+
* (decisions 12 and 16). It stays open while empty, so the live region already exists when the
|
|
71
|
+
* first toast arrives, and moves back to the top of the top layer as each arrives.
|
|
72
|
+
*
|
|
73
|
+
* Timers pause while the pointer is over it or focus is in it.
|
|
74
|
+
*/
|
|
75
|
+
declare function ToastViewport({ className, children, render, ...rest }: ToastViewportProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
76
|
+
|
|
77
|
+
type ToastRootOwnProps = {
|
|
78
|
+
/** The toast this renders — one of `useToastManager().toasts`. */
|
|
79
|
+
toast: ToastObject;
|
|
80
|
+
/** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
|
|
81
|
+
render?: ReactElement;
|
|
82
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
83
|
+
ref?: Ref<HTMLElement>;
|
|
84
|
+
};
|
|
85
|
+
type ToastRootProps = ToastRootOwnProps & Omit<ComponentPropsWithRef<'div'>, keyof ToastRootOwnProps>;
|
|
86
|
+
/**
|
|
87
|
+
* One toast. Closes itself after its `timeout`, counting only while not paused, and on Esc while
|
|
88
|
+
* focus is in it. A `priority: 'high'` toast is `role="alert"`; the rest are announced politely
|
|
89
|
+
* by the Viewport. `data-type` carries its `type`, for styling.
|
|
90
|
+
*/
|
|
91
|
+
declare function ToastRoot({ toast, className, children, render, ...rest }: ToastRootProps): react.JSX.Element;
|
|
92
|
+
|
|
93
|
+
type ToastTitleOwnProps = {
|
|
94
|
+
/** Element to render instead of the default `<p>`. Props and ref are merged onto it. */
|
|
95
|
+
render?: ReactElement;
|
|
96
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
97
|
+
ref?: Ref<HTMLElement>;
|
|
98
|
+
};
|
|
99
|
+
type ToastTitleProps = ToastTitleOwnProps & Omit<ComponentPropsWithRef<'p'>, keyof ToastTitleOwnProps>;
|
|
100
|
+
/** The toast's title. Its children default to the toast's `title`. */
|
|
101
|
+
declare function ToastTitle({ className, children, render, ...rest }: ToastTitleProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
102
|
+
|
|
103
|
+
type ToastDescriptionOwnProps = {
|
|
104
|
+
/** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
|
|
105
|
+
render?: ReactElement;
|
|
106
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
107
|
+
ref?: Ref<HTMLElement>;
|
|
108
|
+
};
|
|
109
|
+
type ToastDescriptionProps = ToastDescriptionOwnProps & Omit<ComponentPropsWithRef<'div'>, keyof ToastDescriptionOwnProps>;
|
|
110
|
+
/** The toast's description. Its children default to the toast's `description`. */
|
|
111
|
+
declare function ToastDescription({ className, children, render, ...rest }: ToastDescriptionProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
112
|
+
|
|
113
|
+
type ToastActionOwnProps = {
|
|
114
|
+
/** Element to render instead of the default `<button>`. Props and ref are merged onto it. */
|
|
115
|
+
render?: ReactElement;
|
|
116
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
117
|
+
ref?: Ref<HTMLElement>;
|
|
118
|
+
};
|
|
119
|
+
type ToastActionProps = ToastActionOwnProps & Omit<ComponentPropsWithRef<'button'>, keyof ToastActionOwnProps>;
|
|
120
|
+
/**
|
|
121
|
+
* A button that does something about the toast — "Undo". It does not close the toast; call
|
|
122
|
+
* `close` in your handler if it should. Keep it optional: a toast can vanish before anyone
|
|
123
|
+
* reaches it, so the same action must be possible elsewhere.
|
|
124
|
+
*/
|
|
125
|
+
declare function ToastAction({ className, children, render, ...rest }: ToastActionProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
126
|
+
|
|
127
|
+
type ToastCloseOwnProps = {
|
|
128
|
+
/** Element to render instead of the default `<button>`. Props and ref are merged onto it. */
|
|
129
|
+
render?: ReactElement;
|
|
130
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
131
|
+
ref?: Ref<HTMLElement>;
|
|
132
|
+
};
|
|
133
|
+
type ToastCloseProps = ToastCloseOwnProps & Omit<ComponentPropsWithRef<'button'>, keyof ToastCloseOwnProps>;
|
|
134
|
+
/** Closes its toast. Give it an `aria-label` — "Dismiss" — if it shows only an icon. */
|
|
135
|
+
declare function ToastClose({ className, children, render, ...rest }: ToastCloseProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
136
|
+
|
|
137
|
+
declare namespace index_parts {
|
|
138
|
+
export { ToastAction as Action, ToastClose as Close, ToastDescription as Description, ToastProvider as Provider, ToastRoot as Root, ToastTitle as Title, ToastViewport as Viewport };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export { index_parts as Toast, type ToastActionProps, type ToastCloseProps, type ToastDescriptionProps, type ToastManager, type ToastObject, type ToastOptions, type ToastPriority, type ToastProviderProps, type ToastRootProps, type ToastTitleProps, type ToastViewportProps, useToastManager };
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__export,
|
|
3
|
+
useRender
|
|
4
|
+
} from "../chunk-OQTWWZ4B.js";
|
|
5
|
+
|
|
6
|
+
// src/toast/index.parts.ts
|
|
7
|
+
var index_parts_exports = {};
|
|
8
|
+
__export(index_parts_exports, {
|
|
9
|
+
Action: () => ToastAction,
|
|
10
|
+
Close: () => ToastClose,
|
|
11
|
+
Description: () => ToastDescription,
|
|
12
|
+
Provider: () => ToastProvider,
|
|
13
|
+
Root: () => ToastRoot,
|
|
14
|
+
Title: () => ToastTitle,
|
|
15
|
+
Viewport: () => ToastViewport
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// src/toast/ToastProvider.tsx
|
|
19
|
+
import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
20
|
+
|
|
21
|
+
// src/toast/ToastContext.ts
|
|
22
|
+
import { createContext, useContext } from "react";
|
|
23
|
+
var ToastContext = createContext(null);
|
|
24
|
+
function useToastContext(part) {
|
|
25
|
+
const context = useContext(ToastContext);
|
|
26
|
+
if (context === null) {
|
|
27
|
+
throw new Error(`${part} must be used inside <Toast.Provider>.`);
|
|
28
|
+
}
|
|
29
|
+
return context;
|
|
30
|
+
}
|
|
31
|
+
function useToastManager() {
|
|
32
|
+
const { toasts, add, update, close } = useToastContext("useToastManager()");
|
|
33
|
+
return { toasts, add, update, close };
|
|
34
|
+
}
|
|
35
|
+
var ToastRootContext = createContext(null);
|
|
36
|
+
function useToastRootContext(part) {
|
|
37
|
+
const toast = useContext(ToastRootContext);
|
|
38
|
+
if (toast === null) {
|
|
39
|
+
throw new Error(`<Toast.${part}> must be rendered inside <Toast.Root>.`);
|
|
40
|
+
}
|
|
41
|
+
return toast;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/toast/ToastProvider.tsx
|
|
45
|
+
import { jsx } from "react/jsx-runtime";
|
|
46
|
+
function ToastProvider({ limit = 3, timeout = 5e3, children }) {
|
|
47
|
+
const [all, setAll] = useState([]);
|
|
48
|
+
const prefix = useId();
|
|
49
|
+
const counter = useRef(0);
|
|
50
|
+
const closedRef = useRef(/* @__PURE__ */ new Set());
|
|
51
|
+
const add = useCallback(
|
|
52
|
+
(options) => {
|
|
53
|
+
const id = options.id ?? `${prefix}-${++counter.current}`;
|
|
54
|
+
const toast = {
|
|
55
|
+
...options,
|
|
56
|
+
id,
|
|
57
|
+
timeout: options.timeout ?? timeout,
|
|
58
|
+
priority: options.priority ?? "low"
|
|
59
|
+
};
|
|
60
|
+
closedRef.current.delete(id);
|
|
61
|
+
setAll((current) => [...current.filter((t) => t.id !== id), toast]);
|
|
62
|
+
return id;
|
|
63
|
+
},
|
|
64
|
+
[prefix, timeout]
|
|
65
|
+
);
|
|
66
|
+
const update = useCallback((id, patch) => {
|
|
67
|
+
setAll(
|
|
68
|
+
(current) => current.map((t) => t.id === id ? { ...t, ...patch, id } : t)
|
|
69
|
+
);
|
|
70
|
+
}, []);
|
|
71
|
+
const close = useCallback((id) => {
|
|
72
|
+
setAll((current) => {
|
|
73
|
+
const toast = current.find((t) => t.id === id);
|
|
74
|
+
if (toast && !closedRef.current.has(id)) {
|
|
75
|
+
closedRef.current.add(id);
|
|
76
|
+
queueMicrotask(() => toast.onClose?.());
|
|
77
|
+
}
|
|
78
|
+
return current.filter((t) => t.id !== id);
|
|
79
|
+
});
|
|
80
|
+
}, []);
|
|
81
|
+
const [pointerInside, setPointerInside] = useState(false);
|
|
82
|
+
const [focusInside, setFocusInside] = useState(false);
|
|
83
|
+
const [pageHidden, setPageHidden] = useState(false);
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
const onVisibility = () => setPageHidden(document.visibilityState === "hidden");
|
|
86
|
+
onVisibility();
|
|
87
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
88
|
+
return () => document.removeEventListener("visibilitychange", onVisibility);
|
|
89
|
+
}, []);
|
|
90
|
+
const viewportRef = useRef(null);
|
|
91
|
+
const toasts = useMemo(() => all.slice(-limit), [all, limit]);
|
|
92
|
+
const context = useMemo(
|
|
93
|
+
() => ({
|
|
94
|
+
toasts,
|
|
95
|
+
add,
|
|
96
|
+
update,
|
|
97
|
+
close,
|
|
98
|
+
paused: pointerInside || focusInside || pageHidden,
|
|
99
|
+
setPointerInside,
|
|
100
|
+
setFocusInside,
|
|
101
|
+
viewportRef
|
|
102
|
+
}),
|
|
103
|
+
[toasts, add, update, close, pointerInside, focusInside, pageHidden]
|
|
104
|
+
);
|
|
105
|
+
return /* @__PURE__ */ jsx(ToastContext.Provider, { value: context, children });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/toast/ToastViewport.tsx
|
|
109
|
+
import { useLayoutEffect, useRef as useRef2 } from "react";
|
|
110
|
+
function ToastViewport({ className, children, render, ...rest }) {
|
|
111
|
+
const { toasts, viewportRef, setPointerInside, setFocusInside } = useToastContext("<Toast.Viewport>");
|
|
112
|
+
const elementRef = useRef2(null);
|
|
113
|
+
const newest = toasts.at(-1)?.id;
|
|
114
|
+
useLayoutEffect(() => {
|
|
115
|
+
const element = elementRef.current;
|
|
116
|
+
viewportRef.current = element;
|
|
117
|
+
if (!element || typeof element.showPopover !== "function") return;
|
|
118
|
+
if (element.matches(":popover-open")) element.hidePopover();
|
|
119
|
+
element.showPopover();
|
|
120
|
+
}, [newest, viewportRef]);
|
|
121
|
+
return useRender({
|
|
122
|
+
render,
|
|
123
|
+
defaultTagName: "section",
|
|
124
|
+
props: {
|
|
125
|
+
popover: "manual",
|
|
126
|
+
"aria-label": "Notifications",
|
|
127
|
+
"aria-live": "polite",
|
|
128
|
+
tabIndex: -1,
|
|
129
|
+
className,
|
|
130
|
+
ref: elementRef,
|
|
131
|
+
children: typeof children === "function" ? children(toasts) : children,
|
|
132
|
+
onPointerEnter() {
|
|
133
|
+
setPointerInside(true);
|
|
134
|
+
},
|
|
135
|
+
onPointerLeave() {
|
|
136
|
+
setPointerInside(false);
|
|
137
|
+
},
|
|
138
|
+
onFocus() {
|
|
139
|
+
setFocusInside(true);
|
|
140
|
+
},
|
|
141
|
+
onBlur(event) {
|
|
142
|
+
const next = event.relatedTarget;
|
|
143
|
+
if (!next || !event.currentTarget.contains(next)) setFocusInside(false);
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
consumerProps: rest
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/toast/ToastRoot.tsx
|
|
151
|
+
import { useEffect as useEffect2, useRef as useRef3 } from "react";
|
|
152
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
153
|
+
function ToastRoot({ toast, className, children, render, ...rest }) {
|
|
154
|
+
const { close, paused, viewportRef } = useToastContext("<Toast.Root>");
|
|
155
|
+
const elementRef = useRef3(null);
|
|
156
|
+
const { id, timeout } = toast;
|
|
157
|
+
const remaining = useRef3(timeout);
|
|
158
|
+
const lastTimeout = useRef3(timeout);
|
|
159
|
+
if (lastTimeout.current !== timeout) {
|
|
160
|
+
lastTimeout.current = timeout;
|
|
161
|
+
remaining.current = timeout;
|
|
162
|
+
}
|
|
163
|
+
useEffect2(() => {
|
|
164
|
+
if (paused || timeout <= 0) return;
|
|
165
|
+
const started = Date.now();
|
|
166
|
+
const timer = setTimeout(() => close(id), remaining.current);
|
|
167
|
+
return () => {
|
|
168
|
+
clearTimeout(timer);
|
|
169
|
+
remaining.current = Math.max(0, remaining.current - (Date.now() - started));
|
|
170
|
+
};
|
|
171
|
+
}, [paused, timeout, id, close]);
|
|
172
|
+
const focusInside = useRef3(false);
|
|
173
|
+
useEffect2(
|
|
174
|
+
() => () => {
|
|
175
|
+
if (focusInside.current) viewportRef.current?.focus();
|
|
176
|
+
},
|
|
177
|
+
[viewportRef]
|
|
178
|
+
);
|
|
179
|
+
const element = useRender({
|
|
180
|
+
render,
|
|
181
|
+
defaultTagName: "div",
|
|
182
|
+
props: {
|
|
183
|
+
role: toast.priority === "high" ? "alert" : void 0,
|
|
184
|
+
"aria-atomic": true,
|
|
185
|
+
"data-type": toast.type,
|
|
186
|
+
className,
|
|
187
|
+
children,
|
|
188
|
+
ref: elementRef,
|
|
189
|
+
onFocus() {
|
|
190
|
+
focusInside.current = true;
|
|
191
|
+
},
|
|
192
|
+
onBlur(event) {
|
|
193
|
+
const next = event.relatedTarget;
|
|
194
|
+
if (!next || !event.currentTarget.contains(next)) focusInside.current = false;
|
|
195
|
+
},
|
|
196
|
+
onKeyDown(event) {
|
|
197
|
+
if (event.key === "Escape") {
|
|
198
|
+
event.preventDefault();
|
|
199
|
+
close(id);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
consumerProps: rest
|
|
204
|
+
});
|
|
205
|
+
return /* @__PURE__ */ jsx2(ToastRootContext.Provider, { value: toast, children: element });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/toast/ToastTitle.tsx
|
|
209
|
+
function ToastTitle({ className, children, render, ...rest }) {
|
|
210
|
+
const toast = useToastRootContext("Title");
|
|
211
|
+
return useRender({
|
|
212
|
+
render,
|
|
213
|
+
defaultTagName: "p",
|
|
214
|
+
props: { className, children: children ?? toast.title },
|
|
215
|
+
consumerProps: rest
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/toast/ToastDescription.tsx
|
|
220
|
+
function ToastDescription({ className, children, render, ...rest }) {
|
|
221
|
+
const toast = useToastRootContext("Description");
|
|
222
|
+
return useRender({
|
|
223
|
+
render,
|
|
224
|
+
defaultTagName: "div",
|
|
225
|
+
props: { className, children: children ?? toast.description },
|
|
226
|
+
consumerProps: rest
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/toast/ToastAction.tsx
|
|
231
|
+
function ToastAction({ className, children, render, ...rest }) {
|
|
232
|
+
useToastRootContext("Action");
|
|
233
|
+
return useRender({
|
|
234
|
+
render,
|
|
235
|
+
defaultTagName: "button",
|
|
236
|
+
props: { type: "button", className, children },
|
|
237
|
+
consumerProps: rest
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/toast/ToastClose.tsx
|
|
242
|
+
function ToastClose({ className, children, render, ...rest }) {
|
|
243
|
+
const { close } = useToastContext("<Toast.Close>");
|
|
244
|
+
const { id } = useToastRootContext("Close");
|
|
245
|
+
return useRender({
|
|
246
|
+
render,
|
|
247
|
+
defaultTagName: "button",
|
|
248
|
+
props: {
|
|
249
|
+
type: "button",
|
|
250
|
+
className,
|
|
251
|
+
children,
|
|
252
|
+
onClick() {
|
|
253
|
+
close(id);
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
consumerProps: rest
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
export {
|
|
260
|
+
index_parts_exports as Toast,
|
|
261
|
+
useToastManager
|
|
262
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arun-dev/headless",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.13.0",
|
|
4
4
|
"description": "Unstyled React behaviour primitives — render engine, controlled/uncontrolled state, and data-* state projection. Ships no CSS and no class names.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -114,6 +114,16 @@
|
|
|
114
114
|
"default": "./dist/field/index.cjs"
|
|
115
115
|
}
|
|
116
116
|
},
|
|
117
|
+
"./toast": {
|
|
118
|
+
"import": {
|
|
119
|
+
"types": "./dist/toast/index.d.ts",
|
|
120
|
+
"default": "./dist/toast/index.js"
|
|
121
|
+
},
|
|
122
|
+
"require": {
|
|
123
|
+
"types": "./dist/toast/index.d.cts",
|
|
124
|
+
"default": "./dist/toast/index.cjs"
|
|
125
|
+
}
|
|
126
|
+
},
|
|
117
127
|
"./switch": {
|
|
118
128
|
"import": {
|
|
119
129
|
"types": "./dist/switch/index.d.ts",
|