@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,249 @@
1
+ import { INPUT_ICON_PATHS } from './chunk-F25CGNQC.js';
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { lazy, forwardRef, useRef, useState, useId, useCallback, useEffect, Suspense } from 'react';
4
+
5
+ var ICON_PROPS = {
6
+ viewBox: "0 0 24 24",
7
+ width: "1em",
8
+ height: "1em",
9
+ fill: "none",
10
+ stroke: "currentColor",
11
+ strokeWidth: 2,
12
+ strokeLinecap: "round",
13
+ strokeLinejoin: "round",
14
+ "aria-hidden": true
15
+ };
16
+ function Icon({ paths }) {
17
+ return /* @__PURE__ */ jsx("svg", { ...ICON_PROPS, children: paths.map((path) => /* @__PURE__ */ jsx("path", { d: path }, path)) });
18
+ }
19
+
20
+ // src/react/input/write-value.ts
21
+ function writeValue(input, next) {
22
+ const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
23
+ if (setter) setter.call(input, next);
24
+ else input.value = next;
25
+ input.dispatchEvent(new Event("input", { bubbles: true }));
26
+ }
27
+ var PasswordExtras = lazy(() => import('./password-3DRQYAYQ.js').then((module) => ({ default: module.PasswordExtras })));
28
+ var SearchExtras = lazy(() => import('./search-UQEXAPQB.js').then((module) => ({ default: module.SearchExtras })));
29
+ var Input = forwardRef(function Input2(props, forwardedRef) {
30
+ const {
31
+ reveal,
32
+ revealLabels,
33
+ generate = false,
34
+ generateLabel = "Generate a password",
35
+ revealOnGenerate = true,
36
+ copyOnGenerate = false,
37
+ onGenerate,
38
+ actions = [],
39
+ position = "end",
40
+ strength = false,
41
+ onStrengthChange,
42
+ breach,
43
+ breachDelay = 500,
44
+ onBreachChange,
45
+ items,
46
+ keys,
47
+ delay,
48
+ limit,
49
+ fuse,
50
+ fuseOptions,
51
+ matcher,
52
+ onResults,
53
+ renderResults,
54
+ clearable,
55
+ clearLabel = "Clear",
56
+ wrapperProps,
57
+ fieldProps,
58
+ classNames,
59
+ children,
60
+ type = "text",
61
+ onChange,
62
+ ...rest
63
+ } = props;
64
+ const innerRef = useRef(null);
65
+ const [element, setElement] = useState(null);
66
+ const [revealed, setRevealed] = useState(false);
67
+ const describedBy = useId();
68
+ const isPassword = type === "password";
69
+ const isSearch = type === "search";
70
+ const showReveal = reveal ?? isPassword;
71
+ const showGenerate = generate !== false && isPassword;
72
+ const showClear = (clearable ?? isSearch) && isSearch;
73
+ const controlled = rest.value !== void 0;
74
+ const [ownValue, setOwnValue] = useState(String(rest.defaultValue ?? ""));
75
+ const value = controlled ? String(rest.value ?? "") : ownValue;
76
+ const handleChange = useCallback((event) => {
77
+ if (!controlled) setOwnValue(event.target.value);
78
+ onChange?.(event);
79
+ }, [controlled, onChange]);
80
+ const caret = useRef(null);
81
+ const toggleReveal = useCallback(() => {
82
+ const input = innerRef.current;
83
+ if (input && typeof document !== "undefined" && document.activeElement === input) {
84
+ try {
85
+ const { selectionStart, selectionEnd } = input;
86
+ caret.current = selectionStart === null || selectionEnd === null ? null : [selectionStart, selectionEnd];
87
+ } catch {
88
+ caret.current = null;
89
+ }
90
+ }
91
+ setRevealed((current) => !current);
92
+ }, []);
93
+ useEffect(() => {
94
+ const selection = caret.current;
95
+ if (!selection) return;
96
+ caret.current = null;
97
+ const restore = () => {
98
+ const input = innerRef.current;
99
+ if (!input || document.activeElement !== input) return;
100
+ try {
101
+ input.setSelectionRange(selection[0], selection[1]);
102
+ } catch {
103
+ }
104
+ };
105
+ restore();
106
+ const timer = setTimeout(restore, 0);
107
+ return () => clearTimeout(timer);
108
+ }, [revealed]);
109
+ const handleGenerate = useCallback(async () => {
110
+ const input = innerRef.current;
111
+ if (!input) return;
112
+ const { generatePassword } = await import('./password-FB2CUEKJ.js');
113
+ const password = generatePassword(typeof generate === "object" ? generate : {});
114
+ writeValue(input, password);
115
+ if (revealOnGenerate) setRevealed(true);
116
+ if (copyOnGenerate) void navigator.clipboard?.writeText(password).catch(() => {
117
+ });
118
+ input.focus();
119
+ onGenerate?.(password);
120
+ }, [generate, revealOnGenerate, copyOnGenerate, onGenerate]);
121
+ const handleClear = useCallback(() => {
122
+ const input = innerRef.current;
123
+ if (!input) return;
124
+ writeValue(input, "");
125
+ input.focus();
126
+ }, []);
127
+ const [score, setScore] = useState(null);
128
+ const [breached, setBreached] = useState(false);
129
+ const handleBreach = useCallback((state) => {
130
+ setBreached(state.status === "breached");
131
+ onBreachChange?.(state);
132
+ }, [onBreachChange]);
133
+ const builtIn = [];
134
+ if (showGenerate) {
135
+ builtIn.push({
136
+ name: "generate",
137
+ label: generateLabel,
138
+ icon: /* @__PURE__ */ jsx(Icon, { paths: INPUT_ICON_PATHS.generate }),
139
+ onSelect: () => {
140
+ void handleGenerate();
141
+ }
142
+ });
143
+ }
144
+ if (showReveal) {
145
+ builtIn.push({
146
+ name: "reveal",
147
+ label: revealed ? revealLabels?.hide ?? "Hide password" : revealLabels?.show ?? "Show password",
148
+ icon: /* @__PURE__ */ jsx(Icon, { paths: revealed ? INPUT_ICON_PATHS.eyeOff : INPUT_ICON_PATHS.eye }),
149
+ pressed: revealed,
150
+ onSelect: toggleReveal
151
+ });
152
+ }
153
+ if (showClear) {
154
+ builtIn.push({
155
+ name: "clear",
156
+ label: clearLabel,
157
+ icon: /* @__PURE__ */ jsx(Icon, { paths: INPUT_ICON_PATHS.clear }),
158
+ // Nothing to clear is nothing to press: the button would be a target that does
159
+ // not respond, which reads as broken rather than as unavailable.
160
+ visible: value.length > 0,
161
+ onSelect: handleClear
162
+ });
163
+ }
164
+ const overridden = new Set(actions.map((action) => action.name));
165
+ const rendered = [...builtIn.filter((action) => !overridden.has(action.name)), ...actions].filter((action) => action.visible !== false);
166
+ const locked = rest.disabled === true || rest.readOnly === true;
167
+ 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(
168
+ "button",
169
+ {
170
+ type: "button",
171
+ "data-enigma-input-action": action.name,
172
+ className: classNames?.action,
173
+ "aria-label": action.label,
174
+ title: action.label,
175
+ "aria-pressed": action.pressed,
176
+ disabled: locked,
177
+ onMouseDown: (event) => event.preventDefault(),
178
+ onClick: action.onSelect,
179
+ children: action.icon
180
+ },
181
+ action.name
182
+ )) });
183
+ const wantsPasswordChunk = isPassword && (strength !== false || Boolean(breach));
184
+ const wantsSearchChunk = isSearch && Boolean(items || matcher);
185
+ return /* @__PURE__ */ jsxs(
186
+ "div",
187
+ {
188
+ ...wrapperProps,
189
+ "data-enigma-input-root": "",
190
+ "data-revealed": revealed ? "" : void 0,
191
+ "data-breached": breached ? "" : void 0,
192
+ "data-score": score ?? void 0,
193
+ children: [
194
+ /* @__PURE__ */ jsxs("div", { ...fieldProps, "data-enigma-input-field": "", children: [
195
+ position === "start" && buttons,
196
+ /* @__PURE__ */ jsx(
197
+ "input",
198
+ {
199
+ ...rest,
200
+ ref: (node) => {
201
+ innerRef.current = node;
202
+ setElement(node);
203
+ if (typeof forwardedRef === "function") forwardedRef(node);
204
+ else if (forwardedRef) forwardedRef.current = node;
205
+ },
206
+ type: revealed && isPassword ? "text" : type,
207
+ onChange: handleChange,
208
+ "data-enigma-input": "",
209
+ "aria-describedby": score !== null ? describedBy : rest["aria-describedby"]
210
+ }
211
+ ),
212
+ position === "end" && buttons
213
+ ] }),
214
+ wantsPasswordChunk && /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
215
+ PasswordExtras,
216
+ {
217
+ id: describedBy,
218
+ value,
219
+ strength,
220
+ onStrengthChange,
221
+ onScore: setScore,
222
+ breach,
223
+ breachDelay,
224
+ onBreachChange: handleBreach,
225
+ classNames: classNames?.strength
226
+ }
227
+ ) }),
228
+ wantsSearchChunk && /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
229
+ SearchExtras,
230
+ {
231
+ input: element,
232
+ items,
233
+ keys,
234
+ delay,
235
+ limit,
236
+ fuse,
237
+ fuseOptions,
238
+ matcher,
239
+ onResults,
240
+ renderResults
241
+ }
242
+ ) }),
243
+ children
244
+ ]
245
+ }
246
+ );
247
+ });
248
+
249
+ export { Input };
@@ -0,0 +1,17 @@
1
+ import { createNetworkMonitor, SERVER_NETWORK_STATE } from './chunk-2QFTRNAZ.js';
2
+ import { useMemo, useRef, useSyncExternalStore } from 'react';
3
+
4
+ function useNetworkState() {
5
+ const monitor = useMemo(() => createNetworkMonitor(), []);
6
+ const snapshot = useRef(monitor.state);
7
+ return useSyncExternalStore(
8
+ (listener) => monitor.subscribe((next) => {
9
+ snapshot.current = next;
10
+ listener();
11
+ }),
12
+ () => snapshot.current,
13
+ () => SERVER_NETWORK_STATE
14
+ );
15
+ }
16
+
17
+ export { useNetworkState };
@@ -0,0 +1,149 @@
1
+ // src/core/notifications.ts
2
+ var DEFAULT_MAX = 4;
3
+ var DEFAULT_DURATION = 5e3;
4
+ function createNotifications(options = {}) {
5
+ const view = typeof document === "undefined" ? null : document;
6
+ const max = options.max ?? DEFAULT_MAX;
7
+ const defaultDuration = options.duration ?? DEFAULT_DURATION;
8
+ const sticky = /* @__PURE__ */ new Set([...options.stickyTones ?? ["error"], "loading"]);
9
+ let items = [];
10
+ let paused = false;
11
+ let sequence = 0;
12
+ const listeners = /* @__PURE__ */ new Set();
13
+ const timers = /* @__PURE__ */ new Map();
14
+ function emit() {
15
+ const snapshot = Object.freeze([...items]);
16
+ for (const listener of listeners) listener(snapshot);
17
+ }
18
+ function clearTimer(id) {
19
+ const timer = timers.get(id);
20
+ if (timer?.handle) clearTimeout(timer.handle);
21
+ timers.delete(id);
22
+ }
23
+ function startTimer(id, remaining) {
24
+ if (!Number.isFinite(remaining) || remaining <= 0) return;
25
+ const handle = paused ? null : setTimeout(() => dismiss(id), remaining);
26
+ timers.set(id, { handle, remaining, startedAt: Date.now() });
27
+ }
28
+ function dismiss(id) {
29
+ const next = items.filter((item) => item.id !== id);
30
+ if (next.length === items.length) return;
31
+ items = next;
32
+ clearTimer(id);
33
+ emit();
34
+ }
35
+ function notify(input) {
36
+ const tone = input.tone ?? "info";
37
+ const duration = input.duration ?? (sticky.has(tone) ? Infinity : defaultDuration);
38
+ const existing = input.key ? items.find((item) => item.key === input.key) : void 0;
39
+ const id = existing?.id ?? `n${++sequence}`;
40
+ const notification = {
41
+ id,
42
+ key: input.key,
43
+ title: input.title,
44
+ body: input.body,
45
+ tone,
46
+ duration,
47
+ action: input.action,
48
+ data: input.data,
49
+ createdAt: Date.now()
50
+ };
51
+ items = existing ? items.map((item) => item.id === id ? notification : item) : [...items, notification];
52
+ if (items.length > max) {
53
+ const evictable = items.find((item) => Number.isFinite(item.duration) && item.id !== id);
54
+ if (evictable) {
55
+ items = items.filter((item) => item.id !== evictable.id);
56
+ clearTimer(evictable.id);
57
+ } else {
58
+ items = items.slice(items.length - max);
59
+ }
60
+ }
61
+ clearTimer(id);
62
+ startTimer(id, duration);
63
+ emit();
64
+ return id;
65
+ }
66
+ const onVisibility = () => {
67
+ if (!view) return;
68
+ if (view.hidden) instance.pause();
69
+ else instance.resume();
70
+ };
71
+ function update(id, patch) {
72
+ const current = items.find((item) => item.id === id);
73
+ if (!current) return;
74
+ const tone = patch.tone ?? current.tone;
75
+ const duration = patch.duration ?? (patch.tone ? sticky.has(tone) ? Infinity : defaultDuration : current.duration);
76
+ items = items.map((item) => item.id === id ? { ...current, ...patch, tone, duration } : item);
77
+ clearTimer(id);
78
+ startTimer(id, duration);
79
+ emit();
80
+ }
81
+ function resolveMessage(message, value) {
82
+ const resolved = typeof message === "function" ? message(value) : message;
83
+ return typeof resolved === "string" ? { title: resolved } : resolved;
84
+ }
85
+ async function promise(work, messages) {
86
+ const start = typeof messages.loading === "string" ? { title: messages.loading } : messages.loading;
87
+ const key = start.key ?? `p${++sequence}`;
88
+ notify({ ...start, key, tone: "loading" });
89
+ try {
90
+ const value = await work;
91
+ notify({ ...resolveMessage(messages.success, value), key, tone: "success" });
92
+ return value;
93
+ } catch (error) {
94
+ notify({ ...resolveMessage(messages.error, error), key, tone: "error" });
95
+ throw error;
96
+ }
97
+ }
98
+ const instance = {
99
+ get items() {
100
+ return Object.freeze([...items]);
101
+ },
102
+ notify,
103
+ update,
104
+ promise,
105
+ dismiss,
106
+ dismissAll() {
107
+ for (const item of items) clearTimer(item.id);
108
+ items = [];
109
+ emit();
110
+ },
111
+ pause() {
112
+ if (paused) return;
113
+ paused = true;
114
+ for (const [id, timer] of timers) {
115
+ if (!timer.handle) continue;
116
+ clearTimeout(timer.handle);
117
+ timers.set(id, {
118
+ handle: null,
119
+ remaining: Math.max(timer.remaining - (Date.now() - timer.startedAt), 0),
120
+ startedAt: Date.now()
121
+ });
122
+ }
123
+ },
124
+ resume() {
125
+ if (!paused) return;
126
+ paused = false;
127
+ for (const [id, timer] of [...timers]) {
128
+ clearTimer(id);
129
+ startTimer(id, timer.remaining);
130
+ }
131
+ },
132
+ subscribe(listener) {
133
+ listeners.add(listener);
134
+ return () => {
135
+ listeners.delete(listener);
136
+ };
137
+ },
138
+ destroy() {
139
+ for (const item of items) clearTimer(item.id);
140
+ items = [];
141
+ listeners.clear();
142
+ view?.removeEventListener("visibilitychange", onVisibility);
143
+ }
144
+ };
145
+ view?.addEventListener("visibilitychange", onVisibility);
146
+ return instance;
147
+ }
148
+
149
+ export { createNotifications };
@@ -0,0 +1,41 @@
1
+ import { createInput } from './chunk-KJINGUQN.js';
2
+ import { useRef, useState, useEffect, useLayoutEffect } from 'react';
3
+
4
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
5
+ function useInput(options = {}) {
6
+ const inputRef = useRef(null);
7
+ const actionsRef = useRef(null);
8
+ const instanceRef = useRef(null);
9
+ const optionsRef = useRef(options);
10
+ optionsRef.current = options;
11
+ const [revealed, setRevealed] = useState(false);
12
+ useIsomorphicLayoutEffect(() => {
13
+ const input = inputRef.current;
14
+ if (!input) return;
15
+ const instance = createInput(input, {
16
+ ...optionsRef.current,
17
+ container: actionsRef.current ?? optionsRef.current.container,
18
+ onRevealChange: (next) => {
19
+ setRevealed(next);
20
+ optionsRef.current.onRevealChange?.(next);
21
+ }
22
+ });
23
+ instanceRef.current = instance;
24
+ return () => {
25
+ instance.destroy();
26
+ instanceRef.current = null;
27
+ };
28
+ }, []);
29
+ useIsomorphicLayoutEffect(() => {
30
+ instanceRef.current?.update(options);
31
+ }, [options.reveal, options.actions, options.position]);
32
+ return {
33
+ inputRef,
34
+ actionsRef,
35
+ revealed,
36
+ reveal: (next) => instanceRef.current?.reveal(next),
37
+ refresh: () => instanceRef.current?.refresh()
38
+ };
39
+ }
40
+
41
+ export { useInput };