@fr0st/ui-passwordstrength 2.0.8 → 3.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 CHANGED
File without changes
package/README.md CHANGED
@@ -1,37 +1,334 @@
1
- # FrostUI PasswordStrength
2
-
3
- **FrostUI PasswordStrength** is a free, open-source Password Strength component for *JavaScript*.
4
-
5
- It is built on top of the [FrostUI](https://github.com/elusivecodes/FrostUI) library.
6
-
7
-
8
- ## Table Of Contents
9
- - [Installation](#installation)
10
- - [Usage](#usage)
11
-
12
-
13
-
14
- ## Installation
15
-
16
- **Dependencies**
17
-
18
- - [fQuery](https://github.com/elusivecodes/fQuery)
19
- - [FrostUI](https://github.com/elusivecodes/FrostUI)
20
-
21
- **CSS**
22
-
23
- ```html
24
- <link rel="stylesheet" href="/path/to/frost-ui.min.css" />
25
- ```
26
-
27
- **JS**
28
-
29
- ```html
30
- <script type="text/javascript" src="/path/to/frost-ui-bundle.min.js"></script>
31
- <script type="text/javascript" src="/path/to/frost-ui-passwordstrength.min.js"></script>
32
- ```
33
-
34
-
35
- ## Usage
36
-
37
- Check the demo folder for examples. Full documentation and website coming soon™.
1
+ # Frost UI PasswordStrength
2
+
3
+ [![CI](https://github.com/frost-js/ui-passwordstrength/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/frost-js/ui-passwordstrength/actions/workflows/ci.yml)
4
+ [![codecov](https://codecov.io/gh/frost-js/ui-passwordstrength/branch/main/graph/badge.svg)](https://codecov.io/gh/frost-js/ui-passwordstrength)
5
+ [![npm version](https://img.shields.io/npm/v/%40fr0st%2Fui-passwordstrength?style=flat-square)](https://www.npmjs.com/package/@fr0st/ui-passwordstrength)
6
+ [![npm downloads](https://img.shields.io/npm/dm/%40fr0st%2Fui-passwordstrength?style=flat-square)](https://www.npmjs.com/package/@fr0st/ui-passwordstrength)
7
+ [![JS gzip size](https://img.badgesize.io/frost-js/ui-passwordstrength/main/dist/frost-ui-passwordstrength.min.js?compression=gzip&label=JS%20gzip%20size&style=flat-square)](https://github.com/frost-js/ui-passwordstrength/blob/main/dist/frost-ui-passwordstrength.min.js)
8
+ [![license](https://img.shields.io/github/license/frost-js/ui-passwordstrength?style=flat-square)](./LICENSE)
9
+
10
+ Password-strength indicator for Frost UI with configurable thresholds, semantic feedback, accessible progress state, and instance or static scoring APIs.
11
+
12
+ ## Highlights
13
+
14
+ - Live scoring driven by the password input's native `input` event
15
+ - Automatic refresh after a form reset, unless the reset is canceled
16
+ - Configurable ordered thresholds, labels, and Frost UI semantic color classes
17
+ - Default or explicit progress-container placement
18
+ - Optional UI v4 striped progress treatment
19
+ - Existing-instance reuse with frozen resolved options
20
+ - Accessible progress markup with reversible `aria-describedby` integration
21
+ - Native `PasswordStrength` class and `passwordstrength` fQuery plugin
22
+ - Public instance and static scoring methods with an optional custom scorer
23
+ - Configurable common-password penalties with a small built-in list
24
+ - Prebuilt ESM and UMD bundles with source maps
25
+ - No component-specific CSS or Sass
26
+ - JSDoc-powered IntelliSense
27
+
28
+ ## Installation
29
+
30
+ ### Browser projects / bundlers
31
+
32
+ Install PasswordStrength with its Frost UI v4 and fQuery v5 peers:
33
+
34
+ ```bash
35
+ npm i @fr0st/ui-passwordstrength @fr0st/ui @fr0st/query
36
+ ```
37
+
38
+ The package root resolves to the compiled ESM bundle. Import the Frost UI stylesheet and the default component export:
39
+
40
+ ```js
41
+ import '@fr0st/ui/dist/frost-ui.min.css';
42
+ import PasswordStrength from '@fr0st/ui-passwordstrength';
43
+
44
+ const passwordStrength = PasswordStrength.init(
45
+ document.querySelector('#password'),
46
+ {
47
+ striped: true,
48
+ },
49
+ );
50
+ ```
51
+
52
+ `@fr0st/ui` and `@fr0st/query` are peer dependencies so the component shares the application's UI and fQuery instances. The package root, `dist/*`, and `src/*` are available through package exports.
53
+
54
+ PasswordStrength requires a browser DOM or a compatible DOM environment configured through fQuery. Server-rendered applications should load the component on the client.
55
+
56
+ ### Browser (ESM)
57
+
58
+ The ESM bundle imports `@fr0st/ui` and `@fr0st/query`. Frost UI and fQuery also require `@fr0st/core`, so map all three dependencies when loading the bundle directly in a browser:
59
+
60
+ ```html
61
+ <link
62
+ rel="stylesheet"
63
+ href="https://cdn.jsdelivr.net/npm/@fr0st/ui@latest/dist/frost-ui.min.css">
64
+
65
+ <script type="importmap">
66
+ {
67
+ "imports": {
68
+ "@fr0st/core": "https://cdn.jsdelivr.net/npm/@fr0st/core@latest/dist/frost-core.esm.min.js",
69
+ "@fr0st/query": "https://cdn.jsdelivr.net/npm/@fr0st/query@latest/dist/fquery.esm.min.js",
70
+ "@fr0st/ui": "https://cdn.jsdelivr.net/npm/@fr0st/ui@latest/dist/frost-ui.esm.min.js"
71
+ }
72
+ }
73
+ </script>
74
+ <script type="module">
75
+ import PasswordStrength from 'https://cdn.jsdelivr.net/npm/@fr0st/ui-passwordstrength@latest/dist/frost-ui-passwordstrength.esm.min.js';
76
+
77
+ PasswordStrength.init(document.querySelector('#password'));
78
+ </script>
79
+ ```
80
+
81
+ ### Browser (UMD)
82
+
83
+ Load Frost UI's all-in-one bundle before PasswordStrength. The UI bundle supplies both the `UI` and `fQuery` globals expected by the component:
84
+
85
+ ```html
86
+ <link
87
+ rel="stylesheet"
88
+ href="https://cdn.jsdelivr.net/npm/@fr0st/ui@latest/dist/frost-ui.min.css">
89
+
90
+ <script src="https://cdn.jsdelivr.net/npm/@fr0st/ui@latest/dist/frost-ui-bundle.min.js"></script>
91
+ <script src="https://cdn.jsdelivr.net/npm/@fr0st/ui-passwordstrength@latest/dist/frost-ui-passwordstrength.min.js"></script>
92
+ <script>
93
+ const passwordStrength = UI.PasswordStrength.init(
94
+ document.querySelector('#password'),
95
+ );
96
+ </script>
97
+ ```
98
+
99
+ The UMD bundle adds `PasswordStrength` to the existing `globalThis.UI` object. It expects `globalThis.UI` and `globalThis.fQuery` to exist before it loads. If the non-bundled Frost UI build is used instead, load fQuery, Frost UI, and PasswordStrength in that order.
100
+
101
+ Do not load the separate fQuery script when using `frost-ui-bundle.js` or `frost-ui-bundle.min.js`.
102
+
103
+ ## Usage
104
+
105
+ Start with a normal password input inside a Frost UI form control. By default, PasswordStrength inserts its progress markup into the closest ancestor that is not `.form-input` or `.input-group`:
106
+
107
+ ```html
108
+ <div id="password-field">
109
+ <div class="form-input">
110
+ <label for="password">Password</label>
111
+ <input
112
+ class="input-outline"
113
+ id="password"
114
+ name="password"
115
+ type="password"
116
+ autocomplete="new-password">
117
+ <div class="form-text">Use a long, unique password.</div>
118
+ </div>
119
+ </div>
120
+ ```
121
+
122
+ ```js
123
+ import PasswordStrength from '@fr0st/ui-passwordstrength';
124
+
125
+ const passwordStrength = PasswordStrength.init(
126
+ document.querySelector('#password'),
127
+ );
128
+
129
+ console.log(passwordStrength.getStrength());
130
+ ```
131
+
132
+ Calling `PasswordStrength.init()` again for the same input returns its existing instance. Dispose the current instance before reinitializing the input with different options.
133
+
134
+ The indicator renders the initial value and refreshes on `input` events. After changing the input's value in JavaScript, dispatch an `input` event to refresh the indicator; `getStrength()` only returns the current score.
135
+
136
+ Form resets refresh the indicator after the browser restores the input's default value. This refresh is deferred until after the reset event finishes and is skipped if the reset is canceled. Inputs associated with a form through the `form` attribute are also supported.
137
+
138
+ ## Options
139
+
140
+ Options are resolved in this order:
141
+
142
+ 1. Component defaults
143
+ 2. The input's `data-ui-*` attributes
144
+ 3. Options passed to `PasswordStrength.init()`
145
+
146
+ Resolved `instance.options` are frozen.
147
+
148
+ Supplied `levels` and `commonPasswords` arrays replace the corresponding defaults completely, including when supplied through data attributes. Arrays passed through JavaScript options take precedence over arrays from data attributes. Supplied arrays and level objects are copied into the resolved configuration.
149
+
150
+ | Option | Type | Default | Description |
151
+ | --- | --- | --- | --- |
152
+ | `commonPasswords` | `string[]` | `PasswordStrength.defaults.commonPasswords` | Passwords the built-in scorer treats as very weak. Matching is case-insensitive and recognizes simple decorations and common leet substitutions. |
153
+ | `container` | `string \| null` | `null` | CSS selector for the element that receives the generated progress markup. `null` uses the closest field container. |
154
+ | `levels` | `PasswordStrengthLevel[]` | See below | Ordered score thresholds with a semantic class and optional label. |
155
+ | `scorer` | `(password, commonPasswords) => number` | Built-in scorer | Calculate a score for the current password. Results are normalized and clamped between `0` and `100`. |
156
+ | `striped` | `boolean` | `false` | Apply UI's `progress-bar-striped` class to the generated progress bar. |
157
+
158
+ The default common-password list is intentionally small to keep the bundle lightweight. Pass `commonPasswords: []` to disable list matching while retaining the other scoring rules. To extend the default list with application-specific terms, include the defaults explicitly:
159
+
160
+ ```js
161
+ PasswordStrength.init(node, {
162
+ commonPasswords: [
163
+ ...PasswordStrength.defaults.commonPasswords,
164
+ 'example-company',
165
+ 'example-product',
166
+ ],
167
+ });
168
+ ```
169
+
170
+ Provide `scorer` to replace the built-in heuristic without adding a scoring dependency to the base bundle. The callback receives the current password and resolved `commonPasswords` option:
171
+
172
+ ```js
173
+ PasswordStrength.init(node, {
174
+ scorer: (password, commonPasswords) => {
175
+ if (commonPasswords.includes(password.toLowerCase())) {
176
+ return 0;
177
+ }
178
+
179
+ return Math.min(password.length * 5, 100);
180
+ },
181
+ });
182
+ ```
183
+
184
+ Each level has a numeric `score`, a CSS `class`, and an optional `text` label. An empty or omitted label clears the previous label. Defaults are ordered from lowest to highest threshold:
185
+
186
+ | Score | Class | Text |
187
+ | ---: | --- | --- |
188
+ | `0` | `text-bg-danger` | Very Weak |
189
+ | `20` | `text-bg-danger` | Weak |
190
+ | `40` | `text-bg-warning` | Normal |
191
+ | `60` | `text-bg-success` | Strong |
192
+ | `80` | `text-bg-success` | Very Strong |
193
+
194
+ Provide a complete array when replacing all default levels:
195
+
196
+ ```js
197
+ const passwordStrength = PasswordStrength.init(node, {
198
+ container: '#password-strength-output',
199
+ levels: [
200
+ { score: 0, class: 'text-bg-danger', text: 'Risky' },
201
+ { score: 20, class: 'text-bg-warning', text: 'Improving' },
202
+ { score: 40, class: 'text-bg-info', text: 'Fair' },
203
+ { score: 60, class: 'text-bg-primary', text: 'Good' },
204
+ { score: 80, class: 'text-bg-success', text: 'Excellent' },
205
+ ],
206
+ striped: true,
207
+ });
208
+ ```
209
+
210
+ Provide at least one level, starting at `0`, and sort levels by ascending score. The last level whose threshold is less than or equal to the score is selected. Scores are clamped between `0` and `100`.
211
+
212
+ ## Data attributes
213
+
214
+ Serializable options can be supplied through `data-ui-*` attributes. Structured values such as `commonPasswords` and `levels` use JSON. The `scorer` callback must be supplied through JavaScript.
215
+
216
+ | Attribute | Example |
217
+ | --- | --- |
218
+ | `data-ui-common-passwords` | `data-ui-common-passwords='["password","example-company"]'` |
219
+ | `data-ui-container` | `data-ui-container="#password-strength-output"` |
220
+ | `data-ui-levels` | `data-ui-levels='[{"score":0,"class":"text-bg-danger","text":"Risky"}]'` |
221
+ | `data-ui-striped` | `data-ui-striped="true"` |
222
+
223
+ ```html
224
+ <input
225
+ id="password"
226
+ type="password"
227
+ data-ui-toggle="passwordstrength"
228
+ data-ui-container="#password-strength-output"
229
+ data-ui-striped="true">
230
+
231
+ <div id="password-strength-output"></div>
232
+ ```
233
+
234
+ The component still needs to be initialized through the class or fQuery plugin. The demo uses `data-ui-toggle="passwordstrength"` as a shared initialization selector:
235
+
236
+ ```js
237
+ $('[data-ui-toggle="passwordstrength"]').passwordstrength();
238
+ ```
239
+
240
+ The `data-ui-toggle` attribute does not initialize PasswordStrength by itself.
241
+
242
+ ## Methods
243
+
244
+ | Method | Returns | Description |
245
+ | --- | --- | --- |
246
+ | `PasswordStrength.init(node, options?)` | `PasswordStrength` | Return the existing instance for an input or create one. |
247
+ | `getStrength()` | `number` | Score the instance input's current value from `0` to `100`. |
248
+ | `dispose()` | `void` | Remove generated markup, input and form-reset listeners, and registered component state, then restore the original ARIA state. |
249
+
250
+ ```js
251
+ const passwordStrength = PasswordStrength.init(node, {
252
+ striped: true,
253
+ });
254
+
255
+ const score = passwordStrength.getStrength();
256
+
257
+ passwordStrength.dispose();
258
+ ```
259
+
260
+ An instance also exposes its original input as `instance.node` and its frozen resolved configuration as `instance.options`. Both become `null` after disposal.
261
+
262
+ ## Static API
263
+
264
+ Use `PasswordStrength.getStrength(password, commonPasswords?)` to run the built-in scorer without creating an instance or rendering progress markup:
265
+
266
+ ```js
267
+ const score = PasswordStrength.getStrength('CorrectHorseBatteryStaple');
268
+
269
+ console.log(score); // 100
270
+ ```
271
+
272
+ The static method always uses the built-in scorer and returns a score from `0` to `100`. It uses `PasswordStrength.defaults.commonPasswords` unless a replacement list is supplied as the second argument. An instance's custom `scorer` and options do not change the static method.
273
+
274
+ The built-in scorer treats length as the primary strength factor. It heavily penalizes configured common passwords and simple variants, repeated characters and substrings, ascending and descending sequences, and keyboard-row patterns. Character variety provides only a small adjustment within each length band. Unicode is normalized with NFKC and counted by code point.
275
+
276
+ Common-password matches after case and Unicode normalization score `0`; recognized variants score `5`. Variant matching handles decorations, numeric prefixes or suffixes, and common leet substitutions, including combinations such as:
277
+
278
+ ```js
279
+ PasswordStrength.getStrength('administrator', ['administrator']); // 0
280
+ PasswordStrength.getStrength('@dministrator!', ['administrator']); // 5
281
+ PasswordStrength.getStrength('!@dministrator123!', ['administrator']); // 5
282
+ ```
283
+
284
+ It is a lightweight UI feedback heuristic, not an entropy estimate, breach check, or substitute for application security policy.
285
+
286
+ ## fQuery API
287
+
288
+ Importing PasswordStrength registers `passwordstrength` on `fQuery.QuerySet`:
289
+
290
+ ```js
291
+ import $ from '@fr0st/query';
292
+ import '@fr0st/ui-passwordstrength';
293
+
294
+ const passwordStrength = $('#password').passwordstrength({
295
+ striped: true,
296
+ });
297
+
298
+ const score = $('#password').passwordstrength('getStrength');
299
+
300
+ $('#password').passwordstrength('dispose');
301
+ ```
302
+
303
+ Pass an options object to initialize every matched input, or pass a public method name followed by its arguments. The first component or method result is returned.
304
+
305
+ ## Accessibility
306
+
307
+ - The generated indicator uses `role="progressbar"` with `aria-valuemin="0"`, `aria-valuemax="100"`, and an updated `aria-valuenow` score.
308
+ - The active level's text is rendered inside the progress bar when the level supplies a label.
309
+ - A unique generated progress ID is appended to the input's existing `aria-describedby` value instead of replacing it.
310
+ - Disposal restores the original `aria-describedby` state exactly, including absent and empty values.
311
+ - Disposal also removes the generated progress markup, namespaced input and form-reset listeners, and registered component data.
312
+ - The original password input remains the interactive and submitted form control.
313
+
314
+ Applications remain responsible for meaningful labels, password requirements, validation feedback, and error messages. Treat the displayed score as guidance rather than proof that a password is safe.
315
+
316
+ ## Development
317
+
318
+ Use Node.js matching `^20.19.0 || ^22.13.0 || >=24`. Install dependencies with `npm ci`, then install Playwright browsers with `npx playwright install --with-deps`.
319
+
320
+ ```bash
321
+ npm test
322
+ npm run lint
323
+ npm run build
324
+ ```
325
+
326
+ `npm test` rebuilds JavaScript, then runs the Playwright suite in Chromium, Firefox, and WebKit. `npm run test:browser` runs the suite against the existing bundles, so rebuild after changing source files.
327
+
328
+ After building, `npm run test:coverage` runs Chromium tests and writes coverage reports to `coverage/`.
329
+
330
+ `npm run test:headed` and `npm run test:ui` also use the existing bundles and open headed browsers or the Playwright UI.
331
+
332
+ ## License
333
+
334
+ Frost UI PasswordStrength is released under the [MIT License](./LICENSE).