@enigmax/primitives 0.21.0 → 0.23.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.
Files changed (83) hide show
  1. package/dist/chunk-3BQVOOAM.js +388 -0
  2. package/dist/{chunk-QQFNAKMY.js → chunk-45UHLZYT.js} +1 -1
  3. package/dist/chunk-4VUHQFAT.js +130 -0
  4. package/dist/{chunk-D5A2ZMAG.js → chunk-4ZPMP47J.js} +6 -1
  5. package/dist/chunk-DSBYVA7V.js +713 -0
  6. package/dist/{chunk-FWVWX67R.js → chunk-FGBIZDV2.js} +3 -3
  7. package/dist/{chunk-R4ZAEE7V.js → chunk-KEVZ5XQV.js} +1 -1
  8. package/dist/chunk-N6PDHMAX.js +213 -0
  9. package/dist/chunk-NONGREXC.js +248 -0
  10. package/dist/chunk-VKL3DEIQ.js +804 -0
  11. package/dist/chunk-WPTBURIC.js +1627 -0
  12. package/dist/chunk-WSQC3PCC.js +466 -0
  13. package/dist/context-menu-D3FtTn7v.d.ts +174 -0
  14. package/dist/{index-dTdAbOWl.d.ts → index-DQNnohoo.d.ts} +1 -1
  15. package/dist/index.d.ts +5 -1
  16. package/dist/index.js +8 -4
  17. package/dist/keys-D2zJs1uB.d.ts +100 -0
  18. package/dist/next/index.d.ts +12 -5
  19. package/dist/next/index.js +21 -14
  20. package/dist/react/button.d.ts +2 -2
  21. package/dist/react/context-menu.d.ts +202 -0
  22. package/dist/react/context-menu.js +6 -0
  23. package/dist/react/index.d.ts +11 -4
  24. package/dist/react/index.js +20 -13
  25. package/dist/react/input.d.ts +2 -2
  26. package/dist/react/input.js +1 -1
  27. package/dist/react/palette.d.ts +1 -1
  28. package/dist/react/palette.js +3 -3
  29. package/dist/react/search.d.ts +1 -1
  30. package/dist/react/search.js +2 -2
  31. package/dist/react/select.d.ts +217 -0
  32. package/dist/react/select.js +7 -0
  33. package/dist/react/selection.d.ts +104 -0
  34. package/dist/react/selection.js +4 -0
  35. package/dist/react/slot.d.ts +2 -2
  36. package/dist/react/toast.d.ts +165 -32
  37. package/dist/react/toast.js +1 -1
  38. package/dist/react-router/index.d.ts +12 -5
  39. package/dist/react-router/index.js +21 -14
  40. package/dist/search/index.d.ts +2 -2
  41. package/dist/search/index.js +1 -1
  42. package/dist/{search-DXxY8SEH.d.ts → search-DYgqRp37.d.ts} +20 -3
  43. package/dist/{search-UQEXAPQB.js → search-PBZORZ7P.js} +1 -1
  44. package/dist/select-ClSy-J1f.d.ts +100 -0
  45. package/dist/selection-B_pmzHpy.d.ts +150 -0
  46. package/package.json +21 -2
  47. package/recipes/context-menu/styles.css +177 -0
  48. package/recipes/input/styles.css +9 -0
  49. package/recipes/palette/styles.css +3 -0
  50. package/recipes/search/tailwind.tsx +3 -2
  51. package/recipes/select/styles.css +230 -0
  52. package/recipes/toast/styles.css +688 -192
  53. package/registry.json +434 -29
  54. package/src/core/context-menu.ts +694 -0
  55. package/src/core/keys.ts +264 -0
  56. package/src/core/search.ts +25 -1
  57. package/src/core/select.ts +404 -0
  58. package/src/core/selection.ts +648 -0
  59. package/src/index.ts +60 -1
  60. package/src/react/context-menu/context.ts +57 -0
  61. package/src/react/context-menu/index.tsx +94 -0
  62. package/src/react/context-menu/root.tsx +846 -0
  63. package/src/react/context-menu/styles.ts +186 -0
  64. package/src/react/index.ts +70 -2
  65. package/src/react/palette/root.tsx +2 -2
  66. package/src/react/select/context.ts +56 -0
  67. package/src/react/select/index.tsx +96 -0
  68. package/src/react/select/root.tsx +839 -0
  69. package/src/react/select/styles.ts +238 -0
  70. package/src/react/selection/index.tsx +115 -0
  71. package/src/react/selection/use-selection.ts +260 -0
  72. package/src/react/toast/NOTICE +20 -0
  73. package/src/react/toast/assets.tsx +85 -0
  74. package/src/react/toast/cn.ts +10 -0
  75. package/src/react/toast/hooks.ts +13 -0
  76. package/src/react/toast/index.tsx +736 -0
  77. package/src/react/toast/state.ts +207 -0
  78. package/src/react/toast/styles.ts +738 -0
  79. package/src/react/toast/types.ts +193 -0
  80. package/src/react/toaster.tsx +76 -341
  81. package/dist/chunk-JCCL7XKC.js +0 -465
  82. package/src/react/toast-styles.ts +0 -247
  83. /package/dist/{chunk-U3V4EHOB.js → chunk-3HDEZ2E7.js} +0 -0
