@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
@@ -1,749 +0,0 @@
1
- import { generatePassword, estimatePasswordStrength, INPUT_ICON_PATHS, createNotifications, createButton, createMarquee, createInput, createNetworkMonitor, SERVER_NETWORK_STATE, relativeTimeView, relativeTimeAttributes, flagView } from './chunk-4GLYRF4B.js';
2
- import { createSearch } from './chunk-UZFEEFMF.js';
3
- import { forwardRef, createElement, useRef, useState, useId, useCallback, useEffect, useMemo, useSyncExternalStore, useLayoutEffect } from 'react';
4
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
-
6
- var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
7
- function useMarquee(options = {}) {
8
- const laneRef = useRef(null);
9
- const trackRef = useRef(null);
10
- const instanceRef = useRef(null);
11
- const optionsRef = useRef(options);
12
- optionsRef.current = options;
13
- const [copies, setCopies] = useState(2);
14
- const [dragging, setDragging] = useState(false);
15
- const [period, setPeriod] = useState(0);
16
- useIsomorphicLayoutEffect(() => {
17
- const lane = laneRef.current;
18
- const track = trackRef.current;
19
- if (!lane || !track) return;
20
- const instance = createMarquee(lane, track, {
21
- ...optionsRef.current,
22
- // React owns the DOM; cloning behind its back would be undone on render.
23
- copies: "external",
24
- onCopyCountChange: setCopies,
25
- onMeasure: (measured) => setPeriod(measured),
26
- onItemClick: (event) => optionsRef.current.onItemClick?.(event)
27
- });
28
- instanceRef.current = instance;
29
- const syncDragging = () => setDragging(instance.dragging);
30
- lane.addEventListener("pointerdown", syncDragging);
31
- window.addEventListener("pointerup", syncDragging);
32
- window.addEventListener("pointercancel", syncDragging);
33
- return () => {
34
- lane.removeEventListener("pointerdown", syncDragging);
35
- window.removeEventListener("pointerup", syncDragging);
36
- window.removeEventListener("pointercancel", syncDragging);
37
- instance.destroy();
38
- instanceRef.current = null;
39
- };
40
- }, []);
41
- useIsomorphicLayoutEffect(() => {
42
- instanceRef.current?.update(options);
43
- }, [options.speed, options.reverse, options.vertical, options.draggable, options.hover, options.decay]);
44
- useIsomorphicLayoutEffect(() => {
45
- instanceRef.current?.measure();
46
- }, [copies]);
47
- return {
48
- laneRef,
49
- trackRef,
50
- copies,
51
- dragging,
52
- period,
53
- measure: () => instanceRef.current?.measure()
54
- };
55
- }
56
- var useIsomorphicLayoutEffect2 = typeof window === "undefined" ? useEffect : useLayoutEffect;
57
- function useInput(options = {}) {
58
- const inputRef = useRef(null);
59
- const actionsRef = useRef(null);
60
- const instanceRef = useRef(null);
61
- const optionsRef = useRef(options);
62
- optionsRef.current = options;
63
- const [revealed, setRevealed] = useState(false);
64
- useIsomorphicLayoutEffect2(() => {
65
- const input = inputRef.current;
66
- if (!input) return;
67
- const instance = createInput(input, {
68
- ...optionsRef.current,
69
- container: actionsRef.current ?? optionsRef.current.container,
70
- onRevealChange: (next) => {
71
- setRevealed(next);
72
- optionsRef.current.onRevealChange?.(next);
73
- }
74
- });
75
- instanceRef.current = instance;
76
- return () => {
77
- instance.destroy();
78
- instanceRef.current = null;
79
- };
80
- }, []);
81
- useIsomorphicLayoutEffect2(() => {
82
- instanceRef.current?.update(options);
83
- }, [options.reveal, options.actions, options.position]);
84
- return {
85
- inputRef,
86
- actionsRef,
87
- revealed,
88
- reveal: (next) => instanceRef.current?.reveal(next),
89
- refresh: () => instanceRef.current?.refresh()
90
- };
91
- }
92
- function useSearch(options = {}) {
93
- const inputRef = useRef(null);
94
- const instanceRef = useRef(null);
95
- const optionsRef = useRef(options);
96
- optionsRef.current = options;
97
- const [query, setQuery] = useState("");
98
- const [results, setResults] = useState([]);
99
- const instance = useMemo(() => {
100
- const created = createSearch({
101
- ...optionsRef.current,
102
- onResults: (next, nextQuery) => {
103
- setResults(next);
104
- setQuery(nextQuery);
105
- optionsRef.current.onResults?.(next, nextQuery);
106
- }
107
- });
108
- instanceRef.current = created;
109
- return created;
110
- }, []);
111
- useEffect(() => () => instance.destroy(), [instance]);
112
- useEffect(() => {
113
- instance.setItems(options.items ?? []);
114
- }, [instance, options.items]);
115
- useEffect(() => {
116
- const input = inputRef.current;
117
- if (!input) return;
118
- return instance.attach(input);
119
- }, [instance]);
120
- return {
121
- inputRef,
122
- query,
123
- results,
124
- search: (next) => instance.search(next),
125
- searchNow: (next) => {
126
- instance.searchNow(next);
127
- },
128
- clear: () => {
129
- if (inputRef.current) inputRef.current.value = "";
130
- instance.searchNow("");
131
- }
132
- };
133
- }
134
- function useButton(options = {}) {
135
- const optionsRef = useRef(options);
136
- optionsRef.current = options;
137
- const instance = useMemo(() => createButton({
138
- ...optionsRef.current,
139
- onPress: (event) => optionsRef.current.onPress?.(event),
140
- onChange: (next) => optionsRef.current.onChange?.(next)
141
- // Built once: recreating it would drop a running cooldown on every render.
142
- // eslint-disable-next-line react-hooks/exhaustive-deps
143
- }), []);
144
- const [state, setState] = useState(() => instance.state);
145
- useEffect(() => {
146
- const unsubscribe = instance.subscribe(setState);
147
- setState(instance.state);
148
- return () => {
149
- unsubscribe();
150
- instance.destroy();
151
- };
152
- }, [instance]);
153
- useEffect(() => {
154
- instance.update({
155
- href: options.href,
156
- disabled: options.disabled,
157
- loading: options.loading,
158
- cooldown: options.cooldown,
159
- shortcut: options.shortcut
160
- });
161
- }, [instance, options.href, options.disabled, options.loading, options.cooldown, options.shortcut]);
162
- const press = useCallback(() => {
163
- void instance.press();
164
- }, [instance]);
165
- return {
166
- ...state,
167
- press,
168
- reset: () => instance.reset(),
169
- props: {
170
- onClick: (event) => {
171
- if (!state.available) {
172
- event.preventDefault();
173
- return;
174
- }
175
- void instance.press(event);
176
- },
177
- "aria-disabled": !state.available,
178
- "aria-busy": state.loading,
179
- ...state.loading ? { "data-loading": "" } : {},
180
- ...state.cooldown > 0 ? { "data-cooldown": "" } : {},
181
- ...state.element === "button" ? { disabled: state.disabled } : { href: options.href },
182
- ...state.shortcut ? { title: `Shortcut: ${state.shortcut.toUpperCase()}` } : {}
183
- }
184
- };
185
- }
186
-
187
- // src/react/link.ts
188
- var linkComponent = null;
189
- function setLinkComponent(component) {
190
- linkComponent = component;
191
- }
192
- function getLinkComponent() {
193
- return linkComponent ?? "a";
194
- }
195
- var Button = forwardRef(function Button2({
196
- as,
197
- children,
198
- pending,
199
- href,
200
- disabled,
201
- loading: loading2,
202
- cooldown,
203
- shortcut,
204
- onPress,
205
- onClick,
206
- onChange,
207
- type = "button",
208
- ...rest
209
- }, ref) {
210
- const button = useButton({ href, disabled, loading: loading2, cooldown, shortcut, onPress: onPress ?? onClick, onChange });
211
- const state = button;
212
- const Tag = as ?? (state.element === "a" ? getLinkComponent() : "button");
213
- const native = Tag === "button" ? { type } : {};
214
- const label = typeof children === "function" ? children(state) : children;
215
- return createElement(
216
- Tag,
217
- { ref, ...button.props, ...native, ...rest },
218
- state.loading && pending !== void 0 ? pending : label
219
- );
220
- });
221
- var ICON_PROPS = {
222
- viewBox: "0 0 24 24",
223
- width: "1em",
224
- height: "1em",
225
- fill: "none",
226
- stroke: "currentColor",
227
- strokeWidth: 2,
228
- strokeLinecap: "round",
229
- strokeLinejoin: "round",
230
- "aria-hidden": true
231
- };
232
- function Icon({ paths }) {
233
- return /* @__PURE__ */ jsx("svg", { ...ICON_PROPS, children: paths.map((path) => /* @__PURE__ */ jsx("path", { d: path }, path)) });
234
- }
235
- function writeValue(input, next) {
236
- const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
237
- if (setter) setter.call(input, next);
238
- else input.value = next;
239
- input.dispatchEvent(new Event("input", { bubbles: true }));
240
- }
241
- var Input = forwardRef(function Input2({
242
- reveal,
243
- revealLabels,
244
- generate = false,
245
- generateLabel = "Generate a password",
246
- revealOnGenerate = true,
247
- copyOnGenerate = false,
248
- onGenerate,
249
- actions = [],
250
- position = "end",
251
- strength = false,
252
- onStrengthChange,
253
- breach,
254
- breachDelay = 500,
255
- onBreachChange,
256
- wrapperProps,
257
- fieldProps,
258
- classNames,
259
- children,
260
- type = "text",
261
- onChange,
262
- ...props
263
- }, forwardedRef) {
264
- const innerRef = useRef(null);
265
- const [revealed, setRevealed] = useState(false);
266
- const describedBy = useId();
267
- const isPassword = type === "password";
268
- const showReveal = reveal ?? isPassword;
269
- const showGenerate = generate !== false && isPassword;
270
- const controlled = props.value !== void 0;
271
- const [ownValue, setOwnValue] = useState(String(props.defaultValue ?? ""));
272
- const value = controlled ? String(props.value ?? "") : ownValue;
273
- const handleChange = useCallback((event) => {
274
- if (!controlled) setOwnValue(event.target.value);
275
- onChange?.(event);
276
- }, [controlled, onChange]);
277
- const caret = useRef(null);
278
- const toggleReveal = useCallback(() => {
279
- const input = innerRef.current;
280
- if (input && typeof document !== "undefined" && document.activeElement === input) {
281
- try {
282
- const { selectionStart, selectionEnd } = input;
283
- caret.current = selectionStart === null || selectionEnd === null ? null : [selectionStart, selectionEnd];
284
- } catch {
285
- caret.current = null;
286
- }
287
- }
288
- setRevealed((current) => !current);
289
- }, []);
290
- useEffect(() => {
291
- const selection = caret.current;
292
- if (!selection) return;
293
- caret.current = null;
294
- const restore = () => {
295
- const input = innerRef.current;
296
- if (!input || document.activeElement !== input) return;
297
- try {
298
- input.setSelectionRange(selection[0], selection[1]);
299
- } catch {
300
- }
301
- };
302
- restore();
303
- const timer = setTimeout(restore, 0);
304
- return () => clearTimeout(timer);
305
- }, [revealed]);
306
- const handleGenerate = useCallback(() => {
307
- const input = innerRef.current;
308
- if (!input) return;
309
- const password = generatePassword(typeof generate === "object" ? generate : {});
310
- writeValue(input, password);
311
- if (revealOnGenerate) setRevealed(true);
312
- if (copyOnGenerate) void navigator.clipboard?.writeText(password).catch(() => {
313
- });
314
- input.focus();
315
- onGenerate?.(password);
316
- }, [generate, revealOnGenerate, copyOnGenerate, onGenerate]);
317
- const estimateOptions = typeof strength === "object" ? strength : void 0;
318
- const userInputs = estimateOptions?.userInputs;
319
- const report = useMemo(
320
- () => strength === false ? null : estimatePasswordStrength(value, { userInputs }),
321
- // The array is compared by identity, so a literal would re-estimate every render.
322
- [strength, value, userInputs]
323
- );
324
- const strengthListener = useRef(onStrengthChange);
325
- strengthListener.current = onStrengthChange;
326
- useEffect(() => {
327
- if (report) strengthListener.current?.(report);
328
- }, [report]);
329
- const [breachState, setBreachState] = useState({ status: "idle", count: 0, error: null });
330
- const breachListener = useRef(onBreachChange);
331
- breachListener.current = onBreachChange;
332
- useEffect(() => {
333
- if (!breach || !value) {
334
- setBreachState({ status: "idle", count: 0, error: null });
335
- return;
336
- }
337
- const controller = new AbortController();
338
- const timer = setTimeout(() => {
339
- setBreachState({ status: "checking", count: 0, error: null });
340
- breach(value, { signal: controller.signal }).then((result) => {
341
- if (controller.signal.aborted) return;
342
- setBreachState({ status: result.breached ? "breached" : "safe", count: result.count, error: null });
343
- }).catch((error) => {
344
- if (controller.signal.aborted) return;
345
- setBreachState({ status: "error", count: 0, error });
346
- });
347
- }, breachDelay);
348
- return () => {
349
- clearTimeout(timer);
350
- controller.abort();
351
- };
352
- }, [breach, value, breachDelay]);
353
- useEffect(() => {
354
- breachListener.current?.(breachState);
355
- }, [breachState]);
356
- const builtIn = [];
357
- if (showGenerate) {
358
- builtIn.push({
359
- name: "generate",
360
- label: generateLabel,
361
- icon: /* @__PURE__ */ jsx(Icon, { paths: INPUT_ICON_PATHS.generate }),
362
- onSelect: handleGenerate
363
- });
364
- }
365
- if (showReveal) {
366
- builtIn.push({
367
- name: "reveal",
368
- label: revealed ? revealLabels?.hide ?? "Hide password" : revealLabels?.show ?? "Show password",
369
- icon: /* @__PURE__ */ jsx(Icon, { paths: revealed ? INPUT_ICON_PATHS.eyeOff : INPUT_ICON_PATHS.eye }),
370
- pressed: revealed,
371
- onSelect: toggleReveal
372
- });
373
- }
374
- const overridden = new Set(actions.map((action) => action.name));
375
- const rendered = [...builtIn.filter((action) => !overridden.has(action.name)), ...actions].filter((action) => action.visible !== false);
376
- const locked = props.disabled === true || props.readOnly === true;
377
- const buttons = rendered.length === 0 ? null : /* @__PURE__ */ jsx("span", { "data-enigma-input-actions": "", "data-position": position, className: classNames?.actions, children: rendered.map((action) => /* @__PURE__ */ jsx(
378
- "button",
379
- {
380
- type: "button",
381
- "data-enigma-input-action": action.name,
382
- className: classNames?.action,
383
- "aria-label": action.label,
384
- title: action.label,
385
- "aria-pressed": action.pressed,
386
- disabled: locked,
387
- onMouseDown: (event) => event.preventDefault(),
388
- onClick: action.onSelect,
389
- children: action.icon
390
- },
391
- action.name
392
- )) });
393
- return /* @__PURE__ */ jsxs(
394
- "div",
395
- {
396
- ...wrapperProps,
397
- "data-enigma-input-root": "",
398
- "data-revealed": revealed ? "" : void 0,
399
- "data-breached": breachState.status === "breached" ? "" : void 0,
400
- "data-score": report && !report.empty ? report.score : void 0,
401
- children: [
402
- /* @__PURE__ */ jsxs("div", { ...fieldProps, "data-enigma-input-field": "", children: [
403
- position === "start" && buttons,
404
- /* @__PURE__ */ jsx(
405
- "input",
406
- {
407
- ...props,
408
- ref: (node) => {
409
- innerRef.current = node;
410
- if (typeof forwardedRef === "function") forwardedRef(node);
411
- else if (forwardedRef) forwardedRef.current = node;
412
- },
413
- type: revealed && isPassword ? "text" : type,
414
- onChange: handleChange,
415
- "data-enigma-input": "",
416
- "aria-describedby": report ? describedBy : props["aria-describedby"]
417
- }
418
- ),
419
- position === "end" && buttons
420
- ] }),
421
- report && /* @__PURE__ */ jsx(PasswordStrength, { id: describedBy, report, classNames: classNames?.strength }),
422
- children
423
- ]
424
- }
425
- );
426
- });
427
- var SCORE_LABELS = ["Very weak", "Weak", "Fair", "Strong", "Very strong"];
428
- function PasswordStrength({
429
- value = "",
430
- report,
431
- userInputs,
432
- segments = 5,
433
- labels = SCORE_LABELS,
434
- showWarning = true,
435
- classNames,
436
- ...props
437
- }) {
438
- const computed = useMemo(
439
- () => report ?? estimatePasswordStrength(value, { userInputs }),
440
- [report, value, userInputs]
441
- );
442
- const score = computed.score;
443
- return /* @__PURE__ */ jsxs(
444
- "div",
445
- {
446
- ...props,
447
- "data-enigma-password-strength": "",
448
- "data-score": computed.empty ? void 0 : score,
449
- "data-empty": computed.empty ? "" : void 0,
450
- children: [
451
- /* @__PURE__ */ jsx("div", { "data-enigma-password-strength-track": "", "aria-hidden": "true", className: classNames?.track, children: Array.from({ length: segments }, (unused, index) => /* @__PURE__ */ jsx(
452
- "span",
453
- {
454
- "data-enigma-password-strength-segment": "",
455
- className: classNames?.segment,
456
- "data-filled": !computed.empty && index <= score ? "" : void 0
457
- },
458
- index
459
- )) }),
460
- /* @__PURE__ */ jsx("p", { "data-enigma-password-strength-label": "", role: "status", "aria-live": "polite", className: classNames?.label, children: computed.empty ? "" : labels[score] ?? "" }),
461
- showWarning && !computed.empty && computed.warnings.length > 0 && /* @__PURE__ */ jsx("p", { "data-enigma-password-strength-warning": "", className: classNames?.warning, children: computed.warnings[0] })
462
- ]
463
- }
464
- );
465
- }
466
- var defaultQueue = createNotifications();
467
- function useNotifications(queue = defaultQueue) {
468
- const snapshot = useRef(queue.items);
469
- const items = useSyncExternalStore(
470
- (listener) => queue.subscribe((next) => {
471
- snapshot.current = next;
472
- listener();
473
- }),
474
- () => snapshot.current,
475
- () => snapshot.current
476
- );
477
- return useMemo(() => ({
478
- items,
479
- notify: queue.notify,
480
- dismiss: queue.dismiss,
481
- dismissAll: queue.dismissAll,
482
- pause: queue.pause,
483
- resume: queue.resume
484
- }), [items, queue]);
485
- }
486
- function createNotificationQueue(options) {
487
- return createNotifications(options);
488
- }
489
- function swipeAxis(position) {
490
- if (position.endsWith("-center")) return { axis: "y", sign: position.startsWith("top") ? -1 : 1 };
491
- return { axis: "x", sign: position.endsWith("-left") ? -1 : 1 };
492
- }
493
- function Toaster({
494
- queue = defaultQueue,
495
- position = "bottom-right",
496
- exitDuration = 200,
497
- swipeThreshold = 60,
498
- children,
499
- label = "Notifications",
500
- className,
501
- style
502
- }) {
503
- const [rendered, setRendered] = useState(
504
- () => queue.items.map((notification) => ({ notification, leaving: false }))
505
- );
506
- const timers = useRef(/* @__PURE__ */ new Map());
507
- useEffect(() => {
508
- const sync = (items) => {
509
- setRendered((current) => {
510
- const live = new Map(items.map((item) => [item.id, item]));
511
- const kept = current.map((entry) => {
512
- const still = live.get(entry.notification.id);
513
- if (still) return { notification: still, leaving: false };
514
- if (entry.leaving) return entry;
515
- const timer = setTimeout(() => {
516
- timers.current.delete(entry.notification.id);
517
- setRendered((later) => later.filter((row) => row.notification.id !== entry.notification.id));
518
- }, exitDuration);
519
- timers.current.set(entry.notification.id, timer);
520
- return { ...entry, leaving: true };
521
- });
522
- const seen = new Set(kept.map((entry) => entry.notification.id));
523
- const added = items.filter((item) => !seen.has(item.id)).map((item) => ({ notification: item, leaving: false }));
524
- return [...kept, ...added];
525
- });
526
- };
527
- sync(queue.items);
528
- return queue.subscribe(sync);
529
- }, [queue, exitDuration]);
530
- useEffect(() => {
531
- const pending = timers.current;
532
- return () => {
533
- for (const timer of pending.values()) clearTimeout(timer);
534
- pending.clear();
535
- };
536
- }, []);
537
- if (!rendered.length) return null;
538
- return /* @__PURE__ */ jsx(
539
- "section",
540
- {
541
- "aria-label": label,
542
- "data-enigma-toaster": "",
543
- "data-position": position,
544
- className,
545
- style,
546
- onPointerEnter: () => queue.pause(),
547
- onPointerLeave: () => queue.resume(),
548
- onFocusCapture: () => queue.pause(),
549
- onBlurCapture: () => queue.resume(),
550
- children: rendered.map((entry, index) => /* @__PURE__ */ jsx(
551
- Toast,
552
- {
553
- entry,
554
- index: rendered.length - 1 - index,
555
- position,
556
- swipeThreshold,
557
- onDismiss: () => queue.dismiss(entry.notification.id),
558
- render: children
559
- },
560
- entry.notification.id
561
- ))
562
- }
563
- );
564
- }
565
- function Toast({ entry, index, position, swipeThreshold, onDismiss, render }) {
566
- const { notification, leaving } = entry;
567
- const [offset, setOffset] = useState(0);
568
- const [swiping, setSwiping] = useState(false);
569
- const start = useRef(0);
570
- const { axis, sign } = swipeAxis(position);
571
- const onPointerDown = useCallback((event) => {
572
- if (event.target.closest("button")) return;
573
- if (event.pointerType === "mouse" && event.button !== 0) return;
574
- event.currentTarget.setPointerCapture(event.pointerId);
575
- start.current = axis === "x" ? event.clientX : event.clientY;
576
- setSwiping(true);
577
- }, [axis]);
578
- const onPointerMove = useCallback((event) => {
579
- if (!swiping) return;
580
- const delta = (axis === "x" ? event.clientX : event.clientY) - start.current;
581
- setOffset(sign > 0 ? Math.max(0, delta) : Math.min(0, delta));
582
- }, [swiping, axis, sign]);
583
- const onPointerUp = useCallback(() => {
584
- if (!swiping) return;
585
- setSwiping(false);
586
- if (Math.abs(offset) >= swipeThreshold) {
587
- onDismiss();
588
- return;
589
- }
590
- setOffset(0);
591
- }, [swiping, offset, swipeThreshold, onDismiss]);
592
- const controls = { dismiss: onDismiss };
593
- return /* @__PURE__ */ jsx(
594
- "article",
595
- {
596
- "data-enigma-toast": "",
597
- "data-tone": notification.tone,
598
- "data-state": leaving ? "leaving" : "open",
599
- "data-index": index,
600
- "data-front": index === 0 ? "" : void 0,
601
- "data-swiping": swiping ? "" : void 0,
602
- role: notification.tone === "error" ? "alert" : "status",
603
- "aria-live": notification.tone === "error" ? "assertive" : "polite",
604
- tabIndex: 0,
605
- onKeyDown: (event) => {
606
- if (event.key === "Escape") onDismiss();
607
- },
608
- onPointerDown,
609
- onPointerMove,
610
- onPointerUp,
611
- onPointerCancel: onPointerUp,
612
- style: {
613
- // Read by the theme, so the transform stays the stylesheet's decision.
614
- "--enigma-toast-swipe": `${offset}px`,
615
- "--enigma-toast-index": index
616
- },
617
- children: render ? render(notification, controls) : /* @__PURE__ */ jsxs(Fragment, { children: [
618
- /* @__PURE__ */ jsx("p", { "data-enigma-toast-title": "", children: notification.title }),
619
- notification.body && /* @__PURE__ */ jsx("p", { "data-enigma-toast-body": "", children: notification.body }),
620
- notification.action && /* @__PURE__ */ jsx(
621
- "button",
622
- {
623
- type: "button",
624
- "data-enigma-toast-action": "",
625
- onClick: () => {
626
- notification.action?.onSelect();
627
- if (notification.action?.dismiss !== false) onDismiss();
628
- },
629
- children: notification.action.label
630
- }
631
- ),
632
- /* @__PURE__ */ jsx("button", { type: "button", "data-enigma-toast-close": "", "aria-label": "Dismiss", onClick: onDismiss, children: "\xD7" })
633
- ] })
634
- }
635
- );
636
- }
637
- function useNetworkState() {
638
- const monitor = useMemo(() => createNetworkMonitor(), []);
639
- const snapshot = useRef(monitor.state);
640
- return useSyncExternalStore(
641
- (listener) => monitor.subscribe((next) => {
642
- snapshot.current = next;
643
- listener();
644
- }),
645
- () => snapshot.current,
646
- () => SERVER_NETWORK_STATE
647
- );
648
- }
649
- var loading = null;
650
- function loadElement() {
651
- if (loading || typeof window === "undefined") return;
652
- loading = import('@github/relative-time-element').catch(() => {
653
- return null;
654
- });
655
- }
656
- function RelativeTime({
657
- date,
658
- fallback = null,
659
- capitalize = false,
660
- capitalizeFirst = true,
661
- now,
662
- format,
663
- tense,
664
- precision,
665
- threshold,
666
- prefix,
667
- formatStyle,
668
- locale,
669
- timeZone,
670
- second,
671
- minute,
672
- hour,
673
- weekday,
674
- day,
675
- month,
676
- year,
677
- timeZoneName,
678
- noTitle,
679
- numericBeyondThreshold,
680
- ...rest
681
- }) {
682
- useEffect(loadElement, []);
683
- const applyPrefix = useCallback((node) => {
684
- if (!node) return;
685
- if (prefix === void 0 || prefix === "") node.removeAttribute("prefix");
686
- else node.setAttribute("prefix", prefix);
687
- }, [prefix]);
688
- const options = {
689
- format,
690
- tense,
691
- precision,
692
- threshold,
693
- prefix,
694
- formatStyle,
695
- locale,
696
- timeZone,
697
- second,
698
- minute,
699
- hour,
700
- weekday,
701
- day,
702
- month,
703
- year,
704
- timeZoneName,
705
- noTitle,
706
- numericBeyondThreshold
707
- };
708
- const view = relativeTimeView(date, options, now);
709
- if (!view.date) return fallback;
710
- const marks = {
711
- "data-relative-time-capitalize": capitalize ? "true" : void 0,
712
- "data-relative-time-capitalize-first-letter": capitalizeFirst ? "true" : void 0,
713
- // The text differs between the server's instant and the browser's, which is the
714
- // one hydration difference React has an escape hatch for rather than a bug.
715
- suppressHydrationWarning: true
716
- };
717
- if (view.absoluteOnly) {
718
- return createElement("time", { dateTime: view.iso, title: noTitle ? void 0 : view.exact, ...marks, ...rest }, view.label);
719
- }
720
- const { prefix: prefixAttribute, ...attributes } = relativeTimeAttributes(view, options);
721
- return createElement("relative-time", { ...attributes, ...marks, ...rest, ref: applyPrefix }, view.label);
722
- }
723
- function Flag({ code, label, shape, format, source, basePath, size, fallback = null, ...rest }) {
724
- const options = { shape, format, source, basePath, size };
725
- for (const key of Object.keys(options)) {
726
- if (options[key] === void 0) delete options[key];
727
- }
728
- const view = flagView(code, options);
729
- if (!view) return fallback;
730
- const name = label?.trim();
731
- return /* @__PURE__ */ jsx(
732
- "img",
733
- {
734
- src: view.src,
735
- alt: name ?? "",
736
- width: view.width,
737
- height: view.height,
738
- loading: "lazy",
739
- decoding: "async",
740
- "aria-hidden": name ? void 0 : true,
741
- "data-enigma-flag": "",
742
- "data-flag-code": view.code,
743
- "data-flag-shape": view.shape,
744
- ...rest
745
- }
746
- );
747
- }
748
-
749
- export { Button, Flag, Input, PasswordStrength, RelativeTime, Toaster, createNotificationQueue, defaultQueue, getLinkComponent, setLinkComponent, useButton, useInput, useMarquee, useNetworkState, useNotifications, useSearch };