@enigmax/primitives 0.17.0 → 0.19.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 (97) hide show
  1. package/dist/button-CaXaqG_K.d.ts +63 -0
  2. package/dist/chunk-2QFTRNAZ.js +88 -0
  3. package/dist/chunk-6BGBYUSZ.js +114 -0
  4. package/dist/chunk-AU3H5WIY.js +107 -0
  5. package/dist/{chunk-UZFEEFMF.js → chunk-D5A2ZMAG.js} +34 -1
  6. package/dist/chunk-DTWZDONY.js +99 -0
  7. package/dist/chunk-F25CGNQC.js +19 -0
  8. package/dist/chunk-FWVWX67R.js +462 -0
  9. package/dist/chunk-HC2ME5PU.js +168 -0
  10. package/dist/chunk-HS3X3XCW.js +43 -0
  11. package/dist/chunk-IXVMRVD4.js +96 -0
  12. package/dist/chunk-KJINGUQN.js +188 -0
  13. package/dist/chunk-MMQPZGSU.js +161 -0
  14. package/dist/chunk-OCMI7R6H.js +79 -0
  15. package/dist/chunk-QQFNAKMY.js +47 -0
  16. package/dist/chunk-QYMUIW5I.js +28 -0
  17. package/dist/chunk-R4ZAEE7V.js +249 -0
  18. package/dist/chunk-S653GLSF.js +17 -0
  19. package/dist/chunk-SNYUBXWQ.js +149 -0
  20. package/dist/chunk-U3V4EHOB.js +41 -0
  21. package/dist/chunk-UOSSNUSC.js +309 -0
  22. package/dist/chunk-XNNQRA35.js +31 -0
  23. package/dist/chunk-XQHCZAPJ.js +102 -0
  24. package/dist/chunk-ZCUFYBPB.js +154 -0
  25. package/dist/chunk-ZWR2EXHQ.js +55 -0
  26. package/dist/flags-BBJc9unY.d.ts +133 -0
  27. package/dist/index-dTdAbOWl.d.ts +144 -0
  28. package/dist/index.d.ts +18 -603
  29. package/dist/index.js +11 -2
  30. package/dist/input-BwXjFenq.d.ts +77 -0
  31. package/dist/marquee-CJ3Uwy3E.d.ts +81 -0
  32. package/dist/network-D2LsBG_k.d.ts +39 -0
  33. package/dist/next/index.d.ts +22 -4
  34. package/dist/next/index.js +25 -4
  35. package/dist/notifications-BpVV6sel.d.ts +70 -0
  36. package/dist/palette-D7iuQh_T.d.ts +86 -0
  37. package/dist/password-3DRQYAYQ.js +2 -0
  38. package/dist/password-C8lG4Zm9.d.ts +71 -0
  39. package/dist/password-FB2CUEKJ.js +1 -0
  40. package/dist/react/button.d.ts +75 -0
  41. package/dist/react/button.js +4 -0
  42. package/dist/react/flag.d.ts +37 -0
  43. package/dist/react/flag.js +3 -0
  44. package/dist/react/index.d.ts +29 -312
  45. package/dist/react/index.js +24 -3
  46. package/dist/react/input.d.ts +4 -0
  47. package/dist/react/input.js +3 -0
  48. package/dist/react/marquee.d.ts +44 -0
  49. package/dist/react/marquee.js +3 -0
  50. package/dist/react/network.d.ts +20 -0
  51. package/dist/react/network.js +3 -0
  52. package/dist/react/notifications.d.ts +17 -0
  53. package/dist/react/notifications.js +3 -0
  54. package/dist/react/palette.d.ts +231 -0
  55. package/dist/react/palette.js +5 -0
  56. package/dist/react/relative-time.d.ts +21 -0
  57. package/dist/react/relative-time.js +3 -0
  58. package/dist/react/search.d.ts +30 -0
  59. package/dist/react/search.js +3 -0
  60. package/dist/react/slot.d.ts +47 -0
  61. package/dist/react/slot.js +2 -0
  62. package/dist/react/toast.d.ts +40 -0
  63. package/dist/react/toast.js +4 -0
  64. package/dist/react-router/index.d.ts +22 -4
  65. package/dist/react-router/index.js +25 -4
  66. package/dist/relative-time-YpRTG7YH.d.ts +106 -0
  67. package/dist/search/index.d.ts +2 -2
  68. package/dist/search/index.js +1 -1
  69. package/dist/{search-CsO3L1Lw.d.ts → search-DXxY8SEH.d.ts} +15 -1
  70. package/dist/search-UQEXAPQB.js +50 -0
  71. package/package.json +54 -3
  72. package/recipes/input/styles.css +60 -20
  73. package/recipes/palette/styles.css +243 -0
  74. package/registry.json +186 -18
  75. package/src/core/flags.ts +88 -42
  76. package/src/core/input-icons.ts +34 -0
  77. package/src/core/input.ts +5 -21
  78. package/src/core/palette.ts +0 -0
  79. package/src/core/search.ts +60 -0
  80. package/src/index.ts +19 -3
  81. package/src/react/button.tsx +70 -4
  82. package/src/react/flag.tsx +18 -6
  83. package/src/react/index.ts +50 -5
  84. package/src/react/input/icon.tsx +17 -0
  85. package/src/react/input/index.tsx +307 -0
  86. package/src/react/input/password.tsx +174 -0
  87. package/src/react/input/search.tsx +82 -0
  88. package/src/react/input/types.ts +146 -0
  89. package/src/react/input/write-value.ts +18 -0
  90. package/src/react/palette/context.ts +60 -0
  91. package/src/react/palette/index.tsx +66 -0
  92. package/src/react/palette/root.tsx +661 -0
  93. package/src/react/slot.tsx +91 -0
  94. package/src/react/use-button.ts +3 -1
  95. package/dist/chunk-4GLYRF4B.js +0 -1259
  96. package/dist/chunk-JFNND6P4.js +0 -749
  97. package/src/react/input.tsx +0 -429
