@virastack/password 1.0.2 → 1.1.0-beta.1

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.
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  </picture>
9
9
  </a>
10
10
 
11
- *Fully accessible and customizable password visibility hook for React.*
11
+ *Accessible password hook for React: visibility, strength, confirmation and server checks*
12
12
 
13
13
  [![ViraStack Password](https://img.shields.io/badge/ViraStack-Password-%23ff2056)](https://virastack.com/password/)
14
14
  [![npm version](https://img.shields.io/npm/v/@virastack/password)](https://www.npmjs.com/package/@virastack/password)
@@ -22,13 +22,15 @@
22
22
 
23
23
  ### [Read the full documentation →](https://virastack.com/password/)
24
24
 
25
- ## Why ViraStack Password?
25
+ ## Features
26
26
 
27
- - **Accessibility First:** Fully ARIA compliant out of the box for better screen reader support.
28
- - **Framework Agnostic:** Logic is separated from React state, making it adaptable and clean.
29
- - **Smart Defaults:** Includes minimal SVG icons by default, so you can start using it immediately.
30
- - **Prop Merging:** Safely merges user props with hook-generated props to prevent overrides.
31
- - **State Sync:** Automatically handles `disabled` and `readOnly` states for a consistent UX.
27
+ - **Headless:** Props for your `<input>` and `<button>`; the bar, colours and text stay yours.
28
+ - **Radix parity:** Hides on submit for password managers, focus returns to the input after a click, controlled `visible`, Caps Lock, `autoComplete`.
29
+ - **Strength, NIST style:** `@virastack/password/strength` checks length (15–64), common passwords, repetition and personal info. No composition rules by default.
30
+ - **Same rules on the server:** `@virastack/password/server` runs the same policy in Server Actions.
31
+ - **Leaked password check:** `@virastack/password/pwned` asks Have I Been Pwned with k-anonymity. Optional.
32
+ - **Error keys, not messages:** `password.tooShort`, `password.mismatch`… Map them in your own i18n.
33
+ - **Zero dependencies:** ~2 KB min+brotli for the hook; the strength engine only ships when you pass a policy.
32
34
 
33
35
  ## Quick Start
34
36
 
@@ -36,10 +38,12 @@
36
38
  npm install @virastack/password
37
39
  ```
38
40
 
41
+ Requires React and React DOM 18.2+ (React 19 supported). Upgrading from 1.0? Nothing breaks; see the [upgrade guide](https://virastack.com/password/docs/upgrade).
42
+
39
43
  ```tsx
40
- import { useViraPassword } from '@virastack/password';
44
+ import { useViraPassword } from "@virastack/password";
41
45
 
42
- function App() {
46
+ function PasswordField() {
43
47
  const { inputProps, btnProps } = useViraPassword();
44
48
 
45
49
  return (
@@ -51,16 +55,131 @@ function App() {
51
55
  }
52
56
  ```
53
57
 
54
- ## Explore the ViraStack Ecosystem
58
+ ## Sign-up form (React Hook Form)
59
+
60
+ ```tsx
61
+ // lib/password-policy.ts – shared by the form and the server, no "use client"
62
+ import { definePolicy } from "@virastack/password/strength";
63
+ export const policy = definePolicy(); // NIST: 15–64 characters
64
+ ```
55
65
 
56
- Discover all ViraStack tools, libraries, and boilerplates at [**virastack.com**](https://virastack.com).
66
+ ```tsx
67
+ import { useViraPassword } from "@virastack/password";
68
+ import { useForm, useWatch } from "react-hook-form";
69
+ import { policy } from "@/lib/password-policy";
57
70
 
58
- ## License
71
+ function Signup() {
72
+ const form = useForm({ defaultValues: { email: "", password: "", confirm: "" } });
73
+ const [email, password, confirmValue] = useWatch({ control: form.control, name: ["email", "password", "confirm"] });
59
74
 
60
- Licensed under the [MIT License](https://github.com/virastack/password/blob/main/LICENSE).
75
+ const pw = useViraPassword({ policy, value: password, forbidden: [email] });
76
+ const confirm = useViraPassword({ matchWith: password, value: confirmValue });
77
+
78
+ return (
79
+ <form method="post" onSubmit={form.handleSubmit(console.log)}>
80
+ <input type="email" autoComplete="username" {...form.register("email")} />
81
+
82
+ {/* deps re-checks the confirm field when the password changes */}
83
+ <input {...pw.inputProps} {...form.register("password", { ...pw.rule, deps: ["confirm"] })} />
84
+ <button {...pw.btnProps} />
85
+ <p {...pw.strengthProps}>{pw.strength?.level}</p>
86
+ {pw.capsLock && <p>Caps Lock is on</p>}
87
+
88
+ <input {...confirm.inputProps} {...form.register("confirm", confirm.rule)} />
89
+ <button {...confirm.btnProps} />
90
+ </form>
91
+ );
92
+ }
93
+ ```
94
+
95
+ - `inputProps` only holds attributes (no ref, no handlers), so spread it before or after `register()` or a ViraStack Mask field.
96
+ - Pass `value` from `useWatch()`: `reset()` and `setValue()` don't fire input events.
97
+ - `deps` only runs on user input. After `setValue("password", …)`, call `trigger("confirm")`.
98
+ - `method="post"`: a submit before hydration never puts the password in the URL.
99
+
100
+ ## Options
101
+
102
+ | Option | Description |
103
+ | :--- | :--- |
104
+ | `defaultVisible`, `visible`, `onVisibleChange` | Uncontrolled or controlled visibility |
105
+ | `hideOnSubmit` | Hide on form submit and reset (default `true`) |
106
+ | `hideOnBlur` | Hide when focus leaves the field or the tab is hidden (default `false`) |
107
+ | `autoComplete` | Default `current-password`; `new-password` with `policy` or `matchWith`; `false` leaves it out |
108
+ | `policy`, `forbidden` | Strength policy and values the password must not contain |
109
+ | `matchWith` | The password a confirm field must repeat |
110
+ | `value` | Current value, e.g. from `useWatch()` |
111
+ | `disabled`, `readOnly` | Disable the toggle and mirror the state on the input |
112
+ | `icons` | `{ show, hide }` custom icons |
113
+ | `id`, `inputProps`, `btnProps` | Props merged into the generated ones (`inputProps.id` wins over `id`) |
114
+
115
+ Returns `{ inputProps, btnProps, isVisible, toggle, setVisible, capsLock, strength, strengthProps, matches, issues, rule }`.
116
+
117
+ ## With ViraStack Mask
118
+
119
+ [ViraStack Mask](https://github.com/virastack/mask) v2 also keeps values in React Hook Form, so both share one `useForm`. Use them on different inputs; to put a toggle on a masked field (a PIN), spread the mask field and `inputProps` together:
120
+
121
+ ```tsx
122
+ const { pin } = useViraMask(form, { pin: { mask: "999999" } });
123
+ const pinToggle = useViraPassword({ autoComplete: false });
124
+
125
+ <input {...pin} {...pinToggle.inputProps} />
126
+ <button {...pinToggle.btnProps} />
127
+ ```
61
128
 
62
- ## Maintainer
129
+ ## Strength
63
130
 
64
- A project by [**Ömer Gülçiçek**](https://omergulcicek.com)
131
+ ```ts
132
+ import { definePolicy, evaluate } from "@virastack/password/strength";
133
+
134
+ const policy = definePolicy({ preset: "nist", blocklist: ["acme"] });
135
+ evaluate("correct horse battery staple", policy, { forbidden: ["omer@example.com"] });
136
+ // { score: 0, level: "weak", valid: false, length: 28, checks: {...}, issues: ["password.common"] }
137
+ ```
138
+
139
+ | Preset | Length | Composition |
140
+ | :--- | :--- | :--- |
141
+ | `nist` (default) | 15–64 | none, per NIST SP 800-63B-4 |
142
+ | `legacy` | 8–64 | lowercase, uppercase, digit and symbol |
143
+
144
+ Passwords are normalized with NFKC and counted in code points (emoji and Turkish letters count once); nothing is cut at the limit. `forbidden` splits emails (local part) and names into parts; parts shorter than `minTokenLength` (default 3) are ignored. Raise it if short names cause false positives.
145
+
146
+ Don't block pasting into password fields: password managers rely on it.
147
+
148
+ ## Server
149
+
150
+ ```ts
151
+ "use server";
152
+ import { validatePassword } from "@virastack/password/server";
153
+ import { policy } from "@/lib/password-policy";
154
+
155
+ export async function signup(formData: FormData) {
156
+ const result = validatePassword(formData.get("password"), policy, {
157
+ forbidden: [formData.get("email")],
158
+ confirm: formData.get("confirm"),
159
+ });
160
+ if (!result.valid) return { errors: result.issues };
161
+ // hash result.password (NFKC-normalized), then create the user
162
+ return { ok: true };
163
+ }
164
+ ```
165
+
166
+ The root import has `"use client"`. In Server Components and Actions, import from `/server` or `/strength`.
167
+
168
+ ## Leaked passwords
169
+
170
+ ```ts
171
+ import { checkPwned } from "@virastack/password/pwned";
172
+
173
+ const result = await checkPwned(password, { signal, timeout: 3000 });
174
+ // { status: "pwned", count: 10434004 } | { status: "safe" } | { status: "unknown", reason: "timeout" }
175
+ ```
176
+
177
+ Only the first 5 characters of the SHA-1 hash are sent. On any failure the status is `unknown` and nothing is blocked. Call it from the server: from a browser, Have I Been Pwned sees the user's IP. `crypto.subtle` needs HTTPS or localhost.
178
+
179
+ ## Ecosystem
180
+
181
+ Part of the [ViraStack](https://virastack.com) ecosystem: [Academy](https://github.com/virastack/academy) · [Start](https://github.com/virastack/start) · [AI](https://github.com/virastack/ai) · [Mask](https://github.com/virastack/mask) · [Guide](https://github.com/virastack/guide)
182
+
183
+ ## License
65
184
 
66
- [![Follow Ömer Gülçiçek](https://img.shields.io/github/followers/omergulcicek?label=Follow&style=social)](https://github.com/omergulcicek)
185
+ [MIT](LICENSE) © [Ömer Gülçiçek](https://omergulcicek.com)
package/dist/index.cjs ADDED
@@ -0,0 +1,351 @@
1
+ "use client";
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/index.ts
32
+ var src_exports = {};
33
+ __export(src_exports, {
34
+ EyeIcon: () => EyeIcon,
35
+ EyeOffIcon: () => EyeOffIcon,
36
+ composeRefs: () => composeRefs,
37
+ getButtonAriaAttributes: () => getButtonAriaAttributes,
38
+ getInputAriaAttributes: () => getInputAriaAttributes,
39
+ getInputType: () => getInputType,
40
+ mergeProps: () => mergeProps,
41
+ useViraPassword: () => useViraPassword
42
+ });
43
+ module.exports = __toCommonJS(src_exports);
44
+
45
+ // src/core/logic.ts
46
+ var getInputType = (isVisible) => {
47
+ return isVisible ? "text" : "password";
48
+ };
49
+ var getButtonAriaAttributes = (isVisible, inputId, label = "Toggle password visibility") => {
50
+ return {
51
+ "aria-label": label,
52
+ "aria-pressed": isVisible,
53
+ "aria-controls": inputId,
54
+ type: "button"
55
+ };
56
+ };
57
+ var getInputAriaAttributes = (isInvalid, describedBy) => {
58
+ const attrs = {};
59
+ if (isInvalid !== void 0) {
60
+ attrs["aria-invalid"] = isInvalid;
61
+ }
62
+ if (describedBy) {
63
+ attrs["aria-describedby"] = describedBy;
64
+ }
65
+ return attrs;
66
+ };
67
+
68
+ // src/react/useViraPassword.ts
69
+ var import_react2 = __toESM(require("react"));
70
+ var import_react_dom = require("react-dom");
71
+
72
+ // src/icons/index.ts
73
+ var import_react = __toESM(require("react"));
74
+ var EyeIcon = (props) => import_react.default.createElement(
75
+ "svg",
76
+ {
77
+ xmlns: "http://www.w3.org/2000/svg",
78
+ width: "16",
79
+ height: "16",
80
+ viewBox: "0 0 24 24",
81
+ fill: "none",
82
+ stroke: "currentColor",
83
+ strokeWidth: "2",
84
+ strokeLinecap: "round",
85
+ strokeLinejoin: "round",
86
+ ...props
87
+ },
88
+ import_react.default.createElement("path", { d: "M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" }),
89
+ import_react.default.createElement("circle", { cx: "12", cy: "12", r: "3" })
90
+ );
91
+ var EyeOffIcon = (props) => import_react.default.createElement(
92
+ "svg",
93
+ {
94
+ xmlns: "http://www.w3.org/2000/svg",
95
+ width: "16",
96
+ height: "16",
97
+ viewBox: "0 0 24 24",
98
+ fill: "none",
99
+ stroke: "currentColor",
100
+ strokeWidth: "2",
101
+ strokeLinecap: "round",
102
+ strokeLinejoin: "round",
103
+ ...props
104
+ },
105
+ import_react.default.createElement("path", { d: "M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" }),
106
+ import_react.default.createElement("path", { d: "M14.084 14.158a3 3 0 0 1-4.242-4.242" }),
107
+ import_react.default.createElement("path", { d: "M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" }),
108
+ import_react.default.createElement("path", { d: "m2 2 20 20" })
109
+ );
110
+
111
+ // src/react/useViraPassword.ts
112
+ var setRef = (ref, value) => {
113
+ if (typeof ref === "function") return ref(value);
114
+ if (ref) ref.current = value;
115
+ };
116
+ var composeRefs = (...refs) => (node) => {
117
+ const cleanups = refs.map((ref) => setRef(ref, node));
118
+ if (!cleanups.some((cleanup) => typeof cleanup === "function")) return;
119
+ return () => {
120
+ refs.forEach((ref, i) => {
121
+ const cleanup = cleanups[i];
122
+ if (typeof cleanup === "function") cleanup();
123
+ else setRef(ref, null);
124
+ });
125
+ };
126
+ };
127
+ function mergeProps(...args) {
128
+ const result = {};
129
+ for (const props of args) {
130
+ if (!props) continue;
131
+ for (const [key, val] of Object.entries(props)) {
132
+ const existing = result[key];
133
+ if (key.startsWith("on") && typeof val === "function" && typeof existing === "function") {
134
+ result[key] = (...args2) => {
135
+ existing(...args2);
136
+ val(...args2);
137
+ };
138
+ } else if ((key === "className" || key === "aria-describedby") && typeof val === "string" && typeof existing === "string") {
139
+ result[key] = [existing, val].filter(Boolean).join(" ");
140
+ } else if (key === "ref" && existing && val) {
141
+ result[key] = composeRefs(existing, val);
142
+ } else {
143
+ result[key] = val;
144
+ }
145
+ }
146
+ }
147
+ return result;
148
+ }
149
+ var sameText = (a, b) => a.normalize("NFKC") === b.normalize("NFKC");
150
+ var useViraPassword = (options = {}) => {
151
+ const {
152
+ defaultVisible = false,
153
+ visible: controlledVisible,
154
+ onVisibleChange,
155
+ hideOnSubmit = true,
156
+ hideOnBlur = false,
157
+ autoComplete,
158
+ policy,
159
+ forbidden,
160
+ matchWith,
161
+ value: providedValue,
162
+ icons,
163
+ disabled = false,
164
+ readOnly = false,
165
+ id: providedId,
166
+ inputProps: userInputProps,
167
+ btnProps: userBtnProps
168
+ } = options;
169
+ const [uncontrolledVisible, setUncontrolledVisible] = (0, import_react2.useState)(defaultVisible);
170
+ const [capsLock, setCapsLock] = (0, import_react2.useState)(false);
171
+ const userValue = userInputProps?.value;
172
+ const checksValue = policy !== void 0 || matchWith !== void 0;
173
+ const tracksValue = checksValue && providedValue === void 0 && typeof userValue !== "string";
174
+ const [typed, setTyped] = (0, import_react2.useState)(() => {
175
+ const initial = userInputProps?.defaultValue;
176
+ return typeof initial === "string" ? initial : "";
177
+ });
178
+ const value = providedValue ?? (typeof userValue === "string" ? userValue : typed);
179
+ const isVisible = controlledVisible ?? uncontrolledVisible;
180
+ const generatedId = (0, import_react2.useId)();
181
+ const inputId = typeof userInputProps?.id === "string" && userInputProps.id || providedId || `virapassword-input-${generatedId}`;
182
+ const latest = (0, import_react2.useRef)({ isVisible, controlled: controlledVisible !== void 0, onVisibleChange, policy, forbidden, matchWith, hideOnBlur, tracksValue });
183
+ latest.current = { isVisible, controlled: controlledVisible !== void 0, onVisibleChange, policy, forbidden, matchWith, hideOnBlur, tracksValue };
184
+ const pointerPress = (0, import_react2.useRef)(null);
185
+ const getInput = (0, import_react2.useCallback)(() => {
186
+ const element = typeof document === "undefined" ? null : document.getElementById(inputId);
187
+ return element instanceof HTMLInputElement ? element : null;
188
+ }, [inputId]);
189
+ const commit = (0, import_react2.useCallback)((next) => {
190
+ const current = latest.current;
191
+ if (next === current.isVisible) return;
192
+ if (!current.controlled) setUncontrolledVisible(next);
193
+ current.onVisibleChange?.(next);
194
+ }, []);
195
+ const toggle = (0, import_react2.useCallback)(() => {
196
+ if (disabled || readOnly) return;
197
+ commit(!latest.current.isVisible);
198
+ }, [disabled, readOnly, commit]);
199
+ const setVisible = (0, import_react2.useCallback)((visible) => {
200
+ if (disabled || readOnly) return;
201
+ commit(visible);
202
+ }, [disabled, readOnly, commit]);
203
+ const hide = (0, import_react2.useCallback)(() => commit(false), [commit]);
204
+ (0, import_react2.useEffect)(() => {
205
+ if (!hideOnSubmit) return;
206
+ const isOwnForm = (event) => {
207
+ const form = getInput()?.form;
208
+ return form != null && event.target === form;
209
+ };
210
+ const onSubmit = (event) => {
211
+ if (isOwnForm(event) && latest.current.isVisible) (0, import_react_dom.flushSync)(hide);
212
+ };
213
+ const onReset = (event) => {
214
+ if (isOwnForm(event) && !event.defaultPrevented) hide();
215
+ };
216
+ document.addEventListener("submit", onSubmit, true);
217
+ document.addEventListener("reset", onReset);
218
+ return () => {
219
+ document.removeEventListener("submit", onSubmit, true);
220
+ document.removeEventListener("reset", onReset);
221
+ };
222
+ }, [hideOnSubmit, getInput, hide]);
223
+ (0, import_react2.useEffect)(() => {
224
+ if (!tracksValue) return;
225
+ const onReset = (event) => {
226
+ const input = getInput();
227
+ if (input?.form && event.target === input.form) setTimeout(() => setTyped(input.value));
228
+ };
229
+ document.addEventListener("reset", onReset);
230
+ return () => document.removeEventListener("reset", onReset);
231
+ }, [tracksValue, getInput]);
232
+ (0, import_react2.useEffect)(() => {
233
+ if (!hideOnBlur) return;
234
+ const onVisibilityChange = () => {
235
+ if (document.visibilityState === "hidden") hide();
236
+ };
237
+ document.addEventListener("visibilitychange", onVisibilityChange);
238
+ return () => document.removeEventListener("visibilitychange", onVisibilityChange);
239
+ }, [hideOnBlur, hide]);
240
+ const hideOnFocusLeave = (0, import_react2.useCallback)((next) => {
241
+ if (!latest.current.hideOnBlur || pointerPress.current) return;
242
+ if (next instanceof Element && (next.id === inputId || next.getAttribute("aria-controls") === inputId)) return;
243
+ hide();
244
+ }, [inputId, hide]);
245
+ (0, import_react2.useEffect)(() => {
246
+ const isInput = (target) => target instanceof HTMLInputElement && target.id === inputId;
247
+ const onKey = (event) => {
248
+ if (isInput(event.target)) setCapsLock(event.getModifierState?.("CapsLock") ?? false);
249
+ };
250
+ const onFocusOut = (event) => {
251
+ if (!isInput(event.target)) return;
252
+ setCapsLock(false);
253
+ hideOnFocusLeave(event.relatedTarget);
254
+ };
255
+ const onInput = (event) => {
256
+ if (latest.current.tracksValue && isInput(event.target)) setTyped(event.target.value);
257
+ };
258
+ document.addEventListener("keydown", onKey, true);
259
+ document.addEventListener("keyup", onKey, true);
260
+ document.addEventListener("focusout", onFocusOut, true);
261
+ document.addEventListener("input", onInput, true);
262
+ return () => {
263
+ document.removeEventListener("keydown", onKey, true);
264
+ document.removeEventListener("keyup", onKey, true);
265
+ document.removeEventListener("focusout", onFocusOut, true);
266
+ document.removeEventListener("input", onInput, true);
267
+ };
268
+ }, [inputId, hideOnFocusLeave]);
269
+ const strength = policy ? policy.evaluate(value, { forbidden }) : null;
270
+ const matches = matchWith === void 0 || value === "" ? null : sameText(value, matchWith);
271
+ const issues = strength ? [...strength.issues] : [];
272
+ if (matches === false) issues.push("password.mismatch");
273
+ const strengthId = `${inputId}-strength`;
274
+ const [rule] = (0, import_react2.useState)(() => ({
275
+ validate: (input) => {
276
+ const { policy: currentPolicy, forbidden: currentForbidden, matchWith: currentMatch } = latest.current;
277
+ const text = typeof input === "string" ? input : "";
278
+ const result = currentPolicy?.evaluate(text, { forbidden: currentForbidden });
279
+ if (result && !result.valid) return result.issues[0];
280
+ if (currentMatch !== void 0 && !sameText(text, currentMatch)) return "password.mismatch";
281
+ return true;
282
+ }
283
+ }));
284
+ const showIcon = icons?.show ?? import_react2.default.createElement(EyeIcon);
285
+ const hideIcon = icons?.hide ?? import_react2.default.createElement(EyeOffIcon);
286
+ const baseInputProps = {
287
+ id: inputId,
288
+ type: getInputType(isVisible),
289
+ disabled,
290
+ readOnly,
291
+ ...autoComplete === false ? {} : { autoComplete: autoComplete ?? (checksValue ? "new-password" : "current-password") },
292
+ spellCheck: false,
293
+ autoCorrect: "off",
294
+ autoCapitalize: "off",
295
+ ...getInputAriaAttributes(void 0, policy ? strengthId : void 0)
296
+ };
297
+ const baseBtnProps = {
298
+ onPointerDown: () => {
299
+ const input = getInput();
300
+ const press = { start: input?.selectionStart ?? null, end: input?.selectionEnd ?? null };
301
+ pointerPress.current = press;
302
+ window.addEventListener("pointerup", () => setTimeout(() => {
303
+ if (pointerPress.current === press) pointerPress.current = null;
304
+ }), { once: true });
305
+ },
306
+ onPointerCancel: () => {
307
+ pointerPress.current = null;
308
+ },
309
+ onClick: () => {
310
+ const press = pointerPress.current;
311
+ pointerPress.current = null;
312
+ (0, import_react_dom.flushSync)(toggle);
313
+ if (!press || disabled || readOnly) return;
314
+ const input = getInput();
315
+ if (!input) return;
316
+ input.focus();
317
+ if (press.start !== null && press.end !== null) input.setSelectionRange(press.start, press.end);
318
+ },
319
+ onBlur: (event) => hideOnFocusLeave(event.relatedTarget),
320
+ disabled: disabled || readOnly,
321
+ children: isVisible ? hideIcon : showIcon,
322
+ ...getButtonAriaAttributes(isVisible, inputId)
323
+ };
324
+ const mergedInputProps = mergeProps(baseInputProps, userInputProps);
325
+ const mergedBtnProps = mergeProps(baseBtnProps, userBtnProps);
326
+ return {
327
+ inputProps: mergedInputProps,
328
+ btnProps: mergedBtnProps,
329
+ isVisible,
330
+ capsLock,
331
+ toggle,
332
+ setVisible,
333
+ strength,
334
+ matches,
335
+ strengthProps: { id: strengthId, role: "status", "aria-live": "polite", "aria-atomic": true },
336
+ issues,
337
+ rule
338
+ };
339
+ };
340
+ // Annotate the CommonJS export names for ESM import in node:
341
+ 0 && (module.exports = {
342
+ EyeIcon,
343
+ EyeOffIcon,
344
+ composeRefs,
345
+ getButtonAriaAttributes,
346
+ getInputAriaAttributes,
347
+ getInputType,
348
+ mergeProps,
349
+ useViraPassword
350
+ });
351
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/core/logic.ts","../src/react/useViraPassword.ts","../src/icons/index.ts"],"sourcesContent":["export * from './core/logic';\nexport * from './react/useViraPassword';\nexport * from './icons';\n","/**\n * Generates the input type based on visibility.\n */\nexport const getInputType = (isVisible: boolean): 'text' | 'password' => {\n return isVisible ? 'text' : 'password';\n};\n\n/**\n * Generates ARIA attributes for the toggle button.\n */\nexport const getButtonAriaAttributes = (\n isVisible: boolean,\n inputId: string,\n label: string = 'Toggle password visibility'\n) => {\n return {\n 'aria-label': label,\n 'aria-pressed': isVisible,\n 'aria-controls': inputId,\n type: 'button' as const,\n };\n};\n\n/**\n * Generates ARIA attributes for the input field.\n */\nexport interface InputAriaAttributes {\n 'aria-invalid'?: boolean;\n 'aria-describedby'?: string;\n}\n\nexport const getInputAriaAttributes = (\n isInvalid?: boolean,\n describedBy?: string\n): InputAriaAttributes => {\n const attrs: InputAriaAttributes = {};\n\n if (isInvalid !== undefined) {\n attrs['aria-invalid'] = isInvalid;\n }\n \n if (describedBy) {\n attrs['aria-describedby'] = describedBy;\n }\n \n return attrs;\n};\n","import React, { useState, useId, useCallback, useEffect, useRef, ReactNode } from 'react';\nimport { flushSync } from 'react-dom';\nimport { EyeIcon, EyeOffIcon } from '../icons';\nimport { getInputType, getButtonAriaAttributes, getInputAriaAttributes } from '../core/logic';\nimport type { PasswordIssue, Policy, StrengthResult } from '../strength/types';\n\nexport type ViraPasswordInputProps = React.InputHTMLAttributes<HTMLInputElement> & {\n ref?: React.Ref<HTMLInputElement>;\n};\n\nexport interface UseViraPasswordOptions {\n /**\n * Initial visibility state of the password.\n * @default false\n */\n defaultVisible?: boolean;\n\n /**\n * Controlled visibility. Pair it with `onVisibleChange`.\n */\n visible?: boolean;\n\n /**\n * Called with the next visibility whenever it changes, including automatic hiding.\n */\n onVisibleChange?: (visible: boolean) => void;\n\n /**\n * Hide the password when its form is submitted or reset, so password managers see a password field.\n * @default true\n */\n hideOnSubmit?: boolean;\n\n /**\n * Hide the password when focus leaves the input and the toggle, or the browser tab is hidden.\n * @default false\n */\n hideOnBlur?: boolean;\n\n /**\n * `autoComplete` for the input. `false` leaves the attribute out.\n * @default \"new-password\" with `policy` or `matchWith`, otherwise \"current-password\"\n */\n autoComplete?: string | false;\n\n /**\n * A policy from `definePolicy()` in `@virastack/password/strength`. Enables `strength` and `issues`.\n */\n policy?: Policy;\n\n /**\n * Values the password must not contain, e.g. the user's email and name.\n */\n forbidden?: readonly unknown[];\n\n /**\n * The password this field must repeat, e.g. `watch(\"password\")` on a confirm field.\n * Enables `matches` and the `password.mismatch` key.\n */\n matchWith?: string;\n\n /**\n * The current value, e.g. `watch(\"password\")`. Without it the hook follows typing in the input,\n * which misses `reset(values)` and `setValue()` from a form library.\n */\n value?: string;\n\n /**\n * Custom icons for show/hide states.\n */\n icons?: {\n show: ReactNode;\n hide: ReactNode;\n };\n\n /**\n * Whether the input is disabled.\n * If true, the toggle button will also be disabled.\n */\n disabled?: boolean;\n\n /**\n * Whether the input is read-only.\n * If true, the toggle button will be disabled.\n */\n readOnly?: boolean;\n\n /**\n * ID for the input element. If not provided, a random ID will be generated.\n */\n id?: string;\n\n /**\n * Props to be merged with the generated input props, e.g. `register(\"password\")`.\n */\n inputProps?: ViraPasswordInputProps;\n\n /**\n * Props to be merged with the generated button props.\n */\n btnProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;\n}\n\nexport interface UseViraPasswordResult {\n inputProps: ViraPasswordInputProps;\n btnProps: React.ButtonHTMLAttributes<HTMLButtonElement>;\n isVisible: boolean;\n /** Caps Lock is on while the input has focus. */\n capsLock: boolean;\n toggle: () => void;\n setVisible: (visible: boolean) => void;\n /** Result of the policy for the current value; `null` without a policy. */\n strength: StrengthResult | null;\n /** Spread on the element that shows the strength, so screen readers announce it. */\n strengthProps: ViraPasswordStrengthProps;\n /** With `matchWith`: whether the value repeats it; `null` while the field is empty or without `matchWith`. */\n matches: boolean | null;\n /** Error keys for the current value. */\n issues: PasswordIssue[];\n /** A validation rule for React Hook Form: `register(\"password\", pw.rule)`. Returns the first error key. */\n rule: ViraPasswordRule;\n}\n\nexport interface ViraPasswordStrengthProps {\n id: string;\n role: 'status';\n 'aria-live': 'polite';\n 'aria-atomic': true;\n}\n\nexport interface ViraPasswordRule {\n validate: (value: unknown) => true | PasswordIssue;\n}\n\ntype Handler = (...args: unknown[]) => unknown;\ntype RefCleanup = ReturnType<React.RefCallback<unknown>>;\n\nconst setRef = <T>(ref: React.Ref<T> | undefined, value: T | null): RefCleanup => {\n if (typeof ref === 'function') return ref(value);\n if (ref) ref.current = value;\n};\n\n/**\n * Combines refs into one callback ref. Supports React 19 ref cleanup functions.\n */\nconst composeRefs = <T>(...refs: (React.Ref<T> | undefined)[]): React.RefCallback<T> => (node) => {\n const cleanups = refs.map((ref) => setRef(ref, node));\n if (!cleanups.some((cleanup) => typeof cleanup === 'function')) return;\n return () => {\n refs.forEach((ref, i) => {\n const cleanup = cleanups[i];\n if (typeof cleanup === 'function') cleanup();\n else setRef(ref, null);\n });\n };\n};\n\n/**\n * A utility to merge event handlers, class names, `aria-describedby` and refs.\n * Later props win; handlers run in order.\n */\nfunction mergeProps<T extends object>(...args: (T | undefined)[]): T;\nfunction mergeProps<A extends object, B extends object>(a: A | undefined, b: B | undefined): Omit<A, keyof B> & B;\nfunction mergeProps(...args: (object | undefined)[]): object {\n const result: Record<string, unknown> = {};\n\n for (const props of args) {\n if (!props) continue;\n\n for (const [key, val] of Object.entries(props)) {\n const existing = result[key];\n\n if (\n key.startsWith('on') &&\n typeof val === 'function' &&\n typeof existing === 'function'\n ) {\n result[key] = (...args: unknown[]) => {\n (existing as Handler)(...args);\n (val as Handler)(...args);\n };\n } else if ((key === 'className' || key === 'aria-describedby') && typeof val === 'string' && typeof existing === 'string') {\n result[key] = [existing, val].filter(Boolean).join(' ');\n } else if (key === 'ref' && existing && val) {\n result[key] = composeRefs(existing as React.Ref<unknown>, val as React.Ref<unknown>);\n } else {\n result[key] = val;\n }\n }\n }\n\n return result;\n}\n\n// Same comparison as the server: NFKC, so \"é\" typed two ways still matches.\nconst sameText = (a: string, b: string) => a.normalize('NFKC') === b.normalize('NFKC');\n\nexport const useViraPassword = (options: UseViraPasswordOptions = {}): UseViraPasswordResult => {\n const {\n defaultVisible = false,\n visible: controlledVisible,\n onVisibleChange,\n hideOnSubmit = true,\n hideOnBlur = false,\n autoComplete,\n policy,\n forbidden,\n matchWith,\n value: providedValue,\n icons,\n disabled = false,\n readOnly = false,\n id: providedId,\n inputProps: userInputProps,\n btnProps: userBtnProps,\n } = options;\n\n const [uncontrolledVisible, setUncontrolledVisible] = useState(defaultVisible);\n const [capsLock, setCapsLock] = useState(false);\n const userValue = userInputProps?.value;\n const checksValue = policy !== undefined || matchWith !== undefined;\n const tracksValue = checksValue && providedValue === undefined && typeof userValue !== 'string';\n const [typed, setTyped] = useState(() => {\n const initial = userInputProps?.defaultValue;\n return typeof initial === 'string' ? initial : '';\n });\n const value = providedValue ?? (typeof userValue === 'string' ? userValue : typed);\n const isVisible = controlledVisible ?? uncontrolledVisible;\n const generatedId = useId();\n // inputProps.id wins over id, so the button and the listeners follow the id the input really gets.\n const inputId = (typeof userInputProps?.id === 'string' && userInputProps.id) || providedId || `virapassword-input-${generatedId}`;\n\n // Read from event handlers and listeners without re-subscribing on every render.\n const latest = useRef({ isVisible, controlled: controlledVisible !== undefined, onVisibleChange, policy, forbidden, matchWith, hideOnBlur, tracksValue });\n latest.current = { isVisible, controlled: controlledVisible !== undefined, onVisibleChange, policy, forbidden, matchWith, hideOnBlur, tracksValue };\n\n // Set between a pointer press on the toggle and its click.\n const pointerPress = useRef<{ start: number | null; end: number | null } | null>(null);\n\n // Found by id: inputProps has no ref, so it never replaces the ref of register() or a mask.\n const getInput = useCallback((): HTMLInputElement | null => {\n const element = typeof document === 'undefined' ? null : document.getElementById(inputId);\n return element instanceof HTMLInputElement ? element : null;\n }, [inputId]);\n\n const commit = useCallback((next: boolean) => {\n const current = latest.current;\n if (next === current.isVisible) return;\n if (!current.controlled) setUncontrolledVisible(next);\n current.onVisibleChange?.(next);\n }, []);\n\n const toggle = useCallback(() => {\n if (disabled || readOnly) return;\n commit(!latest.current.isVisible);\n }, [disabled, readOnly, commit]);\n\n const setVisible = useCallback((visible: boolean) => {\n if (disabled || readOnly) return;\n commit(visible);\n }, [disabled, readOnly, commit]);\n\n // Hiding is always allowed, even while disabled.\n const hide = useCallback(() => commit(false), [commit]);\n\n useEffect(() => {\n if (!hideOnSubmit) return;\n const isOwnForm = (event: Event) => {\n const form = getInput()?.form;\n return form != null && event.target === form;\n };\n // Capture so the field is a password field again before the browser or a form library reads it.\n const onSubmit = (event: Event) => {\n if (isOwnForm(event) && latest.current.isVisible) flushSync(hide);\n };\n const onReset = (event: Event) => {\n if (isOwnForm(event) && !event.defaultPrevented) hide();\n };\n document.addEventListener('submit', onSubmit, true);\n document.addEventListener('reset', onReset);\n return () => {\n document.removeEventListener('submit', onSubmit, true);\n document.removeEventListener('reset', onReset);\n };\n }, [hideOnSubmit, getInput, hide]);\n\n useEffect(() => {\n if (!tracksValue) return;\n // A reset changes the value without an input event; read it once the browser has applied it.\n const onReset = (event: Event) => {\n const input = getInput();\n if (input?.form && event.target === input.form) setTimeout(() => setTyped(input.value));\n };\n document.addEventListener('reset', onReset);\n return () => document.removeEventListener('reset', onReset);\n }, [tracksValue, getInput]);\n\n useEffect(() => {\n if (!hideOnBlur) return;\n const onVisibilityChange = () => {\n if (document.visibilityState === 'hidden') hide();\n };\n document.addEventListener('visibilitychange', onVisibilityChange);\n return () => document.removeEventListener('visibilitychange', onVisibilityChange);\n }, [hideOnBlur, hide]);\n\n const hideOnFocusLeave = useCallback((next: EventTarget | null) => {\n if (!latest.current.hideOnBlur || pointerPress.current) return;\n if (next instanceof Element && (next.id === inputId || next.getAttribute('aria-controls') === inputId)) return;\n hide();\n }, [inputId, hide]);\n\n // inputProps carries attributes only, as in 1.0, so it can be spread before or after register()\n // or a mask field without replacing their handlers. Keys, blur and typing are read natively.\n useEffect(() => {\n const isInput = (target: EventTarget | null): target is HTMLInputElement =>\n target instanceof HTMLInputElement && target.id === inputId;\n const onKey = (event: KeyboardEvent) => {\n if (isInput(event.target)) setCapsLock(event.getModifierState?.('CapsLock') ?? false);\n };\n const onFocusOut = (event: FocusEvent) => {\n if (!isInput(event.target)) return;\n setCapsLock(false);\n hideOnFocusLeave(event.relatedTarget);\n };\n const onInput = (event: Event) => {\n if (latest.current.tracksValue && isInput(event.target)) setTyped(event.target.value);\n };\n document.addEventListener('keydown', onKey, true);\n document.addEventListener('keyup', onKey, true);\n document.addEventListener('focusout', onFocusOut, true);\n document.addEventListener('input', onInput, true);\n return () => {\n document.removeEventListener('keydown', onKey, true);\n document.removeEventListener('keyup', onKey, true);\n document.removeEventListener('focusout', onFocusOut, true);\n document.removeEventListener('input', onInput, true);\n };\n }, [inputId, hideOnFocusLeave]);\n\n const strength = policy ? policy.evaluate(value, { forbidden }) : null;\n const matches = matchWith === undefined || value === '' ? null : sameText(value, matchWith);\n const issues: PasswordIssue[] = strength ? [...strength.issues] : [];\n if (matches === false) issues.push('password.mismatch');\n const strengthId = `${inputId}-strength`;\n\n // Stable, and always checks the latest policy, so it can go straight into register().\n const [rule] = useState<ViraPasswordRule>(() => ({\n validate: (input) => {\n const { policy: currentPolicy, forbidden: currentForbidden, matchWith: currentMatch } = latest.current;\n const text = typeof input === 'string' ? input : '';\n const result = currentPolicy?.evaluate(text, { forbidden: currentForbidden });\n if (result && !result.valid) return result.issues[0];\n if (currentMatch !== undefined && !sameText(text, currentMatch)) return 'password.mismatch';\n return true;\n },\n }));\n\n const showIcon = icons?.show ?? React.createElement(EyeIcon);\n const hideIcon = icons?.hide ?? React.createElement(EyeOffIcon);\n\n // Base props for the input\n const baseInputProps: ViraPasswordInputProps = {\n id: inputId,\n type: getInputType(isVisible),\n disabled,\n readOnly,\n ...(autoComplete === false ? {} : { autoComplete: autoComplete ?? (checksValue ? 'new-password' : 'current-password') }),\n spellCheck: false,\n autoCorrect: 'off',\n autoCapitalize: 'off',\n ...getInputAriaAttributes(undefined, policy ? strengthId : undefined),\n };\n\n // Base props for the button\n const baseBtnProps: React.ButtonHTMLAttributes<HTMLButtonElement> = {\n onPointerDown: () => {\n // The input still has focus here; keep its caret for when focus comes back.\n const input = getInput();\n const press = { start: input?.selectionStart ?? null, end: input?.selectionEnd ?? null };\n pointerPress.current = press;\n // A press released outside the button never clicks; forget it after the click would have run.\n window.addEventListener('pointerup', () => setTimeout(() => {\n if (pointerPress.current === press) pointerPress.current = null;\n }), { once: true });\n },\n onPointerCancel: () => {\n pointerPress.current = null;\n },\n onClick: () => {\n const press = pointerPress.current;\n pointerPress.current = null;\n flushSync(toggle);\n // A mouse or touch press returns focus to the input; a keyboard press keeps it on the button.\n if (!press || disabled || readOnly) return;\n const input = getInput();\n if (!input) return;\n input.focus();\n if (press.start !== null && press.end !== null) input.setSelectionRange(press.start, press.end);\n },\n onBlur: (event) => hideOnFocusLeave(event.relatedTarget),\n disabled: disabled || readOnly,\n children: isVisible ? hideIcon : showIcon,\n ...getButtonAriaAttributes(isVisible, inputId),\n };\n\n // Merge user props with base props\n const mergedInputProps = mergeProps(baseInputProps, userInputProps);\n const mergedBtnProps = mergeProps(baseBtnProps, userBtnProps);\n\n return {\n inputProps: mergedInputProps,\n btnProps: mergedBtnProps,\n isVisible,\n capsLock,\n toggle,\n setVisible,\n strength,\n matches,\n strengthProps: { id: strengthId, role: 'status', 'aria-live': 'polite', 'aria-atomic': true },\n issues,\n rule,\n };\n};\n\n// Re-export the mergeProps utility if the user needs it manually\nexport { mergeProps, composeRefs };\n","import React from 'react';\n\nexport const EyeIcon = (props: React.SVGProps<SVGSVGElement>) => (\n React.createElement('svg', {\n xmlns: \"http://www.w3.org/2000/svg\",\n width: \"16\",\n height: \"16\",\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: \"2\",\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n ...props\n },\n React.createElement('path', { d: \"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0\" }),\n React.createElement('circle', { cx: \"12\", cy: \"12\", r: \"3\" })\n )\n);\n\nexport const EyeOffIcon = (props: React.SVGProps<SVGSVGElement>) => (\n React.createElement('svg', {\n xmlns: \"http://www.w3.org/2000/svg\",\n width: \"16\",\n height: \"16\",\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: \"2\",\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n ...props\n },\n React.createElement('path', { d: \"M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49\" }),\n React.createElement('path', { d: \"M14.084 14.158a3 3 0 0 1-4.242-4.242\" }),\n React.createElement('path', { d: \"M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143\" }),\n React.createElement('path', { d: \"m2 2 20 20\" })\n )\n);\n"],"mappings":"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","names":["import_react","React","args","React"]}