@arun-dev/headless 4.11.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/field/index.cjs +287 -0
- package/dist/field/index.d.cts +92 -0
- package/dist/field/index.d.ts +92 -0
- package/dist/field/index.js +176 -0
- 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 +21 -1
|
@@ -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",
|
|
@@ -104,6 +104,26 @@
|
|
|
104
104
|
"default": "./dist/radio-group/index.cjs"
|
|
105
105
|
}
|
|
106
106
|
},
|
|
107
|
+
"./field": {
|
|
108
|
+
"import": {
|
|
109
|
+
"types": "./dist/field/index.d.ts",
|
|
110
|
+
"default": "./dist/field/index.js"
|
|
111
|
+
},
|
|
112
|
+
"require": {
|
|
113
|
+
"types": "./dist/field/index.d.cts",
|
|
114
|
+
"default": "./dist/field/index.cjs"
|
|
115
|
+
}
|
|
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
|
+
},
|
|
107
127
|
"./switch": {
|
|
108
128
|
"import": {
|
|
109
129
|
"types": "./dist/switch/index.d.ts",
|