@virastack/password 1.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026-present Ömer Gülçiçek (ViraStack)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,66 @@
1
+ <div align="center">
2
+ <img src="./public/password.jpg" alt="Vira Stack Password" />
3
+ </div>
4
+
5
+ <br />
6
+
7
+ <div align="center">
8
+ <a href="https://www.npmjs.com/package/@virastack/password">
9
+ <img src="https://img.shields.io/npm/v/@virastack/password" alt="npm version" />
10
+ </a>
11
+ <a href="https://www.npmjs.com/package/@virastack/password">
12
+ <img src="https://img.shields.io/npm/dt/@virastack/password" alt="npm downloads" />
13
+ </a>
14
+ <a href="https://bundlephobia.com/package/@virastack/password">
15
+ <img src="https://img.shields.io/bundlephobia/minzip/@virastack/password" alt="bundle size" />
16
+ </a>
17
+ </div>
18
+
19
+ <br />
20
+
21
+ # ViraStack Password Toggle
22
+
23
+ Fully accessible and customizable password visibility hook for React.
24
+
25
+ - ♿ **Accessibility First:** Fully ARIA compliant out of the box for better screen reader support.
26
+ - 🧩 **Framework Agnostic:** Logic is separated from React state, making it adaptable and clean.
27
+ - 🎨 **Smart Defaults:** Includes minimal SVG icons by default, so you can start using it immediately.
28
+ - 🔄 **Prop Merging:** Safely merges user props with hook-generated props to prevent overrides.
29
+ - 🔒 **State Sync:** Automatically handles `disabled` and `readOnly` states for a consistent UX.
30
+
31
+ ### [Read the docs →](https://virastack.com/password/)
32
+
33
+ ## Quick Start
34
+
35
+ ```bash
36
+ npm install @virastack/password
37
+ ```
38
+
39
+ ```tsx
40
+ import { useViraPassword } from '@virastack/password';
41
+
42
+ function App() {
43
+ const { inputProps, btnProps } = useViraPassword();
44
+
45
+ return (
46
+ <div>
47
+ <input {...inputProps} placeholder="Enter password" />
48
+ <button {...btnProps} />
49
+ </div>
50
+ );
51
+ }
52
+ ```
53
+
54
+ ## Explore the ViraStack Ecosystem
55
+
56
+ Discover all ViraStack tools, libraries, and boilerplates at [**virastack.com**](https://virastack.com).
57
+
58
+ ## License
59
+
60
+ Licensed under the <a href="https://github.com/virastack/password/blob/main/LICENSE">MIT License</a>.
61
+
62
+ ## Maintainer
63
+
64
+ A project by [**Ömer Gülçiçek**](https://omergulcicek.com)
65
+
66
+ [![Follow Ömer Gülçiçek](https://img.shields.io/github/followers/omergulcicek?label=Follow&style=social)](https://github.com/omergulcicek)
@@ -0,0 +1,73 @@
1
+ import React, { ReactNode } from 'react';
2
+
3
+ /**
4
+ * Generates the input type based on visibility.
5
+ */
6
+ declare const getInputType: (isVisible: boolean) => "text" | "password";
7
+ /**
8
+ * Generates ARIA attributes for the toggle button.
9
+ */
10
+ declare const getButtonAriaAttributes: (isVisible: boolean, inputId: string, label?: string) => {
11
+ 'aria-label': string;
12
+ 'aria-pressed': boolean;
13
+ 'aria-controls': string;
14
+ type: "button";
15
+ };
16
+ /**
17
+ * Generates ARIA attributes for the input field.
18
+ */
19
+ declare const getInputAriaAttributes: (isInvalid?: boolean, describedBy?: string) => Record<string, any>;
20
+
21
+ interface UseViraPasswordOptions {
22
+ /**
23
+ * Initial visibility state of the password.
24
+ * @default false
25
+ */
26
+ defaultVisible?: boolean;
27
+ /**
28
+ * Custom icons for show/hide states.
29
+ */
30
+ icons?: {
31
+ show: ReactNode;
32
+ hide: ReactNode;
33
+ };
34
+ /**
35
+ * Whether the input is disabled.
36
+ * If true, the toggle button will also be disabled.
37
+ */
38
+ disabled?: boolean;
39
+ /**
40
+ * Whether the input is read-only.
41
+ * If true, the toggle button will be disabled.
42
+ */
43
+ readOnly?: boolean;
44
+ /**
45
+ * ID for the input element. If not provided, a random ID will be generated.
46
+ */
47
+ id?: string;
48
+ /**
49
+ * Props to be merged with the generated input props.
50
+ */
51
+ inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
52
+ /**
53
+ * Props to be merged with the generated button props.
54
+ */
55
+ btnProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;
56
+ }
57
+ interface UseViraPasswordResult {
58
+ inputProps: React.InputHTMLAttributes<HTMLInputElement>;
59
+ btnProps: React.ButtonHTMLAttributes<HTMLButtonElement>;
60
+ isVisible: boolean;
61
+ toggle: () => void;
62
+ setVisible: (visible: boolean) => void;
63
+ }
64
+ /**
65
+ * A utility to merge event handlers and other props.
66
+ */
67
+ declare const mergeProps: <T extends Record<string, any>>(...args: (T | undefined)[]) => T;
68
+ declare const useViraPassword: (options?: UseViraPasswordOptions) => UseViraPasswordResult;
69
+
70
+ declare const EyeIcon: (props: React.SVGProps<SVGSVGElement>) => React.ReactSVGElement;
71
+ declare const EyeOffIcon: (props: React.SVGProps<SVGSVGElement>) => React.ReactSVGElement;
72
+
73
+ export { EyeIcon, EyeOffIcon, type UseViraPasswordOptions, type UseViraPasswordResult, getButtonAriaAttributes, getInputAriaAttributes, getInputType, mergeProps, useViraPassword };
@@ -0,0 +1,73 @@
1
+ import React, { ReactNode } from 'react';
2
+
3
+ /**
4
+ * Generates the input type based on visibility.
5
+ */
6
+ declare const getInputType: (isVisible: boolean) => "text" | "password";
7
+ /**
8
+ * Generates ARIA attributes for the toggle button.
9
+ */
10
+ declare const getButtonAriaAttributes: (isVisible: boolean, inputId: string, label?: string) => {
11
+ 'aria-label': string;
12
+ 'aria-pressed': boolean;
13
+ 'aria-controls': string;
14
+ type: "button";
15
+ };
16
+ /**
17
+ * Generates ARIA attributes for the input field.
18
+ */
19
+ declare const getInputAriaAttributes: (isInvalid?: boolean, describedBy?: string) => Record<string, any>;
20
+
21
+ interface UseViraPasswordOptions {
22
+ /**
23
+ * Initial visibility state of the password.
24
+ * @default false
25
+ */
26
+ defaultVisible?: boolean;
27
+ /**
28
+ * Custom icons for show/hide states.
29
+ */
30
+ icons?: {
31
+ show: ReactNode;
32
+ hide: ReactNode;
33
+ };
34
+ /**
35
+ * Whether the input is disabled.
36
+ * If true, the toggle button will also be disabled.
37
+ */
38
+ disabled?: boolean;
39
+ /**
40
+ * Whether the input is read-only.
41
+ * If true, the toggle button will be disabled.
42
+ */
43
+ readOnly?: boolean;
44
+ /**
45
+ * ID for the input element. If not provided, a random ID will be generated.
46
+ */
47
+ id?: string;
48
+ /**
49
+ * Props to be merged with the generated input props.
50
+ */
51
+ inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
52
+ /**
53
+ * Props to be merged with the generated button props.
54
+ */
55
+ btnProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;
56
+ }
57
+ interface UseViraPasswordResult {
58
+ inputProps: React.InputHTMLAttributes<HTMLInputElement>;
59
+ btnProps: React.ButtonHTMLAttributes<HTMLButtonElement>;
60
+ isVisible: boolean;
61
+ toggle: () => void;
62
+ setVisible: (visible: boolean) => void;
63
+ }
64
+ /**
65
+ * A utility to merge event handlers and other props.
66
+ */
67
+ declare const mergeProps: <T extends Record<string, any>>(...args: (T | undefined)[]) => T;
68
+ declare const useViraPassword: (options?: UseViraPasswordOptions) => UseViraPasswordResult;
69
+
70
+ declare const EyeIcon: (props: React.SVGProps<SVGSVGElement>) => React.ReactSVGElement;
71
+ declare const EyeOffIcon: (props: React.SVGProps<SVGSVGElement>) => React.ReactSVGElement;
72
+
73
+ export { EyeIcon, EyeOffIcon, type UseViraPasswordOptions, type UseViraPasswordResult, getButtonAriaAttributes, getInputAriaAttributes, getInputType, mergeProps, useViraPassword };
package/dist/index.js ADDED
@@ -0,0 +1,146 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var React = require('react');
5
+
6
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
+
8
+ var React__default = /*#__PURE__*/_interopDefault(React);
9
+
10
+ // src/core/logic.ts
11
+ var getInputType = (isVisible) => {
12
+ return isVisible ? "text" : "password";
13
+ };
14
+ var getButtonAriaAttributes = (isVisible, inputId, label = "Toggle password visibility") => {
15
+ return {
16
+ "aria-label": label,
17
+ "aria-pressed": isVisible,
18
+ "aria-controls": inputId,
19
+ type: "button"
20
+ };
21
+ };
22
+ var getInputAriaAttributes = (isInvalid, describedBy) => {
23
+ const attrs = {};
24
+ if (isInvalid !== void 0) {
25
+ attrs["aria-invalid"] = isInvalid;
26
+ }
27
+ if (describedBy) {
28
+ attrs["aria-describedby"] = describedBy;
29
+ }
30
+ return attrs;
31
+ };
32
+ var EyeIcon = (props) => React__default.default.createElement(
33
+ "svg",
34
+ {
35
+ xmlns: "http://www.w3.org/2000/svg",
36
+ width: "16",
37
+ height: "16",
38
+ viewBox: "0 0 24 24",
39
+ fill: "none",
40
+ stroke: "currentColor",
41
+ strokeWidth: "2",
42
+ strokeLinecap: "round",
43
+ strokeLinejoin: "round",
44
+ ...props
45
+ },
46
+ React__default.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" }),
47
+ React__default.default.createElement("circle", { cx: "12", cy: "12", r: "3" })
48
+ );
49
+ var EyeOffIcon = (props) => React__default.default.createElement(
50
+ "svg",
51
+ {
52
+ xmlns: "http://www.w3.org/2000/svg",
53
+ width: "16",
54
+ height: "16",
55
+ viewBox: "0 0 24 24",
56
+ fill: "none",
57
+ stroke: "currentColor",
58
+ strokeWidth: "2",
59
+ strokeLinecap: "round",
60
+ strokeLinejoin: "round",
61
+ ...props
62
+ },
63
+ React__default.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" }),
64
+ React__default.default.createElement("path", { d: "M14.084 14.158a3 3 0 0 1-4.242-4.242" }),
65
+ React__default.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" }),
66
+ React__default.default.createElement("path", { d: "m2 2 20 20" })
67
+ );
68
+
69
+ // src/react/useViraPassword.ts
70
+ var mergeProps = (...args) => {
71
+ const result = {};
72
+ for (const props of args) {
73
+ if (!props) continue;
74
+ for (const key in props) {
75
+ const val = props[key];
76
+ const existing = result[key];
77
+ if (key.startsWith("on") && typeof val === "function" && typeof existing === "function") {
78
+ result[key] = (...args2) => {
79
+ existing(...args2);
80
+ val(...args2);
81
+ };
82
+ } else if (key === "className" && typeof val === "string" && typeof existing === "string") {
83
+ result[key] = [existing, val].filter(Boolean).join(" ");
84
+ } else {
85
+ result[key] = val;
86
+ }
87
+ }
88
+ }
89
+ return result;
90
+ };
91
+ var useViraPassword = (options = {}) => {
92
+ const {
93
+ defaultVisible = false,
94
+ icons,
95
+ disabled = false,
96
+ readOnly = false,
97
+ id: providedId,
98
+ inputProps: userInputProps,
99
+ btnProps: userBtnProps
100
+ } = options;
101
+ const [isVisible, setIsVisible] = React.useState(defaultVisible);
102
+ const generatedId = React.useId();
103
+ const inputId = providedId || `virapassword-input-${generatedId}`;
104
+ const toggle = React.useCallback(() => {
105
+ if (disabled || readOnly) return;
106
+ setIsVisible((prev) => !prev);
107
+ }, [disabled, readOnly]);
108
+ const setVisible = React.useCallback((visible) => {
109
+ if (disabled || readOnly) return;
110
+ setIsVisible(visible);
111
+ }, [disabled, readOnly]);
112
+ const showIcon = icons?.show ?? React__default.default.createElement(EyeIcon);
113
+ const hideIcon = icons?.hide ?? React__default.default.createElement(EyeOffIcon);
114
+ const baseInputProps = {
115
+ id: inputId,
116
+ type: getInputType(isVisible),
117
+ disabled,
118
+ readOnly,
119
+ ...getInputAriaAttributes()
120
+ };
121
+ const baseBtnProps = {
122
+ onClick: toggle,
123
+ disabled: disabled || readOnly,
124
+ children: isVisible ? hideIcon : showIcon,
125
+ ...getButtonAriaAttributes(isVisible, inputId)
126
+ };
127
+ const mergedInputProps = mergeProps(baseInputProps, userInputProps);
128
+ const mergedBtnProps = mergeProps(baseBtnProps, userBtnProps);
129
+ return {
130
+ inputProps: mergedInputProps,
131
+ btnProps: mergedBtnProps,
132
+ isVisible,
133
+ toggle,
134
+ setVisible
135
+ };
136
+ };
137
+
138
+ exports.EyeIcon = EyeIcon;
139
+ exports.EyeOffIcon = EyeOffIcon;
140
+ exports.getButtonAriaAttributes = getButtonAriaAttributes;
141
+ exports.getInputAriaAttributes = getInputAriaAttributes;
142
+ exports.getInputType = getInputType;
143
+ exports.mergeProps = mergeProps;
144
+ exports.useViraPassword = useViraPassword;
145
+ //# sourceMappingURL=index.js.map
146
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/logic.ts","../src/icons/index.ts","../src/react/useViraPassword.ts"],"names":["React","args","useState","useId","useCallback"],"mappings":"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","file":"index.js","sourcesContent":["/**\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 const getInputAriaAttributes = (\n isInvalid?: boolean,\n describedBy?: string\n) => {\n const attrs: Record<string, any> = {};\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 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","'use client';\n\nimport React, { useState, useId, useCallback, ReactNode } from 'react';\nimport { EyeIcon, EyeOffIcon } from '../icons';\nimport { getInputType, getButtonAriaAttributes, getInputAriaAttributes } from '../core/logic';\n\nexport interface UseViraPasswordOptions {\n /**\n * Initial visibility state of the password.\n * @default false\n */\n defaultVisible?: boolean;\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.\n */\n inputProps?: React.InputHTMLAttributes<HTMLInputElement>;\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: React.InputHTMLAttributes<HTMLInputElement>;\n btnProps: React.ButtonHTMLAttributes<HTMLButtonElement>;\n isVisible: boolean;\n toggle: () => void;\n setVisible: (visible: boolean) => void;\n}\n\n/**\n * A utility to merge event handlers and other props.\n */\nconst mergeProps = <T extends Record<string, any>>(\n ...args: (T | undefined)[]\n): T => {\n const result: Record<string, any> = {};\n\n for (const props of args) {\n if (!props) continue;\n\n for (const key in props) {\n const val = props[key];\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: any[]) => {\n existing(...args);\n val(...args);\n };\n } else if (key === 'className' && typeof val === 'string' && typeof existing === 'string') {\n result[key] = [existing, val].filter(Boolean).join(' ');\n } else {\n result[key] = val;\n }\n }\n }\n\n return result as T;\n};\n\nexport const useViraPassword = (options: UseViraPasswordOptions = {}): UseViraPasswordResult => {\n const {\n defaultVisible = false,\n icons,\n disabled = false,\n readOnly = false,\n id: providedId,\n inputProps: userInputProps,\n btnProps: userBtnProps,\n } = options;\n\n const [isVisible, setIsVisible] = useState(defaultVisible);\n const generatedId = useId();\n const inputId = providedId || `virapassword-input-${generatedId}`;\n\n const toggle = useCallback(() => {\n if (disabled || readOnly) return;\n setIsVisible((prev) => !prev);\n }, [disabled, readOnly]);\n\n const setVisible = useCallback((visible: boolean) => {\n if (disabled || readOnly) return;\n setIsVisible(visible);\n }, [disabled, readOnly]);\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: React.InputHTMLAttributes<HTMLInputElement> = {\n id: inputId,\n type: getInputType(isVisible),\n disabled,\n readOnly,\n ...getInputAriaAttributes(),\n };\n\n // Base props for the button\n const baseBtnProps: React.ButtonHTMLAttributes<HTMLButtonElement> = {\n onClick: toggle,\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 toggle,\n setVisible,\n };\n};\n\n// Re-export the mergeProps utility if the user needs it manually\nexport { mergeProps };\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,134 @@
1
+ "use client";
2
+ import React, { useState, useId, useCallback } from 'react';
3
+
4
+ // src/core/logic.ts
5
+ var getInputType = (isVisible) => {
6
+ return isVisible ? "text" : "password";
7
+ };
8
+ var getButtonAriaAttributes = (isVisible, inputId, label = "Toggle password visibility") => {
9
+ return {
10
+ "aria-label": label,
11
+ "aria-pressed": isVisible,
12
+ "aria-controls": inputId,
13
+ type: "button"
14
+ };
15
+ };
16
+ var getInputAriaAttributes = (isInvalid, describedBy) => {
17
+ const attrs = {};
18
+ if (isInvalid !== void 0) {
19
+ attrs["aria-invalid"] = isInvalid;
20
+ }
21
+ if (describedBy) {
22
+ attrs["aria-describedby"] = describedBy;
23
+ }
24
+ return attrs;
25
+ };
26
+ var EyeIcon = (props) => React.createElement(
27
+ "svg",
28
+ {
29
+ xmlns: "http://www.w3.org/2000/svg",
30
+ width: "16",
31
+ height: "16",
32
+ viewBox: "0 0 24 24",
33
+ fill: "none",
34
+ stroke: "currentColor",
35
+ strokeWidth: "2",
36
+ strokeLinecap: "round",
37
+ strokeLinejoin: "round",
38
+ ...props
39
+ },
40
+ 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" }),
41
+ React.createElement("circle", { cx: "12", cy: "12", r: "3" })
42
+ );
43
+ var EyeOffIcon = (props) => React.createElement(
44
+ "svg",
45
+ {
46
+ xmlns: "http://www.w3.org/2000/svg",
47
+ width: "16",
48
+ height: "16",
49
+ viewBox: "0 0 24 24",
50
+ fill: "none",
51
+ stroke: "currentColor",
52
+ strokeWidth: "2",
53
+ strokeLinecap: "round",
54
+ strokeLinejoin: "round",
55
+ ...props
56
+ },
57
+ 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" }),
58
+ React.createElement("path", { d: "M14.084 14.158a3 3 0 0 1-4.242-4.242" }),
59
+ 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" }),
60
+ React.createElement("path", { d: "m2 2 20 20" })
61
+ );
62
+
63
+ // src/react/useViraPassword.ts
64
+ var mergeProps = (...args) => {
65
+ const result = {};
66
+ for (const props of args) {
67
+ if (!props) continue;
68
+ for (const key in props) {
69
+ const val = props[key];
70
+ const existing = result[key];
71
+ if (key.startsWith("on") && typeof val === "function" && typeof existing === "function") {
72
+ result[key] = (...args2) => {
73
+ existing(...args2);
74
+ val(...args2);
75
+ };
76
+ } else if (key === "className" && typeof val === "string" && typeof existing === "string") {
77
+ result[key] = [existing, val].filter(Boolean).join(" ");
78
+ } else {
79
+ result[key] = val;
80
+ }
81
+ }
82
+ }
83
+ return result;
84
+ };
85
+ var useViraPassword = (options = {}) => {
86
+ const {
87
+ defaultVisible = false,
88
+ icons,
89
+ disabled = false,
90
+ readOnly = false,
91
+ id: providedId,
92
+ inputProps: userInputProps,
93
+ btnProps: userBtnProps
94
+ } = options;
95
+ const [isVisible, setIsVisible] = useState(defaultVisible);
96
+ const generatedId = useId();
97
+ const inputId = providedId || `virapassword-input-${generatedId}`;
98
+ const toggle = useCallback(() => {
99
+ if (disabled || readOnly) return;
100
+ setIsVisible((prev) => !prev);
101
+ }, [disabled, readOnly]);
102
+ const setVisible = useCallback((visible) => {
103
+ if (disabled || readOnly) return;
104
+ setIsVisible(visible);
105
+ }, [disabled, readOnly]);
106
+ const showIcon = icons?.show ?? React.createElement(EyeIcon);
107
+ const hideIcon = icons?.hide ?? React.createElement(EyeOffIcon);
108
+ const baseInputProps = {
109
+ id: inputId,
110
+ type: getInputType(isVisible),
111
+ disabled,
112
+ readOnly,
113
+ ...getInputAriaAttributes()
114
+ };
115
+ const baseBtnProps = {
116
+ onClick: toggle,
117
+ disabled: disabled || readOnly,
118
+ children: isVisible ? hideIcon : showIcon,
119
+ ...getButtonAriaAttributes(isVisible, inputId)
120
+ };
121
+ const mergedInputProps = mergeProps(baseInputProps, userInputProps);
122
+ const mergedBtnProps = mergeProps(baseBtnProps, userBtnProps);
123
+ return {
124
+ inputProps: mergedInputProps,
125
+ btnProps: mergedBtnProps,
126
+ isVisible,
127
+ toggle,
128
+ setVisible
129
+ };
130
+ };
131
+
132
+ export { EyeIcon, EyeOffIcon, getButtonAriaAttributes, getInputAriaAttributes, getInputType, mergeProps, useViraPassword };
133
+ //# sourceMappingURL=index.mjs.map
134
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/logic.ts","../src/icons/index.ts","../src/react/useViraPassword.ts"],"names":["args","React"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\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 const getInputAriaAttributes = (\n isInvalid?: boolean,\n describedBy?: string\n) => {\n const attrs: Record<string, any> = {};\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 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","'use client';\n\nimport React, { useState, useId, useCallback, ReactNode } from 'react';\nimport { EyeIcon, EyeOffIcon } from '../icons';\nimport { getInputType, getButtonAriaAttributes, getInputAriaAttributes } from '../core/logic';\n\nexport interface UseViraPasswordOptions {\n /**\n * Initial visibility state of the password.\n * @default false\n */\n defaultVisible?: boolean;\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.\n */\n inputProps?: React.InputHTMLAttributes<HTMLInputElement>;\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: React.InputHTMLAttributes<HTMLInputElement>;\n btnProps: React.ButtonHTMLAttributes<HTMLButtonElement>;\n isVisible: boolean;\n toggle: () => void;\n setVisible: (visible: boolean) => void;\n}\n\n/**\n * A utility to merge event handlers and other props.\n */\nconst mergeProps = <T extends Record<string, any>>(\n ...args: (T | undefined)[]\n): T => {\n const result: Record<string, any> = {};\n\n for (const props of args) {\n if (!props) continue;\n\n for (const key in props) {\n const val = props[key];\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: any[]) => {\n existing(...args);\n val(...args);\n };\n } else if (key === 'className' && typeof val === 'string' && typeof existing === 'string') {\n result[key] = [existing, val].filter(Boolean).join(' ');\n } else {\n result[key] = val;\n }\n }\n }\n\n return result as T;\n};\n\nexport const useViraPassword = (options: UseViraPasswordOptions = {}): UseViraPasswordResult => {\n const {\n defaultVisible = false,\n icons,\n disabled = false,\n readOnly = false,\n id: providedId,\n inputProps: userInputProps,\n btnProps: userBtnProps,\n } = options;\n\n const [isVisible, setIsVisible] = useState(defaultVisible);\n const generatedId = useId();\n const inputId = providedId || `virapassword-input-${generatedId}`;\n\n const toggle = useCallback(() => {\n if (disabled || readOnly) return;\n setIsVisible((prev) => !prev);\n }, [disabled, readOnly]);\n\n const setVisible = useCallback((visible: boolean) => {\n if (disabled || readOnly) return;\n setIsVisible(visible);\n }, [disabled, readOnly]);\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: React.InputHTMLAttributes<HTMLInputElement> = {\n id: inputId,\n type: getInputType(isVisible),\n disabled,\n readOnly,\n ...getInputAriaAttributes(),\n };\n\n // Base props for the button\n const baseBtnProps: React.ButtonHTMLAttributes<HTMLButtonElement> = {\n onClick: toggle,\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 toggle,\n setVisible,\n };\n};\n\n// Re-export the mergeProps utility if the user needs it manually\nexport { mergeProps };\n"]}
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@virastack/password",
3
+ "version": "1.0.0",
4
+ "description": "Fully accessible and customizable password visibility hook for React.",
5
+ "author": {
6
+ "name": "Ömer Gülçiçek (ViraStack)",
7
+ "email": "iletisim@omergulcicek.com",
8
+ "url": "https://omergulcicek.com"
9
+ },
10
+ "license": "MIT",
11
+ "homepage": "https://virastack.com",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/virastack/password.git"
15
+ },
16
+ "bugs": {
17
+ "url": "https://github.com/virastack/password/issues"
18
+ },
19
+ "main": "./dist/index.js",
20
+ "module": "./dist/index.mjs",
21
+ "types": "./dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.mjs",
26
+ "require": "./dist/index.js"
27
+ }
28
+ },
29
+ "files": [
30
+ "dist"
31
+ ],
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "sideEffects": false,
36
+ "engines": {
37
+ "node": ">=18"
38
+ },
39
+ "scripts": {
40
+ "build": "tsup",
41
+ "dev": "tsup --watch",
42
+ "test": "vitest",
43
+ "lint": "tsc"
44
+ },
45
+ "keywords": [
46
+ "virastack",
47
+ "react",
48
+ "hook",
49
+ "password",
50
+ "password-visibility",
51
+ "accessibility",
52
+ "a11y",
53
+ "form-validation",
54
+ "dx"
55
+ ],
56
+ "peerDependencies": {
57
+ "react": ">=18.2.0",
58
+ "react-dom": ">=18.2.0"
59
+ },
60
+ "devDependencies": {
61
+ "@testing-library/jest-dom": "^6.4.2",
62
+ "@testing-library/react": "^14.2.1",
63
+ "@types/react": "^18.2.64",
64
+ "@types/react-dom": "^18.2.21",
65
+ "jsdom": "^24.0.0",
66
+ "react": "^18.2.0",
67
+ "react-dom": "^18.2.0",
68
+ "tsup": "^8.0.2",
69
+ "typescript": "^5.3.3",
70
+ "vitest": "^1.3.1"
71
+ }
72
+ }