@@ -1,465 +0,0 @@
1
- import { defaultQueue } from './chunk-QYMUIW5I.js';
2
- import { useState, useRef, useLayoutEffect, useEffect, useCallback } from 'react';
3
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
-
5
- // src/react/toast-styles.ts
6
- var TOAST_STYLES = `
7
- [data-enigma-toaster] {
8
- --enigma-toast-width: 356px;
9
- --enigma-toast-edge: 24px;
10
- --enigma-toast-gap: 14px;
11
- --enigma-toast-peek: 14px;
12
- --enigma-toast-scale-step: 0.06;
13
- --enigma-toast-radius: 12px;
14
- --enigma-toast-enter: 420ms;
15
- --enigma-toast-exit: 350ms;
16
-
17
- --enigma-toast-bg: #101010;
18
- --enigma-toast-border: #2a2a2a;
19
- --enigma-toast-text: #f5f5f5;
20
- --enigma-toast-muted: #a3a3a3;
21
- --enigma-toast-shadow: 0 6px 22px rgb(0 0 0 / 34%);
22
-
23
- --enigma-toast-success: hsl(150 80% 72%);
24
- --enigma-toast-success-bg: hsl(150 38% 9%);
25
- --enigma-toast-success-border: hsl(147 55% 18%);
26
- --enigma-toast-error: hsl(358 100% 82%);
27
- --enigma-toast-error-bg: hsl(358 42% 12%);
28
- --enigma-toast-error-border: hsl(357 55% 24%);
29
- --enigma-toast-info: hsl(216 87% 74%);
30
- --enigma-toast-info-bg: hsl(215 45% 11%);
31
- --enigma-toast-info-border: hsl(223 50% 24%);
32
- --enigma-toast-warning: hsl(46 87% 70%);
33
- --enigma-toast-warning-bg: hsl(40 55% 9%);
34
- --enigma-toast-warning-border: hsl(40 55% 20%);
35
-
36
- position: fixed;
37
- z-index: 999999;
38
- width: var(--enigma-toast-width);
39
- margin: 0;
40
- padding: 0;
41
- list-style: none;
42
- outline: none;
43
- font-family: inherit;
44
- font-size: 13px;
45
- /* The stack is a positioning context: every toast is absolute inside it, which is what
46
- lets the ones behind be clipped to the front one's height. */
47
- transition: transform 350ms ease;
48
- }
49
-
50
- @media (prefers-color-scheme: light) {
51
- [data-enigma-toaster] {
52
- --enigma-toast-bg: #ffffff;
53
- --enigma-toast-border: #e5e5e5;
54
- --enigma-toast-text: #171717;
55
- --enigma-toast-muted: #737373;
56
- --enigma-toast-shadow: 0 6px 22px rgb(0 0 0 / 12%);
57
- --enigma-toast-success-bg: hsl(143 85% 96%);
58
- --enigma-toast-success-border: hsl(145 92% 87%);
59
- --enigma-toast-success: hsl(140 100% 27%);
60
- --enigma-toast-error-bg: hsl(359 100% 97%);
61
- --enigma-toast-error-border: hsl(359 100% 94%);
62
- --enigma-toast-error: hsl(360 100% 45%);
63
- --enigma-toast-info-bg: hsl(208 100% 97%);
64
- --enigma-toast-info-border: hsl(221 91% 93%);
65
- --enigma-toast-info: hsl(210 92% 45%);
66
- --enigma-toast-warning-bg: hsl(49 100% 97%);
67
- --enigma-toast-warning-border: hsl(49 91% 84%);
68
- --enigma-toast-warning: hsl(31 92% 45%);
69
- }
70
- }
71
-
72
- [data-enigma-toaster][data-position^="top"] { top: var(--enigma-toast-edge); }
73
- [data-enigma-toaster][data-position^="bottom"] { bottom: var(--enigma-toast-edge); }
74
- [data-enigma-toaster][data-position$="-right"] { right: var(--enigma-toast-edge); }
75
- [data-enigma-toaster][data-position$="-left"] { left: var(--enigma-toast-edge); }
76
- [data-enigma-toaster][data-position$="-center"] { left: 50%; transform: translateX(-50%); }
77
- /* The whole stack lifts a little when it fans out, so the front toast does not sit on the
78
- exact pixel it did a moment ago. */
79
- [data-enigma-toaster][data-expanded][data-position^="bottom"] { transform: translateY(-6px); }
80
- [data-enigma-toaster][data-expanded][data-position^="top"] { transform: translateY(6px); }
81
- [data-enigma-toaster][data-expanded][data-position="bottom-center"] { transform: translateX(-50%) translateY(-6px); }
82
- [data-enigma-toaster][data-expanded][data-position="top-center"] { transform: translateX(-50%) translateY(6px); }
83
-
84
- [data-enigma-toast] {
85
- position: absolute;
86
- left: 0;
87
- right: 0;
88
- width: 100%;
89
- box-sizing: border-box;
90
- display: flex;
91
- align-items: center;
92
- gap: 10px;
93
- padding: 15px 16px;
94
- color: var(--enigma-toast-text);
95
- background: var(--enigma-toast-bg);
96
- border: 1px solid var(--enigma-toast-border);
97
- border-radius: var(--enigma-toast-radius);
98
- box-shadow: var(--enigma-toast-shadow);
99
- overflow-wrap: anywhere;
100
- touch-action: none;
101
- outline: none;
102
- z-index: var(--enigma-toast-z, 1);
103
- transition:
104
- transform var(--enigma-toast-enter) cubic-bezier(0.21, 1.02, 0.73, 1),
105
- opacity var(--enigma-toast-exit),
106
- height 350ms;
107
- }
108
-
109
- [data-enigma-toaster][data-position^="bottom"] [data-enigma-toast] { bottom: 0; transform-origin: bottom center; }
110
- [data-enigma-toaster][data-position^="top"] [data-enigma-toast] { top: 0; transform-origin: top center; }
111
-
112
- /* Enter from off screen, in the direction the stack is pinned to. */
113
- [data-enigma-toaster][data-position^="bottom"] [data-enigma-toast] { transform: translateY(110%); opacity: 0; }
114
- [data-enigma-toaster][data-position^="top"] [data-enigma-toast] { transform: translateY(-110%); opacity: 0; }
115
- /* Scoped to the toaster so it OUTRANKS the enter rules above, which are scoped the same
116
- way: at equal specificity the later rule wins, and a mounted toast that lost this one sat
117
- at its entry offset forever - visible, and 110% of its own height off the bottom. */
118
- [data-enigma-toaster] [data-enigma-toast][data-mounted] { opacity: 1; transform: translateY(0); }
119
-
120
- /* Collapsed: the ones behind are clipped to the FRONT toast's height and scaled back, so
121
- the stack reads as depth rather than as a list. Their content is hidden but present -
122
- removing it would make the box jump when the stack expands. */
123
- [data-enigma-toaster]:not([data-expanded]) [data-enigma-toast][data-mounted]:not([data-front]) {
124
- height: var(--enigma-toast-front-height);
125
- transform: translateY(calc(var(--enigma-toast-before) * var(--enigma-toast-peek) * -1))
126
- scale(calc(1 - var(--enigma-toast-before) * var(--enigma-toast-scale-step)));
127
- }
128
- [data-enigma-toaster][data-position^="top"]:not([data-expanded]) [data-enigma-toast][data-mounted]:not([data-front]) {
129
- transform: translateY(calc(var(--enigma-toast-before) * var(--enigma-toast-peek)))
130
- scale(calc(1 - var(--enigma-toast-before) * var(--enigma-toast-scale-step)));
131
- }
132
- [data-enigma-toaster]:not([data-expanded]) [data-enigma-toast]:not([data-front]) > * { opacity: 0; }
133
- [data-enigma-toast] > * { transition: opacity 350ms; }
134
-
135
- /* Expanded: each toast is lifted by the summed height of the ones in front of it. */
136
- [data-enigma-toaster][data-expanded][data-position^="bottom"] [data-enigma-toast][data-mounted] {
137
- transform: translateY(calc(var(--enigma-toast-offset) * -1));
138
- }
139
- [data-enigma-toaster][data-expanded][data-position^="top"] [data-enigma-toast][data-mounted] {
140
- transform: translateY(var(--enigma-toast-offset));
141
- }
142
-
143
- /* Past the visible count it is still in the DOM, still counted, and not on screen. */
144
- [data-enigma-toast][data-hidden] { opacity: 0; pointer-events: none; }
145
-
146
- [data-enigma-toast][data-state="leaving"] { opacity: 0; }
147
- [data-enigma-toaster]:not([data-expanded]) [data-enigma-toast][data-state="leaving"] { transform: translateY(150%) scale(0.9); }
148
- [data-enigma-toaster][data-expanded][data-position^="bottom"] [data-enigma-toast][data-state="leaving"] {
149
- transform: translateY(calc(var(--enigma-toast-offset) * -1 + 110%));
150
- }
151
-
152
- /* While a finger is down the toast tracks it exactly: a transition here would lag behind the
153
- pointer, which reads as a broken gesture rather than a smooth one. */
154
- [data-enigma-toast][data-swiping] {
155
- transition: opacity 200ms;
156
- transform: translateX(var(--enigma-toast-swipe, 0px));
157
- }
158
-
159
- [data-enigma-toast]:focus-visible { box-shadow: var(--enigma-toast-shadow), 0 0 0 2px var(--enigma-toast-info); }
160
-
161
- [data-enigma-toast][data-tone="success"] {
162
- color: var(--enigma-toast-success);
163
- background: var(--enigma-toast-success-bg);
164
- border-color: var(--enigma-toast-success-border);
165
- }
166
- [data-enigma-toast][data-tone="error"] {
167
- color: var(--enigma-toast-error);
168
- background: var(--enigma-toast-error-bg);
169
- border-color: var(--enigma-toast-error-border);
170
- }
171
- [data-enigma-toast][data-tone="info"] {
172
- color: var(--enigma-toast-info);
173
- background: var(--enigma-toast-info-bg);
174
- border-color: var(--enigma-toast-info-border);
175
- }
176
- [data-enigma-toast][data-tone="warning"] {
177
- color: var(--enigma-toast-warning);
178
- background: var(--enigma-toast-warning-bg);
179
- border-color: var(--enigma-toast-warning-border);
180
- }
181
-
182
- [data-enigma-toast-icon] { display: flex; flex-shrink: 0; width: 20px; height: 20px; }
183
- [data-enigma-toast-icon] svg { width: 20px; height: 20px; display: block; }
184
- [data-enigma-toast-icon][data-tone="loading"] svg { animation: enigma-toast-spin 900ms linear infinite; }
185
- @keyframes enigma-toast-spin { to { transform: rotate(360deg); } }
186
-
187
- [data-enigma-toast-content] { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
188
- [data-enigma-toast-title] { margin: 0; font-weight: 600; line-height: 1.4; }
189
- [data-enigma-toast-body] { margin: 0; font-size: 12px; line-height: 1.45; opacity: 0.75; }
190
-
191
- [data-enigma-toast-action] {
192
- flex-shrink: 0;
193
- padding: 5px 10px;
194
- font: inherit;
195
- font-size: 12px;
196
- font-weight: 600;
197
- color: var(--enigma-toast-bg);
198
- background: currentColor;
199
- border: 0;
200
- border-radius: 6px;
201
- cursor: pointer;
202
- }
203
-
204
- [data-enigma-toast-close] {
205
- flex-shrink: 0;
206
- display: grid;
207
- place-items: center;
208
- width: 20px;
209
- height: 20px;
210
- padding: 0;
211
- color: inherit;
212
- background: none;
213
- border: 0;
214
- border-radius: 5px;
215
- cursor: pointer;
216
- /* Shown on hover or focus, so it never covers the message while it is being read - but
217
- always reachable by keyboard, which display:none would take away. */
218
- opacity: 0;
219
- transition: opacity 120ms ease-out;
220
- }
221
- [data-enigma-toast]:hover [data-enigma-toast-close],
222
- [data-enigma-toast-close]:focus-visible { opacity: 0.7; }
223
- [data-enigma-toast-close]:hover { opacity: 1; }
224
-
225
- @media (max-width: 600px) {
226
- [data-enigma-toaster] { right: 14px; left: 14px; width: auto; }
227
- }
228
-
229
- @media (prefers-reduced-motion: reduce) {
230
- /* The movement goes, the state change stays: a toast still appears and still leaves. */
231
- [data-enigma-toast], [data-enigma-toast] > * { transition: opacity 200ms !important; }
232
- [data-enigma-toast][data-mounted] { transform: none !important; }
233
- }
234
- `;
235
- var VISIBLE = 3;
236
- var injected = false;
237
- function injectStyles() {
238
- if (injected || typeof document === "undefined") return;
239
- injected = true;
240
- if (document.querySelector("[data-enigma-toast-styles]")) return;
241
- const element = document.createElement("style");
242
- element.setAttribute("data-enigma-toast-styles", "");
243
- element.textContent = TOAST_STYLES;
244
- document.head.prepend(element);
245
- }
246
- var ICONS = {
247
- success: /* @__PURE__ */ jsx("path", { d: "M10 18a8 8 0 100-16 8 8 0 000 16zm3.86-9.81a.75.75 0 00-1.22-.88l-3.48 4.79-1.88-1.88a.75.75 0 10-1.06 1.06l2.5 2.5a.75.75 0 001.14-.09l4-5.5z" }),
248
- error: /* @__PURE__ */ jsx("path", { d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z" }),
249
- warning: /* @__PURE__ */ jsx("path", { d: "M9.4 3a1.5 1.5 0 012.6 0l7.36 12.75A1.5 1.5 0 0118.06 18H3.94a1.5 1.5 0 01-1.3-2.25L10 3zm.85 4.5a.75.75 0 00-1.5 0v4a.75.75 0 001.5 0v-4zM10 15a.9.9 0 100-1.8.9.9 0 000 1.8z" }),
250
- info: /* @__PURE__ */ jsx("path", { d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.25a.25.25 0 01.25.3l-.46 2.07A1.75 1.75 0 0010.75 15H11a.75.75 0 000-1.5h-.25a.25.25 0 01-.25-.3l.46-2.07A1.75 1.75 0 009.25 9H9z" })
251
- };
252
- function ToneIcon({ tone }) {
253
- if (!tone || tone === "default") return null;
254
- if (tone === "loading") {
255
- return /* @__PURE__ */ jsx("span", { "data-enigma-toast-icon": "", "data-tone": "loading", children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.22-8.56", opacity: "0.85" }) }) });
256
- }
257
- const glyph = ICONS[tone];
258
- if (!glyph) return null;
259
- return /* @__PURE__ */ jsx("span", { "data-enigma-toast-icon": "", "data-tone": tone, children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true", children: glyph }) });
260
- }
261
- function swipeAxis(position) {
262
- if (position.endsWith("-center")) return { axis: "y", sign: position.startsWith("top") ? -1 : 1 };
263
- return { axis: "x", sign: position.endsWith("-left") ? -1 : 1 };
264
- }
265
- function Toaster({
266
- queue = defaultQueue,
267
- position = "bottom-right",
268
- exitDuration = 350,
269
- swipeThreshold = 60,
270
- visibleCount = VISIBLE,
271
- expand = false,
272
- styles: withStyles = true,
273
- children,
274
- label = "Notifications",
275
- className,
276
- style
277
- }) {
278
- const [rendered, setRendered] = useState(
279
- () => queue.items.map((notification) => ({ notification, leaving: false }))
280
- );
281
- const timers = useRef(/* @__PURE__ */ new Map());
282
- const [hovered, setHovered] = useState(false);
283
- const [heights, setHeights] = useState({});
284
- useLayoutEffect(() => {
285
- if (withStyles) injectStyles();
286
- }, [withStyles]);
287
- useEffect(() => {
288
- const sync = (items) => {
289
- setRendered((current) => {
290
- const live = new Map(items.map((item) => [item.id, item]));
291
- const kept = current.map((entry) => {
292
- const still = live.get(entry.notification.id);
293
- if (still) return { notification: still, leaving: false };
294
- if (entry.leaving) return entry;
295
- const timer = setTimeout(() => {
296
- timers.current.delete(entry.notification.id);
297
- setRendered((later) => later.filter((row) => row.notification.id !== entry.notification.id));
298
- }, exitDuration);
299
- timers.current.set(entry.notification.id, timer);
300
- return { ...entry, leaving: true };
301
- });
302
- const seen = new Set(kept.map((entry) => entry.notification.id));
303
- const added = items.filter((item) => !seen.has(item.id)).map((item) => ({ notification: item, leaving: false }));
304
- return [...kept, ...added];
305
- });
306
- };
307
- sync(queue.items);
308
- return queue.subscribe(sync);
309
- }, [queue, exitDuration]);
310
- useEffect(() => {
311
- const pending = timers.current;
312
- return () => {
313
- for (const timer of pending.values()) clearTimeout(timer);
314
- pending.clear();
315
- };
316
- }, []);
317
- if (!rendered.length) return null;
318
- const expanded = expand || hovered;
319
- const stack = [...rendered].reverse();
320
- const frontHeight = heights[stack[0]?.notification.id ?? ""] ?? 0;
321
- return /* @__PURE__ */ jsx(
322
- "section",
323
- {
324
- "aria-label": label,
325
- "data-enigma-toaster": "",
326
- "data-position": position,
327
- "data-expanded": expanded ? "" : void 0,
328
- style: { ...style, "--enigma-toast-front-height": `${frontHeight}px` },
329
- className,
330
- onPointerEnter: () => {
331
- queue.pause();
332
- setHovered(true);
333
- },
334
- onPointerLeave: () => {
335
- queue.resume();
336
- setHovered(false);
337
- },
338
- onFocusCapture: () => {
339
- queue.pause();
340
- setHovered(true);
341
- },
342
- onBlurCapture: () => {
343
- queue.resume();
344
- setHovered(false);
345
- },
346
- children: stack.map((entry, index) => /* @__PURE__ */ jsx(
347
- Toast,
348
- {
349
- entry,
350
- index,
351
- hidden: index >= visibleCount,
352
- offset: stack.slice(0, index).reduce((total, row) => total + (heights[row.notification.id] ?? 0) + 14, 0),
353
- onMeasure: (height) => setHeights((current) => current[entry.notification.id] === height ? current : { ...current, [entry.notification.id]: height }),
354
- position,
355
- swipeThreshold,
356
- onDismiss: () => queue.dismiss(entry.notification.id),
357
- render: children
358
- },
359
- entry.notification.id
360
- ))
361
- }
362
- );
363
- }
364
- function Toast({ entry, index, hidden, offset: stackOffset, onMeasure, position, swipeThreshold, onDismiss, render }) {
365
- const { notification, leaving } = entry;
366
- const [offset, setOffset] = useState(0);
367
- const [swiping, setSwiping] = useState(false);
368
- const [mounted, setMounted] = useState(false);
369
- const node = useRef(null);
370
- const start = useRef(0);
371
- const { axis, sign } = swipeAxis(position);
372
- useLayoutEffect(() => {
373
- const element = node.current;
374
- if (!element) return;
375
- const report = () => onMeasure(element.getBoundingClientRect().height);
376
- report();
377
- if (typeof ResizeObserver === "undefined") return;
378
- const observer = new ResizeObserver(report);
379
- observer.observe(element);
380
- return () => observer.disconnect();
381
- }, [notification.title, notification.body]);
382
- useEffect(() => {
383
- const frame = requestAnimationFrame(() => setMounted(true));
384
- return () => cancelAnimationFrame(frame);
385
- }, []);
386
- const onPointerDown = useCallback((event) => {
387
- if (event.target.closest("button")) return;
388
- if (event.pointerType === "mouse" && event.button !== 0) return;
389
- event.currentTarget.setPointerCapture(event.pointerId);
390
- start.current = axis === "x" ? event.clientX : event.clientY;
391
- setSwiping(true);
392
- }, [axis]);
393
- const onPointerMove = useCallback((event) => {
394
- if (!swiping) return;
395
- const delta = (axis === "x" ? event.clientX : event.clientY) - start.current;
396
- setOffset(sign > 0 ? Math.max(0, delta) : Math.min(0, delta));
397
- }, [swiping, axis, sign]);
398
- const onPointerUp = useCallback(() => {
399
- if (!swiping) return;
400
- setSwiping(false);
401
- if (Math.abs(offset) >= swipeThreshold) {
402
- onDismiss();
403
- return;
404
- }
405
- setOffset(0);
406
- }, [swiping, offset, swipeThreshold, onDismiss]);
407
- const controls = { dismiss: onDismiss };
408
- return /* @__PURE__ */ jsx(
409
- "article",
410
- {
411
- ref: (element) => {
412
- node.current = element;
413
- },
414
- "data-enigma-toast": "",
415
- "data-tone": notification.tone,
416
- "data-state": leaving ? "leaving" : "open",
417
- "data-index": index,
418
- "data-front": index === 0 ? "" : void 0,
419
- "data-mounted": mounted && !leaving ? "" : void 0,
420
- "data-hidden": hidden ? "" : void 0,
421
- "data-swiping": swiping ? "" : void 0,
422
- role: notification.tone === "error" ? "alert" : "status",
423
- "aria-live": notification.tone === "error" ? "assertive" : "polite",
424
- tabIndex: 0,
425
- onKeyDown: (event) => {
426
- if (event.key === "Escape") onDismiss();
427
- },
428
- onPointerDown,
429
- onPointerMove,
430
- onPointerUp,
431
- onPointerCancel: onPointerUp,
432
- style: {
433
- // Read by the theme, so every transform stays the stylesheet's decision.
434
- "--enigma-toast-swipe": `${offset}px`,
435
- "--enigma-toast-index": index,
436
- "--enigma-toast-before": index,
437
- "--enigma-toast-offset": `${stackOffset}px`,
438
- // The front toast is on top, and each one behind it a layer lower.
439
- "--enigma-toast-z": 100 - index
440
- },
441
- children: render ? render(notification, controls) : /* @__PURE__ */ jsxs(Fragment, { children: [
442
- /* @__PURE__ */ jsx(ToneIcon, { tone: notification.tone }),
443
- /* @__PURE__ */ jsxs("span", { "data-enigma-toast-content": "", children: [
444
- /* @__PURE__ */ jsx("p", { "data-enigma-toast-title": "", children: notification.title }),
445
- notification.body && /* @__PURE__ */ jsx("p", { "data-enigma-toast-body": "", children: notification.body })
446
- ] }),
447
- notification.action && /* @__PURE__ */ jsx(
448
- "button",
449
- {
450
- type: "button",
451
- "data-enigma-toast-action": "",
452
- onClick: () => {
453
- notification.action?.onSelect();
454
- if (notification.action?.dismiss !== false) onDismiss();
455
- },
456
- children: notification.action.label
457
- }
458
- ),
459
- /* @__PURE__ */ jsx("button", { type: "button", "data-enigma-toast-close": "", "aria-label": "Dismiss", onClick: onDismiss, children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "12", height: "12", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" }) }) })
460
- ] })
461
- }
462
- );
463
- }
464
-
465
- export { Toaster };