@enigmax/primitives 0.17.0 → 0.18.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 (89) 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-F25CGNQC.js +19 -0
  6. package/dist/chunk-HC2ME5PU.js +168 -0
  7. package/dist/chunk-HS3X3XCW.js +43 -0
  8. package/dist/chunk-IIT6U7LM.js +249 -0
  9. package/dist/chunk-IXVMRVD4.js +96 -0
  10. package/dist/chunk-KJINGUQN.js +188 -0
  11. package/dist/chunk-LKA2UG4P.js +542 -0
  12. package/dist/chunk-MMQPZGSU.js +161 -0
  13. package/dist/chunk-OCMI7R6H.js +79 -0
  14. package/dist/chunk-QYMUIW5I.js +28 -0
  15. package/dist/chunk-S653GLSF.js +17 -0
  16. package/dist/chunk-SNYUBXWQ.js +149 -0
  17. package/dist/chunk-U3V4EHOB.js +41 -0
  18. package/dist/chunk-UOSSNUSC.js +309 -0
  19. package/dist/chunk-WSB655JB.js +47 -0
  20. package/dist/chunk-XNNQRA35.js +31 -0
  21. package/dist/chunk-XQHCZAPJ.js +102 -0
  22. package/dist/chunk-ZCUFYBPB.js +154 -0
  23. package/dist/chunk-ZWR2EXHQ.js +55 -0
  24. package/dist/flags-BBJc9unY.d.ts +133 -0
  25. package/dist/index-D-ARvWpQ.d.ts +306 -0
  26. package/dist/index-DNXPtY9X.d.ts +144 -0
  27. package/dist/index.d.ts +16 -602
  28. package/dist/index.js +9 -1
  29. package/dist/input-BwXjFenq.d.ts +77 -0
  30. package/dist/marquee-CJ3Uwy3E.d.ts +81 -0
  31. package/dist/network-D2LsBG_k.d.ts +39 -0
  32. package/dist/next/index.d.ts +20 -3
  33. package/dist/next/index.js +23 -3
  34. package/dist/notifications-BpVV6sel.d.ts +70 -0
  35. package/dist/password-3DRQYAYQ.js +2 -0
  36. package/dist/password-C8lG4Zm9.d.ts +71 -0
  37. package/dist/password-FB2CUEKJ.js +1 -0
  38. package/dist/react/button.d.ts +75 -0
  39. package/dist/react/button.js +4 -0
  40. package/dist/react/flag.d.ts +37 -0
  41. package/dist/react/flag.js +3 -0
  42. package/dist/react/index.d.ts +28 -312
  43. package/dist/react/index.js +22 -2
  44. package/dist/react/input.d.ts +4 -0
  45. package/dist/react/input.js +3 -0
  46. package/dist/react/marquee.d.ts +44 -0
  47. package/dist/react/marquee.js +3 -0
  48. package/dist/react/network.d.ts +20 -0
  49. package/dist/react/network.js +3 -0
  50. package/dist/react/notifications.d.ts +17 -0
  51. package/dist/react/notifications.js +3 -0
  52. package/dist/react/palette.d.ts +3 -0
  53. package/dist/react/palette.js +4 -0
  54. package/dist/react/relative-time.d.ts +21 -0
  55. package/dist/react/relative-time.js +3 -0
  56. package/dist/react/search.d.ts +30 -0
  57. package/dist/react/search.js +3 -0
  58. package/dist/react/slot.d.ts +47 -0
  59. package/dist/react/slot.js +2 -0
  60. package/dist/react/toast.d.ts +40 -0
  61. package/dist/react/toast.js +4 -0
  62. package/dist/react-router/index.d.ts +20 -3
  63. package/dist/react-router/index.js +23 -3
  64. package/dist/relative-time-YpRTG7YH.d.ts +106 -0
  65. package/dist/search-P4OHCRXD.js +50 -0
  66. package/package.json +52 -2
  67. package/registry.json +175 -18
  68. package/src/core/flags.ts +88 -42
  69. package/src/core/input-icons.ts +34 -0
  70. package/src/core/input.ts +5 -21
  71. package/src/core/palette.ts +0 -0
  72. package/src/index.ts +2 -2
  73. package/src/react/button.tsx +70 -4
  74. package/src/react/flag.tsx +18 -6
  75. package/src/react/index.ts +40 -5
  76. package/src/react/input/icon.tsx +17 -0
  77. package/src/react/input/index.tsx +307 -0
  78. package/src/react/input/password.tsx +174 -0
  79. package/src/react/input/search.tsx +82 -0
  80. package/src/react/input/types.ts +146 -0
  81. package/src/react/input/write-value.ts +18 -0
  82. package/src/react/palette/context.ts +60 -0
  83. package/src/react/palette/index.tsx +66 -0
  84. package/src/react/palette/root.tsx +621 -0
  85. package/src/react/slot.tsx +91 -0
  86. package/src/react/use-button.ts +3 -1
  87. package/dist/chunk-4GLYRF4B.js +0 -1259
  88. package/dist/chunk-JFNND6P4.js +0 -749
  89. package/src/react/input.tsx +0 -429
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Button behaviour: what makes it unavailable, and everything that follows from that.
3
+ *
4
+ * Disabled, loading and a cooldown are three reasons for the same state, so they collapse
5
+ * into one `available` the renderer reads, instead of three flags every call site has to
6
+ * combine correctly. The element to render is reported rather than chosen, because a
7
+ * framework-agnostic package cannot import next/link.
8
+ */
9
+ /** Which tag the consumer should render. An href makes it a link, and a link is not a button. */
10
+ type ButtonElement = "button" | "a";
11
+ interface ButtonCooldown {
12
+ /** How long the button stays unavailable after a press, in ms. */
13
+ ms: number;
14
+ /**
15
+ * Survive a reload under this key. Without it the cooldown is in memory only, and a
16
+ * refresh is a free retry - which is the whole thing a cooldown exists to prevent.
17
+ */
18
+ key?: string;
19
+ storage?: "local" | "session";
20
+ }
21
+ interface ButtonOptions {
22
+ /** Turns into an `a`, and a link cannot be `disabled` - only `aria-disabled`. */
23
+ href?: string;
24
+ disabled?: boolean;
25
+ /** Unavailable and busy. Set it yourself, or let an async `onPress` manage it. */
26
+ loading?: boolean;
27
+ /** ms, or the full shape for a cooldown that outlives a reload. */
28
+ cooldown?: number | ButtonCooldown;
29
+ /**
30
+ * A single key that presses the button. Ignored while the visitor is typing, and
31
+ * while any modifier is held, so it never steals a real shortcut.
32
+ */
33
+ shortcut?: string;
34
+ /** Async work flips `loading` for its duration and only then starts the cooldown. */
35
+ onPress?: (event?: Event) => void | Promise<void>;
36
+ /** Called whenever anything below changes. */
37
+ onChange?: (state: ButtonState) => void;
38
+ }
39
+ interface ButtonState {
40
+ /** The tag to render. */
41
+ element: ButtonElement;
42
+ /** Pressable: not disabled, not loading, not cooling down. */
43
+ available: boolean;
44
+ loading: boolean;
45
+ disabled: boolean;
46
+ /** ms left on the cooldown, 0 when there is none. */
47
+ cooldown: number;
48
+ /** The accessible name for the shortcut, when there is one. */
49
+ shortcut: string | null;
50
+ }
51
+ interface ButtonInstance {
52
+ readonly state: ButtonState;
53
+ /** Run the press as if it had been clicked. Ignored while unavailable. */
54
+ press(event?: Event): Promise<void>;
55
+ update(options: Partial<ButtonOptions>): void;
56
+ /** Clear a cooldown early, including its stored entry. */
57
+ reset(): void;
58
+ subscribe(listener: (state: ButtonState) => void): () => void;
59
+ destroy(): void;
60
+ }
61
+ declare function createButton(options?: ButtonOptions): ButtonInstance;
62
+
63
+ export { type ButtonCooldown as B, type ButtonElement as a, type ButtonInstance as b, type ButtonOptions as c, type ButtonState as d, createButton as e };
@@ -0,0 +1,88 @@
1
+ // src/core/network.ts
2
+ var SERVER_NETWORK_STATE = {
3
+ online: true,
4
+ downlink: null,
5
+ downlinkMax: null,
6
+ effectiveType: null,
7
+ rtt: null,
8
+ saveData: null,
9
+ type: null,
10
+ recovered: false,
11
+ slow: false
12
+ };
13
+ function connection() {
14
+ if (typeof navigator === "undefined") return null;
15
+ const nav = navigator;
16
+ return nav.connection ?? nav.mozConnection ?? nav.webkitConnection ?? null;
17
+ }
18
+ function shallowEqual(left, right) {
19
+ for (const key of Object.keys(left)) {
20
+ if (left[key] !== right[key]) return false;
21
+ }
22
+ return true;
23
+ }
24
+ function createNetworkMonitor() {
25
+ if (typeof window === "undefined") {
26
+ return {
27
+ get state() {
28
+ return SERVER_NETWORK_STATE;
29
+ },
30
+ subscribe() {
31
+ return () => {
32
+ };
33
+ },
34
+ destroy() {
35
+ }
36
+ };
37
+ }
38
+ const listeners = /* @__PURE__ */ new Set();
39
+ let hasBeenOffline = false;
40
+ let state = read();
41
+ function read() {
42
+ const link2 = connection();
43
+ const online = navigator.onLine;
44
+ const effectiveType = link2?.effectiveType ?? null;
45
+ const saveData = link2?.saveData ?? null;
46
+ return {
47
+ online,
48
+ downlink: link2?.downlink ?? null,
49
+ downlinkMax: link2?.downlinkMax ?? null,
50
+ effectiveType,
51
+ rtt: link2?.rtt ?? null,
52
+ saveData,
53
+ type: link2?.type ?? null,
54
+ recovered: online && hasBeenOffline,
55
+ slow: saveData === true || effectiveType === "2g" || effectiveType === "slow-2g"
56
+ };
57
+ }
58
+ function update() {
59
+ if (!navigator.onLine) hasBeenOffline = true;
60
+ const next = read();
61
+ if (shallowEqual(state, next)) return;
62
+ state = next;
63
+ for (const listener of listeners) listener(state);
64
+ }
65
+ const link = connection();
66
+ window.addEventListener("online", update, { passive: true });
67
+ window.addEventListener("offline", update, { passive: true });
68
+ link?.addEventListener("change", update, { passive: true });
69
+ return {
70
+ get state() {
71
+ return state;
72
+ },
73
+ subscribe(listener) {
74
+ listeners.add(listener);
75
+ return () => {
76
+ listeners.delete(listener);
77
+ };
78
+ },
79
+ destroy() {
80
+ listeners.clear();
81
+ window.removeEventListener("online", update);
82
+ window.removeEventListener("offline", update);
83
+ link?.removeEventListener("change", update);
84
+ }
85
+ };
86
+ }
87
+
88
+ export { SERVER_NETWORK_STATE, createNetworkMonitor };
@@ -0,0 +1,114 @@
1
+ import { createButton } from './chunk-MMQPZGSU.js';
2
+ import { Slot } from './chunk-HS3X3XCW.js';
3
+ import { forwardRef, createElement, useRef, useMemo, useState, useEffect, useCallback, isValidElement } from 'react';
4
+
5
+ function useButton(options = {}) {
6
+ const optionsRef = useRef(options);
7
+ optionsRef.current = options;
8
+ const instance = useMemo(() => createButton({
9
+ ...optionsRef.current,
10
+ onPress: (event) => optionsRef.current.onPress?.(event),
11
+ onChange: (next) => optionsRef.current.onChange?.(next)
12
+ // Built once: recreating it would drop a running cooldown on every render.
13
+ // eslint-disable-next-line react-hooks/exhaustive-deps
14
+ }), []);
15
+ const [state, setState] = useState(() => instance.state);
16
+ useEffect(() => {
17
+ const unsubscribe = instance.subscribe(setState);
18
+ setState(instance.state);
19
+ return () => {
20
+ unsubscribe();
21
+ instance.destroy();
22
+ };
23
+ }, [instance]);
24
+ useEffect(() => {
25
+ instance.update({
26
+ href: options.href,
27
+ disabled: options.disabled,
28
+ loading: options.loading,
29
+ cooldown: options.cooldown,
30
+ shortcut: options.shortcut
31
+ });
32
+ }, [instance, options.href, options.disabled, options.loading, options.cooldown, options.shortcut]);
33
+ const press = useCallback(() => {
34
+ void instance.press();
35
+ }, [instance]);
36
+ return {
37
+ ...state,
38
+ press,
39
+ reset: () => instance.reset(),
40
+ props: {
41
+ onClick: (event) => {
42
+ if (!state.available) {
43
+ event.preventDefault();
44
+ return;
45
+ }
46
+ void instance.press(event);
47
+ },
48
+ "aria-disabled": !state.available,
49
+ "aria-busy": state.loading,
50
+ ...state.loading ? { "data-loading": "" } : {},
51
+ ...state.cooldown > 0 ? { "data-cooldown": "" } : {},
52
+ ...state.element === "button" ? { disabled: state.disabled } : { href: options.href },
53
+ ...state.shortcut ? { title: `Shortcut: ${state.shortcut.toUpperCase()}`, "aria-keyshortcuts": state.shortcut } : {}
54
+ }
55
+ };
56
+ }
57
+
58
+ // src/react/link.ts
59
+ var linkComponent = null;
60
+ function setLinkComponent(component) {
61
+ linkComponent = component;
62
+ }
63
+ function getLinkComponent() {
64
+ return linkComponent ?? "a";
65
+ }
66
+ function hasText(node) {
67
+ if (node === null || node === void 0 || typeof node === "boolean") return false;
68
+ if (typeof node === "string") return node.trim().length > 0;
69
+ if (typeof node === "number") return true;
70
+ if (Array.isArray(node)) return node.some(hasText);
71
+ if (isValidElement(node)) return hasText(node.props.children);
72
+ return false;
73
+ }
74
+ var Button = forwardRef(function Button2({
75
+ as,
76
+ children,
77
+ pending,
78
+ href,
79
+ disabled,
80
+ loading,
81
+ cooldown,
82
+ shortcut,
83
+ onPress,
84
+ onClick,
85
+ onChange,
86
+ type = "button",
87
+ shortcutHint = "auto",
88
+ renderShortcut,
89
+ asChild = false,
90
+ ...rest
91
+ }, ref) {
92
+ const button = useButton({ href, disabled, loading, cooldown, shortcut, onPress: onPress ?? onClick, onChange });
93
+ const state = button;
94
+ const Tag = asChild ? Slot : as ?? (state.element === "a" ? getLinkComponent() : "button");
95
+ const native = Tag === "button" ? { type } : {};
96
+ const label = typeof children === "function" ? children(state) : children;
97
+ const content = state.loading && pending !== void 0 ? pending : label;
98
+ const showHint = !asChild && Boolean(state.shortcut) && !state.loading && (shortcutHint === "auto" ? hasText(content) : shortcutHint);
99
+ const hint = showHint && state.shortcut ? renderShortcut?.(state.shortcut) ?? createElement(
100
+ "kbd",
101
+ // Hidden from the accessible name - the button already carries
102
+ // `aria-keyshortcuts`, and "Save n" is not what anyone wants read out.
103
+ { "data-enigma-button-key": "", "aria-hidden": true },
104
+ state.shortcut
105
+ ) : null;
106
+ return createElement(
107
+ Tag,
108
+ { ref, ...button.props, ...native, ...rest },
109
+ content,
110
+ hint
111
+ );
112
+ });
113
+
114
+ export { Button, getLinkComponent, setLinkComponent, useButton };
@@ -0,0 +1,107 @@
1
+ // src/core/flags.ts
2
+ var FLAG_CDN = "https://cdn.jsdelivr.net/gh/FJRG2007/enigma@main/assets/flags";
3
+ var DEFAULTS = {
4
+ shape: "rect",
5
+ format: "svg",
6
+ source: "cdn",
7
+ basePath: "/flags",
8
+ size: 16
9
+ };
10
+ var config = { ...DEFAULTS };
11
+ function configureFlags(patch) {
12
+ config = { ...config, ...patch };
13
+ return config;
14
+ }
15
+ function flagConfig() {
16
+ return config;
17
+ }
18
+ function resetFlagConfig() {
19
+ config = { ...DEFAULTS };
20
+ return config;
21
+ }
22
+ var FLAG_RATIO = { rect: 4 / 3, square: 1, circle: 1 };
23
+ var EMOJI_FLAG = /^[\u{1F1E6}-\u{1F1FF}]{2}$/u;
24
+ var LOCALE_TAG = /^([A-Za-z]{2,3})[-_]([A-Z]{2})$/;
25
+ var FILE_CODE = /^[a-z]{2,}(?:[-_][a-z0-9]+)*$/;
26
+ var COUNTRY_CODE = /^[a-z]{2}$/;
27
+ function normalizeFlagCode(value) {
28
+ if (!value) return null;
29
+ const raw = value.trim();
30
+ if (!raw) return null;
31
+ if (EMOJI_FLAG.test(raw)) {
32
+ const letters = [...raw].map((char) => String.fromCharCode((char.codePointAt(0) ?? 0) - 127462 + 97));
33
+ return letters.join("");
34
+ }
35
+ const locale = LOCALE_TAG.exec(raw);
36
+ if (locale) return locale[2].toLowerCase();
37
+ const code = raw.toLowerCase();
38
+ return FILE_CODE.test(code) ? code : null;
39
+ }
40
+ function flagName(code, locale) {
41
+ const normalized = normalizeFlagCode(code);
42
+ if (!normalized || !COUNTRY_CODE.test(normalized)) return null;
43
+ const region = normalized.toUpperCase();
44
+ try {
45
+ const names = new Intl.DisplayNames([locale ?? documentLocale() ?? "en"], { type: "region" });
46
+ const name = names.of(region);
47
+ return !name || name === region ? null : name;
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
52
+ function documentLocale() {
53
+ if (typeof document === "undefined") return void 0;
54
+ return document.documentElement.lang || void 0;
55
+ }
56
+ var warnedAboutFormat = false;
57
+ function flagSrc(code, options = {}) {
58
+ const settings = { ...config, ...options };
59
+ const normalized = normalizeFlagCode(code);
60
+ if (!normalized) return null;
61
+ const remote = settings.source === "cdn";
62
+ let format = settings.format;
63
+ if (remote && format !== "svg") {
64
+ if (!warnedAboutFormat && typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
65
+ warnedAboutFormat = true;
66
+ console.warn(`[enigma/flags] The flags are served as SVG, so "${format}" was served as SVG. Download a local set for raster formats: enigma add flags --flags local --flag-formats ${format}`);
67
+ }
68
+ format = "svg";
69
+ }
70
+ const base = remote ? FLAG_CDN : (settings.source === "local" ? settings.basePath : settings.source).replace(/\/+$/, "");
71
+ return `${base}/${settings.shape}/${normalized}.${format}`;
72
+ }
73
+ function flagView(code, options = {}) {
74
+ const settings = { ...config, ...options };
75
+ const normalized = normalizeFlagCode(code);
76
+ const src = flagSrc(normalized, options);
77
+ if (!normalized || !src) return null;
78
+ const height = Math.max(1, Math.round(settings.size));
79
+ return {
80
+ code: normalized,
81
+ src,
82
+ shape: settings.shape,
83
+ format: settings.source === "cdn" ? "svg" : settings.format,
84
+ width: Math.round(height * FLAG_RATIO[settings.shape]),
85
+ height,
86
+ name: flagName(normalized, settings.locale)
87
+ };
88
+ }
89
+ function flagAttributes(code, options = {}) {
90
+ const view = flagView(code, options);
91
+ if (!view) return null;
92
+ const label = options.decorative ? "" : options.label?.trim() || view.name || "";
93
+ return {
94
+ src: view.src,
95
+ alt: label,
96
+ width: view.width,
97
+ height: view.height,
98
+ loading: "lazy",
99
+ decoding: "async",
100
+ "data-enigma-flag": "",
101
+ "data-flag-code": view.code,
102
+ "data-flag-shape": view.shape,
103
+ ...label ? {} : { "aria-hidden": "true" }
104
+ };
105
+ }
106
+
107
+ export { FLAG_CDN, FLAG_RATIO, configureFlags, flagAttributes, flagConfig, flagName, flagSrc, flagView, normalizeFlagCode, resetFlagConfig };
@@ -0,0 +1,19 @@
1
+ // src/core/input-icons.ts
2
+ var INPUT_ICON_PATHS = {
3
+ eye: ["M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z", "M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"],
4
+ eyeOff: [
5
+ "M10.6 5.2A9.8 9.8 0 0 1 12 5c6.5 0 10 7 10 7a17.6 17.6 0 0 1-3.2 4.2M6.2 6.2A17.7 17.7 0 0 0 2 12s3.5 7 10 7a9.6 9.6 0 0 0 4.2-.9",
6
+ "m2 2 20 20",
7
+ "M9.9 9.9a3 3 0 0 0 4.2 4.2"
8
+ ],
9
+ generate: ["m12 3 1.9 4.6L18.5 9.5 13.9 11.4 12 16l-1.9-4.6L5.5 9.5l4.6-1.9Z", "M19 15l.8 2.2 2.2.8-2.2.8-.8 2.2-.8-2.2-2.2-.8 2.2-.8Z"],
10
+ /** Clears a search field. The platform draws one only in WebKit, and not on mobile. */
11
+ clear: ["m6 6 12 12", "m18 6-12 12"],
12
+ search: ["M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14Z", "m20 20-4.2-4.2"]
13
+ };
14
+ function iconMarkup(paths) {
15
+ const body = paths.map((path) => `<path d="${path}"/>`).join("");
16
+ return `<svg viewBox="0 0 24 24" width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${body}</svg>`;
17
+ }
18
+
19
+ export { INPUT_ICON_PATHS, iconMarkup };
@@ -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 };