@@ -0,0 +1,309 @@
1
+ // src/core/marquee.ts
2
+ var DEFAULT_DECAY = 0.12;
3
+ var DRAG_THRESHOLD = 6;
4
+ var STILL_MS = 90;
5
+ var MAX_DT = 0.05;
6
+ var VELOCITY_SMOOTHING = 0.6;
7
+ var TOUCH_HOVER_SUPPRESS_MS = 1e3;
8
+ var IDLE_EPSILON = 0.01;
9
+ var DEFAULTS = {
10
+ speed: 60,
11
+ reverse: false,
12
+ vertical: false,
13
+ draggable: true,
14
+ hover: "off",
15
+ decay: DEFAULT_DECAY,
16
+ manageStyles: true,
17
+ copies: "clone"
18
+ };
19
+ var MAX_COPIES = 24;
20
+ function createMarquee(lane, track, options = {}) {
21
+ let opts = { ...DEFAULTS, ...options };
22
+ let period = 0;
23
+ let copyCount = Math.max(2, track.children.length);
24
+ let offset = 0;
25
+ let velocity = 0;
26
+ let running = false;
27
+ let paused = false;
28
+ let destroyed = false;
29
+ let frame = 0;
30
+ let lastTime = 0;
31
+ let dragging = false;
32
+ let pointerId = null;
33
+ let lastPointer = 0;
34
+ let startPointer = 0;
35
+ let lastMoveTime = 0;
36
+ let maxTravel = 0;
37
+ let hovering = false;
38
+ let lastTouchAt = 0;
39
+ const motionQuery = typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)") : null;
40
+ let reducedMotion = motionQuery?.matches ?? false;
41
+ function hoverTarget(cruise) {
42
+ const hover = opts.hover ?? "off";
43
+ if (hover === "off") return cruise;
44
+ if (hover === "pause") return 0;
45
+ if (typeof hover === "number") return cruise * hover;
46
+ return (cruise < 0 ? -1 : 1) * Math.abs(hover.speed);
47
+ }
48
+ function target() {
49
+ if (reducedMotion || paused) return 0;
50
+ const cruise = opts.reverse ? -opts.speed : opts.speed;
51
+ return hovering ? hoverTarget(cruise) : cruise;
52
+ }
53
+ function axisSize(element) {
54
+ return opts.vertical ? element.offsetHeight : element.offsetWidth;
55
+ }
56
+ function axisStart(element) {
57
+ return opts.vertical ? element.offsetTop : element.offsetLeft;
58
+ }
59
+ function applyStyles() {
60
+ if (!opts.manageStyles) return;
61
+ lane.style.overflow = "hidden";
62
+ lane.style.touchAction = opts.vertical ? "pan-x" : "pan-y";
63
+ lane.style.userSelect = "none";
64
+ lane.style.webkitUserSelect = "none";
65
+ track.style.willChange = "transform";
66
+ }
67
+ function render() {
68
+ const x = opts.vertical ? 0 : -offset;
69
+ const y = opts.vertical ? -offset : 0;
70
+ track.style.transform = `translate3d(${x}px, ${y}px, 0)`;
71
+ }
72
+ function readPeriod() {
73
+ const first = track.children[0];
74
+ const second = track.children[1];
75
+ if (!first) return 0;
76
+ if (second) {
77
+ const step = axisStart(second) - axisStart(first);
78
+ if (step > 0) return step;
79
+ }
80
+ return axisSize(first);
81
+ }
82
+ function requiredCopies(measured) {
83
+ if (measured <= 0) return 2;
84
+ return Math.min(MAX_COPIES, Math.max(2, Math.ceil(axisSize(lane) / measured) + 1));
85
+ }
86
+ function syncClones(count) {
87
+ const source = track.children[0];
88
+ if (!source) return;
89
+ while (track.children.length > count) track.removeChild(track.lastElementChild);
90
+ while (track.children.length < count) {
91
+ const clone = source.cloneNode(true);
92
+ clone.setAttribute("aria-hidden", "true");
93
+ clone.dataset.enigmaMarqueeCopy = String(track.children.length);
94
+ track.appendChild(clone);
95
+ }
96
+ }
97
+ function measure() {
98
+ if (destroyed) return;
99
+ const cloning = opts.copies === "clone";
100
+ if (cloning && track.children.length < 2) syncClones(2);
101
+ let measured = readPeriod();
102
+ let needed = requiredCopies(measured);
103
+ if (cloning && track.children.length !== needed) {
104
+ syncClones(needed);
105
+ measured = readPeriod();
106
+ needed = requiredCopies(measured);
107
+ }
108
+ const changed = measured !== period || needed !== copyCount;
109
+ period = measured;
110
+ if (needed !== copyCount) {
111
+ copyCount = needed;
112
+ opts.onCopyCountChange?.(needed);
113
+ }
114
+ if (period > 0) offset = wrap(offset);
115
+ render();
116
+ if (changed) opts.onMeasure?.(period, copyCount);
117
+ start();
118
+ }
119
+ function wrap(value) {
120
+ if (period <= 0) return value;
121
+ return (value % period + period) % period;
122
+ }
123
+ function tick(now) {
124
+ frame = 0;
125
+ if (destroyed) return;
126
+ const dt = Math.min((now - lastTime) / 1e3, MAX_DT);
127
+ lastTime = now;
128
+ if (!dragging) {
129
+ velocity += (target() - velocity) * (1 - Math.pow(opts.decay, dt));
130
+ offset = wrap(offset + velocity * dt);
131
+ }
132
+ render();
133
+ const idle = !dragging && Math.abs(velocity) < IDLE_EPSILON && Math.abs(target()) < IDLE_EPSILON;
134
+ if (idle) {
135
+ velocity = 0;
136
+ running = false;
137
+ return;
138
+ }
139
+ frame = requestAnimationFrame(tick);
140
+ }
141
+ function start() {
142
+ if (destroyed || running) return;
143
+ running = true;
144
+ lastTime = performance.now();
145
+ frame = requestAnimationFrame(tick);
146
+ }
147
+ function pointerDelta(event) {
148
+ return opts.vertical ? event.clientY : event.clientX;
149
+ }
150
+ function onPointerDown(event) {
151
+ if (event.pointerType !== "mouse") noteTouch();
152
+ if (!opts.draggable || event.button > 0 || pointerId !== null) return;
153
+ pointerId = event.pointerId;
154
+ dragging = true;
155
+ lastPointer = pointerDelta(event);
156
+ startPointer = lastPointer;
157
+ lastMoveTime = event.timeStamp;
158
+ maxTravel = 0;
159
+ lane.dataset.dragging = "true";
160
+ window.addEventListener("pointermove", onPointerMove);
161
+ window.addEventListener("pointerup", onPointerUp);
162
+ window.addEventListener("pointercancel", onPointerUp);
163
+ start();
164
+ }
165
+ function onPointerMove(event) {
166
+ if (event.pointerId !== pointerId) return;
167
+ const position = pointerDelta(event);
168
+ const delta = position - lastPointer;
169
+ lastPointer = position;
170
+ const dt = Math.max((event.timeStamp - lastMoveTime) / 1e3, 1e-3);
171
+ lastMoveTime = event.timeStamp;
172
+ maxTravel = Math.max(maxTravel, Math.abs(position - startPointer));
173
+ offset = wrap(offset - delta);
174
+ const instant = -delta / dt;
175
+ velocity = velocity * VELOCITY_SMOOTHING + instant * (1 - VELOCITY_SMOOTHING);
176
+ }
177
+ function onPointerUp(event) {
178
+ if (event.pointerId !== pointerId) return;
179
+ pointerId = null;
180
+ dragging = false;
181
+ delete lane.dataset.dragging;
182
+ window.removeEventListener("pointermove", onPointerMove);
183
+ window.removeEventListener("pointerup", onPointerUp);
184
+ window.removeEventListener("pointercancel", onPointerUp);
185
+ if (event.timeStamp - lastMoveTime > STILL_MS) velocity = 0;
186
+ start();
187
+ }
188
+ function onClickCapture(event) {
189
+ if (maxTravel <= DRAG_THRESHOLD) return;
190
+ event.preventDefault();
191
+ event.stopPropagation();
192
+ }
193
+ function onClick(event) {
194
+ if (maxTravel > DRAG_THRESHOLD) return;
195
+ opts.onItemClick?.(event);
196
+ }
197
+ function onPointerEnter(event) {
198
+ if (event.pointerType !== "mouse") {
199
+ noteTouch();
200
+ return;
201
+ }
202
+ if (Date.now() - lastTouchAt < TOUCH_HOVER_SUPPRESS_MS) return;
203
+ hovering = true;
204
+ lane.dataset.hovering = "true";
205
+ start();
206
+ }
207
+ function onPointerLeave() {
208
+ hovering = false;
209
+ delete lane.dataset.hovering;
210
+ start();
211
+ }
212
+ function noteTouch() {
213
+ lastTouchAt = Date.now();
214
+ if (!hovering) return;
215
+ hovering = false;
216
+ delete lane.dataset.hovering;
217
+ start();
218
+ }
219
+ function onMotionChange(event) {
220
+ reducedMotion = event.matches;
221
+ lane.dataset.reducedMotion = String(reducedMotion);
222
+ start();
223
+ }
224
+ const resizeObserver = typeof ResizeObserver === "function" ? new ResizeObserver(() => measure()) : null;
225
+ function init() {
226
+ applyStyles();
227
+ lane.dataset.enigmaMarquee = "";
228
+ lane.dataset.reducedMotion = String(reducedMotion);
229
+ track.dataset.enigmaMarqueeTrack = "";
230
+ for (const child of Array.from(track.children)) {
231
+ child.dataset.enigmaMarqueeCopy ??= "0";
232
+ }
233
+ lane.addEventListener("pointerdown", onPointerDown);
234
+ lane.addEventListener("pointerenter", onPointerEnter);
235
+ lane.addEventListener("pointerleave", onPointerLeave);
236
+ lane.addEventListener("touchstart", noteTouch, { passive: true });
237
+ lane.addEventListener("click", onClickCapture, true);
238
+ lane.addEventListener("click", onClick);
239
+ motionQuery?.addEventListener("change", onMotionChange);
240
+ resizeObserver?.observe(lane);
241
+ const first = track.children[0];
242
+ if (first) resizeObserver?.observe(first);
243
+ measure();
244
+ void document.fonts?.ready.then(() => measure());
245
+ remeasureOnImageLoad();
246
+ }
247
+ function remeasureOnImageLoad() {
248
+ for (const image of track.querySelectorAll("img")) {
249
+ if (image.complete) continue;
250
+ const done = () => {
251
+ image.removeEventListener("load", done);
252
+ image.removeEventListener("error", done);
253
+ if (!destroyed) measure();
254
+ };
255
+ image.addEventListener("load", done);
256
+ image.addEventListener("error", done);
257
+ }
258
+ }
259
+ init();
260
+ return {
261
+ get offset() {
262
+ return offset;
263
+ },
264
+ get period() {
265
+ return period;
266
+ },
267
+ get copyCount() {
268
+ return copyCount;
269
+ },
270
+ get dragging() {
271
+ return dragging;
272
+ },
273
+ get reducedMotion() {
274
+ return reducedMotion;
275
+ },
276
+ update(next) {
277
+ const styleKeys = next.vertical !== void 0 || next.manageStyles !== void 0;
278
+ opts = { ...opts, ...next };
279
+ if (styleKeys) applyStyles();
280
+ measure();
281
+ },
282
+ measure,
283
+ pause() {
284
+ paused = true;
285
+ start();
286
+ },
287
+ resume() {
288
+ paused = false;
289
+ start();
290
+ },
291
+ destroy() {
292
+ destroyed = true;
293
+ if (frame) cancelAnimationFrame(frame);
294
+ resizeObserver?.disconnect();
295
+ motionQuery?.removeEventListener("change", onMotionChange);
296
+ lane.removeEventListener("pointerdown", onPointerDown);
297
+ lane.removeEventListener("pointerenter", onPointerEnter);
298
+ lane.removeEventListener("pointerleave", onPointerLeave);
299
+ lane.removeEventListener("touchstart", noteTouch);
300
+ lane.removeEventListener("click", onClickCapture, true);
301
+ lane.removeEventListener("click", onClick);
302
+ window.removeEventListener("pointermove", onPointerMove);
303
+ window.removeEventListener("pointerup", onPointerUp);
304
+ window.removeEventListener("pointercancel", onPointerUp);
305
+ }
306
+ };
307
+ }
308
+
309
+ export { createMarquee };
@@ -0,0 +1,31 @@
1
+ import { flagView } from './chunk-AU3H5WIY.js';
2
+ import { jsx } from 'react/jsx-runtime';
3
+
4
+ function Flag({ code, label, decorative = false, locale, shape, format, source, basePath, size, fallback = null, ...rest }) {
5
+ const options = { shape, format, source, basePath, size, locale };
6
+ for (const key of Object.keys(options)) {
7
+ if (options[key] === void 0) delete options[key];
8
+ }
9
+ const view = flagView(code, options);
10
+ if (!view) return fallback;
11
+ const name = decorative ? "" : label?.trim() || view.name || "";
12
+ return /* @__PURE__ */ jsx(
13
+ "img",
14
+ {
15
+ src: view.src,
16
+ alt: name,
17
+ width: view.width,
18
+ height: view.height,
19
+ loading: "lazy",
20
+ decoding: "async",
21
+ "aria-hidden": name ? void 0 : true,
22
+ suppressHydrationWarning: true,
23
+ "data-enigma-flag": "",
24
+ "data-flag-code": view.code,
25
+ "data-flag-shape": view.shape,
26
+ ...rest
27
+ }
28
+ );
29
+ }
30
+
31
+ export { Flag };
@@ -0,0 +1,102 @@
1
+ import { estimatePasswordStrength } from './chunk-HC2ME5PU.js';
2
+ import { useMemo, useRef, useEffect, useState } from 'react';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+
5
+ var SCORE_LABELS = ["Very weak", "Weak", "Fair", "Strong", "Very strong"];
6
+ function PasswordExtras({
7
+ id,
8
+ value,
9
+ strength,
10
+ onStrengthChange,
11
+ onScore,
12
+ breach,
13
+ breachDelay,
14
+ onBreachChange,
15
+ classNames
16
+ }) {
17
+ const estimateOptions = typeof strength === "object" ? strength : void 0;
18
+ const userInputs = estimateOptions?.userInputs;
19
+ const report = useMemo(
20
+ () => strength === false ? null : estimatePasswordStrength(value, { userInputs }),
21
+ // The array is compared by identity, so a literal would re-estimate every render.
22
+ [strength, value, userInputs]
23
+ );
24
+ const strengthListener = useRef(onStrengthChange);
25
+ strengthListener.current = onStrengthChange;
26
+ const scoreListener = useRef(onScore);
27
+ scoreListener.current = onScore;
28
+ useEffect(() => {
29
+ if (!report) return;
30
+ strengthListener.current?.(report);
31
+ scoreListener.current(report.empty ? null : report.score);
32
+ }, [report]);
33
+ const [breachState, setBreachState] = useState({ status: "idle", count: 0, error: null });
34
+ const breachListener = useRef(onBreachChange);
35
+ breachListener.current = onBreachChange;
36
+ useEffect(() => {
37
+ if (!breach || !value) {
38
+ setBreachState({ status: "idle", count: 0, error: null });
39
+ return;
40
+ }
41
+ const controller = new AbortController();
42
+ const timer = setTimeout(() => {
43
+ setBreachState({ status: "checking", count: 0, error: null });
44
+ breach(value, { signal: controller.signal }).then((result) => {
45
+ if (controller.signal.aborted) return;
46
+ setBreachState({ status: result.breached ? "breached" : "safe", count: result.count, error: null });
47
+ }).catch((error) => {
48
+ if (controller.signal.aborted) return;
49
+ setBreachState({ status: "error", count: 0, error });
50
+ });
51
+ }, breachDelay);
52
+ return () => {
53
+ clearTimeout(timer);
54
+ controller.abort();
55
+ };
56
+ }, [breach, value, breachDelay]);
57
+ useEffect(() => {
58
+ breachListener.current(breachState);
59
+ }, [breachState]);
60
+ if (!report) return null;
61
+ return /* @__PURE__ */ jsx(PasswordStrength, { id, report, classNames });
62
+ }
63
+ function PasswordStrength({
64
+ value = "",
65
+ report,
66
+ userInputs,
67
+ segments = 5,
68
+ labels = SCORE_LABELS,
69
+ showWarning = true,
70
+ classNames,
71
+ ...props
72
+ }) {
73
+ const computed = useMemo(
74
+ () => report ?? estimatePasswordStrength(value, { userInputs }),
75
+ [report, value, userInputs]
76
+ );
77
+ const score = computed.score;
78
+ return /* @__PURE__ */ jsxs(
79
+ "div",
80
+ {
81
+ ...props,
82
+ "data-enigma-password-strength": "",
83
+ "data-score": computed.empty ? void 0 : score,
84
+ "data-empty": computed.empty ? "" : void 0,
85
+ children: [
86
+ /* @__PURE__ */ jsx("div", { "data-enigma-password-strength-track": "", "aria-hidden": "true", className: classNames?.track, children: Array.from({ length: segments }, (unused, index) => /* @__PURE__ */ jsx(
87
+ "span",
88
+ {
89
+ "data-enigma-password-strength-segment": "",
90
+ className: classNames?.segment,
91
+ "data-filled": !computed.empty && index <= score ? "" : void 0
92
+ },
93
+ index
94
+ )) }),
95
+ /* @__PURE__ */ jsx("p", { "data-enigma-password-strength-label": "", role: "status", "aria-live": "polite", className: classNames?.label, children: computed.empty ? "" : labels[score] ?? "" }),
96
+ showWarning && !computed.empty && computed.warnings.length > 0 && /* @__PURE__ */ jsx("p", { "data-enigma-password-strength-warning": "", className: classNames?.warning, children: computed.warnings[0] })
97
+ ]
98
+ }
99
+ );
100
+ }
101
+
102
+ export { PasswordExtras, PasswordStrength };
@@ -0,0 +1,154 @@
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 };
@@ -0,0 +1,55 @@
1
+ import { createMarquee } from './chunk-UOSSNUSC.js';
2
+ import { useRef, useState, useEffect, useLayoutEffect } from 'react';
3
+
4
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
5
+ function useMarquee(options = {}) {
6
+ const laneRef = useRef(null);
7
+ const trackRef = useRef(null);
8
+ const instanceRef = useRef(null);
9
+ const optionsRef = useRef(options);
10
+ optionsRef.current = options;
11
+ const [copies, setCopies] = useState(2);
12
+ const [dragging, setDragging] = useState(false);
13
+ const [period, setPeriod] = useState(0);
14
+ useIsomorphicLayoutEffect(() => {
15
+ const lane = laneRef.current;
16
+ const track = trackRef.current;
17
+ if (!lane || !track) return;
18
+ const instance = createMarquee(lane, track, {
19
+ ...optionsRef.current,
20
+ // React owns the DOM; cloning behind its back would be undone on render.
21
+ copies: "external",
22
+ onCopyCountChange: setCopies,
23
+ onMeasure: (measured) => setPeriod(measured),
24
+ onItemClick: (event) => optionsRef.current.onItemClick?.(event)
25
+ });
26
+ instanceRef.current = instance;
27
+ const syncDragging = () => setDragging(instance.dragging);
28
+ lane.addEventListener("pointerdown", syncDragging);
29
+ window.addEventListener("pointerup", syncDragging);
30
+ window.addEventListener("pointercancel", syncDragging);
31
+ return () => {
32
+ lane.removeEventListener("pointerdown", syncDragging);
33
+ window.removeEventListener("pointerup", syncDragging);
34
+ window.removeEventListener("pointercancel", syncDragging);
35
+ instance.destroy();
36
+ instanceRef.current = null;
37
+ };
38
+ }, []);
39
+ useIsomorphicLayoutEffect(() => {
40
+ instanceRef.current?.update(options);
41
+ }, [options.speed, options.reverse, options.vertical, options.draggable, options.hover, options.decay]);
42
+ useIsomorphicLayoutEffect(() => {
43
+ instanceRef.current?.measure();
44
+ }, [copies]);
45
+ return {
46
+ laneRef,
47
+ trackRef,
48
+ copies,
49
+ dragging,
50
+ period,
51
+ measure: () => instanceRef.current?.measure()
52
+ };
53
+ }
54
+
55
+ export { useMarquee };