@enigmax/primitives 0.20.0 → 0.22.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.
@@ -1,154 +0,0 @@
1
- import { defaultQueue } from './chunk-QYMUIW5I.js';
2
- import { useState, useRef, useEffect, useCallback } from 'react';
3
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
-
5
- function swipeAxis(position) {
6
- if (position.endsWith("-center")) return { axis: "y", sign: position.startsWith("top") ? -1 : 1 };
7
- return { axis: "x", sign: position.endsWith("-left") ? -1 : 1 };
8
- }
9
- function Toaster({
10
- queue = defaultQueue,
11
- position = "bottom-right",
12
- exitDuration = 200,
13
- swipeThreshold = 60,
14
- children,
15
- label = "Notifications",
16
- className,
17
- style
18
- }) {
19
- const [rendered, setRendered] = useState(
20
- () => queue.items.map((notification) => ({ notification, leaving: false }))
21
- );
22
- const timers = useRef(/* @__PURE__ */ new Map());
23
- useEffect(() => {
24
- const sync = (items) => {
25
- setRendered((current) => {
26
- const live = new Map(items.map((item) => [item.id, item]));
27
- const kept = current.map((entry) => {
28
- const still = live.get(entry.notification.id);
29
- if (still) return { notification: still, leaving: false };
30
- if (entry.leaving) return entry;
31
- const timer = setTimeout(() => {
32
- timers.current.delete(entry.notification.id);
33
- setRendered((later) => later.filter((row) => row.notification.id !== entry.notification.id));
34
- }, exitDuration);
35
- timers.current.set(entry.notification.id, timer);
36
- return { ...entry, leaving: true };
37
- });
38
- const seen = new Set(kept.map((entry) => entry.notification.id));
39
- const added = items.filter((item) => !seen.has(item.id)).map((item) => ({ notification: item, leaving: false }));
40
- return [...kept, ...added];
41
- });
42
- };
43
- sync(queue.items);
44
- return queue.subscribe(sync);
45
- }, [queue, exitDuration]);
46
- useEffect(() => {
47
- const pending = timers.current;
48
- return () => {
49
- for (const timer of pending.values()) clearTimeout(timer);
50
- pending.clear();
51
- };
52
- }, []);
53
- if (!rendered.length) return null;
54
- return /* @__PURE__ */ jsx(
55
- "section",
56
- {
57
- "aria-label": label,
58
- "data-enigma-toaster": "",
59
- "data-position": position,
60
- className,
61
- style,
62
- onPointerEnter: () => queue.pause(),
63
- onPointerLeave: () => queue.resume(),
64
- onFocusCapture: () => queue.pause(),
65
- onBlurCapture: () => queue.resume(),
66
- children: rendered.map((entry, index) => /* @__PURE__ */ jsx(
67
- Toast,
68
- {
69
- entry,
70
- index: rendered.length - 1 - index,
71
- position,
72
- swipeThreshold,
73
- onDismiss: () => queue.dismiss(entry.notification.id),
74
- render: children
75
- },
76
- entry.notification.id
77
- ))
78
- }
79
- );
80
- }
81
- function Toast({ entry, index, position, swipeThreshold, onDismiss, render }) {
82
- const { notification, leaving } = entry;
83
- const [offset, setOffset] = useState(0);
84
- const [swiping, setSwiping] = useState(false);
85
- const start = useRef(0);
86
- const { axis, sign } = swipeAxis(position);
87
- const onPointerDown = useCallback((event) => {
88
- if (event.target.closest("button")) return;
89
- if (event.pointerType === "mouse" && event.button !== 0) return;
90
- event.currentTarget.setPointerCapture(event.pointerId);
91
- start.current = axis === "x" ? event.clientX : event.clientY;
92
- setSwiping(true);
93
- }, [axis]);
94
- const onPointerMove = useCallback((event) => {
95
- if (!swiping) return;
96
- const delta = (axis === "x" ? event.clientX : event.clientY) - start.current;
97
- setOffset(sign > 0 ? Math.max(0, delta) : Math.min(0, delta));
98
- }, [swiping, axis, sign]);
99
- const onPointerUp = useCallback(() => {
100
- if (!swiping) return;
101
- setSwiping(false);
102
- if (Math.abs(offset) >= swipeThreshold) {
103
- onDismiss();
104
- return;
105
- }
106
- setOffset(0);
107
- }, [swiping, offset, swipeThreshold, onDismiss]);
108
- const controls = { dismiss: onDismiss };
109
- return /* @__PURE__ */ jsx(
110
- "article",
111
- {
112
- "data-enigma-toast": "",
113
- "data-tone": notification.tone,
114
- "data-state": leaving ? "leaving" : "open",
115
- "data-index": index,
116
- "data-front": index === 0 ? "" : void 0,
117
- "data-swiping": swiping ? "" : void 0,
118
- role: notification.tone === "error" ? "alert" : "status",
119
- "aria-live": notification.tone === "error" ? "assertive" : "polite",
120
- tabIndex: 0,
121
- onKeyDown: (event) => {
122
- if (event.key === "Escape") onDismiss();
123
- },
124
- onPointerDown,
125
- onPointerMove,
126
- onPointerUp,
127
- onPointerCancel: onPointerUp,
128
- style: {
129
- // Read by the theme, so the transform stays the stylesheet's decision.
130
- "--enigma-toast-swipe": `${offset}px`,
131
- "--enigma-toast-index": index
132
- },
133
- children: render ? render(notification, controls) : /* @__PURE__ */ jsxs(Fragment, { children: [
134
- /* @__PURE__ */ jsx("p", { "data-enigma-toast-title": "", children: notification.title }),
135
- notification.body && /* @__PURE__ */ jsx("p", { "data-enigma-toast-body": "", children: notification.body }),
136
- notification.action && /* @__PURE__ */ jsx(
137
- "button",
138
- {
139
- type: "button",
140
- "data-enigma-toast-action": "",
141
- onClick: () => {
142
- notification.action?.onSelect();
143
- if (notification.action?.dismiss !== false) onDismiss();
144
- },
145
- children: notification.action.label
146
- }
147
- ),
148
- /* @__PURE__ */ jsx("button", { type: "button", "data-enigma-toast-close": "", "aria-label": "Dismiss", onClick: onDismiss, children: "\xD7" })
149
- ] })
150
- }
151
- );
152
- }
153
-
154
- export { Toaster };