@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,462 @@
1
+ import { createRecentStore, shortcutLabel, moveActive, groupRows, isPaletteShortcut } from './chunk-DTWZDONY.js';
2
+ import { subsequenceMatcher, createSearch } from './chunk-D5A2ZMAG.js';
3
+ import { Slot } from './chunk-HS3X3XCW.js';
4
+ import { createPortal } from 'react-dom';
5
+ import { createContext, useContext, useState, useRef, useId, useMemo, useCallback, useEffect } from 'react';
6
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
+
8
+ var PaletteContext = createContext(null);
9
+ function usePaletteContext(part) {
10
+ const value = useContext(PaletteContext);
11
+ if (!value) throw new Error(`<${part}> must be rendered inside <SearchPalette.Root>.`);
12
+ return value;
13
+ }
14
+ var RECENTS_SHOWN = 5;
15
+ function firstString(item) {
16
+ if (typeof item === "string") return item;
17
+ if (item && typeof item === "object") {
18
+ for (const value of Object.values(item)) {
19
+ if (typeof value === "string" && value.trim()) return value;
20
+ }
21
+ }
22
+ return "";
23
+ }
24
+ function PaletteRoot({
25
+ items,
26
+ keys,
27
+ fuse,
28
+ fuseOptions,
29
+ matcher,
30
+ delay = 120,
31
+ limit = 40,
32
+ groupBy,
33
+ labelOf = firstString,
34
+ descriptionOf,
35
+ sections = [],
36
+ onSelect,
37
+ recents = true,
38
+ recentsKey,
39
+ recentsLimit = 8,
40
+ shortcut = "k",
41
+ open: openProp,
42
+ onOpenChange,
43
+ defaultOpen = false,
44
+ resultsLabel = "Results",
45
+ recentsLabel = "Recent",
46
+ children
47
+ }) {
48
+ const [ownOpen, setOwnOpen] = useState(defaultOpen);
49
+ const controlled = openProp !== void 0;
50
+ const open = controlled ? openProp : ownOpen;
51
+ const [query, setQuery] = useState("");
52
+ const [active, setActive] = useState(0);
53
+ const [results, setResults] = useState([]);
54
+ const [remembered, setRemembered] = useState([]);
55
+ const [busy, setBusy] = useState(false);
56
+ const triggerRef = useRef(null);
57
+ const fieldRef = useRef(null);
58
+ const base = useId();
59
+ const ids = useMemo(() => ({ field: `${base}-field`, list: `${base}-list`, title: `${base}-title` }), [base]);
60
+ const store = useMemo(
61
+ () => createRecentStore({ key: recentsKey, limit: recentsLimit }),
62
+ [recentsKey, recentsLimit]
63
+ );
64
+ const setOpen = useCallback((next) => {
65
+ if (!controlled) setOwnOpen(next);
66
+ onOpenChange?.(next);
67
+ }, [controlled, onOpenChange]);
68
+ const ranking = useMemo(
69
+ () => matcher ?? (fuse ? void 0 : subsequenceMatcher(keys ?? [])),
70
+ [matcher, fuse, keys]
71
+ );
72
+ const engine = useMemo(() => createSearch({
73
+ items,
74
+ keys,
75
+ fuse,
76
+ fuseOptions,
77
+ matcher: ranking,
78
+ debounce: delay,
79
+ limit,
80
+ onResults: (next) => {
81
+ setResults(next);
82
+ setBusy(false);
83
+ }
84
+ // Built once: the engine indexes on construction, so rebuilding it per render would
85
+ // re-index the whole corpus on every keystroke.
86
+ // eslint-disable-next-line react-hooks/exhaustive-deps
87
+ }), []);
88
+ useEffect(() => () => engine.destroy(), [engine]);
89
+ useEffect(() => {
90
+ engine.setItems(items ?? []);
91
+ }, [engine, items]);
92
+ useEffect(() => {
93
+ engine.update({ keys, fuse, fuseOptions, matcher: ranking, debounce: delay, limit });
94
+ }, [engine, keys, fuse, fuseOptions, ranking, delay, limit]);
95
+ useEffect(() => {
96
+ if (!open) return;
97
+ setRemembered(recents ? store.list() : []);
98
+ setQuery("");
99
+ engine.searchNow("");
100
+ setActive(0);
101
+ }, [open, recents, store, engine]);
102
+ useEffect(() => {
103
+ if (shortcut === null || typeof window === "undefined") return;
104
+ const onKeyDown = (event) => {
105
+ if (!isPaletteShortcut(event, shortcut)) return;
106
+ event.preventDefault();
107
+ setOpen(!open);
108
+ };
109
+ window.addEventListener("keydown", onKeyDown);
110
+ return () => window.removeEventListener("keydown", onKeyDown);
111
+ }, [shortcut, open, setOpen]);
112
+ const rows = useMemo(() => {
113
+ const trimmed = query.trim();
114
+ const out = [];
115
+ if (!trimmed && recents && remembered.length) {
116
+ remembered.slice(0, RECENTS_SHOWN).forEach((entry, index) => {
117
+ out.push({
118
+ id: `recent-${index}`,
119
+ kind: "recent",
120
+ group: recentsLabel,
121
+ recent: entry,
122
+ label: entry.label ?? entry.term
123
+ });
124
+ });
125
+ }
126
+ for (const section of sections) {
127
+ const pool = section.always || !trimmed ? section.items : section.items.filter((item) => labelOf(item).toLowerCase().includes(trimmed.toLowerCase()));
128
+ pool.forEach((item, index) => {
129
+ out.push({
130
+ id: `section-${section.label}-${index}`,
131
+ kind: "action",
132
+ group: section.label,
133
+ item,
134
+ label: labelOf(item),
135
+ description: descriptionOf?.(item)
136
+ });
137
+ });
138
+ }
139
+ results.forEach((match, index) => {
140
+ out.push({
141
+ id: `result-${index}`,
142
+ kind: "item",
143
+ group: groupBy?.(match.item) ?? resultsLabel,
144
+ item: match.item,
145
+ match,
146
+ label: labelOf(match.item),
147
+ description: descriptionOf?.(match.item)
148
+ });
149
+ });
150
+ return out;
151
+ }, [query, recents, remembered, recentsLabel, sections, results, groupBy, labelOf, descriptionOf, resultsLabel]);
152
+ useEffect(() => {
153
+ setActive((current) => current < rows.length ? current : 0);
154
+ }, [rows.length]);
155
+ const select = useCallback((row) => {
156
+ if (!row) return;
157
+ if (row.kind === "recent" && row.recent) {
158
+ if (!row.recent.href) {
159
+ setQuery(row.recent.term);
160
+ setBusy(true);
161
+ engine.search(row.recent.term);
162
+ fieldRef.current?.focus();
163
+ return;
164
+ }
165
+ }
166
+ const stay = row.onSelect ? row.onSelect() : row.item !== void 0 ? onSelect?.(row.item) : void 0;
167
+ if (recents) {
168
+ setRemembered(store.remember({
169
+ term: query.trim(),
170
+ label: row.label,
171
+ scope: row.group
172
+ }));
173
+ }
174
+ if (stay !== false) setOpen(false);
175
+ }, [engine, onSelect, query, recents, setOpen, store]);
176
+ const handleQuery = useCallback((next) => {
177
+ setQuery(next);
178
+ setActive(0);
179
+ setBusy(Boolean(next.trim()));
180
+ engine.search(next);
181
+ }, [engine]);
182
+ const value = useMemo(() => ({
183
+ open,
184
+ setOpen,
185
+ query,
186
+ setQuery: handleQuery,
187
+ rows,
188
+ active,
189
+ setActive,
190
+ select,
191
+ clearRecents: () => {
192
+ store.clear();
193
+ setRemembered([]);
194
+ },
195
+ forgetRecent: (entry) => setRemembered(store.forget(entry)),
196
+ busy,
197
+ ids,
198
+ shortcutLabel: shortcutLabel(shortcut ?? "k"),
199
+ triggerRef,
200
+ fieldRef,
201
+ rowId: (row) => `${ids.list}-${row.id}`
202
+ }), [open, setOpen, query, handleQuery, rows, active, select, busy, ids, shortcut, store]);
203
+ return /* @__PURE__ */ jsx(PaletteContext.Provider, { value, children });
204
+ }
205
+ function PaletteTrigger({ asChild = false, children, onClick, ...props }) {
206
+ const palette = usePaletteContext("SearchPalette.Trigger");
207
+ const Tag = asChild ? Slot : "button";
208
+ return /* @__PURE__ */ jsx(
209
+ Tag,
210
+ {
211
+ ...asChild ? {} : { type: "button" },
212
+ ...props,
213
+ ref: palette.triggerRef,
214
+ "data-enigma-palette-trigger": "",
215
+ "aria-haspopup": "dialog",
216
+ "aria-expanded": palette.open,
217
+ "aria-keyshortcuts": "Control+K Meta+K",
218
+ onClick: (event) => {
219
+ onClick?.(event);
220
+ if (!event.defaultPrevented) palette.setOpen(true);
221
+ },
222
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
223
+ /* @__PURE__ */ jsx("span", { "data-enigma-palette-trigger-label": "", children: "Search" }),
224
+ /* @__PURE__ */ jsx("kbd", { "data-enigma-palette-trigger-key": "", children: palette.shortcutLabel })
225
+ ] })
226
+ }
227
+ );
228
+ }
229
+ function PaletteContent({ title = "Search", portal = true, overlayProps, closeDuration = 160, children, ...props }) {
230
+ const palette = usePaletteContext("SearchPalette.Content");
231
+ const panelRef = useRef(null);
232
+ const [mounted, setMounted] = useState(false);
233
+ const [present, setPresent] = useState(palette.open);
234
+ useEffect(() => setMounted(true), []);
235
+ useEffect(() => {
236
+ if (palette.open) {
237
+ setPresent(true);
238
+ return;
239
+ }
240
+ if (!present) return;
241
+ const reduced = typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
242
+ const timer = setTimeout(() => setPresent(false), reduced ? 0 : closeDuration);
243
+ return () => clearTimeout(timer);
244
+ }, [palette.open, present, closeDuration]);
245
+ useEffect(() => {
246
+ if (!palette.open || typeof document === "undefined") return;
247
+ const previous = document.activeElement;
248
+ const body = document.body;
249
+ const overflow = body.style.overflow;
250
+ const gap = window.innerWidth - document.documentElement.clientWidth;
251
+ const padding = body.style.paddingRight;
252
+ body.style.overflow = "hidden";
253
+ if (gap > 0) body.style.paddingRight = `${gap}px`;
254
+ const onKeyDown = (event) => {
255
+ if (event.key === "Escape") {
256
+ event.preventDefault();
257
+ palette.setOpen(false);
258
+ return;
259
+ }
260
+ if (event.key !== "Tab") return;
261
+ const focusable = panelRef.current?.querySelectorAll(
262
+ 'a[href], button:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex="-1"])'
263
+ );
264
+ if (!focusable || focusable.length === 0) return;
265
+ const first = focusable[0];
266
+ const last = focusable[focusable.length - 1];
267
+ if (!event.shiftKey && document.activeElement === last) {
268
+ event.preventDefault();
269
+ first.focus();
270
+ } else if (event.shiftKey && document.activeElement === first) {
271
+ event.preventDefault();
272
+ last.focus();
273
+ }
274
+ };
275
+ document.addEventListener("keydown", onKeyDown);
276
+ return () => {
277
+ document.removeEventListener("keydown", onKeyDown);
278
+ body.style.overflow = overflow;
279
+ body.style.paddingRight = padding;
280
+ (palette.triggerRef.current ?? previous)?.focus?.();
281
+ };
282
+ }, [palette.open, palette.setOpen, palette.triggerRef]);
283
+ if (!present) return null;
284
+ const panel = /* @__PURE__ */ jsxs("div", { "data-enigma-palette-portal": "", "data-state": palette.open ? "open" : "closed", children: [
285
+ overlayProps !== null && /* @__PURE__ */ jsx(
286
+ "div",
287
+ {
288
+ ...overlayProps,
289
+ "data-enigma-palette-overlay": "",
290
+ "data-state": palette.open ? "open" : "closed",
291
+ onClick: (event) => {
292
+ overlayProps?.onClick?.(event);
293
+ if (!event.defaultPrevented) palette.setOpen(false);
294
+ }
295
+ }
296
+ ),
297
+ /* @__PURE__ */ jsxs(
298
+ "div",
299
+ {
300
+ ...props,
301
+ ref: panelRef,
302
+ role: "dialog",
303
+ "aria-modal": "true",
304
+ "aria-labelledby": palette.ids.title,
305
+ "data-enigma-palette-content": "",
306
+ "data-state": palette.open ? "open" : "closed",
307
+ children: [
308
+ /* @__PURE__ */ jsx("h2", { id: palette.ids.title, "data-enigma-palette-title": "", children: title }),
309
+ children
310
+ ]
311
+ }
312
+ )
313
+ ] });
314
+ if (!portal) return panel;
315
+ if (!mounted || typeof document === "undefined") return null;
316
+ return createPortal(panel, document.body);
317
+ }
318
+ function PaletteField({ asChild = false, onKeyDown, ...props }) {
319
+ const palette = usePaletteContext("SearchPalette.Field");
320
+ const Tag = asChild ? Slot : "input";
321
+ const activeRow = palette.rows[palette.active];
322
+ return /* @__PURE__ */ jsx(
323
+ Tag,
324
+ {
325
+ ...props,
326
+ ref: palette.fieldRef,
327
+ id: palette.ids.field,
328
+ type: "search",
329
+ value: palette.query,
330
+ autoFocus: true,
331
+ autoComplete: "off",
332
+ autoCorrect: "off",
333
+ autoCapitalize: "none",
334
+ spellCheck: false,
335
+ enterKeyHint: "go",
336
+ role: "combobox",
337
+ "aria-expanded": true,
338
+ "aria-autocomplete": "list",
339
+ "aria-controls": palette.ids.list,
340
+ "aria-activedescendant": activeRow ? palette.rowId(activeRow) : void 0,
341
+ "data-enigma-palette-field": "",
342
+ onChange: (event) => palette.setQuery(event.target.value),
343
+ onKeyDown: (event) => {
344
+ onKeyDown?.(event);
345
+ if (event.defaultPrevented) return;
346
+ const key = event.key;
347
+ if (key === "Enter") {
348
+ event.preventDefault();
349
+ palette.select(palette.rows[palette.active]);
350
+ return;
351
+ }
352
+ if (["ArrowDown", "ArrowUp", "Home", "End", "PageDown", "PageUp"].includes(key)) {
353
+ event.preventDefault();
354
+ palette.setActive(moveActive(palette.active, palette.rows.length, key));
355
+ }
356
+ }
357
+ }
358
+ );
359
+ }
360
+ function PaletteList({ children, empty, heading, ...props }) {
361
+ const palette = usePaletteContext("SearchPalette.List");
362
+ const listRef = useRef(null);
363
+ const groups = useMemo(() => groupRows(palette.rows, (row) => row.group), [palette.rows]);
364
+ useEffect(() => {
365
+ listRef.current?.querySelector('[data-active="true"]')?.scrollIntoView({ block: "nearest" });
366
+ }, [palette.active, palette.rows]);
367
+ return /* @__PURE__ */ jsx(
368
+ "div",
369
+ {
370
+ ...props,
371
+ ref: listRef,
372
+ id: palette.ids.list,
373
+ role: "listbox",
374
+ "aria-label": "Results",
375
+ "data-enigma-palette-list": "",
376
+ children: palette.rows.length === 0 ? empty ?? /* @__PURE__ */ jsx("p", { "data-enigma-palette-empty": "", children: palette.query.trim() ? `Nothing matches "${palette.query.trim()}".` : "Type to search." }) : groups.map((group) => /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": group.label, "data-enigma-palette-group": "", children: [
377
+ heading !== null && (heading?.(group.label) ?? // The group carries the name already, so announcing the
378
+ // heading again would only repeat it.
379
+ /* @__PURE__ */ jsx("p", { "data-enigma-palette-group-label": "", "aria-hidden": "true", children: group.label })),
380
+ group.rows.map(({ row, position }) => /* @__PURE__ */ jsx(
381
+ PaletteItem,
382
+ {
383
+ row,
384
+ index: position,
385
+ children: children?.(row, { active: position === palette.active, index: position })
386
+ },
387
+ row.id
388
+ ))
389
+ ] }, group.label))
390
+ }
391
+ );
392
+ }
393
+ function PaletteItem({ row, index, children, ...props }) {
394
+ const palette = usePaletteContext("SearchPalette.Item");
395
+ const active = index === palette.active;
396
+ return /* @__PURE__ */ jsx(
397
+ "div",
398
+ {
399
+ ...props,
400
+ id: palette.rowId(row),
401
+ role: "option",
402
+ "aria-selected": active,
403
+ "data-enigma-palette-item": "",
404
+ "data-kind": row.kind,
405
+ "data-active": active ? "true" : void 0,
406
+ onMouseMove: () => {
407
+ if (!active) palette.setActive(index);
408
+ },
409
+ onClick: () => palette.select(row),
410
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
411
+ /* @__PURE__ */ jsx("span", { "data-enigma-palette-item-label": "", children: row.label }),
412
+ row.description && /* @__PURE__ */ jsx("span", { "data-enigma-palette-item-description": "", children: row.description })
413
+ ] })
414
+ }
415
+ );
416
+ }
417
+ function PaletteFooter({ hints, children, ...props }) {
418
+ usePaletteContext("SearchPalette.Footer");
419
+ return /* @__PURE__ */ jsx("div", { ...props, "data-enigma-palette-footer": "", children: children ?? hints ?? /* @__PURE__ */ jsxs(Fragment, { children: [
420
+ /* @__PURE__ */ jsxs("span", { "data-enigma-palette-hint": "", children: [
421
+ /* @__PURE__ */ jsx("kbd", { children: "up" }),
422
+ /* @__PURE__ */ jsx("kbd", { children: "down" }),
423
+ " to move"
424
+ ] }),
425
+ /* @__PURE__ */ jsxs("span", { "data-enigma-palette-hint": "", children: [
426
+ /* @__PURE__ */ jsx("kbd", { children: "enter" }),
427
+ " to open"
428
+ ] }),
429
+ /* @__PURE__ */ jsxs("span", { "data-enigma-palette-hint": "", children: [
430
+ /* @__PURE__ */ jsx("kbd", { children: "esc" }),
431
+ " to close"
432
+ ] })
433
+ ] }) });
434
+ }
435
+ function SearchPalette({
436
+ placeholder = "Search",
437
+ trigger,
438
+ renderItem,
439
+ empty,
440
+ footer,
441
+ children,
442
+ ...root
443
+ }) {
444
+ return /* @__PURE__ */ jsxs(PaletteRoot, { ...root, children: [
445
+ trigger !== null && /* @__PURE__ */ jsx(PaletteTrigger, { children: trigger }),
446
+ /* @__PURE__ */ jsxs(PaletteContent, { children: [
447
+ /* @__PURE__ */ jsx(PaletteField, { placeholder, "aria-label": placeholder }),
448
+ /* @__PURE__ */ jsx(PaletteList, { empty, children: renderItem }),
449
+ footer === null ? null : /* @__PURE__ */ jsx(PaletteFooter, { children: footer })
450
+ ] }),
451
+ children
452
+ ] });
453
+ }
454
+ SearchPalette.Root = PaletteRoot;
455
+ SearchPalette.Trigger = PaletteTrigger;
456
+ SearchPalette.Content = PaletteContent;
457
+ SearchPalette.Field = PaletteField;
458
+ SearchPalette.List = PaletteList;
459
+ SearchPalette.Item = PaletteItem;
460
+ SearchPalette.Footer = PaletteFooter;
461
+
462
+ export { PaletteContent, PaletteField, PaletteFooter, PaletteItem, PaletteList, PaletteRoot, PaletteTrigger, SearchPalette, usePaletteContext };
@@ -0,0 +1,168 @@
1
+ // src/core/password.ts
2
+ var LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
3
+ var UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
4
+ var DIGITS = "0123456789";
5
+ var SYMBOLS = "!@#$%^&*()-_=+[]{};:,.?";
6
+ var AMBIGUOUS = "Il1O0";
7
+ function randomIndex(bound) {
8
+ const random = globalThis.crypto?.getRandomValues?.bind(globalThis.crypto);
9
+ if (!random) {
10
+ throw new Error("Generating a password needs crypto.getRandomValues, which browsers only expose over HTTPS (or on localhost).");
11
+ }
12
+ const limit = Math.floor(2 ** 32 / bound) * bound;
13
+ const buffer = new Uint32Array(1);
14
+ let value;
15
+ do {
16
+ random(buffer);
17
+ value = buffer[0];
18
+ } while (value >= limit);
19
+ return value % bound;
20
+ }
21
+ function pick(alphabet) {
22
+ return alphabet[randomIndex(alphabet.length)];
23
+ }
24
+ function classes(options) {
25
+ const { lowercase = true, uppercase = true, digits = true, symbols = true, excludeAmbiguous = false, exclude = "" } = options;
26
+ const banned = /* @__PURE__ */ new Set([...excludeAmbiguous ? AMBIGUOUS : "", ...exclude]);
27
+ const clean = (source) => [...source].filter((character) => !banned.has(character)).join("");
28
+ return [
29
+ lowercase ? clean(LOWERCASE) : "",
30
+ uppercase ? clean(UPPERCASE) : "",
31
+ digits ? clean(DIGITS) : "",
32
+ symbols ? clean(SYMBOLS) : ""
33
+ ].filter(Boolean);
34
+ }
35
+ function shuffle(characters) {
36
+ for (let index = characters.length - 1; index > 0; index--) {
37
+ const swap = randomIndex(index + 1);
38
+ [characters[index], characters[swap]] = [characters[swap], characters[index]];
39
+ }
40
+ return characters;
41
+ }
42
+ function generatePassword(options = {}) {
43
+ const { length = 20, requireEachClass = true } = options;
44
+ const pools = classes(options);
45
+ if (!pools.length) throw new Error("generatePassword: every character class was excluded.");
46
+ if (length < 1) throw new Error("generatePassword: length must be at least 1.");
47
+ if (requireEachClass && length < pools.length) {
48
+ throw new Error(`generatePassword: length ${length} cannot hold one character from each of the ${pools.length} classes requested.`);
49
+ }
50
+ const everything = pools.join("");
51
+ const required = requireEachClass ? pools.map(pick) : [];
52
+ const rest = Array.from({ length: length - required.length }, () => pick(everything));
53
+ return shuffle([...required, ...rest]).join("");
54
+ }
55
+ var COMMON = /* @__PURE__ */ new Set([
56
+ "password",
57
+ "passwd",
58
+ "123456",
59
+ "12345678",
60
+ "123456789",
61
+ "1234567890",
62
+ "qwerty",
63
+ "qwertyuiop",
64
+ "abc123",
65
+ "111111",
66
+ "123123",
67
+ "admin",
68
+ "letmein",
69
+ "welcome",
70
+ "monkey",
71
+ "dragon",
72
+ "sunshine",
73
+ "iloveyou",
74
+ "princess",
75
+ "football",
76
+ "baseball",
77
+ "master",
78
+ "shadow",
79
+ "superman",
80
+ "batman",
81
+ "trustno1",
82
+ "hello",
83
+ "freedom",
84
+ "whatever",
85
+ "starwars",
86
+ "changeme",
87
+ "secret",
88
+ "login",
89
+ "root",
90
+ "toor",
91
+ "test",
92
+ "guest",
93
+ "azerty",
94
+ "1q2w3e4r",
95
+ "zaq12wsx"
96
+ ]);
97
+ var SEQUENCES = ["abcdefghijklmnopqrstuvwxyz", "0123456789", "qwertyuiop", "asdfghjkl", "zxcvbnm"];
98
+ function poolSize(password) {
99
+ let size = 0;
100
+ if (/[a-z]/.test(password)) size += 26;
101
+ if (/[A-Z]/.test(password)) size += 26;
102
+ if (/\d/.test(password)) size += 10;
103
+ if (/[^\w\s]|_/.test(password)) size += SYMBOLS.length;
104
+ if (/\s/.test(password)) size += 1;
105
+ return size || 1;
106
+ }
107
+ function longestRun(password) {
108
+ let longest = 1, run = 1;
109
+ for (let index = 1; index < password.length; index++) {
110
+ run = password[index] === password[index - 1] ? run + 1 : 1;
111
+ longest = Math.max(longest, run);
112
+ }
113
+ return longest;
114
+ }
115
+ function longestSequence(password) {
116
+ const lower = password.toLowerCase();
117
+ let longest = 0;
118
+ for (const line of SEQUENCES) {
119
+ const reversed = [...line].reverse().join("");
120
+ for (const source of [line, reversed]) {
121
+ for (let start = 0; start < source.length; start++) {
122
+ for (let end = source.length; end > start + longest; end--) {
123
+ if (lower.includes(source.slice(start, end))) {
124
+ longest = Math.max(longest, end - start);
125
+ break;
126
+ }
127
+ }
128
+ }
129
+ }
130
+ }
131
+ return longest;
132
+ }
133
+ function core(password) {
134
+ return password.toLowerCase().replace(/^[^a-z]+/, "").replace(/[^a-z]+$/, "").replace(/[0!@$]/g, (character) => ({ "0": "o", "!": "i", "@": "a", "$": "s" })[character] ?? character);
135
+ }
136
+ function estimatePasswordStrength(password, options = {}) {
137
+ if (!password) return { score: 0, bits: 0, warnings: [], empty: true };
138
+ const warnings = [];
139
+ let bits = password.length * Math.log2(poolSize(password));
140
+ const stripped = core(password);
141
+ if (COMMON.has(password.toLowerCase()) || COMMON.has(stripped)) {
142
+ bits = Math.min(bits, 8);
143
+ warnings.push("This is one of the most common passwords there is.");
144
+ }
145
+ for (const input of options.userInputs ?? []) {
146
+ const needle = input.trim().toLowerCase();
147
+ if (needle.length < 4 || !password.toLowerCase().includes(needle)) continue;
148
+ bits = Math.min(bits, 16);
149
+ warnings.push("It contains something you already typed on this form.");
150
+ break;
151
+ }
152
+ const run = longestRun(password);
153
+ if (run >= 3) {
154
+ bits -= (run - 2) * Math.log2(poolSize(password));
155
+ warnings.push("A character repeats several times in a row.");
156
+ }
157
+ const sequence = longestSequence(password);
158
+ if (sequence >= 4) {
159
+ bits -= sequence * Math.log2(poolSize(password)) * 0.75;
160
+ warnings.push("Part of it runs straight along the keyboard or the alphabet.");
161
+ }
162
+ if (password.length < 8) warnings.push("Short passwords fall to a brute force whatever they contain.");
163
+ bits = Math.max(0, Math.round(bits));
164
+ const score = bits < 28 ? 0 : bits < 40 ? 1 : bits < 60 ? 2 : bits < 80 ? 3 : 4;
165
+ return { score, bits, warnings, empty: false };
166
+ }
167
+
168
+ export { estimatePasswordStrength, generatePassword };
@@ -0,0 +1,43 @@
1
+ import { forwardRef, Children, isValidElement, cloneElement } from 'react';
2
+
3
+ // src/react/slot.tsx
4
+ function composeRefs(...refs) {
5
+ return (node) => {
6
+ for (const ref of refs) {
7
+ if (typeof ref === "function") ref(node);
8
+ else if (ref) ref.current = node;
9
+ }
10
+ };
11
+ }
12
+ function mergeSlotProps(slotProps, childProps) {
13
+ const merged = { ...slotProps, ...childProps };
14
+ for (const key of Object.keys(slotProps)) {
15
+ const slotValue = slotProps[key];
16
+ const childValue = childProps[key];
17
+ const isHandler = /^on[A-Z]/.test(key);
18
+ if (isHandler && typeof slotValue === "function" && typeof childValue === "function") {
19
+ merged[key] = (...args) => {
20
+ slotValue(...args);
21
+ return childValue(...args);
22
+ };
23
+ } else if (isHandler && typeof slotValue === "function" && childValue === void 0) {
24
+ merged[key] = slotValue;
25
+ } else if (key === "style") {
26
+ merged[key] = { ...slotValue, ...childValue };
27
+ } else if (key === "className") {
28
+ merged[key] = [slotValue, childValue].filter(Boolean).join(" ");
29
+ }
30
+ }
31
+ return merged;
32
+ }
33
+ var Slot = forwardRef(function Slot2({ children, ...slotProps }, forwardedRef) {
34
+ const child = Children.only(children);
35
+ if (!isValidElement(child)) return null;
36
+ const childProps = child.props;
37
+ const childRef = child.ref;
38
+ const merged = mergeSlotProps(slotProps, childProps);
39
+ if (forwardedRef || childRef) merged.ref = composeRefs(forwardedRef, childRef ?? null);
40
+ return cloneElement(child, merged);
41
+ });
42
+
43
+ export { Slot, mergeSlotProps };