@fr0st/ui-passwordstrength 3.0.0 → 3.0.2

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
@@ -16,52 +16,42 @@ Password-strength indicator for Frost UI with configurable thresholds, semantic
16
16
  - Configurable ordered thresholds, labels, and Frost UI semantic color classes
17
17
  - Default or explicit progress-container placement
18
18
  - Optional UI v4 striped progress treatment
19
- - Existing-instance reuse with frozen resolved options
20
19
  - Accessible progress markup with reversible `aria-describedby` integration
21
- - Native `PasswordStrength` class and `passwordstrength` fQuery plugin
22
20
  - Public instance and static scoring methods with an optional custom scorer
23
21
  - Configurable common-password penalties with a small built-in list
22
+ - Native `PasswordStrength` class and `passwordstrength` fQuery plugin
23
+ - Existing-instance reuse with frozen resolved options
24
24
  - Prebuilt ESM and UMD bundles with source maps
25
25
  - No component-specific CSS or Sass
26
26
  - JSDoc-powered IntelliSense
27
27
 
28
+ Explore [the demo](./demo/index.html) for interactive examples.
29
+
28
30
  ## Installation
29
31
 
30
32
  ### Browser projects / bundlers
31
33
 
32
- Install PasswordStrength with its Frost UI v4 and fQuery v5 peers:
33
-
34
34
  ```bash
35
- npm i @fr0st/ui-passwordstrength @fr0st/ui @fr0st/query
35
+ npm i @fr0st/ui-passwordstrength
36
36
  ```
37
37
 
38
- The package root resolves to the compiled ESM bundle. Import the Frost UI stylesheet and the default component export:
38
+ Frost UI PasswordStrength's package entry point is ESM-only and requires a browser DOM. Import the default `PasswordStrength` export and the stylesheets in browser projects and bundlers.
39
39
 
40
40
  ```js
41
41
  import '@fr0st/ui/dist/frost-ui.min.css';
42
42
  import PasswordStrength from '@fr0st/ui-passwordstrength';
43
-
44
- const passwordStrength = PasswordStrength.init(
45
- document.querySelector('#password'),
46
- {
47
- striped: true,
48
- },
49
- );
50
43
  ```
51
44
 
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.
45
+ `@fr0st/ui` and `@fr0st/query` are peer dependencies so the component shares the application's instances.
55
46
 
56
47
  ### Browser (ESM)
57
48
 
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:
49
+ The ESM bundle imports `@fr0st/ui` and `@fr0st/query`. fQuery also imports `@fr0st/core`, so map all three dependencies when loading the bundle directly in a browser:
59
50
 
60
51
  ```html
61
52
  <link
62
53
  rel="stylesheet"
63
54
  href="https://cdn.jsdelivr.net/npm/@fr0st/ui@latest/dist/frost-ui.min.css">
64
-
65
55
  <script type="importmap">
66
56
  {
67
57
  "imports": {
@@ -73,32 +63,33 @@ The ESM bundle imports `@fr0st/ui` and `@fr0st/query`. Frost UI and fQuery also
73
63
  </script>
74
64
  <script type="module">
75
65
  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
66
  </script>
79
67
  ```
80
68
 
81
69
  ### Browser (UMD)
82
70
 
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:
71
+ Load the bundles from your own copy or a CDN:
84
72
 
85
73
  ```html
74
+ <link
75
+ rel="stylesheet"
76
+ href="/path/to/dist/frost-ui.min.css">
77
+ <script src="/path/to/dist/frost-ui-bundle.min.js"></script>
78
+ <script src="/path/to/dist/frost-ui-passwordstrength.min.js"></script>
79
+ <!-- or -->
86
80
  <link
87
81
  rel="stylesheet"
88
82
  href="https://cdn.jsdelivr.net/npm/@fr0st/ui@latest/dist/frost-ui.min.css">
89
-
90
83
  <script src="https://cdn.jsdelivr.net/npm/@fr0st/ui@latest/dist/frost-ui-bundle.min.js"></script>
91
84
  <script src="https://cdn.jsdelivr.net/npm/@fr0st/ui-passwordstrength@latest/dist/frost-ui-passwordstrength.min.js"></script>
92
85
  <script>
93
- const passwordStrength = UI.PasswordStrength.init(
94
- document.querySelector('#password'),
95
- );
86
+ const { PasswordStrength } = globalThis.UI;
96
87
  </script>
97
88
  ```
98
89
 
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.
90
+ The UMD bundle adds `PasswordStrength` to the existing `globalThis.UI` object. Load Frost UI's all-in-one bundle first; it supplies the `UI` and `fQuery` globals.
100
91
 
101
- Do not load the separate fQuery script when using `frost-ui-bundle.js` or `frost-ui-bundle.min.js`.
92
+ The package root resolves to the prebuilt ESM bundle. Published files under `dist/` and `src/` are also available through matching package subpaths.
102
93
 
103
94
  ## Usage
104
95
 
@@ -140,10 +131,10 @@ Form resets refresh the indicator after the browser restores the input's default
140
131
  Options are resolved in this order:
141
132
 
142
133
  1. Component defaults
143
- 2. The input's `data-ui-*` attributes
134
+ 2. The element's `data-ui-*` attributes
144
135
  3. Options passed to `PasswordStrength.init()`
145
136
 
146
- Resolved `instance.options` are frozen.
137
+ Resolved `instance.options` are shallow-frozen.
147
138
 
148
139
  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
140
 
@@ -211,7 +202,7 @@ Provide at least one level, starting at `0`, and sort levels by ascending score.
211
202
 
212
203
  ## Data attributes
213
204
 
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.
205
+ Use kebab-case `data-ui-*` attributes for serializable options. Arrays and objects use JSON. Supply callbacks and DOM nodes through JavaScript.
215
206
 
216
207
  | Attribute | Example |
217
208
  | --- | --- |
@@ -231,21 +222,22 @@ Serializable options can be supplied through `data-ui-*` attributes. Structured
231
222
  <div id="password-strength-output"></div>
232
223
  ```
233
224
 
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
225
  ```js
226
+ import $ from '@fr0st/query';
227
+ import '@fr0st/ui-passwordstrength';
228
+
237
229
  $('[data-ui-toggle="passwordstrength"]').passwordstrength();
238
230
  ```
239
231
 
240
- The `data-ui-toggle` attribute does not initialize PasswordStrength by itself.
232
+ Data attributes configure options; they do not initialize PasswordStrength by themselves. Initialize the component through the class or fQuery plugin. Changing an option's data attribute after initialization does not reconfigure the existing instance.
241
233
 
242
234
  ## Methods
243
235
 
244
236
  | Method | Returns | Description |
245
237
  | --- | --- | --- |
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`. |
238
+ | `PasswordStrength.init(node, options?)` | `PasswordStrength` | Return the existing instance for an element or create one. |
248
239
  | `dispose()` | `void` | Remove generated markup, input and form-reset listeners, and registered component state, then restore the original ARIA state. |
240
+ | `getStrength()` | `number` | Score the instance input's current value from `0` to `100`. |
249
241
 
250
242
  ```js
251
243
  const passwordStrength = PasswordStrength.init(node, {
@@ -257,8 +249,6 @@ const score = passwordStrength.getStrength();
257
249
  passwordStrength.dispose();
258
250
  ```
259
251
 
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
252
  ## Static API
263
253
 
264
254
  Use `PasswordStrength.getStrength(password, commonPasswords?)` to run the built-in scorer without creating an instance or rendering progress markup:
@@ -283,6 +273,16 @@ PasswordStrength.getStrength('!@dministrator123!', ['administrator']); // 5
283
273
 
284
274
  It is a lightweight UI feedback heuristic, not an entropy estimate, breach check, or substitute for application security policy.
285
275
 
276
+ ## Lifecycle
277
+
278
+ Calling `PasswordStrength.init()` again for the same element returns its existing instance. Dispose the current instance before reinitializing with different options.
279
+
280
+ An instance exposes its original element as `instance.node` and its shallow-frozen resolved configuration as `instance.options`. Both become `null` after disposal.
281
+
282
+ `dispose()` releases resources owned by the component and removes its registered instance. Repeated disposal is safe and does not affect a new instance initialized on the same element. Use a new instance before calling other methods after disposal.
283
+
284
+ If initialization fails, the component releases resources it created and removes its registered instance before rethrowing the error. The element can then be initialized again.
285
+
286
286
  ## fQuery API
287
287
 
288
288
  Importing PasswordStrength registers `passwordstrength` on `fQuery.QuerySet`:
@@ -300,7 +300,7 @@ const score = $('#password').passwordstrength('getStrength');
300
300
  $('#password').passwordstrength('dispose');
301
301
  ```
302
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.
303
+ Pass an options object to initialize every matched element, or pass a public method name followed by its arguments. The first component or method result is returned.
304
304
 
305
305
  ## Accessibility
306
306
 
@@ -313,9 +313,15 @@ Pass an options object to initialize every matched input, or pass a public metho
313
313
 
314
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
315
 
316
+ ## Themes and RTL
317
+
318
+ Frost UI follows the user's preferred color scheme by default. Set `data-ui-theme="light"` or `data-ui-theme="dark"` on the document or an ancestor to select a theme explicitly.
319
+
320
+ PasswordStrength uses Frost UI progress and semantic color classes. Labels and progress markup follow the surrounding text direction.
321
+
316
322
  ## Development
317
323
 
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`.
324
+ Install dependencies with `npm ci`, then install Playwright browsers with `npx playwright install --with-deps`.
319
325
 
320
326
  ```bash
321
327
  npm test
@@ -323,12 +329,14 @@ npm run lint
323
329
  npm run build
324
330
  ```
325
331
 
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.
332
+ `npm test` rebuilds the bundles, 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
333
 
328
334
  After building, `npm run test:coverage` runs Chromium tests and writes coverage reports to `coverage/`.
329
335
 
330
336
  `npm run test:headed` and `npm run test:ui` also use the existing bundles and open headed browsers or the Playwright UI.
331
337
 
338
+ To view the demo, open `demo/index.html` in your browser after building.
339
+
332
340
  ## License
333
341
 
334
342
  Frost UI PasswordStrength is released under the [MIT License](./LICENSE).
@@ -1,4 +1,4 @@
1
- import { BaseComponent, generateId, getDataset, initComponent } from "@fr0st/ui";
1
+ import { BaseComponent, generateId, initComponent } from "@fr0st/ui";
2
2
  import $ from "@fr0st/query";
3
3
 
4
4
  //#region src/strength.js
@@ -274,12 +274,11 @@ var PasswordStrength = class extends BaseComponent {
274
274
  scorer: getStrength,
275
275
  striped: false
276
276
  };
277
- #container;
277
+ #container = null;
278
278
  #describedBy;
279
- #form;
280
- #progress;
281
- #progressBar;
282
- #resetHandler;
279
+ #form = null;
280
+ #progress = null;
281
+ #progressBar = null;
283
282
  /**
284
283
  * Calculates the strength of a password.
285
284
  * @param {string} password The password.
@@ -291,38 +290,36 @@ var PasswordStrength = class extends BaseComponent {
291
290
  }
292
291
  /**
293
292
  * Creates a PasswordStrength.
294
- * @param {HTMLElement} node The input node.
293
+ * @param {HTMLInputElement} node The input node.
295
294
  * @param {PasswordStrengthOptions} [options] The PasswordStrength options.
295
+ * @throws {TypeError} When the node is not an input element.
296
296
  */
297
297
  constructor(node, options) {
298
+ if (!$._isElement(node) || !$.is(node, "input")) throw new TypeError("PasswordStrength must be created on an input element.");
298
299
  super(node, options);
299
- const overrides = {
300
- ...getDataset(node),
301
- ...options
302
- };
303
- for (const key of ["levels", "commonPasswords"]) if (Array.isArray(overrides[key])) {
304
- this.options[key].length = 0;
305
- $._extend(this.options[key], overrides[key]);
300
+ try {
301
+ this.#describedBy = $.getAttribute(this.node, "aria-describedby");
302
+ this.#form = $.getProperty(this.node, "form");
303
+ if (this.options.container) this.#container = $.findOne(this.options.container);
304
+ else this.#container = $.closest(this.node, ":not(.form-input):not(.input-group)").shift();
305
+ this.#render();
306
+ this.#refresh();
307
+ this.#events();
308
+ } catch (error) {
309
+ this.dispose();
310
+ throw error;
306
311
  }
307
- this.#form = this.node.form;
308
- if (this.options.container) this.#container = $.findOne(this.options.container);
309
- else this.#container = $.closest(this.node, ":not(.form-input):not(.input-group)").shift();
310
- this.#render();
311
- this.#refresh();
312
- this.#events();
313
312
  }
314
313
  /** @inheritdoc */
315
314
  dispose() {
315
+ if (!this.node) return;
316
316
  $.remove(this.#progress);
317
- $.removeEvent(this.node, "input.ui.passwordstrength");
318
- if (this.#form) $.removeEvent(this.#form, "reset.ui.passwordstrength", this.#resetHandler);
319
317
  if (this.#describedBy === null) $.removeAttribute(this.node, "aria-describedby");
320
318
  else $.setAttribute(this.node, { "aria-describedby": this.#describedBy });
321
319
  this.#container = null;
322
320
  this.#form = null;
323
321
  this.#progress = null;
324
322
  this.#progressBar = null;
325
- this.#resetHandler = null;
326
323
  super.dispose();
327
324
  }
328
325
  /**
@@ -338,15 +335,12 @@ var PasswordStrength = class extends BaseComponent {
338
335
  * Attaches events for the PasswordStrength.
339
336
  */
340
337
  #events() {
341
- if (this.#form) {
342
- this.#resetHandler = (event) => {
343
- setTimeout(() => {
344
- if (this.node && !event.defaultPrevented) this.#refresh();
345
- }, 0);
346
- };
347
- $.addEvent(this.#form, "reset.ui.passwordstrength", this.#resetHandler);
348
- }
349
- $.addEvent(this.node, "input.ui.passwordstrength", (_) => {
338
+ if (this.#form) this.listen(this.#form, "reset.ui.passwordstrength", (event) => {
339
+ setTimeout(() => {
340
+ if (this.node && !event.defaultPrevented) this.#refresh();
341
+ }, 0);
342
+ });
343
+ this.listen(this.node, "input.ui.passwordstrength", () => {
350
344
  this.#refresh();
351
345
  });
352
346
  }
@@ -369,11 +363,10 @@ var PasswordStrength = class extends BaseComponent {
369
363
  * Renders the password strength element.
370
364
  */
371
365
  #render() {
372
- this.#describedBy = $.getAttribute(this.node, "aria-describedby");
373
366
  this.#progress = $.create("div", { class: this.constructor.classes.progress });
374
367
  const id = generateId("password-strength");
375
368
  this.#progressBar = $.create("div", { attributes: {
376
- "id": id,
369
+ id,
377
370
  "role": "progressbar",
378
371
  "aria-valuemin": 0,
379
372
  "aria-valuemax": 100
@@ -1 +1 @@
1
- {"version":3,"file":"frost-ui-passwordstrength.esm.js","names":[],"sources":["../src/strength.js","../src/password-strength.js","../src/index.js"],"sourcesContent":["import $ from '@fr0st/query';\n\nconst characterTypes = [\n { pattern: /\\p{Ll}/u, type: 'lower' },\n { pattern: /\\p{Lu}/u, type: 'upper' },\n { pattern: /\\p{N}/u, type: 'number' },\n];\n\nconst keyboardSequences = [\n '1234567890',\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n].flatMap(\n (row) => [\n row,\n Array.from(row).reverse().join(''),\n ],\n);\n\nconst leetCharacters = {\n '$': 's',\n '0': 'o',\n '3': 'e',\n '4': 'a',\n '5': 's',\n '7': 't',\n '@': 'a',\n};\n\nconst lengthBands = [\n { end: 5, max: 19, min: 1, start: 1 },\n { end: 8, max: 39, min: 20, start: 6 },\n { end: 11, max: 59, min: 40, start: 9 },\n { end: 15, max: 79, min: 60, start: 12 },\n { end: 24, max: 100, min: 80, start: 16 },\n];\n\n/**\n * Gets the type of a password character.\n * @param {string} character The character.\n * @returns {string} The character type.\n */\nconst getCharacterType = (character) =>\n characterTypes.find(\n ({ pattern }) => pattern.test(character),\n )?.type || 'symbol';\n\n/**\n * Gets a length-based password strength.\n * @param {string[]} characters The password characters.\n * @returns {number} The password strength.\n */\nconst getLengthStrength = (characters) => {\n const band = lengthBands.find(\n ({ end }) => characters.length <= end,\n ) || lengthBands.at(-1);\n const score = Math.round(\n $._map(\n Math.min(characters.length, band.end),\n band.start,\n band.end,\n band.min,\n band.max,\n ),\n );\n const diversity = new Set(\n characters.map(getCharacterType),\n ).size;\n\n return Math.min(\n score +\n (diversity - 1) * 2,\n band.max,\n );\n};\n\n/**\n * Gets the smallest repeated pattern.\n * @param {string[]} characters The password characters.\n * @returns {string[]|null} The repeated pattern.\n */\nconst getRepeatedPattern = (characters) => {\n const maxLength = Math.floor(characters.length / 2);\n\n for (let length = 1; length <= maxLength; length++) {\n if (\n characters.length % length === 0 &&\n characters.every(\n (character, index) =>\n character === characters[index % length],\n )\n ) {\n return characters.slice(0, length);\n }\n }\n\n return null;\n};\n\n/**\n * Normalizes common password matching.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizePassword = (password) =>\n String(password)\n .normalize('NFKC')\n .toLowerCase();\n\n/**\n * Normalizes common leet substitutions.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizeLeet = (password) =>\n Array.from(\n password,\n (character) => leetCharacters[character] || character,\n ).join('');\n\n/**\n * Gets normalized variants used to match common passwords.\n * @param {string} password The normalized password.\n * @returns {Set<string>} The password variants.\n */\nconst getPasswordVariants = (password) => {\n // Preserve digits for decoration stripping while resolving symbol substitutions.\n const substituted = password.replace(/[^\\p{L}\\p{N}]/gu, normalizeLeet);\n const variants = [password, substituted].flatMap((value) => {\n const undecorated = value.replace(\n /^[^\\p{L}\\p{N}]+|[^\\p{L}\\p{N}]+$/gu,\n '',\n );\n\n return [\n undecorated,\n undecorated.replace(/^\\d+|\\d+$/g, ''),\n ];\n });\n\n return new Set([\n normalizeLeet(password),\n ...variants,\n ...variants.map(normalizeLeet),\n ]);\n};\n\n/**\n * Gets the score for a common password.\n * @param {string} password The normalized password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number|null} The common-password score.\n */\nconst getCommonPasswordStrength = (password, commonPasswords) => {\n const common = new Set(\n commonPasswords.map(normalizePassword),\n );\n\n if (common.has(password)) {\n return 0;\n }\n\n return common.isDisjointFrom(getPasswordVariants(password)) ? null : 5;\n};\n\n/**\n * Checks whether characters form an ascending or descending sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a sequence.\n */\nconst isSequence = (characters) => {\n const [first, second, third] = characters.map(\n (character) => character.toLowerCase(),\n );\n const type = getCharacterType(first);\n const direction = second.codePointAt(0) - first.codePointAt(0);\n\n return ['lower', 'number'].includes(type) &&\n getCharacterType(second) === type &&\n getCharacterType(third) === type &&\n Math.abs(direction) === 1 &&\n third.codePointAt(0) - second.codePointAt(0) === direction;\n};\n\n/**\n * Checks whether characters form a keyboard sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a keyboard sequence.\n */\nconst isKeyboardSequence = (characters) => {\n const sequence = characters.join('').toLowerCase();\n\n return keyboardSequences.some(\n (keyboardSequence) => keyboardSequence.includes(sequence),\n );\n};\n\n/**\n * Checks whether characters form a predictable pattern.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a predictable pattern.\n */\nconst isPredictable = (characters) =>\n new Set(characters).size === 1 ||\n isSequence(characters) ||\n isKeyboardSequence(characters);\n\n/**\n * Gets the number of characters in predictable patterns.\n * @param {string[]} characters The password characters.\n * @returns {number} The number of predictable characters.\n */\nconst getPredictableCount = (characters) => {\n const indices = new Set();\n\n for (let i = 0; i + 2 < characters.length; i++) {\n if (isPredictable(characters.slice(i, i + 3))) {\n indices.add(i);\n indices.add(i + 1);\n indices.add(i + 2);\n }\n }\n\n return indices.size;\n};\n\n/**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=[]] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\nexport const getStrength = (password, commonPasswords = []) => {\n const normalizedPassword = password.normalize('NFKC');\n const characters = Array.from(normalizedPassword);\n\n if (!characters.length) {\n return 0;\n }\n\n const commonStrength = getCommonPasswordStrength(\n normalizedPassword.toLowerCase(),\n commonPasswords,\n );\n\n if (commonStrength !== null) {\n return commonStrength;\n }\n\n const score = getLengthStrength(characters);\n const repeatedPattern = getRepeatedPattern(characters);\n\n if (repeatedPattern?.length === 1) {\n return 0;\n }\n\n if (repeatedPattern) {\n return Math.min(\n score,\n getLengthStrength(repeatedPattern),\n 15,\n );\n }\n\n const predictableCount = getPredictableCount(characters);\n\n return $._clamp(\n score - predictableCount,\n 0,\n predictableCount === characters.length ? 15 : 100,\n );\n};\n","import $ from '@fr0st/query';\nimport { BaseComponent, generateId, getDataset } from '@fr0st/ui';\nimport { getStrength } from './strength.js';\n\n/**\n * @typedef {object} PasswordStrengthLevel\n * @property {string} class The CSS class applied to the progress bar.\n * @property {number} score The minimum score for the level.\n * @property {string} [text] The label displayed in the progress bar.\n */\n\n/**\n * @callback PasswordStrengthScorer\n * @param {string} password The password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number} The password strength, from 0 to 100.\n */\n\n/**\n * @typedef {object} PasswordStrengthOptions\n * @property {string[]} [commonPasswords] Passwords to score as very weak.\n * @property {string|null} [container=null] The selector for the progress container.\n * @property {PasswordStrengthLevel[]} [levels] The ordered password-strength levels.\n * @property {PasswordStrengthScorer} [scorer] Calculates the password strength.\n * @property {boolean} [striped=false] Whether to display a striped progress bar.\n */\n\n/**\n * Displays the strength of a password input as a progress bar.\n * @augments {BaseComponent<PasswordStrengthOptions>}\n */\nexport default class PasswordStrength extends BaseComponent {\n static classes = {\n progress: 'progress mt-2',\n progressBar: 'progress-bar',\n progressBarStriped: 'progress-bar-striped',\n };\n /** @type {PasswordStrengthOptions} */\n static defaults = {\n commonPasswords: [\n '123456',\n '12345678',\n '123456789',\n '1234567890',\n '000000',\n '111111',\n 'abc123',\n 'admin',\n 'dragon',\n 'iloveyou',\n 'letmein',\n 'monkey',\n 'password',\n 'password1',\n 'password123',\n 'qwerty',\n 'qwerty123',\n 'welcome',\n ],\n levels: [\n {\n score: 0,\n class: 'text-bg-danger',\n text: 'Very Weak',\n },\n {\n score: 20,\n class: 'text-bg-danger',\n text: 'Weak',\n },\n {\n score: 40,\n class: 'text-bg-warning',\n text: 'Normal',\n },\n {\n score: 60,\n class: 'text-bg-success',\n text: 'Strong',\n },\n {\n score: 80,\n class: 'text-bg-success',\n text: 'Very Strong',\n },\n ],\n container: null,\n scorer: getStrength,\n striped: false,\n };\n\n #container;\n #describedBy;\n #form;\n #progress;\n #progressBar;\n #resetHandler;\n\n /**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=this.defaults.commonPasswords] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\n static getStrength(password, commonPasswords = this.defaults.commonPasswords) {\n return getStrength(password, commonPasswords);\n }\n\n /**\n * Creates a PasswordStrength.\n * @param {HTMLElement} node The input node.\n * @param {PasswordStrengthOptions} [options] The PasswordStrength options.\n */\n constructor(node, options) {\n super(node, options);\n\n const overrides = { ...getDataset(node), ...options };\n\n // Replace supplied arrays instead of merging them with default entries.\n for (const key of ['levels', 'commonPasswords']) {\n if (Array.isArray(overrides[key])) {\n this.options[key].length = 0;\n $._extend(this.options[key], overrides[key]);\n }\n }\n\n this.#form = this.node.form;\n\n if (this.options.container) {\n this.#container = $.findOne(this.options.container);\n } else {\n this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();\n }\n\n this.#render();\n this.#refresh();\n this.#events();\n }\n\n /** @inheritdoc */\n dispose() {\n $.remove(this.#progress);\n $.removeEvent(this.node, 'input.ui.passwordstrength');\n\n if (this.#form) {\n $.removeEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);\n }\n\n if (this.#describedBy === null) {\n $.removeAttribute(this.node, 'aria-describedby');\n } else {\n $.setAttribute(this.node, { 'aria-describedby': this.#describedBy });\n }\n\n this.#container = null;\n this.#form = null;\n this.#progress = null;\n this.#progressBar = null;\n this.#resetHandler = null;\n\n super.dispose();\n }\n\n /**\n * Gets the password strength.\n * @returns {number} The password strength, from 0 to 100.\n */\n getStrength() {\n const value = $.getValue(this.node);\n const strength = Number(this.options.scorer(\n value,\n this.options.commonPasswords,\n ));\n\n return Number.isFinite(strength) ?\n $._clamp(strength, 0, 100) :\n 0;\n }\n\n /**\n * Attaches events for the PasswordStrength.\n */\n #events() {\n if (this.#form) {\n this.#resetHandler = (event) => {\n setTimeout(() => {\n if (this.node && !event.defaultPrevented) {\n this.#refresh();\n }\n }, 0);\n };\n\n $.addEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);\n }\n\n $.addEvent(this.node, 'input.ui.passwordstrength', (_) => {\n this.#refresh();\n });\n }\n\n /**\n * Refreshes the password strength.\n */\n #refresh() {\n const strength = this.getStrength();\n\n const nextLevel = this.options.levels.findLast(\n (level) => strength >= level.score,\n );\n\n $.setStyle(this.#progressBar, { width: `${strength}%` });\n $.setAttribute(this.#progressBar, {\n 'class': this.constructor.classes.progressBar,\n 'aria-valuenow': strength,\n });\n\n $.addClass(this.#progressBar, nextLevel.class);\n\n if (this.options.striped) {\n $.addClass(this.#progressBar, this.constructor.classes.progressBarStriped);\n }\n\n $.setText(this.#progressBar, nextLevel.text ?? '');\n }\n\n /**\n * Renders the password strength element.\n */\n #render() {\n this.#describedBy = $.getAttribute(this.node, 'aria-describedby');\n this.#progress = $.create('div', {\n class: this.constructor.classes.progress,\n });\n\n const id = generateId('password-strength');\n\n this.#progressBar = $.create('div', {\n attributes: {\n 'id': id,\n 'role': 'progressbar',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n },\n });\n\n $.append(this.#progress, this.#progressBar);\n $.append(this.#container, this.#progress);\n\n const describedBy = [this.#describedBy, id]\n .filter(Boolean)\n .join(' ');\n\n $.setAttribute(this.node, { 'aria-describedby': describedBy });\n }\n}\n","import { initComponent } from '@fr0st/ui';\nimport PasswordStrength from './password-strength.js';\n\ninitComponent('passwordstrength', PasswordStrength);\n\nexport default PasswordStrength;\n"],"mappings":"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"}
1
+ {"version":3,"file":"frost-ui-passwordstrength.esm.js","names":[],"sources":["../src/strength.js","../src/password-strength.js","../src/index.js"],"sourcesContent":["import $ from '@fr0st/query';\n\nconst characterTypes = [\n { pattern: /\\p{Ll}/u, type: 'lower' },\n { pattern: /\\p{Lu}/u, type: 'upper' },\n { pattern: /\\p{N}/u, type: 'number' },\n];\n\nconst keyboardSequences = [\n '1234567890',\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n].flatMap(\n (row) => [\n row,\n Array.from(row).reverse().join(''),\n ],\n);\n\nconst leetCharacters = {\n '$': 's',\n '0': 'o',\n '3': 'e',\n '4': 'a',\n '5': 's',\n '7': 't',\n '@': 'a',\n};\n\nconst lengthBands = [\n { end: 5, max: 19, min: 1, start: 1 },\n { end: 8, max: 39, min: 20, start: 6 },\n { end: 11, max: 59, min: 40, start: 9 },\n { end: 15, max: 79, min: 60, start: 12 },\n { end: 24, max: 100, min: 80, start: 16 },\n];\n\n/**\n * Gets the type of a password character.\n * @param {string} character The character.\n * @returns {string} The character type.\n */\nconst getCharacterType = (character) =>\n characterTypes.find(\n ({ pattern }) => pattern.test(character),\n )?.type || 'symbol';\n\n/**\n * Gets a length-based password strength.\n * @param {string[]} characters The password characters.\n * @returns {number} The password strength.\n */\nconst getLengthStrength = (characters) => {\n const band = lengthBands.find(\n ({ end }) => characters.length <= end,\n ) || lengthBands.at(-1);\n const score = Math.round(\n $._map(\n Math.min(characters.length, band.end),\n band.start,\n band.end,\n band.min,\n band.max,\n ),\n );\n const diversity = new Set(\n characters.map(getCharacterType),\n ).size;\n\n return Math.min(\n score +\n (diversity - 1) * 2,\n band.max,\n );\n};\n\n/**\n * Gets the smallest repeated pattern.\n * @param {string[]} characters The password characters.\n * @returns {string[]|null} The repeated pattern.\n */\nconst getRepeatedPattern = (characters) => {\n const maxLength = Math.floor(characters.length / 2);\n\n for (let length = 1; length <= maxLength; length++) {\n if (\n characters.length % length === 0 &&\n characters.every(\n (character, index) =>\n character === characters[index % length],\n )\n ) {\n return characters.slice(0, length);\n }\n }\n\n return null;\n};\n\n/**\n * Normalizes common password matching.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizePassword = (password) =>\n String(password)\n .normalize('NFKC')\n .toLowerCase();\n\n/**\n * Normalizes common leet substitutions.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizeLeet = (password) =>\n Array.from(\n password,\n (character) => leetCharacters[character] || character,\n ).join('');\n\n/**\n * Gets normalized variants used to match common passwords.\n * @param {string} password The normalized password.\n * @returns {Set<string>} The password variants.\n */\nconst getPasswordVariants = (password) => {\n // Preserve digits for decoration stripping while resolving symbol substitutions.\n const substituted = password.replace(/[^\\p{L}\\p{N}]/gu, normalizeLeet);\n const variants = [password, substituted].flatMap((value) => {\n const undecorated = value.replace(\n /^[^\\p{L}\\p{N}]+|[^\\p{L}\\p{N}]+$/gu,\n '',\n );\n\n return [\n undecorated,\n undecorated.replace(/^\\d+|\\d+$/g, ''),\n ];\n });\n\n return new Set([\n normalizeLeet(password),\n ...variants,\n ...variants.map(normalizeLeet),\n ]);\n};\n\n/**\n * Gets the score for a common password.\n * @param {string} password The normalized password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number|null} The common-password score.\n */\nconst getCommonPasswordStrength = (password, commonPasswords) => {\n const common = new Set(\n commonPasswords.map(normalizePassword),\n );\n\n if (common.has(password)) {\n return 0;\n }\n\n return common.isDisjointFrom(getPasswordVariants(password)) ? null : 5;\n};\n\n/**\n * Checks whether characters form an ascending or descending sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a sequence.\n */\nconst isSequence = (characters) => {\n const [first, second, third] = characters.map(\n (character) => character.toLowerCase(),\n );\n const type = getCharacterType(first);\n const direction = second.codePointAt(0) - first.codePointAt(0);\n\n return ['lower', 'number'].includes(type) &&\n getCharacterType(second) === type &&\n getCharacterType(third) === type &&\n Math.abs(direction) === 1 &&\n third.codePointAt(0) - second.codePointAt(0) === direction;\n};\n\n/**\n * Checks whether characters form a keyboard sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a keyboard sequence.\n */\nconst isKeyboardSequence = (characters) => {\n const sequence = characters.join('').toLowerCase();\n\n return keyboardSequences.some(\n (keyboardSequence) => keyboardSequence.includes(sequence),\n );\n};\n\n/**\n * Checks whether characters form a predictable pattern.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a predictable pattern.\n */\nconst isPredictable = (characters) =>\n new Set(characters).size === 1 ||\n isSequence(characters) ||\n isKeyboardSequence(characters);\n\n/**\n * Gets the number of characters in predictable patterns.\n * @param {string[]} characters The password characters.\n * @returns {number} The number of predictable characters.\n */\nconst getPredictableCount = (characters) => {\n const indices = new Set();\n\n for (let i = 0; i + 2 < characters.length; i++) {\n if (isPredictable(characters.slice(i, i + 3))) {\n indices.add(i);\n indices.add(i + 1);\n indices.add(i + 2);\n }\n }\n\n return indices.size;\n};\n\n/**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=[]] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\nexport const getStrength = (password, commonPasswords = []) => {\n const normalizedPassword = password.normalize('NFKC');\n const characters = Array.from(normalizedPassword);\n\n if (!characters.length) {\n return 0;\n }\n\n const commonStrength = getCommonPasswordStrength(\n normalizedPassword.toLowerCase(),\n commonPasswords,\n );\n\n if (commonStrength !== null) {\n return commonStrength;\n }\n\n const score = getLengthStrength(characters);\n const repeatedPattern = getRepeatedPattern(characters);\n\n if (repeatedPattern?.length === 1) {\n return 0;\n }\n\n if (repeatedPattern) {\n return Math.min(\n score,\n getLengthStrength(repeatedPattern),\n 15,\n );\n }\n\n const predictableCount = getPredictableCount(characters);\n\n return $._clamp(\n score - predictableCount,\n 0,\n predictableCount === characters.length ? 15 : 100,\n );\n};\n","import $ from '@fr0st/query';\nimport { BaseComponent, generateId } from '@fr0st/ui';\nimport { getStrength } from './strength.js';\n\n/**\n * @typedef {object} PasswordStrengthLevel\n * @property {string} class The CSS class applied to the progress bar.\n * @property {number} score The minimum score for the level.\n * @property {string} [text] The label displayed in the progress bar.\n */\n\n/**\n * @callback PasswordStrengthScorer\n * @param {string} password The password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number} The password strength, from 0 to 100.\n */\n\n/**\n * @typedef {object} PasswordStrengthOptions\n * @property {string[]} [commonPasswords] Passwords to score as very weak.\n * @property {string|null} [container=null] The selector for the progress container.\n * @property {PasswordStrengthLevel[]} [levels] The ordered password-strength levels.\n * @property {PasswordStrengthScorer} [scorer] Calculates the password strength.\n * @property {boolean} [striped=false] Whether to display a striped progress bar.\n */\n\n/**\n * Displays the strength of a password input as a progress bar.\n * @augments {BaseComponent<PasswordStrengthOptions>}\n */\nexport default class PasswordStrength extends BaseComponent {\n static classes = {\n progress: 'progress mt-2',\n progressBar: 'progress-bar',\n progressBarStriped: 'progress-bar-striped',\n };\n /** @type {PasswordStrengthOptions} */\n static defaults = {\n commonPasswords: [\n '123456',\n '12345678',\n '123456789',\n '1234567890',\n '000000',\n '111111',\n 'abc123',\n 'admin',\n 'dragon',\n 'iloveyou',\n 'letmein',\n 'monkey',\n 'password',\n 'password1',\n 'password123',\n 'qwerty',\n 'qwerty123',\n 'welcome',\n ],\n levels: [\n {\n score: 0,\n class: 'text-bg-danger',\n text: 'Very Weak',\n },\n {\n score: 20,\n class: 'text-bg-danger',\n text: 'Weak',\n },\n {\n score: 40,\n class: 'text-bg-warning',\n text: 'Normal',\n },\n {\n score: 60,\n class: 'text-bg-success',\n text: 'Strong',\n },\n {\n score: 80,\n class: 'text-bg-success',\n text: 'Very Strong',\n },\n ],\n container: null,\n scorer: getStrength,\n striped: false,\n };\n\n #container = null;\n #describedBy;\n #form = null;\n #progress = null;\n #progressBar = null;\n\n /**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=this.defaults.commonPasswords] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\n static getStrength(password, commonPasswords = this.defaults.commonPasswords) {\n return getStrength(password, commonPasswords);\n }\n\n /**\n * Creates a PasswordStrength.\n * @param {HTMLInputElement} node The input node.\n * @param {PasswordStrengthOptions} [options] The PasswordStrength options.\n * @throws {TypeError} When the node is not an input element.\n */\n constructor(node, options) {\n if (!$._isElement(node) || !$.is(node, 'input')) {\n throw new TypeError('PasswordStrength must be created on an input element.');\n }\n\n super(node, options);\n\n try {\n this.#describedBy = $.getAttribute(this.node, 'aria-describedby');\n\n this.#form = $.getProperty(this.node, 'form');\n\n if (this.options.container) {\n this.#container = $.findOne(this.options.container);\n } else {\n this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();\n }\n\n this.#render();\n this.#refresh();\n this.#events();\n } catch (error) {\n this.dispose();\n throw error;\n }\n }\n\n /** @inheritdoc */\n dispose() {\n if (!this.node) {\n return;\n }\n\n $.remove(this.#progress);\n\n if (this.#describedBy === null) {\n $.removeAttribute(this.node, 'aria-describedby');\n } else {\n $.setAttribute(this.node, { 'aria-describedby': this.#describedBy });\n }\n\n this.#container = null;\n this.#form = null;\n this.#progress = null;\n this.#progressBar = null;\n\n super.dispose();\n }\n\n /**\n * Gets the password strength.\n * @returns {number} The password strength, from 0 to 100.\n */\n getStrength() {\n const value = $.getValue(this.node);\n const strength = Number(this.options.scorer(\n value,\n this.options.commonPasswords,\n ));\n\n return Number.isFinite(strength) ?\n $._clamp(strength, 0, 100) :\n 0;\n }\n\n /**\n * Attaches events for the PasswordStrength.\n */\n #events() {\n if (this.#form) {\n this.listen(this.#form, 'reset.ui.passwordstrength', (event) => {\n setTimeout(() => {\n if (this.node && !event.defaultPrevented) {\n this.#refresh();\n }\n }, 0);\n });\n }\n\n this.listen(this.node, 'input.ui.passwordstrength', () => {\n this.#refresh();\n });\n }\n\n /**\n * Refreshes the password strength.\n */\n #refresh() {\n const strength = this.getStrength();\n\n const nextLevel = this.options.levels.findLast(\n (level) => strength >= level.score,\n );\n\n $.setStyle(this.#progressBar, { width: `${strength}%` });\n $.setAttribute(this.#progressBar, {\n 'class': this.constructor.classes.progressBar,\n 'aria-valuenow': strength,\n });\n\n $.addClass(this.#progressBar, nextLevel.class);\n\n if (this.options.striped) {\n $.addClass(this.#progressBar, this.constructor.classes.progressBarStriped);\n }\n\n $.setText(this.#progressBar, nextLevel.text ?? '');\n }\n\n /**\n * Renders the password strength element.\n */\n #render() {\n this.#progress = $.create('div', {\n class: this.constructor.classes.progress,\n });\n\n const id = generateId('password-strength');\n\n this.#progressBar = $.create('div', {\n attributes: {\n id,\n 'role': 'progressbar',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n },\n });\n\n $.append(this.#progress, this.#progressBar);\n $.append(this.#container, this.#progress);\n\n const describedBy = [this.#describedBy, id]\n .filter(Boolean)\n .join(' ');\n\n $.setAttribute(this.node, { 'aria-describedby': describedBy });\n }\n}\n","import { initComponent } from '@fr0st/ui';\nimport PasswordStrength from './password-strength.js';\n\ninitComponent('passwordstrength', PasswordStrength);\n\nexport default PasswordStrength;\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{BaseComponent as e,generateId as t,getDataset as n,initComponent as r}from"@fr0st/ui";import i from"@fr0st/query";var a=[{pattern:/\p{Ll}/u,type:`lower`},{pattern:/\p{Lu}/u,type:`upper`},{pattern:/\p{N}/u,type:`number`}],o=[`1234567890`,`qwertyuiop`,`asdfghjkl`,`zxcvbnm`].flatMap(e=>[e,Array.from(e).reverse().join(``)]),s={$:`s`,0:`o`,3:`e`,4:`a`,5:`s`,7:`t`,"@":`a`},c=[{end:5,max:19,min:1,start:1},{end:8,max:39,min:20,start:6},{end:11,max:59,min:40,start:9},{end:15,max:79,min:60,start:12},{end:24,max:100,min:80,start:16}],l=e=>a.find(({pattern:t})=>t.test(e))?.type||`symbol`,u=e=>{let t=c.find(({end:t})=>e.length<=t)||c.at(-1),n=Math.round(i._map(Math.min(e.length,t.end),t.start,t.end,t.min,t.max)),r=new Set(e.map(l)).size;return Math.min(n+(r-1)*2,t.max)},d=e=>{let t=Math.floor(e.length/2);for(let n=1;n<=t;n++)if(e.length%n===0&&e.every((t,r)=>t===e[r%n]))return e.slice(0,n);return null},f=e=>String(e).normalize(`NFKC`).toLowerCase(),p=e=>Array.from(e,e=>s[e]||e).join(``),m=e=>{let t=[e,e.replace(/[^\p{L}\p{N}]/gu,p)].flatMap(e=>{let t=e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,``);return[t,t.replace(/^\d+|\d+$/g,``)]});return new Set([p(e),...t,...t.map(p)])},h=(e,t)=>{let n=new Set(t.map(f));return n.has(e)?0:n.isDisjointFrom(m(e))?null:5},g=e=>{let[t,n,r]=e.map(e=>e.toLowerCase()),i=l(t),a=n.codePointAt(0)-t.codePointAt(0);return[`lower`,`number`].includes(i)&&l(n)===i&&l(r)===i&&Math.abs(a)===1&&r.codePointAt(0)-n.codePointAt(0)===a},_=e=>{let t=e.join(``).toLowerCase();return o.some(e=>e.includes(t))},v=e=>new Set(e).size===1||g(e)||_(e),y=e=>{let t=new Set;for(let n=0;n+2<e.length;n++)v(e.slice(n,n+3))&&(t.add(n),t.add(n+1),t.add(n+2));return t.size},b=(e,t=[])=>{let n=e.normalize(`NFKC`),r=Array.from(n);if(!r.length)return 0;let a=h(n.toLowerCase(),t);if(a!==null)return a;let o=u(r),s=d(r);if(s?.length===1)return 0;if(s)return Math.min(o,u(s),15);let c=y(r);return i._clamp(o-c,0,c===r.length?15:100)},x=class extends e{static classes={progress:`progress mt-2`,progressBar:`progress-bar`,progressBarStriped:`progress-bar-striped`};static defaults={commonPasswords:[`123456`,`12345678`,`123456789`,`1234567890`,`000000`,`111111`,`abc123`,`admin`,`dragon`,`iloveyou`,`letmein`,`monkey`,`password`,`password1`,`password123`,`qwerty`,`qwerty123`,`welcome`],levels:[{score:0,class:`text-bg-danger`,text:`Very Weak`},{score:20,class:`text-bg-danger`,text:`Weak`},{score:40,class:`text-bg-warning`,text:`Normal`},{score:60,class:`text-bg-success`,text:`Strong`},{score:80,class:`text-bg-success`,text:`Very Strong`}],container:null,scorer:b,striped:!1};#e;#t;#n;#r;#i;#a;static getStrength(e,t=this.defaults.commonPasswords){return b(e,t)}constructor(e,t){super(e,t);let r={...n(e),...t};for(let e of[`levels`,`commonPasswords`])Array.isArray(r[e])&&(this.options[e].length=0,i._extend(this.options[e],r[e]));this.#n=this.node.form,this.#e=this.options.container?i.findOne(this.options.container):i.closest(this.node,`:not(.form-input):not(.input-group)`).shift(),this.#c(),this.#s(),this.#o()}dispose(){i.remove(this.#r),i.removeEvent(this.node,`input.ui.passwordstrength`),this.#n&&i.removeEvent(this.#n,`reset.ui.passwordstrength`,this.#a),this.#t===null?i.removeAttribute(this.node,`aria-describedby`):i.setAttribute(this.node,{"aria-describedby":this.#t}),this.#e=null,this.#n=null,this.#r=null,this.#i=null,this.#a=null,super.dispose()}getStrength(){let e=i.getValue(this.node),t=Number(this.options.scorer(e,this.options.commonPasswords));return Number.isFinite(t)?i._clamp(t,0,100):0}#o(){this.#n&&(this.#a=e=>{setTimeout(()=>{this.node&&!e.defaultPrevented&&this.#s()},0)},i.addEvent(this.#n,`reset.ui.passwordstrength`,this.#a)),i.addEvent(this.node,`input.ui.passwordstrength`,e=>{this.#s()})}#s(){let e=this.getStrength(),t=this.options.levels.findLast(t=>e>=t.score);i.setStyle(this.#i,{width:`${e}%`}),i.setAttribute(this.#i,{class:this.constructor.classes.progressBar,"aria-valuenow":e}),i.addClass(this.#i,t.class),this.options.striped&&i.addClass(this.#i,this.constructor.classes.progressBarStriped),i.setText(this.#i,t.text??``)}#c(){this.#t=i.getAttribute(this.node,`aria-describedby`),this.#r=i.create(`div`,{class:this.constructor.classes.progress});let e=t(`password-strength`);this.#i=i.create(`div`,{attributes:{id:e,role:`progressbar`,"aria-valuemin":0,"aria-valuemax":100}}),i.append(this.#r,this.#i),i.append(this.#e,this.#r);let n=[this.#t,e].filter(Boolean).join(` `);i.setAttribute(this.node,{"aria-describedby":n})}};r(`passwordstrength`,x);var S=x;export{S as default};
1
+ import{BaseComponent as e,generateId as t,initComponent as n}from"@fr0st/ui";import r from"@fr0st/query";var i=[{pattern:/\p{Ll}/u,type:`lower`},{pattern:/\p{Lu}/u,type:`upper`},{pattern:/\p{N}/u,type:`number`}],a=[`1234567890`,`qwertyuiop`,`asdfghjkl`,`zxcvbnm`].flatMap(e=>[e,Array.from(e).reverse().join(``)]),o={$:`s`,0:`o`,3:`e`,4:`a`,5:`s`,7:`t`,"@":`a`},s=[{end:5,max:19,min:1,start:1},{end:8,max:39,min:20,start:6},{end:11,max:59,min:40,start:9},{end:15,max:79,min:60,start:12},{end:24,max:100,min:80,start:16}],c=e=>i.find(({pattern:t})=>t.test(e))?.type||`symbol`,l=e=>{let t=s.find(({end:t})=>e.length<=t)||s.at(-1),n=Math.round(r._map(Math.min(e.length,t.end),t.start,t.end,t.min,t.max)),i=new Set(e.map(c)).size;return Math.min(n+(i-1)*2,t.max)},u=e=>{let t=Math.floor(e.length/2);for(let n=1;n<=t;n++)if(e.length%n===0&&e.every((t,r)=>t===e[r%n]))return e.slice(0,n);return null},d=e=>String(e).normalize(`NFKC`).toLowerCase(),f=e=>Array.from(e,e=>o[e]||e).join(``),p=e=>{let t=[e,e.replace(/[^\p{L}\p{N}]/gu,f)].flatMap(e=>{let t=e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,``);return[t,t.replace(/^\d+|\d+$/g,``)]});return/* @__PURE__ */ new Set([f(e),...t,...t.map(f)])},m=(e,t)=>{let n=new Set(t.map(d));return n.has(e)?0:n.isDisjointFrom(p(e))?null:5},h=e=>{let[t,n,r]=e.map(e=>e.toLowerCase()),i=c(t),a=n.codePointAt(0)-t.codePointAt(0);return[`lower`,`number`].includes(i)&&c(n)===i&&c(r)===i&&Math.abs(a)===1&&r.codePointAt(0)-n.codePointAt(0)===a},g=e=>{let t=e.join(``).toLowerCase();return a.some(e=>e.includes(t))},_=e=>new Set(e).size===1||h(e)||g(e),v=e=>{let t=/* @__PURE__ */ new Set;for(let n=0;n+2<e.length;n++)_(e.slice(n,n+3))&&(t.add(n),t.add(n+1),t.add(n+2));return t.size},y=(e,t=[])=>{let n=e.normalize(`NFKC`),i=Array.from(n);if(!i.length)return 0;let a=m(n.toLowerCase(),t);if(a!==null)return a;let o=l(i),s=u(i);if(s?.length===1)return 0;if(s)return Math.min(o,l(s),15);let c=v(i);return r._clamp(o-c,0,c===i.length?15:100)},b=class extends e{static classes={progress:`progress mt-2`,progressBar:`progress-bar`,progressBarStriped:`progress-bar-striped`};static defaults={commonPasswords:[`123456`,`12345678`,`123456789`,`1234567890`,`000000`,`111111`,`abc123`,`admin`,`dragon`,`iloveyou`,`letmein`,`monkey`,`password`,`password1`,`password123`,`qwerty`,`qwerty123`,`welcome`],levels:[{score:0,class:`text-bg-danger`,text:`Very Weak`},{score:20,class:`text-bg-danger`,text:`Weak`},{score:40,class:`text-bg-warning`,text:`Normal`},{score:60,class:`text-bg-success`,text:`Strong`},{score:80,class:`text-bg-success`,text:`Very Strong`}],container:null,scorer:y,striped:!1};#e=null;#t;#n=null;#r=null;#i=null;static getStrength(e,t=this.defaults.commonPasswords){return y(e,t)}constructor(e,t){if(!r._isElement(e)||!r.is(e,`input`))throw TypeError(`PasswordStrength must be created on an input element.`);super(e,t);try{this.#t=r.getAttribute(this.node,`aria-describedby`),this.#n=r.getProperty(this.node,`form`),this.#e=this.options.container?r.findOne(this.options.container):r.closest(this.node,`:not(.form-input):not(.input-group)`).shift(),this.#s(),this.#o(),this.#a()}catch(e){throw this.dispose(),e}}dispose(){this.node&&(r.remove(this.#r),this.#t===null?r.removeAttribute(this.node,`aria-describedby`):r.setAttribute(this.node,{"aria-describedby":this.#t}),this.#e=null,this.#n=null,this.#r=null,this.#i=null,super.dispose())}getStrength(){let e=r.getValue(this.node),t=Number(this.options.scorer(e,this.options.commonPasswords));return Number.isFinite(t)?r._clamp(t,0,100):0}#a(){this.#n&&this.listen(this.#n,`reset.ui.passwordstrength`,e=>{setTimeout(()=>{this.node&&!e.defaultPrevented&&this.#o()},0)}),this.listen(this.node,`input.ui.passwordstrength`,()=>{this.#o()})}#o(){let e=this.getStrength(),t=this.options.levels.findLast(t=>e>=t.score);r.setStyle(this.#i,{width:`${e}%`}),r.setAttribute(this.#i,{class:this.constructor.classes.progressBar,"aria-valuenow":e}),r.addClass(this.#i,t.class),this.options.striped&&r.addClass(this.#i,this.constructor.classes.progressBarStriped),r.setText(this.#i,t.text??``)}#s(){this.#r=r.create(`div`,{class:this.constructor.classes.progress});let e=t(`password-strength`);this.#i=r.create(`div`,{attributes:{id:e,role:`progressbar`,"aria-valuemin":0,"aria-valuemax":100}}),r.append(this.#r,this.#i),r.append(this.#e,this.#r);let n=[this.#t,e].filter(Boolean).join(` `);r.setAttribute(this.node,{"aria-describedby":n})}};n(`passwordstrength`,b);var x=b;export{x as default};
2
2
  //# sourceMappingURL=frost-ui-passwordstrength.esm.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"frost-ui-passwordstrength.esm.min.js","names":[],"sources":["../src/strength.js","../src/password-strength.js","../src/index.js"],"sourcesContent":["import $ from '@fr0st/query';\n\nconst characterTypes = [\n { pattern: /\\p{Ll}/u, type: 'lower' },\n { pattern: /\\p{Lu}/u, type: 'upper' },\n { pattern: /\\p{N}/u, type: 'number' },\n];\n\nconst keyboardSequences = [\n '1234567890',\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n].flatMap(\n (row) => [\n row,\n Array.from(row).reverse().join(''),\n ],\n);\n\nconst leetCharacters = {\n '$': 's',\n '0': 'o',\n '3': 'e',\n '4': 'a',\n '5': 's',\n '7': 't',\n '@': 'a',\n};\n\nconst lengthBands = [\n { end: 5, max: 19, min: 1, start: 1 },\n { end: 8, max: 39, min: 20, start: 6 },\n { end: 11, max: 59, min: 40, start: 9 },\n { end: 15, max: 79, min: 60, start: 12 },\n { end: 24, max: 100, min: 80, start: 16 },\n];\n\n/**\n * Gets the type of a password character.\n * @param {string} character The character.\n * @returns {string} The character type.\n */\nconst getCharacterType = (character) =>\n characterTypes.find(\n ({ pattern }) => pattern.test(character),\n )?.type || 'symbol';\n\n/**\n * Gets a length-based password strength.\n * @param {string[]} characters The password characters.\n * @returns {number} The password strength.\n */\nconst getLengthStrength = (characters) => {\n const band = lengthBands.find(\n ({ end }) => characters.length <= end,\n ) || lengthBands.at(-1);\n const score = Math.round(\n $._map(\n Math.min(characters.length, band.end),\n band.start,\n band.end,\n band.min,\n band.max,\n ),\n );\n const diversity = new Set(\n characters.map(getCharacterType),\n ).size;\n\n return Math.min(\n score +\n (diversity - 1) * 2,\n band.max,\n );\n};\n\n/**\n * Gets the smallest repeated pattern.\n * @param {string[]} characters The password characters.\n * @returns {string[]|null} The repeated pattern.\n */\nconst getRepeatedPattern = (characters) => {\n const maxLength = Math.floor(characters.length / 2);\n\n for (let length = 1; length <= maxLength; length++) {\n if (\n characters.length % length === 0 &&\n characters.every(\n (character, index) =>\n character === characters[index % length],\n )\n ) {\n return characters.slice(0, length);\n }\n }\n\n return null;\n};\n\n/**\n * Normalizes common password matching.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizePassword = (password) =>\n String(password)\n .normalize('NFKC')\n .toLowerCase();\n\n/**\n * Normalizes common leet substitutions.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizeLeet = (password) =>\n Array.from(\n password,\n (character) => leetCharacters[character] || character,\n ).join('');\n\n/**\n * Gets normalized variants used to match common passwords.\n * @param {string} password The normalized password.\n * @returns {Set<string>} The password variants.\n */\nconst getPasswordVariants = (password) => {\n // Preserve digits for decoration stripping while resolving symbol substitutions.\n const substituted = password.replace(/[^\\p{L}\\p{N}]/gu, normalizeLeet);\n const variants = [password, substituted].flatMap((value) => {\n const undecorated = value.replace(\n /^[^\\p{L}\\p{N}]+|[^\\p{L}\\p{N}]+$/gu,\n '',\n );\n\n return [\n undecorated,\n undecorated.replace(/^\\d+|\\d+$/g, ''),\n ];\n });\n\n return new Set([\n normalizeLeet(password),\n ...variants,\n ...variants.map(normalizeLeet),\n ]);\n};\n\n/**\n * Gets the score for a common password.\n * @param {string} password The normalized password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number|null} The common-password score.\n */\nconst getCommonPasswordStrength = (password, commonPasswords) => {\n const common = new Set(\n commonPasswords.map(normalizePassword),\n );\n\n if (common.has(password)) {\n return 0;\n }\n\n return common.isDisjointFrom(getPasswordVariants(password)) ? null : 5;\n};\n\n/**\n * Checks whether characters form an ascending or descending sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a sequence.\n */\nconst isSequence = (characters) => {\n const [first, second, third] = characters.map(\n (character) => character.toLowerCase(),\n );\n const type = getCharacterType(first);\n const direction = second.codePointAt(0) - first.codePointAt(0);\n\n return ['lower', 'number'].includes(type) &&\n getCharacterType(second) === type &&\n getCharacterType(third) === type &&\n Math.abs(direction) === 1 &&\n third.codePointAt(0) - second.codePointAt(0) === direction;\n};\n\n/**\n * Checks whether characters form a keyboard sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a keyboard sequence.\n */\nconst isKeyboardSequence = (characters) => {\n const sequence = characters.join('').toLowerCase();\n\n return keyboardSequences.some(\n (keyboardSequence) => keyboardSequence.includes(sequence),\n );\n};\n\n/**\n * Checks whether characters form a predictable pattern.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a predictable pattern.\n */\nconst isPredictable = (characters) =>\n new Set(characters).size === 1 ||\n isSequence(characters) ||\n isKeyboardSequence(characters);\n\n/**\n * Gets the number of characters in predictable patterns.\n * @param {string[]} characters The password characters.\n * @returns {number} The number of predictable characters.\n */\nconst getPredictableCount = (characters) => {\n const indices = new Set();\n\n for (let i = 0; i + 2 < characters.length; i++) {\n if (isPredictable(characters.slice(i, i + 3))) {\n indices.add(i);\n indices.add(i + 1);\n indices.add(i + 2);\n }\n }\n\n return indices.size;\n};\n\n/**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=[]] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\nexport const getStrength = (password, commonPasswords = []) => {\n const normalizedPassword = password.normalize('NFKC');\n const characters = Array.from(normalizedPassword);\n\n if (!characters.length) {\n return 0;\n }\n\n const commonStrength = getCommonPasswordStrength(\n normalizedPassword.toLowerCase(),\n commonPasswords,\n );\n\n if (commonStrength !== null) {\n return commonStrength;\n }\n\n const score = getLengthStrength(characters);\n const repeatedPattern = getRepeatedPattern(characters);\n\n if (repeatedPattern?.length === 1) {\n return 0;\n }\n\n if (repeatedPattern) {\n return Math.min(\n score,\n getLengthStrength(repeatedPattern),\n 15,\n );\n }\n\n const predictableCount = getPredictableCount(characters);\n\n return $._clamp(\n score - predictableCount,\n 0,\n predictableCount === characters.length ? 15 : 100,\n );\n};\n","import $ from '@fr0st/query';\nimport { BaseComponent, generateId, getDataset } from '@fr0st/ui';\nimport { getStrength } from './strength.js';\n\n/**\n * @typedef {object} PasswordStrengthLevel\n * @property {string} class The CSS class applied to the progress bar.\n * @property {number} score The minimum score for the level.\n * @property {string} [text] The label displayed in the progress bar.\n */\n\n/**\n * @callback PasswordStrengthScorer\n * @param {string} password The password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number} The password strength, from 0 to 100.\n */\n\n/**\n * @typedef {object} PasswordStrengthOptions\n * @property {string[]} [commonPasswords] Passwords to score as very weak.\n * @property {string|null} [container=null] The selector for the progress container.\n * @property {PasswordStrengthLevel[]} [levels] The ordered password-strength levels.\n * @property {PasswordStrengthScorer} [scorer] Calculates the password strength.\n * @property {boolean} [striped=false] Whether to display a striped progress bar.\n */\n\n/**\n * Displays the strength of a password input as a progress bar.\n * @augments {BaseComponent<PasswordStrengthOptions>}\n */\nexport default class PasswordStrength extends BaseComponent {\n static classes = {\n progress: 'progress mt-2',\n progressBar: 'progress-bar',\n progressBarStriped: 'progress-bar-striped',\n };\n /** @type {PasswordStrengthOptions} */\n static defaults = {\n commonPasswords: [\n '123456',\n '12345678',\n '123456789',\n '1234567890',\n '000000',\n '111111',\n 'abc123',\n 'admin',\n 'dragon',\n 'iloveyou',\n 'letmein',\n 'monkey',\n 'password',\n 'password1',\n 'password123',\n 'qwerty',\n 'qwerty123',\n 'welcome',\n ],\n levels: [\n {\n score: 0,\n class: 'text-bg-danger',\n text: 'Very Weak',\n },\n {\n score: 20,\n class: 'text-bg-danger',\n text: 'Weak',\n },\n {\n score: 40,\n class: 'text-bg-warning',\n text: 'Normal',\n },\n {\n score: 60,\n class: 'text-bg-success',\n text: 'Strong',\n },\n {\n score: 80,\n class: 'text-bg-success',\n text: 'Very Strong',\n },\n ],\n container: null,\n scorer: getStrength,\n striped: false,\n };\n\n #container;\n #describedBy;\n #form;\n #progress;\n #progressBar;\n #resetHandler;\n\n /**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=this.defaults.commonPasswords] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\n static getStrength(password, commonPasswords = this.defaults.commonPasswords) {\n return getStrength(password, commonPasswords);\n }\n\n /**\n * Creates a PasswordStrength.\n * @param {HTMLElement} node The input node.\n * @param {PasswordStrengthOptions} [options] The PasswordStrength options.\n */\n constructor(node, options) {\n super(node, options);\n\n const overrides = { ...getDataset(node), ...options };\n\n // Replace supplied arrays instead of merging them with default entries.\n for (const key of ['levels', 'commonPasswords']) {\n if (Array.isArray(overrides[key])) {\n this.options[key].length = 0;\n $._extend(this.options[key], overrides[key]);\n }\n }\n\n this.#form = this.node.form;\n\n if (this.options.container) {\n this.#container = $.findOne(this.options.container);\n } else {\n this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();\n }\n\n this.#render();\n this.#refresh();\n this.#events();\n }\n\n /** @inheritdoc */\n dispose() {\n $.remove(this.#progress);\n $.removeEvent(this.node, 'input.ui.passwordstrength');\n\n if (this.#form) {\n $.removeEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);\n }\n\n if (this.#describedBy === null) {\n $.removeAttribute(this.node, 'aria-describedby');\n } else {\n $.setAttribute(this.node, { 'aria-describedby': this.#describedBy });\n }\n\n this.#container = null;\n this.#form = null;\n this.#progress = null;\n this.#progressBar = null;\n this.#resetHandler = null;\n\n super.dispose();\n }\n\n /**\n * Gets the password strength.\n * @returns {number} The password strength, from 0 to 100.\n */\n getStrength() {\n const value = $.getValue(this.node);\n const strength = Number(this.options.scorer(\n value,\n this.options.commonPasswords,\n ));\n\n return Number.isFinite(strength) ?\n $._clamp(strength, 0, 100) :\n 0;\n }\n\n /**\n * Attaches events for the PasswordStrength.\n */\n #events() {\n if (this.#form) {\n this.#resetHandler = (event) => {\n setTimeout(() => {\n if (this.node && !event.defaultPrevented) {\n this.#refresh();\n }\n }, 0);\n };\n\n $.addEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);\n }\n\n $.addEvent(this.node, 'input.ui.passwordstrength', (_) => {\n this.#refresh();\n });\n }\n\n /**\n * Refreshes the password strength.\n */\n #refresh() {\n const strength = this.getStrength();\n\n const nextLevel = this.options.levels.findLast(\n (level) => strength >= level.score,\n );\n\n $.setStyle(this.#progressBar, { width: `${strength}%` });\n $.setAttribute(this.#progressBar, {\n 'class': this.constructor.classes.progressBar,\n 'aria-valuenow': strength,\n });\n\n $.addClass(this.#progressBar, nextLevel.class);\n\n if (this.options.striped) {\n $.addClass(this.#progressBar, this.constructor.classes.progressBarStriped);\n }\n\n $.setText(this.#progressBar, nextLevel.text ?? '');\n }\n\n /**\n * Renders the password strength element.\n */\n #render() {\n this.#describedBy = $.getAttribute(this.node, 'aria-describedby');\n this.#progress = $.create('div', {\n class: this.constructor.classes.progress,\n });\n\n const id = generateId('password-strength');\n\n this.#progressBar = $.create('div', {\n attributes: {\n 'id': id,\n 'role': 'progressbar',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n },\n });\n\n $.append(this.#progress, this.#progressBar);\n $.append(this.#container, this.#progress);\n\n const describedBy = [this.#describedBy, id]\n .filter(Boolean)\n .join(' ');\n\n $.setAttribute(this.node, { 'aria-describedby': describedBy });\n }\n}\n","import { initComponent } from '@fr0st/ui';\nimport PasswordStrength from './password-strength.js';\n\ninitComponent('passwordstrength', PasswordStrength);\n\nexport default PasswordStrength;\n"],"mappings":"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"}
1
+ {"version":3,"file":"frost-ui-passwordstrength.esm.min.js","names":["$","characterTypes","keyboardSequences","row","leetCharacters","\"$\"","lengthBands","getCharacterType","pattern","character","getLengthStrength","band","characters","end","score","diversity","getRepeatedPattern","maxLength","length","index","normalizePassword","password","normalizeLeet","getPasswordVariants","variants","undecorated","value","getCommonPasswordStrength","common","commonPasswords","isSequence","first","second","third","type","direction","isKeyboardSequence","sequence","keyboardSequence","isPredictable","getPredictableCount","indices","i","getStrength","normalizedPassword","commonStrength","repeatedPattern","predictableCount","PasswordStrength","BaseComponent","node","options","#describedBy","#form","#container","#render","#refresh","#events","error","#progress","#progressBar","strength","event","nextLevel","level","\"class\"","id","generateId","\"role\"","describedBy","initComponent","src_default"],"sources":["../src/strength.js","../src/password-strength.js","../src/index.js"],"sourcesContent":["import $ from '@fr0st/query';\n\nconst characterTypes = [\n { pattern: /\\p{Ll}/u, type: 'lower' },\n { pattern: /\\p{Lu}/u, type: 'upper' },\n { pattern: /\\p{N}/u, type: 'number' },\n];\n\nconst keyboardSequences = [\n '1234567890',\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n].flatMap(\n (row) => [\n row,\n Array.from(row).reverse().join(''),\n ],\n);\n\nconst leetCharacters = {\n '$': 's',\n '0': 'o',\n '3': 'e',\n '4': 'a',\n '5': 's',\n '7': 't',\n '@': 'a',\n};\n\nconst lengthBands = [\n { end: 5, max: 19, min: 1, start: 1 },\n { end: 8, max: 39, min: 20, start: 6 },\n { end: 11, max: 59, min: 40, start: 9 },\n { end: 15, max: 79, min: 60, start: 12 },\n { end: 24, max: 100, min: 80, start: 16 },\n];\n\n/**\n * Gets the type of a password character.\n * @param {string} character The character.\n * @returns {string} The character type.\n */\nconst getCharacterType = (character) =>\n characterTypes.find(\n ({ pattern }) => pattern.test(character),\n )?.type || 'symbol';\n\n/**\n * Gets a length-based password strength.\n * @param {string[]} characters The password characters.\n * @returns {number} The password strength.\n */\nconst getLengthStrength = (characters) => {\n const band = lengthBands.find(\n ({ end }) => characters.length <= end,\n ) || lengthBands.at(-1);\n const score = Math.round(\n $._map(\n Math.min(characters.length, band.end),\n band.start,\n band.end,\n band.min,\n band.max,\n ),\n );\n const diversity = new Set(\n characters.map(getCharacterType),\n ).size;\n\n return Math.min(\n score +\n (diversity - 1) * 2,\n band.max,\n );\n};\n\n/**\n * Gets the smallest repeated pattern.\n * @param {string[]} characters The password characters.\n * @returns {string[]|null} The repeated pattern.\n */\nconst getRepeatedPattern = (characters) => {\n const maxLength = Math.floor(characters.length / 2);\n\n for (let length = 1; length <= maxLength; length++) {\n if (\n characters.length % length === 0 &&\n characters.every(\n (character, index) =>\n character === characters[index % length],\n )\n ) {\n return characters.slice(0, length);\n }\n }\n\n return null;\n};\n\n/**\n * Normalizes common password matching.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizePassword = (password) =>\n String(password)\n .normalize('NFKC')\n .toLowerCase();\n\n/**\n * Normalizes common leet substitutions.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizeLeet = (password) =>\n Array.from(\n password,\n (character) => leetCharacters[character] || character,\n ).join('');\n\n/**\n * Gets normalized variants used to match common passwords.\n * @param {string} password The normalized password.\n * @returns {Set<string>} The password variants.\n */\nconst getPasswordVariants = (password) => {\n // Preserve digits for decoration stripping while resolving symbol substitutions.\n const substituted = password.replace(/[^\\p{L}\\p{N}]/gu, normalizeLeet);\n const variants = [password, substituted].flatMap((value) => {\n const undecorated = value.replace(\n /^[^\\p{L}\\p{N}]+|[^\\p{L}\\p{N}]+$/gu,\n '',\n );\n\n return [\n undecorated,\n undecorated.replace(/^\\d+|\\d+$/g, ''),\n ];\n });\n\n return new Set([\n normalizeLeet(password),\n ...variants,\n ...variants.map(normalizeLeet),\n ]);\n};\n\n/**\n * Gets the score for a common password.\n * @param {string} password The normalized password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number|null} The common-password score.\n */\nconst getCommonPasswordStrength = (password, commonPasswords) => {\n const common = new Set(\n commonPasswords.map(normalizePassword),\n );\n\n if (common.has(password)) {\n return 0;\n }\n\n return common.isDisjointFrom(getPasswordVariants(password)) ? null : 5;\n};\n\n/**\n * Checks whether characters form an ascending or descending sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a sequence.\n */\nconst isSequence = (characters) => {\n const [first, second, third] = characters.map(\n (character) => character.toLowerCase(),\n );\n const type = getCharacterType(first);\n const direction = second.codePointAt(0) - first.codePointAt(0);\n\n return ['lower', 'number'].includes(type) &&\n getCharacterType(second) === type &&\n getCharacterType(third) === type &&\n Math.abs(direction) === 1 &&\n third.codePointAt(0) - second.codePointAt(0) === direction;\n};\n\n/**\n * Checks whether characters form a keyboard sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a keyboard sequence.\n */\nconst isKeyboardSequence = (characters) => {\n const sequence = characters.join('').toLowerCase();\n\n return keyboardSequences.some(\n (keyboardSequence) => keyboardSequence.includes(sequence),\n );\n};\n\n/**\n * Checks whether characters form a predictable pattern.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a predictable pattern.\n */\nconst isPredictable = (characters) =>\n new Set(characters).size === 1 ||\n isSequence(characters) ||\n isKeyboardSequence(characters);\n\n/**\n * Gets the number of characters in predictable patterns.\n * @param {string[]} characters The password characters.\n * @returns {number} The number of predictable characters.\n */\nconst getPredictableCount = (characters) => {\n const indices = new Set();\n\n for (let i = 0; i + 2 < characters.length; i++) {\n if (isPredictable(characters.slice(i, i + 3))) {\n indices.add(i);\n indices.add(i + 1);\n indices.add(i + 2);\n }\n }\n\n return indices.size;\n};\n\n/**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=[]] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\nexport const getStrength = (password, commonPasswords = []) => {\n const normalizedPassword = password.normalize('NFKC');\n const characters = Array.from(normalizedPassword);\n\n if (!characters.length) {\n return 0;\n }\n\n const commonStrength = getCommonPasswordStrength(\n normalizedPassword.toLowerCase(),\n commonPasswords,\n );\n\n if (commonStrength !== null) {\n return commonStrength;\n }\n\n const score = getLengthStrength(characters);\n const repeatedPattern = getRepeatedPattern(characters);\n\n if (repeatedPattern?.length === 1) {\n return 0;\n }\n\n if (repeatedPattern) {\n return Math.min(\n score,\n getLengthStrength(repeatedPattern),\n 15,\n );\n }\n\n const predictableCount = getPredictableCount(characters);\n\n return $._clamp(\n score - predictableCount,\n 0,\n predictableCount === characters.length ? 15 : 100,\n );\n};\n","import $ from '@fr0st/query';\nimport { BaseComponent, generateId } from '@fr0st/ui';\nimport { getStrength } from './strength.js';\n\n/**\n * @typedef {object} PasswordStrengthLevel\n * @property {string} class The CSS class applied to the progress bar.\n * @property {number} score The minimum score for the level.\n * @property {string} [text] The label displayed in the progress bar.\n */\n\n/**\n * @callback PasswordStrengthScorer\n * @param {string} password The password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number} The password strength, from 0 to 100.\n */\n\n/**\n * @typedef {object} PasswordStrengthOptions\n * @property {string[]} [commonPasswords] Passwords to score as very weak.\n * @property {string|null} [container=null] The selector for the progress container.\n * @property {PasswordStrengthLevel[]} [levels] The ordered password-strength levels.\n * @property {PasswordStrengthScorer} [scorer] Calculates the password strength.\n * @property {boolean} [striped=false] Whether to display a striped progress bar.\n */\n\n/**\n * Displays the strength of a password input as a progress bar.\n * @augments {BaseComponent<PasswordStrengthOptions>}\n */\nexport default class PasswordStrength extends BaseComponent {\n static classes = {\n progress: 'progress mt-2',\n progressBar: 'progress-bar',\n progressBarStriped: 'progress-bar-striped',\n };\n /** @type {PasswordStrengthOptions} */\n static defaults = {\n commonPasswords: [\n '123456',\n '12345678',\n '123456789',\n '1234567890',\n '000000',\n '111111',\n 'abc123',\n 'admin',\n 'dragon',\n 'iloveyou',\n 'letmein',\n 'monkey',\n 'password',\n 'password1',\n 'password123',\n 'qwerty',\n 'qwerty123',\n 'welcome',\n ],\n levels: [\n {\n score: 0,\n class: 'text-bg-danger',\n text: 'Very Weak',\n },\n {\n score: 20,\n class: 'text-bg-danger',\n text: 'Weak',\n },\n {\n score: 40,\n class: 'text-bg-warning',\n text: 'Normal',\n },\n {\n score: 60,\n class: 'text-bg-success',\n text: 'Strong',\n },\n {\n score: 80,\n class: 'text-bg-success',\n text: 'Very Strong',\n },\n ],\n container: null,\n scorer: getStrength,\n striped: false,\n };\n\n #container = null;\n #describedBy;\n #form = null;\n #progress = null;\n #progressBar = null;\n\n /**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=this.defaults.commonPasswords] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\n static getStrength(password, commonPasswords = this.defaults.commonPasswords) {\n return getStrength(password, commonPasswords);\n }\n\n /**\n * Creates a PasswordStrength.\n * @param {HTMLInputElement} node The input node.\n * @param {PasswordStrengthOptions} [options] The PasswordStrength options.\n * @throws {TypeError} When the node is not an input element.\n */\n constructor(node, options) {\n if (!$._isElement(node) || !$.is(node, 'input')) {\n throw new TypeError('PasswordStrength must be created on an input element.');\n }\n\n super(node, options);\n\n try {\n this.#describedBy = $.getAttribute(this.node, 'aria-describedby');\n\n this.#form = $.getProperty(this.node, 'form');\n\n if (this.options.container) {\n this.#container = $.findOne(this.options.container);\n } else {\n this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();\n }\n\n this.#render();\n this.#refresh();\n this.#events();\n } catch (error) {\n this.dispose();\n throw error;\n }\n }\n\n /** @inheritdoc */\n dispose() {\n if (!this.node) {\n return;\n }\n\n $.remove(this.#progress);\n\n if (this.#describedBy === null) {\n $.removeAttribute(this.node, 'aria-describedby');\n } else {\n $.setAttribute(this.node, { 'aria-describedby': this.#describedBy });\n }\n\n this.#container = null;\n this.#form = null;\n this.#progress = null;\n this.#progressBar = null;\n\n super.dispose();\n }\n\n /**\n * Gets the password strength.\n * @returns {number} The password strength, from 0 to 100.\n */\n getStrength() {\n const value = $.getValue(this.node);\n const strength = Number(this.options.scorer(\n value,\n this.options.commonPasswords,\n ));\n\n return Number.isFinite(strength) ?\n $._clamp(strength, 0, 100) :\n 0;\n }\n\n /**\n * Attaches events for the PasswordStrength.\n */\n #events() {\n if (this.#form) {\n this.listen(this.#form, 'reset.ui.passwordstrength', (event) => {\n setTimeout(() => {\n if (this.node && !event.defaultPrevented) {\n this.#refresh();\n }\n }, 0);\n });\n }\n\n this.listen(this.node, 'input.ui.passwordstrength', () => {\n this.#refresh();\n });\n }\n\n /**\n * Refreshes the password strength.\n */\n #refresh() {\n const strength = this.getStrength();\n\n const nextLevel = this.options.levels.findLast(\n (level) => strength >= level.score,\n );\n\n $.setStyle(this.#progressBar, { width: `${strength}%` });\n $.setAttribute(this.#progressBar, {\n 'class': this.constructor.classes.progressBar,\n 'aria-valuenow': strength,\n });\n\n $.addClass(this.#progressBar, nextLevel.class);\n\n if (this.options.striped) {\n $.addClass(this.#progressBar, this.constructor.classes.progressBarStriped);\n }\n\n $.setText(this.#progressBar, nextLevel.text ?? '');\n }\n\n /**\n * Renders the password strength element.\n */\n #render() {\n this.#progress = $.create('div', {\n class: this.constructor.classes.progress,\n });\n\n const id = generateId('password-strength');\n\n this.#progressBar = $.create('div', {\n attributes: {\n id,\n 'role': 'progressbar',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n },\n });\n\n $.append(this.#progress, this.#progressBar);\n $.append(this.#container, this.#progress);\n\n const describedBy = [this.#describedBy, id]\n .filter(Boolean)\n .join(' ');\n\n $.setAttribute(this.node, { 'aria-describedby': describedBy });\n }\n}\n","import { initComponent } from '@fr0st/ui';\nimport PasswordStrength from './password-strength.js';\n\ninitComponent('passwordstrength', PasswordStrength);\n\nexport default PasswordStrength;\n"],"mappings":"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"}
@@ -306,12 +306,11 @@ _fr0st_query = __toESM(_fr0st_query, 1);
306
306
  scorer: getStrength,
307
307
  striped: false
308
308
  };
309
- #container;
309
+ #container = null;
310
310
  #describedBy;
311
- #form;
312
- #progress;
313
- #progressBar;
314
- #resetHandler;
311
+ #form = null;
312
+ #progress = null;
313
+ #progressBar = null;
315
314
  /**
316
315
  * Calculates the strength of a password.
317
316
  * @param {string} password The password.
@@ -323,38 +322,36 @@ _fr0st_query = __toESM(_fr0st_query, 1);
323
322
  }
324
323
  /**
325
324
  * Creates a PasswordStrength.
326
- * @param {HTMLElement} node The input node.
325
+ * @param {HTMLInputElement} node The input node.
327
326
  * @param {PasswordStrengthOptions} [options] The PasswordStrength options.
327
+ * @throws {TypeError} When the node is not an input element.
328
328
  */
329
329
  constructor(node, options) {
330
+ if (!_fr0st_query.default._isElement(node) || !_fr0st_query.default.is(node, "input")) throw new TypeError("PasswordStrength must be created on an input element.");
330
331
  super(node, options);
331
- const overrides = {
332
- ...(0, _fr0st_ui.getDataset)(node),
333
- ...options
334
- };
335
- for (const key of ["levels", "commonPasswords"]) if (Array.isArray(overrides[key])) {
336
- this.options[key].length = 0;
337
- _fr0st_query.default._extend(this.options[key], overrides[key]);
332
+ try {
333
+ this.#describedBy = _fr0st_query.default.getAttribute(this.node, "aria-describedby");
334
+ this.#form = _fr0st_query.default.getProperty(this.node, "form");
335
+ if (this.options.container) this.#container = _fr0st_query.default.findOne(this.options.container);
336
+ else this.#container = _fr0st_query.default.closest(this.node, ":not(.form-input):not(.input-group)").shift();
337
+ this.#render();
338
+ this.#refresh();
339
+ this.#events();
340
+ } catch (error) {
341
+ this.dispose();
342
+ throw error;
338
343
  }
339
- this.#form = this.node.form;
340
- if (this.options.container) this.#container = _fr0st_query.default.findOne(this.options.container);
341
- else this.#container = _fr0st_query.default.closest(this.node, ":not(.form-input):not(.input-group)").shift();
342
- this.#render();
343
- this.#refresh();
344
- this.#events();
345
344
  }
346
345
  /** @inheritdoc */
347
346
  dispose() {
347
+ if (!this.node) return;
348
348
  _fr0st_query.default.remove(this.#progress);
349
- _fr0st_query.default.removeEvent(this.node, "input.ui.passwordstrength");
350
- if (this.#form) _fr0st_query.default.removeEvent(this.#form, "reset.ui.passwordstrength", this.#resetHandler);
351
349
  if (this.#describedBy === null) _fr0st_query.default.removeAttribute(this.node, "aria-describedby");
352
350
  else _fr0st_query.default.setAttribute(this.node, { "aria-describedby": this.#describedBy });
353
351
  this.#container = null;
354
352
  this.#form = null;
355
353
  this.#progress = null;
356
354
  this.#progressBar = null;
357
- this.#resetHandler = null;
358
355
  super.dispose();
359
356
  }
360
357
  /**
@@ -370,15 +367,12 @@ _fr0st_query = __toESM(_fr0st_query, 1);
370
367
  * Attaches events for the PasswordStrength.
371
368
  */
372
369
  #events() {
373
- if (this.#form) {
374
- this.#resetHandler = (event) => {
375
- setTimeout(() => {
376
- if (this.node && !event.defaultPrevented) this.#refresh();
377
- }, 0);
378
- };
379
- _fr0st_query.default.addEvent(this.#form, "reset.ui.passwordstrength", this.#resetHandler);
380
- }
381
- _fr0st_query.default.addEvent(this.node, "input.ui.passwordstrength", (_) => {
370
+ if (this.#form) this.listen(this.#form, "reset.ui.passwordstrength", (event) => {
371
+ setTimeout(() => {
372
+ if (this.node && !event.defaultPrevented) this.#refresh();
373
+ }, 0);
374
+ });
375
+ this.listen(this.node, "input.ui.passwordstrength", () => {
382
376
  this.#refresh();
383
377
  });
384
378
  }
@@ -401,11 +395,10 @@ _fr0st_query = __toESM(_fr0st_query, 1);
401
395
  * Renders the password strength element.
402
396
  */
403
397
  #render() {
404
- this.#describedBy = _fr0st_query.default.getAttribute(this.node, "aria-describedby");
405
398
  this.#progress = _fr0st_query.default.create("div", { class: this.constructor.classes.progress });
406
399
  const id = (0, _fr0st_ui.generateId)("password-strength");
407
400
  this.#progressBar = _fr0st_query.default.create("div", { attributes: {
408
- "id": id,
401
+ id,
409
402
  "role": "progressbar",
410
403
  "aria-valuemin": 0,
411
404
  "aria-valuemax": 100
@@ -1 +1 @@
1
- {"version":3,"file":"frost-ui-passwordstrength.js","names":["$","BaseComponent","getDataset","$","generateId"],"sources":["../src/strength.js","../src/password-strength.js","../src/index.js"],"sourcesContent":["import $ from '@fr0st/query';\n\nconst characterTypes = [\n { pattern: /\\p{Ll}/u, type: 'lower' },\n { pattern: /\\p{Lu}/u, type: 'upper' },\n { pattern: /\\p{N}/u, type: 'number' },\n];\n\nconst keyboardSequences = [\n '1234567890',\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n].flatMap(\n (row) => [\n row,\n Array.from(row).reverse().join(''),\n ],\n);\n\nconst leetCharacters = {\n '$': 's',\n '0': 'o',\n '3': 'e',\n '4': 'a',\n '5': 's',\n '7': 't',\n '@': 'a',\n};\n\nconst lengthBands = [\n { end: 5, max: 19, min: 1, start: 1 },\n { end: 8, max: 39, min: 20, start: 6 },\n { end: 11, max: 59, min: 40, start: 9 },\n { end: 15, max: 79, min: 60, start: 12 },\n { end: 24, max: 100, min: 80, start: 16 },\n];\n\n/**\n * Gets the type of a password character.\n * @param {string} character The character.\n * @returns {string} The character type.\n */\nconst getCharacterType = (character) =>\n characterTypes.find(\n ({ pattern }) => pattern.test(character),\n )?.type || 'symbol';\n\n/**\n * Gets a length-based password strength.\n * @param {string[]} characters The password characters.\n * @returns {number} The password strength.\n */\nconst getLengthStrength = (characters) => {\n const band = lengthBands.find(\n ({ end }) => characters.length <= end,\n ) || lengthBands.at(-1);\n const score = Math.round(\n $._map(\n Math.min(characters.length, band.end),\n band.start,\n band.end,\n band.min,\n band.max,\n ),\n );\n const diversity = new Set(\n characters.map(getCharacterType),\n ).size;\n\n return Math.min(\n score +\n (diversity - 1) * 2,\n band.max,\n );\n};\n\n/**\n * Gets the smallest repeated pattern.\n * @param {string[]} characters The password characters.\n * @returns {string[]|null} The repeated pattern.\n */\nconst getRepeatedPattern = (characters) => {\n const maxLength = Math.floor(characters.length / 2);\n\n for (let length = 1; length <= maxLength; length++) {\n if (\n characters.length % length === 0 &&\n characters.every(\n (character, index) =>\n character === characters[index % length],\n )\n ) {\n return characters.slice(0, length);\n }\n }\n\n return null;\n};\n\n/**\n * Normalizes common password matching.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizePassword = (password) =>\n String(password)\n .normalize('NFKC')\n .toLowerCase();\n\n/**\n * Normalizes common leet substitutions.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizeLeet = (password) =>\n Array.from(\n password,\n (character) => leetCharacters[character] || character,\n ).join('');\n\n/**\n * Gets normalized variants used to match common passwords.\n * @param {string} password The normalized password.\n * @returns {Set<string>} The password variants.\n */\nconst getPasswordVariants = (password) => {\n // Preserve digits for decoration stripping while resolving symbol substitutions.\n const substituted = password.replace(/[^\\p{L}\\p{N}]/gu, normalizeLeet);\n const variants = [password, substituted].flatMap((value) => {\n const undecorated = value.replace(\n /^[^\\p{L}\\p{N}]+|[^\\p{L}\\p{N}]+$/gu,\n '',\n );\n\n return [\n undecorated,\n undecorated.replace(/^\\d+|\\d+$/g, ''),\n ];\n });\n\n return new Set([\n normalizeLeet(password),\n ...variants,\n ...variants.map(normalizeLeet),\n ]);\n};\n\n/**\n * Gets the score for a common password.\n * @param {string} password The normalized password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number|null} The common-password score.\n */\nconst getCommonPasswordStrength = (password, commonPasswords) => {\n const common = new Set(\n commonPasswords.map(normalizePassword),\n );\n\n if (common.has(password)) {\n return 0;\n }\n\n return common.isDisjointFrom(getPasswordVariants(password)) ? null : 5;\n};\n\n/**\n * Checks whether characters form an ascending or descending sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a sequence.\n */\nconst isSequence = (characters) => {\n const [first, second, third] = characters.map(\n (character) => character.toLowerCase(),\n );\n const type = getCharacterType(first);\n const direction = second.codePointAt(0) - first.codePointAt(0);\n\n return ['lower', 'number'].includes(type) &&\n getCharacterType(second) === type &&\n getCharacterType(third) === type &&\n Math.abs(direction) === 1 &&\n third.codePointAt(0) - second.codePointAt(0) === direction;\n};\n\n/**\n * Checks whether characters form a keyboard sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a keyboard sequence.\n */\nconst isKeyboardSequence = (characters) => {\n const sequence = characters.join('').toLowerCase();\n\n return keyboardSequences.some(\n (keyboardSequence) => keyboardSequence.includes(sequence),\n );\n};\n\n/**\n * Checks whether characters form a predictable pattern.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a predictable pattern.\n */\nconst isPredictable = (characters) =>\n new Set(characters).size === 1 ||\n isSequence(characters) ||\n isKeyboardSequence(characters);\n\n/**\n * Gets the number of characters in predictable patterns.\n * @param {string[]} characters The password characters.\n * @returns {number} The number of predictable characters.\n */\nconst getPredictableCount = (characters) => {\n const indices = new Set();\n\n for (let i = 0; i + 2 < characters.length; i++) {\n if (isPredictable(characters.slice(i, i + 3))) {\n indices.add(i);\n indices.add(i + 1);\n indices.add(i + 2);\n }\n }\n\n return indices.size;\n};\n\n/**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=[]] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\nexport const getStrength = (password, commonPasswords = []) => {\n const normalizedPassword = password.normalize('NFKC');\n const characters = Array.from(normalizedPassword);\n\n if (!characters.length) {\n return 0;\n }\n\n const commonStrength = getCommonPasswordStrength(\n normalizedPassword.toLowerCase(),\n commonPasswords,\n );\n\n if (commonStrength !== null) {\n return commonStrength;\n }\n\n const score = getLengthStrength(characters);\n const repeatedPattern = getRepeatedPattern(characters);\n\n if (repeatedPattern?.length === 1) {\n return 0;\n }\n\n if (repeatedPattern) {\n return Math.min(\n score,\n getLengthStrength(repeatedPattern),\n 15,\n );\n }\n\n const predictableCount = getPredictableCount(characters);\n\n return $._clamp(\n score - predictableCount,\n 0,\n predictableCount === characters.length ? 15 : 100,\n );\n};\n","import $ from '@fr0st/query';\nimport { BaseComponent, generateId, getDataset } from '@fr0st/ui';\nimport { getStrength } from './strength.js';\n\n/**\n * @typedef {object} PasswordStrengthLevel\n * @property {string} class The CSS class applied to the progress bar.\n * @property {number} score The minimum score for the level.\n * @property {string} [text] The label displayed in the progress bar.\n */\n\n/**\n * @callback PasswordStrengthScorer\n * @param {string} password The password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number} The password strength, from 0 to 100.\n */\n\n/**\n * @typedef {object} PasswordStrengthOptions\n * @property {string[]} [commonPasswords] Passwords to score as very weak.\n * @property {string|null} [container=null] The selector for the progress container.\n * @property {PasswordStrengthLevel[]} [levels] The ordered password-strength levels.\n * @property {PasswordStrengthScorer} [scorer] Calculates the password strength.\n * @property {boolean} [striped=false] Whether to display a striped progress bar.\n */\n\n/**\n * Displays the strength of a password input as a progress bar.\n * @augments {BaseComponent<PasswordStrengthOptions>}\n */\nexport default class PasswordStrength extends BaseComponent {\n static classes = {\n progress: 'progress mt-2',\n progressBar: 'progress-bar',\n progressBarStriped: 'progress-bar-striped',\n };\n /** @type {PasswordStrengthOptions} */\n static defaults = {\n commonPasswords: [\n '123456',\n '12345678',\n '123456789',\n '1234567890',\n '000000',\n '111111',\n 'abc123',\n 'admin',\n 'dragon',\n 'iloveyou',\n 'letmein',\n 'monkey',\n 'password',\n 'password1',\n 'password123',\n 'qwerty',\n 'qwerty123',\n 'welcome',\n ],\n levels: [\n {\n score: 0,\n class: 'text-bg-danger',\n text: 'Very Weak',\n },\n {\n score: 20,\n class: 'text-bg-danger',\n text: 'Weak',\n },\n {\n score: 40,\n class: 'text-bg-warning',\n text: 'Normal',\n },\n {\n score: 60,\n class: 'text-bg-success',\n text: 'Strong',\n },\n {\n score: 80,\n class: 'text-bg-success',\n text: 'Very Strong',\n },\n ],\n container: null,\n scorer: getStrength,\n striped: false,\n };\n\n #container;\n #describedBy;\n #form;\n #progress;\n #progressBar;\n #resetHandler;\n\n /**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=this.defaults.commonPasswords] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\n static getStrength(password, commonPasswords = this.defaults.commonPasswords) {\n return getStrength(password, commonPasswords);\n }\n\n /**\n * Creates a PasswordStrength.\n * @param {HTMLElement} node The input node.\n * @param {PasswordStrengthOptions} [options] The PasswordStrength options.\n */\n constructor(node, options) {\n super(node, options);\n\n const overrides = { ...getDataset(node), ...options };\n\n // Replace supplied arrays instead of merging them with default entries.\n for (const key of ['levels', 'commonPasswords']) {\n if (Array.isArray(overrides[key])) {\n this.options[key].length = 0;\n $._extend(this.options[key], overrides[key]);\n }\n }\n\n this.#form = this.node.form;\n\n if (this.options.container) {\n this.#container = $.findOne(this.options.container);\n } else {\n this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();\n }\n\n this.#render();\n this.#refresh();\n this.#events();\n }\n\n /** @inheritdoc */\n dispose() {\n $.remove(this.#progress);\n $.removeEvent(this.node, 'input.ui.passwordstrength');\n\n if (this.#form) {\n $.removeEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);\n }\n\n if (this.#describedBy === null) {\n $.removeAttribute(this.node, 'aria-describedby');\n } else {\n $.setAttribute(this.node, { 'aria-describedby': this.#describedBy });\n }\n\n this.#container = null;\n this.#form = null;\n this.#progress = null;\n this.#progressBar = null;\n this.#resetHandler = null;\n\n super.dispose();\n }\n\n /**\n * Gets the password strength.\n * @returns {number} The password strength, from 0 to 100.\n */\n getStrength() {\n const value = $.getValue(this.node);\n const strength = Number(this.options.scorer(\n value,\n this.options.commonPasswords,\n ));\n\n return Number.isFinite(strength) ?\n $._clamp(strength, 0, 100) :\n 0;\n }\n\n /**\n * Attaches events for the PasswordStrength.\n */\n #events() {\n if (this.#form) {\n this.#resetHandler = (event) => {\n setTimeout(() => {\n if (this.node && !event.defaultPrevented) {\n this.#refresh();\n }\n }, 0);\n };\n\n $.addEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);\n }\n\n $.addEvent(this.node, 'input.ui.passwordstrength', (_) => {\n this.#refresh();\n });\n }\n\n /**\n * Refreshes the password strength.\n */\n #refresh() {\n const strength = this.getStrength();\n\n const nextLevel = this.options.levels.findLast(\n (level) => strength >= level.score,\n );\n\n $.setStyle(this.#progressBar, { width: `${strength}%` });\n $.setAttribute(this.#progressBar, {\n 'class': this.constructor.classes.progressBar,\n 'aria-valuenow': strength,\n });\n\n $.addClass(this.#progressBar, nextLevel.class);\n\n if (this.options.striped) {\n $.addClass(this.#progressBar, this.constructor.classes.progressBarStriped);\n }\n\n $.setText(this.#progressBar, nextLevel.text ?? '');\n }\n\n /**\n * Renders the password strength element.\n */\n #render() {\n this.#describedBy = $.getAttribute(this.node, 'aria-describedby');\n this.#progress = $.create('div', {\n class: this.constructor.classes.progress,\n });\n\n const id = generateId('password-strength');\n\n this.#progressBar = $.create('div', {\n attributes: {\n 'id': id,\n 'role': 'progressbar',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n },\n });\n\n $.append(this.#progress, this.#progressBar);\n $.append(this.#container, this.#progress);\n\n const describedBy = [this.#describedBy, id]\n .filter(Boolean)\n .join(' ');\n\n $.setAttribute(this.node, { 'aria-describedby': describedBy });\n }\n}\n","import { initComponent } from '@fr0st/ui';\nimport PasswordStrength from './password-strength.js';\n\ninitComponent('passwordstrength', PasswordStrength);\n\nexport default PasswordStrength;\n"],"mappings":"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"}
1
+ {"version":3,"file":"frost-ui-passwordstrength.js","names":["$","BaseComponent","$","generateId"],"sources":["../src/strength.js","../src/password-strength.js","../src/index.js"],"sourcesContent":["import $ from '@fr0st/query';\n\nconst characterTypes = [\n { pattern: /\\p{Ll}/u, type: 'lower' },\n { pattern: /\\p{Lu}/u, type: 'upper' },\n { pattern: /\\p{N}/u, type: 'number' },\n];\n\nconst keyboardSequences = [\n '1234567890',\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n].flatMap(\n (row) => [\n row,\n Array.from(row).reverse().join(''),\n ],\n);\n\nconst leetCharacters = {\n '$': 's',\n '0': 'o',\n '3': 'e',\n '4': 'a',\n '5': 's',\n '7': 't',\n '@': 'a',\n};\n\nconst lengthBands = [\n { end: 5, max: 19, min: 1, start: 1 },\n { end: 8, max: 39, min: 20, start: 6 },\n { end: 11, max: 59, min: 40, start: 9 },\n { end: 15, max: 79, min: 60, start: 12 },\n { end: 24, max: 100, min: 80, start: 16 },\n];\n\n/**\n * Gets the type of a password character.\n * @param {string} character The character.\n * @returns {string} The character type.\n */\nconst getCharacterType = (character) =>\n characterTypes.find(\n ({ pattern }) => pattern.test(character),\n )?.type || 'symbol';\n\n/**\n * Gets a length-based password strength.\n * @param {string[]} characters The password characters.\n * @returns {number} The password strength.\n */\nconst getLengthStrength = (characters) => {\n const band = lengthBands.find(\n ({ end }) => characters.length <= end,\n ) || lengthBands.at(-1);\n const score = Math.round(\n $._map(\n Math.min(characters.length, band.end),\n band.start,\n band.end,\n band.min,\n band.max,\n ),\n );\n const diversity = new Set(\n characters.map(getCharacterType),\n ).size;\n\n return Math.min(\n score +\n (diversity - 1) * 2,\n band.max,\n );\n};\n\n/**\n * Gets the smallest repeated pattern.\n * @param {string[]} characters The password characters.\n * @returns {string[]|null} The repeated pattern.\n */\nconst getRepeatedPattern = (characters) => {\n const maxLength = Math.floor(characters.length / 2);\n\n for (let length = 1; length <= maxLength; length++) {\n if (\n characters.length % length === 0 &&\n characters.every(\n (character, index) =>\n character === characters[index % length],\n )\n ) {\n return characters.slice(0, length);\n }\n }\n\n return null;\n};\n\n/**\n * Normalizes common password matching.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizePassword = (password) =>\n String(password)\n .normalize('NFKC')\n .toLowerCase();\n\n/**\n * Normalizes common leet substitutions.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizeLeet = (password) =>\n Array.from(\n password,\n (character) => leetCharacters[character] || character,\n ).join('');\n\n/**\n * Gets normalized variants used to match common passwords.\n * @param {string} password The normalized password.\n * @returns {Set<string>} The password variants.\n */\nconst getPasswordVariants = (password) => {\n // Preserve digits for decoration stripping while resolving symbol substitutions.\n const substituted = password.replace(/[^\\p{L}\\p{N}]/gu, normalizeLeet);\n const variants = [password, substituted].flatMap((value) => {\n const undecorated = value.replace(\n /^[^\\p{L}\\p{N}]+|[^\\p{L}\\p{N}]+$/gu,\n '',\n );\n\n return [\n undecorated,\n undecorated.replace(/^\\d+|\\d+$/g, ''),\n ];\n });\n\n return new Set([\n normalizeLeet(password),\n ...variants,\n ...variants.map(normalizeLeet),\n ]);\n};\n\n/**\n * Gets the score for a common password.\n * @param {string} password The normalized password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number|null} The common-password score.\n */\nconst getCommonPasswordStrength = (password, commonPasswords) => {\n const common = new Set(\n commonPasswords.map(normalizePassword),\n );\n\n if (common.has(password)) {\n return 0;\n }\n\n return common.isDisjointFrom(getPasswordVariants(password)) ? null : 5;\n};\n\n/**\n * Checks whether characters form an ascending or descending sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a sequence.\n */\nconst isSequence = (characters) => {\n const [first, second, third] = characters.map(\n (character) => character.toLowerCase(),\n );\n const type = getCharacterType(first);\n const direction = second.codePointAt(0) - first.codePointAt(0);\n\n return ['lower', 'number'].includes(type) &&\n getCharacterType(second) === type &&\n getCharacterType(third) === type &&\n Math.abs(direction) === 1 &&\n third.codePointAt(0) - second.codePointAt(0) === direction;\n};\n\n/**\n * Checks whether characters form a keyboard sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a keyboard sequence.\n */\nconst isKeyboardSequence = (characters) => {\n const sequence = characters.join('').toLowerCase();\n\n return keyboardSequences.some(\n (keyboardSequence) => keyboardSequence.includes(sequence),\n );\n};\n\n/**\n * Checks whether characters form a predictable pattern.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a predictable pattern.\n */\nconst isPredictable = (characters) =>\n new Set(characters).size === 1 ||\n isSequence(characters) ||\n isKeyboardSequence(characters);\n\n/**\n * Gets the number of characters in predictable patterns.\n * @param {string[]} characters The password characters.\n * @returns {number} The number of predictable characters.\n */\nconst getPredictableCount = (characters) => {\n const indices = new Set();\n\n for (let i = 0; i + 2 < characters.length; i++) {\n if (isPredictable(characters.slice(i, i + 3))) {\n indices.add(i);\n indices.add(i + 1);\n indices.add(i + 2);\n }\n }\n\n return indices.size;\n};\n\n/**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=[]] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\nexport const getStrength = (password, commonPasswords = []) => {\n const normalizedPassword = password.normalize('NFKC');\n const characters = Array.from(normalizedPassword);\n\n if (!characters.length) {\n return 0;\n }\n\n const commonStrength = getCommonPasswordStrength(\n normalizedPassword.toLowerCase(),\n commonPasswords,\n );\n\n if (commonStrength !== null) {\n return commonStrength;\n }\n\n const score = getLengthStrength(characters);\n const repeatedPattern = getRepeatedPattern(characters);\n\n if (repeatedPattern?.length === 1) {\n return 0;\n }\n\n if (repeatedPattern) {\n return Math.min(\n score,\n getLengthStrength(repeatedPattern),\n 15,\n );\n }\n\n const predictableCount = getPredictableCount(characters);\n\n return $._clamp(\n score - predictableCount,\n 0,\n predictableCount === characters.length ? 15 : 100,\n );\n};\n","import $ from '@fr0st/query';\nimport { BaseComponent, generateId } from '@fr0st/ui';\nimport { getStrength } from './strength.js';\n\n/**\n * @typedef {object} PasswordStrengthLevel\n * @property {string} class The CSS class applied to the progress bar.\n * @property {number} score The minimum score for the level.\n * @property {string} [text] The label displayed in the progress bar.\n */\n\n/**\n * @callback PasswordStrengthScorer\n * @param {string} password The password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number} The password strength, from 0 to 100.\n */\n\n/**\n * @typedef {object} PasswordStrengthOptions\n * @property {string[]} [commonPasswords] Passwords to score as very weak.\n * @property {string|null} [container=null] The selector for the progress container.\n * @property {PasswordStrengthLevel[]} [levels] The ordered password-strength levels.\n * @property {PasswordStrengthScorer} [scorer] Calculates the password strength.\n * @property {boolean} [striped=false] Whether to display a striped progress bar.\n */\n\n/**\n * Displays the strength of a password input as a progress bar.\n * @augments {BaseComponent<PasswordStrengthOptions>}\n */\nexport default class PasswordStrength extends BaseComponent {\n static classes = {\n progress: 'progress mt-2',\n progressBar: 'progress-bar',\n progressBarStriped: 'progress-bar-striped',\n };\n /** @type {PasswordStrengthOptions} */\n static defaults = {\n commonPasswords: [\n '123456',\n '12345678',\n '123456789',\n '1234567890',\n '000000',\n '111111',\n 'abc123',\n 'admin',\n 'dragon',\n 'iloveyou',\n 'letmein',\n 'monkey',\n 'password',\n 'password1',\n 'password123',\n 'qwerty',\n 'qwerty123',\n 'welcome',\n ],\n levels: [\n {\n score: 0,\n class: 'text-bg-danger',\n text: 'Very Weak',\n },\n {\n score: 20,\n class: 'text-bg-danger',\n text: 'Weak',\n },\n {\n score: 40,\n class: 'text-bg-warning',\n text: 'Normal',\n },\n {\n score: 60,\n class: 'text-bg-success',\n text: 'Strong',\n },\n {\n score: 80,\n class: 'text-bg-success',\n text: 'Very Strong',\n },\n ],\n container: null,\n scorer: getStrength,\n striped: false,\n };\n\n #container = null;\n #describedBy;\n #form = null;\n #progress = null;\n #progressBar = null;\n\n /**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=this.defaults.commonPasswords] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\n static getStrength(password, commonPasswords = this.defaults.commonPasswords) {\n return getStrength(password, commonPasswords);\n }\n\n /**\n * Creates a PasswordStrength.\n * @param {HTMLInputElement} node The input node.\n * @param {PasswordStrengthOptions} [options] The PasswordStrength options.\n * @throws {TypeError} When the node is not an input element.\n */\n constructor(node, options) {\n if (!$._isElement(node) || !$.is(node, 'input')) {\n throw new TypeError('PasswordStrength must be created on an input element.');\n }\n\n super(node, options);\n\n try {\n this.#describedBy = $.getAttribute(this.node, 'aria-describedby');\n\n this.#form = $.getProperty(this.node, 'form');\n\n if (this.options.container) {\n this.#container = $.findOne(this.options.container);\n } else {\n this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();\n }\n\n this.#render();\n this.#refresh();\n this.#events();\n } catch (error) {\n this.dispose();\n throw error;\n }\n }\n\n /** @inheritdoc */\n dispose() {\n if (!this.node) {\n return;\n }\n\n $.remove(this.#progress);\n\n if (this.#describedBy === null) {\n $.removeAttribute(this.node, 'aria-describedby');\n } else {\n $.setAttribute(this.node, { 'aria-describedby': this.#describedBy });\n }\n\n this.#container = null;\n this.#form = null;\n this.#progress = null;\n this.#progressBar = null;\n\n super.dispose();\n }\n\n /**\n * Gets the password strength.\n * @returns {number} The password strength, from 0 to 100.\n */\n getStrength() {\n const value = $.getValue(this.node);\n const strength = Number(this.options.scorer(\n value,\n this.options.commonPasswords,\n ));\n\n return Number.isFinite(strength) ?\n $._clamp(strength, 0, 100) :\n 0;\n }\n\n /**\n * Attaches events for the PasswordStrength.\n */\n #events() {\n if (this.#form) {\n this.listen(this.#form, 'reset.ui.passwordstrength', (event) => {\n setTimeout(() => {\n if (this.node && !event.defaultPrevented) {\n this.#refresh();\n }\n }, 0);\n });\n }\n\n this.listen(this.node, 'input.ui.passwordstrength', () => {\n this.#refresh();\n });\n }\n\n /**\n * Refreshes the password strength.\n */\n #refresh() {\n const strength = this.getStrength();\n\n const nextLevel = this.options.levels.findLast(\n (level) => strength >= level.score,\n );\n\n $.setStyle(this.#progressBar, { width: `${strength}%` });\n $.setAttribute(this.#progressBar, {\n 'class': this.constructor.classes.progressBar,\n 'aria-valuenow': strength,\n });\n\n $.addClass(this.#progressBar, nextLevel.class);\n\n if (this.options.striped) {\n $.addClass(this.#progressBar, this.constructor.classes.progressBarStriped);\n }\n\n $.setText(this.#progressBar, nextLevel.text ?? '');\n }\n\n /**\n * Renders the password strength element.\n */\n #render() {\n this.#progress = $.create('div', {\n class: this.constructor.classes.progress,\n });\n\n const id = generateId('password-strength');\n\n this.#progressBar = $.create('div', {\n attributes: {\n id,\n 'role': 'progressbar',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n },\n });\n\n $.append(this.#progress, this.#progressBar);\n $.append(this.#container, this.#progress);\n\n const describedBy = [this.#describedBy, id]\n .filter(Boolean)\n .join(' ');\n\n $.setAttribute(this.node, { 'aria-describedby': describedBy });\n }\n}\n","import { initComponent } from '@fr0st/ui';\nimport PasswordStrength from './password-strength.js';\n\ninitComponent('passwordstrength', PasswordStrength);\n\nexport default PasswordStrength;\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("@fr0st/ui"),require("@fr0st/query")):typeof define==`function`&&define.amd?define([`exports`,`@fr0st/ui`,`@fr0st/query`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.UI=e.UI||{},e.UI,e.fQuery))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=Object.create,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,s=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty,l=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var s=o(t),l=0,u=s.length,d;l<u;l++)d=s[l],!c.call(e,d)&&d!==n&&i(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(r=a(t,d))||r.enumerable});return e};n=((e,t,n)=>(n=e==null?{}:r(s(e)),l(t||!e||!e.__esModule||!c.call(e,`default`)?i(n,`default`,{value:e,enumerable:!0}):n,e)))(n,1);var u=[{pattern:/\p{Ll}/u,type:`lower`},{pattern:/\p{Lu}/u,type:`upper`},{pattern:/\p{N}/u,type:`number`}],d=[`1234567890`,`qwertyuiop`,`asdfghjkl`,`zxcvbnm`].flatMap(e=>[e,Array.from(e).reverse().join(``)]),f={$:`s`,0:`o`,3:`e`,4:`a`,5:`s`,7:`t`,"@":`a`},p=[{end:5,max:19,min:1,start:1},{end:8,max:39,min:20,start:6},{end:11,max:59,min:40,start:9},{end:15,max:79,min:60,start:12},{end:24,max:100,min:80,start:16}],m=e=>u.find(({pattern:t})=>t.test(e))?.type||`symbol`,h=e=>{let t=p.find(({end:t})=>e.length<=t)||p.at(-1),r=Math.round(n.default._map(Math.min(e.length,t.end),t.start,t.end,t.min,t.max)),i=new Set(e.map(m)).size;return Math.min(r+(i-1)*2,t.max)},g=e=>{let t=Math.floor(e.length/2);for(let n=1;n<=t;n++)if(e.length%n===0&&e.every((t,r)=>t===e[r%n]))return e.slice(0,n);return null},_=e=>String(e).normalize(`NFKC`).toLowerCase(),v=e=>Array.from(e,e=>f[e]||e).join(``),y=e=>{let t=[e,e.replace(/[^\p{L}\p{N}]/gu,v)].flatMap(e=>{let t=e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,``);return[t,t.replace(/^\d+|\d+$/g,``)]});return new Set([v(e),...t,...t.map(v)])},b=(e,t)=>{let n=new Set(t.map(_));return n.has(e)?0:n.isDisjointFrom(y(e))?null:5},x=e=>{let[t,n,r]=e.map(e=>e.toLowerCase()),i=m(t),a=n.codePointAt(0)-t.codePointAt(0);return[`lower`,`number`].includes(i)&&m(n)===i&&m(r)===i&&Math.abs(a)===1&&r.codePointAt(0)-n.codePointAt(0)===a},S=e=>{let t=e.join(``).toLowerCase();return d.some(e=>e.includes(t))},C=e=>new Set(e).size===1||x(e)||S(e),w=e=>{let t=new Set;for(let n=0;n+2<e.length;n++)C(e.slice(n,n+3))&&(t.add(n),t.add(n+1),t.add(n+2));return t.size},T=(e,t=[])=>{let r=e.normalize(`NFKC`),i=Array.from(r);if(!i.length)return 0;let a=b(r.toLowerCase(),t);if(a!==null)return a;let o=h(i),s=g(i);if(s?.length===1)return 0;if(s)return Math.min(o,h(s),15);let c=w(i);return n.default._clamp(o-c,0,c===i.length?15:100)},E=class extends t.BaseComponent{static classes={progress:`progress mt-2`,progressBar:`progress-bar`,progressBarStriped:`progress-bar-striped`};static defaults={commonPasswords:[`123456`,`12345678`,`123456789`,`1234567890`,`000000`,`111111`,`abc123`,`admin`,`dragon`,`iloveyou`,`letmein`,`monkey`,`password`,`password1`,`password123`,`qwerty`,`qwerty123`,`welcome`],levels:[{score:0,class:`text-bg-danger`,text:`Very Weak`},{score:20,class:`text-bg-danger`,text:`Weak`},{score:40,class:`text-bg-warning`,text:`Normal`},{score:60,class:`text-bg-success`,text:`Strong`},{score:80,class:`text-bg-success`,text:`Very Strong`}],container:null,scorer:T,striped:!1};#e;#t;#n;#r;#i;#a;static getStrength(e,t=this.defaults.commonPasswords){return T(e,t)}constructor(e,r){super(e,r);let i={...(0,t.getDataset)(e),...r};for(let e of[`levels`,`commonPasswords`])Array.isArray(i[e])&&(this.options[e].length=0,n.default._extend(this.options[e],i[e]));this.#n=this.node.form,this.#e=this.options.container?n.default.findOne(this.options.container):n.default.closest(this.node,`:not(.form-input):not(.input-group)`).shift(),this.#c(),this.#s(),this.#o()}dispose(){n.default.remove(this.#r),n.default.removeEvent(this.node,`input.ui.passwordstrength`),this.#n&&n.default.removeEvent(this.#n,`reset.ui.passwordstrength`,this.#a),this.#t===null?n.default.removeAttribute(this.node,`aria-describedby`):n.default.setAttribute(this.node,{"aria-describedby":this.#t}),this.#e=null,this.#n=null,this.#r=null,this.#i=null,this.#a=null,super.dispose()}getStrength(){let e=n.default.getValue(this.node),t=Number(this.options.scorer(e,this.options.commonPasswords));return Number.isFinite(t)?n.default._clamp(t,0,100):0}#o(){this.#n&&(this.#a=e=>{setTimeout(()=>{this.node&&!e.defaultPrevented&&this.#s()},0)},n.default.addEvent(this.#n,`reset.ui.passwordstrength`,this.#a)),n.default.addEvent(this.node,`input.ui.passwordstrength`,e=>{this.#s()})}#s(){let e=this.getStrength(),t=this.options.levels.findLast(t=>e>=t.score);n.default.setStyle(this.#i,{width:`${e}%`}),n.default.setAttribute(this.#i,{class:this.constructor.classes.progressBar,"aria-valuenow":e}),n.default.addClass(this.#i,t.class),this.options.striped&&n.default.addClass(this.#i,this.constructor.classes.progressBarStriped),n.default.setText(this.#i,t.text??``)}#c(){this.#t=n.default.getAttribute(this.node,`aria-describedby`),this.#r=n.default.create(`div`,{class:this.constructor.classes.progress});let e=(0,t.generateId)(`password-strength`);this.#i=n.default.create(`div`,{attributes:{id:e,role:`progressbar`,"aria-valuemin":0,"aria-valuemax":100}}),n.default.append(this.#r,this.#i),n.default.append(this.#e,this.#r);let r=[this.#t,e].filter(Boolean).join(` `);n.default.setAttribute(this.node,{"aria-describedby":r})}};(0,t.initComponent)(`passwordstrength`,E),e.PasswordStrength=E});
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("@fr0st/ui"),require("@fr0st/query")):typeof define==`function`&&define.amd?define([`exports`,`@fr0st/ui`,`@fr0st/query`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.UI=e.UI||{},e.UI,e.fQuery))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=Object.create,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,s=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty,l=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var s=o(t),l=0,u=s.length,d;l<u;l++)d=s[l],!c.call(e,d)&&d!==n&&i(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(r=a(t,d))||r.enumerable});return e};n=((e,t,n)=>(n=e==null?{}:r(s(e)),l(t||!e||!e.__esModule||!c.call(e,`default`)?i(n,`default`,{value:e,enumerable:!0}):n,e)))(n,1);var u=[{pattern:/\p{Ll}/u,type:`lower`},{pattern:/\p{Lu}/u,type:`upper`},{pattern:/\p{N}/u,type:`number`}],d=[`1234567890`,`qwertyuiop`,`asdfghjkl`,`zxcvbnm`].flatMap(e=>[e,Array.from(e).reverse().join(``)]),f={$:`s`,0:`o`,3:`e`,4:`a`,5:`s`,7:`t`,"@":`a`},p=[{end:5,max:19,min:1,start:1},{end:8,max:39,min:20,start:6},{end:11,max:59,min:40,start:9},{end:15,max:79,min:60,start:12},{end:24,max:100,min:80,start:16}],m=e=>u.find(({pattern:t})=>t.test(e))?.type||`symbol`,h=e=>{let t=p.find(({end:t})=>e.length<=t)||p.at(-1),r=Math.round(n.default._map(Math.min(e.length,t.end),t.start,t.end,t.min,t.max)),i=new Set(e.map(m)).size;return Math.min(r+(i-1)*2,t.max)},g=e=>{let t=Math.floor(e.length/2);for(let n=1;n<=t;n++)if(e.length%n===0&&e.every((t,r)=>t===e[r%n]))return e.slice(0,n);return null},_=e=>String(e).normalize(`NFKC`).toLowerCase(),v=e=>Array.from(e,e=>f[e]||e).join(``),y=e=>{let t=[e,e.replace(/[^\p{L}\p{N}]/gu,v)].flatMap(e=>{let t=e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,``);return[t,t.replace(/^\d+|\d+$/g,``)]});return/* @__PURE__ */ new Set([v(e),...t,...t.map(v)])},b=(e,t)=>{let n=new Set(t.map(_));return n.has(e)?0:n.isDisjointFrom(y(e))?null:5},x=e=>{let[t,n,r]=e.map(e=>e.toLowerCase()),i=m(t),a=n.codePointAt(0)-t.codePointAt(0);return[`lower`,`number`].includes(i)&&m(n)===i&&m(r)===i&&Math.abs(a)===1&&r.codePointAt(0)-n.codePointAt(0)===a},S=e=>{let t=e.join(``).toLowerCase();return d.some(e=>e.includes(t))},C=e=>new Set(e).size===1||x(e)||S(e),w=e=>{let t=/* @__PURE__ */ new Set;for(let n=0;n+2<e.length;n++)C(e.slice(n,n+3))&&(t.add(n),t.add(n+1),t.add(n+2));return t.size},T=(e,t=[])=>{let r=e.normalize(`NFKC`),i=Array.from(r);if(!i.length)return 0;let a=b(r.toLowerCase(),t);if(a!==null)return a;let o=h(i),s=g(i);if(s?.length===1)return 0;if(s)return Math.min(o,h(s),15);let c=w(i);return n.default._clamp(o-c,0,c===i.length?15:100)},E=class extends t.BaseComponent{static classes={progress:`progress mt-2`,progressBar:`progress-bar`,progressBarStriped:`progress-bar-striped`};static defaults={commonPasswords:[`123456`,`12345678`,`123456789`,`1234567890`,`000000`,`111111`,`abc123`,`admin`,`dragon`,`iloveyou`,`letmein`,`monkey`,`password`,`password1`,`password123`,`qwerty`,`qwerty123`,`welcome`],levels:[{score:0,class:`text-bg-danger`,text:`Very Weak`},{score:20,class:`text-bg-danger`,text:`Weak`},{score:40,class:`text-bg-warning`,text:`Normal`},{score:60,class:`text-bg-success`,text:`Strong`},{score:80,class:`text-bg-success`,text:`Very Strong`}],container:null,scorer:T,striped:!1};#e=null;#t;#n=null;#r=null;#i=null;static getStrength(e,t=this.defaults.commonPasswords){return T(e,t)}constructor(e,t){if(!n.default._isElement(e)||!n.default.is(e,`input`))throw TypeError(`PasswordStrength must be created on an input element.`);super(e,t);try{this.#t=n.default.getAttribute(this.node,`aria-describedby`),this.#n=n.default.getProperty(this.node,`form`),this.#e=this.options.container?n.default.findOne(this.options.container):n.default.closest(this.node,`:not(.form-input):not(.input-group)`).shift(),this.#s(),this.#o(),this.#a()}catch(e){throw this.dispose(),e}}dispose(){this.node&&(n.default.remove(this.#r),this.#t===null?n.default.removeAttribute(this.node,`aria-describedby`):n.default.setAttribute(this.node,{"aria-describedby":this.#t}),this.#e=null,this.#n=null,this.#r=null,this.#i=null,super.dispose())}getStrength(){let e=n.default.getValue(this.node),t=Number(this.options.scorer(e,this.options.commonPasswords));return Number.isFinite(t)?n.default._clamp(t,0,100):0}#a(){this.#n&&this.listen(this.#n,`reset.ui.passwordstrength`,e=>{setTimeout(()=>{this.node&&!e.defaultPrevented&&this.#o()},0)}),this.listen(this.node,`input.ui.passwordstrength`,()=>{this.#o()})}#o(){let e=this.getStrength(),t=this.options.levels.findLast(t=>e>=t.score);n.default.setStyle(this.#i,{width:`${e}%`}),n.default.setAttribute(this.#i,{class:this.constructor.classes.progressBar,"aria-valuenow":e}),n.default.addClass(this.#i,t.class),this.options.striped&&n.default.addClass(this.#i,this.constructor.classes.progressBarStriped),n.default.setText(this.#i,t.text??``)}#s(){this.#r=n.default.create(`div`,{class:this.constructor.classes.progress});let e=(0,t.generateId)(`password-strength`);this.#i=n.default.create(`div`,{attributes:{id:e,role:`progressbar`,"aria-valuemin":0,"aria-valuemax":100}}),n.default.append(this.#r,this.#i),n.default.append(this.#e,this.#r);let r=[this.#t,e].filter(Boolean).join(` `);n.default.setAttribute(this.node,{"aria-describedby":r})}};(0,t.initComponent)(`passwordstrength`,E),e.PasswordStrength=E});
2
2
  //# sourceMappingURL=frost-ui-passwordstrength.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"frost-ui-passwordstrength.min.js","names":["$","BaseComponent","getDataset","$","generateId"],"sources":["../src/strength.js","../src/password-strength.js","../src/index.js"],"sourcesContent":["import $ from '@fr0st/query';\n\nconst characterTypes = [\n { pattern: /\\p{Ll}/u, type: 'lower' },\n { pattern: /\\p{Lu}/u, type: 'upper' },\n { pattern: /\\p{N}/u, type: 'number' },\n];\n\nconst keyboardSequences = [\n '1234567890',\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n].flatMap(\n (row) => [\n row,\n Array.from(row).reverse().join(''),\n ],\n);\n\nconst leetCharacters = {\n '$': 's',\n '0': 'o',\n '3': 'e',\n '4': 'a',\n '5': 's',\n '7': 't',\n '@': 'a',\n};\n\nconst lengthBands = [\n { end: 5, max: 19, min: 1, start: 1 },\n { end: 8, max: 39, min: 20, start: 6 },\n { end: 11, max: 59, min: 40, start: 9 },\n { end: 15, max: 79, min: 60, start: 12 },\n { end: 24, max: 100, min: 80, start: 16 },\n];\n\n/**\n * Gets the type of a password character.\n * @param {string} character The character.\n * @returns {string} The character type.\n */\nconst getCharacterType = (character) =>\n characterTypes.find(\n ({ pattern }) => pattern.test(character),\n )?.type || 'symbol';\n\n/**\n * Gets a length-based password strength.\n * @param {string[]} characters The password characters.\n * @returns {number} The password strength.\n */\nconst getLengthStrength = (characters) => {\n const band = lengthBands.find(\n ({ end }) => characters.length <= end,\n ) || lengthBands.at(-1);\n const score = Math.round(\n $._map(\n Math.min(characters.length, band.end),\n band.start,\n band.end,\n band.min,\n band.max,\n ),\n );\n const diversity = new Set(\n characters.map(getCharacterType),\n ).size;\n\n return Math.min(\n score +\n (diversity - 1) * 2,\n band.max,\n );\n};\n\n/**\n * Gets the smallest repeated pattern.\n * @param {string[]} characters The password characters.\n * @returns {string[]|null} The repeated pattern.\n */\nconst getRepeatedPattern = (characters) => {\n const maxLength = Math.floor(characters.length / 2);\n\n for (let length = 1; length <= maxLength; length++) {\n if (\n characters.length % length === 0 &&\n characters.every(\n (character, index) =>\n character === characters[index % length],\n )\n ) {\n return characters.slice(0, length);\n }\n }\n\n return null;\n};\n\n/**\n * Normalizes common password matching.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizePassword = (password) =>\n String(password)\n .normalize('NFKC')\n .toLowerCase();\n\n/**\n * Normalizes common leet substitutions.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizeLeet = (password) =>\n Array.from(\n password,\n (character) => leetCharacters[character] || character,\n ).join('');\n\n/**\n * Gets normalized variants used to match common passwords.\n * @param {string} password The normalized password.\n * @returns {Set<string>} The password variants.\n */\nconst getPasswordVariants = (password) => {\n // Preserve digits for decoration stripping while resolving symbol substitutions.\n const substituted = password.replace(/[^\\p{L}\\p{N}]/gu, normalizeLeet);\n const variants = [password, substituted].flatMap((value) => {\n const undecorated = value.replace(\n /^[^\\p{L}\\p{N}]+|[^\\p{L}\\p{N}]+$/gu,\n '',\n );\n\n return [\n undecorated,\n undecorated.replace(/^\\d+|\\d+$/g, ''),\n ];\n });\n\n return new Set([\n normalizeLeet(password),\n ...variants,\n ...variants.map(normalizeLeet),\n ]);\n};\n\n/**\n * Gets the score for a common password.\n * @param {string} password The normalized password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number|null} The common-password score.\n */\nconst getCommonPasswordStrength = (password, commonPasswords) => {\n const common = new Set(\n commonPasswords.map(normalizePassword),\n );\n\n if (common.has(password)) {\n return 0;\n }\n\n return common.isDisjointFrom(getPasswordVariants(password)) ? null : 5;\n};\n\n/**\n * Checks whether characters form an ascending or descending sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a sequence.\n */\nconst isSequence = (characters) => {\n const [first, second, third] = characters.map(\n (character) => character.toLowerCase(),\n );\n const type = getCharacterType(first);\n const direction = second.codePointAt(0) - first.codePointAt(0);\n\n return ['lower', 'number'].includes(type) &&\n getCharacterType(second) === type &&\n getCharacterType(third) === type &&\n Math.abs(direction) === 1 &&\n third.codePointAt(0) - second.codePointAt(0) === direction;\n};\n\n/**\n * Checks whether characters form a keyboard sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a keyboard sequence.\n */\nconst isKeyboardSequence = (characters) => {\n const sequence = characters.join('').toLowerCase();\n\n return keyboardSequences.some(\n (keyboardSequence) => keyboardSequence.includes(sequence),\n );\n};\n\n/**\n * Checks whether characters form a predictable pattern.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a predictable pattern.\n */\nconst isPredictable = (characters) =>\n new Set(characters).size === 1 ||\n isSequence(characters) ||\n isKeyboardSequence(characters);\n\n/**\n * Gets the number of characters in predictable patterns.\n * @param {string[]} characters The password characters.\n * @returns {number} The number of predictable characters.\n */\nconst getPredictableCount = (characters) => {\n const indices = new Set();\n\n for (let i = 0; i + 2 < characters.length; i++) {\n if (isPredictable(characters.slice(i, i + 3))) {\n indices.add(i);\n indices.add(i + 1);\n indices.add(i + 2);\n }\n }\n\n return indices.size;\n};\n\n/**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=[]] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\nexport const getStrength = (password, commonPasswords = []) => {\n const normalizedPassword = password.normalize('NFKC');\n const characters = Array.from(normalizedPassword);\n\n if (!characters.length) {\n return 0;\n }\n\n const commonStrength = getCommonPasswordStrength(\n normalizedPassword.toLowerCase(),\n commonPasswords,\n );\n\n if (commonStrength !== null) {\n return commonStrength;\n }\n\n const score = getLengthStrength(characters);\n const repeatedPattern = getRepeatedPattern(characters);\n\n if (repeatedPattern?.length === 1) {\n return 0;\n }\n\n if (repeatedPattern) {\n return Math.min(\n score,\n getLengthStrength(repeatedPattern),\n 15,\n );\n }\n\n const predictableCount = getPredictableCount(characters);\n\n return $._clamp(\n score - predictableCount,\n 0,\n predictableCount === characters.length ? 15 : 100,\n );\n};\n","import $ from '@fr0st/query';\nimport { BaseComponent, generateId, getDataset } from '@fr0st/ui';\nimport { getStrength } from './strength.js';\n\n/**\n * @typedef {object} PasswordStrengthLevel\n * @property {string} class The CSS class applied to the progress bar.\n * @property {number} score The minimum score for the level.\n * @property {string} [text] The label displayed in the progress bar.\n */\n\n/**\n * @callback PasswordStrengthScorer\n * @param {string} password The password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number} The password strength, from 0 to 100.\n */\n\n/**\n * @typedef {object} PasswordStrengthOptions\n * @property {string[]} [commonPasswords] Passwords to score as very weak.\n * @property {string|null} [container=null] The selector for the progress container.\n * @property {PasswordStrengthLevel[]} [levels] The ordered password-strength levels.\n * @property {PasswordStrengthScorer} [scorer] Calculates the password strength.\n * @property {boolean} [striped=false] Whether to display a striped progress bar.\n */\n\n/**\n * Displays the strength of a password input as a progress bar.\n * @augments {BaseComponent<PasswordStrengthOptions>}\n */\nexport default class PasswordStrength extends BaseComponent {\n static classes = {\n progress: 'progress mt-2',\n progressBar: 'progress-bar',\n progressBarStriped: 'progress-bar-striped',\n };\n /** @type {PasswordStrengthOptions} */\n static defaults = {\n commonPasswords: [\n '123456',\n '12345678',\n '123456789',\n '1234567890',\n '000000',\n '111111',\n 'abc123',\n 'admin',\n 'dragon',\n 'iloveyou',\n 'letmein',\n 'monkey',\n 'password',\n 'password1',\n 'password123',\n 'qwerty',\n 'qwerty123',\n 'welcome',\n ],\n levels: [\n {\n score: 0,\n class: 'text-bg-danger',\n text: 'Very Weak',\n },\n {\n score: 20,\n class: 'text-bg-danger',\n text: 'Weak',\n },\n {\n score: 40,\n class: 'text-bg-warning',\n text: 'Normal',\n },\n {\n score: 60,\n class: 'text-bg-success',\n text: 'Strong',\n },\n {\n score: 80,\n class: 'text-bg-success',\n text: 'Very Strong',\n },\n ],\n container: null,\n scorer: getStrength,\n striped: false,\n };\n\n #container;\n #describedBy;\n #form;\n #progress;\n #progressBar;\n #resetHandler;\n\n /**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=this.defaults.commonPasswords] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\n static getStrength(password, commonPasswords = this.defaults.commonPasswords) {\n return getStrength(password, commonPasswords);\n }\n\n /**\n * Creates a PasswordStrength.\n * @param {HTMLElement} node The input node.\n * @param {PasswordStrengthOptions} [options] The PasswordStrength options.\n */\n constructor(node, options) {\n super(node, options);\n\n const overrides = { ...getDataset(node), ...options };\n\n // Replace supplied arrays instead of merging them with default entries.\n for (const key of ['levels', 'commonPasswords']) {\n if (Array.isArray(overrides[key])) {\n this.options[key].length = 0;\n $._extend(this.options[key], overrides[key]);\n }\n }\n\n this.#form = this.node.form;\n\n if (this.options.container) {\n this.#container = $.findOne(this.options.container);\n } else {\n this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();\n }\n\n this.#render();\n this.#refresh();\n this.#events();\n }\n\n /** @inheritdoc */\n dispose() {\n $.remove(this.#progress);\n $.removeEvent(this.node, 'input.ui.passwordstrength');\n\n if (this.#form) {\n $.removeEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);\n }\n\n if (this.#describedBy === null) {\n $.removeAttribute(this.node, 'aria-describedby');\n } else {\n $.setAttribute(this.node, { 'aria-describedby': this.#describedBy });\n }\n\n this.#container = null;\n this.#form = null;\n this.#progress = null;\n this.#progressBar = null;\n this.#resetHandler = null;\n\n super.dispose();\n }\n\n /**\n * Gets the password strength.\n * @returns {number} The password strength, from 0 to 100.\n */\n getStrength() {\n const value = $.getValue(this.node);\n const strength = Number(this.options.scorer(\n value,\n this.options.commonPasswords,\n ));\n\n return Number.isFinite(strength) ?\n $._clamp(strength, 0, 100) :\n 0;\n }\n\n /**\n * Attaches events for the PasswordStrength.\n */\n #events() {\n if (this.#form) {\n this.#resetHandler = (event) => {\n setTimeout(() => {\n if (this.node && !event.defaultPrevented) {\n this.#refresh();\n }\n }, 0);\n };\n\n $.addEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);\n }\n\n $.addEvent(this.node, 'input.ui.passwordstrength', (_) => {\n this.#refresh();\n });\n }\n\n /**\n * Refreshes the password strength.\n */\n #refresh() {\n const strength = this.getStrength();\n\n const nextLevel = this.options.levels.findLast(\n (level) => strength >= level.score,\n );\n\n $.setStyle(this.#progressBar, { width: `${strength}%` });\n $.setAttribute(this.#progressBar, {\n 'class': this.constructor.classes.progressBar,\n 'aria-valuenow': strength,\n });\n\n $.addClass(this.#progressBar, nextLevel.class);\n\n if (this.options.striped) {\n $.addClass(this.#progressBar, this.constructor.classes.progressBarStriped);\n }\n\n $.setText(this.#progressBar, nextLevel.text ?? '');\n }\n\n /**\n * Renders the password strength element.\n */\n #render() {\n this.#describedBy = $.getAttribute(this.node, 'aria-describedby');\n this.#progress = $.create('div', {\n class: this.constructor.classes.progress,\n });\n\n const id = generateId('password-strength');\n\n this.#progressBar = $.create('div', {\n attributes: {\n 'id': id,\n 'role': 'progressbar',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n },\n });\n\n $.append(this.#progress, this.#progressBar);\n $.append(this.#container, this.#progress);\n\n const describedBy = [this.#describedBy, id]\n .filter(Boolean)\n .join(' ');\n\n $.setAttribute(this.node, { 'aria-describedby': describedBy });\n }\n}\n","import { initComponent } from '@fr0st/ui';\nimport PasswordStrength from './password-strength.js';\n\ninitComponent('passwordstrength', PasswordStrength);\n\nexport default PasswordStrength;\n"],"mappings":"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"}
1
+ {"version":3,"file":"frost-ui-passwordstrength.min.js","names":["$","BaseComponent","$","generateId","factory","global","exports","__create","__defProp","__getOwnPropDesc","__getOwnPropNames","__getProtoOf","__hasOwnProp","__copyProps","from","keys","i","n","key","to","except","k","desc","mod","isNodeMode","target","_fr0st_query","characterTypes","keyboardSequences","row","leetCharacters","\"$\"","lengthBands","getCharacterType","pattern","character","getLengthStrength","band","characters","end","score","diversity","getRepeatedPattern","maxLength","length","index","normalizePassword","password","normalizeLeet","getPasswordVariants","variants","undecorated","value","getCommonPasswordStrength","common","commonPasswords","isSequence","first","second","third","type","direction","isKeyboardSequence","sequence","keyboardSequence","isPredictable","getPredictableCount","indices","getStrength","normalizedPassword","commonStrength","repeatedPattern","predictableCount","PasswordStrength","_fr0st_ui","node","options","#describedBy","#form","#container","#render","#refresh","#events","error","#progress","#progressBar","strength","event","nextLevel","level","\"class\"","id","\"role\"","describedBy","src_default"],"sources":["../src/strength.js","../src/password-strength.js","../src/index.js"],"sourcesContent":["import $ from '@fr0st/query';\n\nconst characterTypes = [\n { pattern: /\\p{Ll}/u, type: 'lower' },\n { pattern: /\\p{Lu}/u, type: 'upper' },\n { pattern: /\\p{N}/u, type: 'number' },\n];\n\nconst keyboardSequences = [\n '1234567890',\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n].flatMap(\n (row) => [\n row,\n Array.from(row).reverse().join(''),\n ],\n);\n\nconst leetCharacters = {\n '$': 's',\n '0': 'o',\n '3': 'e',\n '4': 'a',\n '5': 's',\n '7': 't',\n '@': 'a',\n};\n\nconst lengthBands = [\n { end: 5, max: 19, min: 1, start: 1 },\n { end: 8, max: 39, min: 20, start: 6 },\n { end: 11, max: 59, min: 40, start: 9 },\n { end: 15, max: 79, min: 60, start: 12 },\n { end: 24, max: 100, min: 80, start: 16 },\n];\n\n/**\n * Gets the type of a password character.\n * @param {string} character The character.\n * @returns {string} The character type.\n */\nconst getCharacterType = (character) =>\n characterTypes.find(\n ({ pattern }) => pattern.test(character),\n )?.type || 'symbol';\n\n/**\n * Gets a length-based password strength.\n * @param {string[]} characters The password characters.\n * @returns {number} The password strength.\n */\nconst getLengthStrength = (characters) => {\n const band = lengthBands.find(\n ({ end }) => characters.length <= end,\n ) || lengthBands.at(-1);\n const score = Math.round(\n $._map(\n Math.min(characters.length, band.end),\n band.start,\n band.end,\n band.min,\n band.max,\n ),\n );\n const diversity = new Set(\n characters.map(getCharacterType),\n ).size;\n\n return Math.min(\n score +\n (diversity - 1) * 2,\n band.max,\n );\n};\n\n/**\n * Gets the smallest repeated pattern.\n * @param {string[]} characters The password characters.\n * @returns {string[]|null} The repeated pattern.\n */\nconst getRepeatedPattern = (characters) => {\n const maxLength = Math.floor(characters.length / 2);\n\n for (let length = 1; length <= maxLength; length++) {\n if (\n characters.length % length === 0 &&\n characters.every(\n (character, index) =>\n character === characters[index % length],\n )\n ) {\n return characters.slice(0, length);\n }\n }\n\n return null;\n};\n\n/**\n * Normalizes common password matching.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizePassword = (password) =>\n String(password)\n .normalize('NFKC')\n .toLowerCase();\n\n/**\n * Normalizes common leet substitutions.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizeLeet = (password) =>\n Array.from(\n password,\n (character) => leetCharacters[character] || character,\n ).join('');\n\n/**\n * Gets normalized variants used to match common passwords.\n * @param {string} password The normalized password.\n * @returns {Set<string>} The password variants.\n */\nconst getPasswordVariants = (password) => {\n // Preserve digits for decoration stripping while resolving symbol substitutions.\n const substituted = password.replace(/[^\\p{L}\\p{N}]/gu, normalizeLeet);\n const variants = [password, substituted].flatMap((value) => {\n const undecorated = value.replace(\n /^[^\\p{L}\\p{N}]+|[^\\p{L}\\p{N}]+$/gu,\n '',\n );\n\n return [\n undecorated,\n undecorated.replace(/^\\d+|\\d+$/g, ''),\n ];\n });\n\n return new Set([\n normalizeLeet(password),\n ...variants,\n ...variants.map(normalizeLeet),\n ]);\n};\n\n/**\n * Gets the score for a common password.\n * @param {string} password The normalized password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number|null} The common-password score.\n */\nconst getCommonPasswordStrength = (password, commonPasswords) => {\n const common = new Set(\n commonPasswords.map(normalizePassword),\n );\n\n if (common.has(password)) {\n return 0;\n }\n\n return common.isDisjointFrom(getPasswordVariants(password)) ? null : 5;\n};\n\n/**\n * Checks whether characters form an ascending or descending sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a sequence.\n */\nconst isSequence = (characters) => {\n const [first, second, third] = characters.map(\n (character) => character.toLowerCase(),\n );\n const type = getCharacterType(first);\n const direction = second.codePointAt(0) - first.codePointAt(0);\n\n return ['lower', 'number'].includes(type) &&\n getCharacterType(second) === type &&\n getCharacterType(third) === type &&\n Math.abs(direction) === 1 &&\n third.codePointAt(0) - second.codePointAt(0) === direction;\n};\n\n/**\n * Checks whether characters form a keyboard sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a keyboard sequence.\n */\nconst isKeyboardSequence = (characters) => {\n const sequence = characters.join('').toLowerCase();\n\n return keyboardSequences.some(\n (keyboardSequence) => keyboardSequence.includes(sequence),\n );\n};\n\n/**\n * Checks whether characters form a predictable pattern.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a predictable pattern.\n */\nconst isPredictable = (characters) =>\n new Set(characters).size === 1 ||\n isSequence(characters) ||\n isKeyboardSequence(characters);\n\n/**\n * Gets the number of characters in predictable patterns.\n * @param {string[]} characters The password characters.\n * @returns {number} The number of predictable characters.\n */\nconst getPredictableCount = (characters) => {\n const indices = new Set();\n\n for (let i = 0; i + 2 < characters.length; i++) {\n if (isPredictable(characters.slice(i, i + 3))) {\n indices.add(i);\n indices.add(i + 1);\n indices.add(i + 2);\n }\n }\n\n return indices.size;\n};\n\n/**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=[]] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\nexport const getStrength = (password, commonPasswords = []) => {\n const normalizedPassword = password.normalize('NFKC');\n const characters = Array.from(normalizedPassword);\n\n if (!characters.length) {\n return 0;\n }\n\n const commonStrength = getCommonPasswordStrength(\n normalizedPassword.toLowerCase(),\n commonPasswords,\n );\n\n if (commonStrength !== null) {\n return commonStrength;\n }\n\n const score = getLengthStrength(characters);\n const repeatedPattern = getRepeatedPattern(characters);\n\n if (repeatedPattern?.length === 1) {\n return 0;\n }\n\n if (repeatedPattern) {\n return Math.min(\n score,\n getLengthStrength(repeatedPattern),\n 15,\n );\n }\n\n const predictableCount = getPredictableCount(characters);\n\n return $._clamp(\n score - predictableCount,\n 0,\n predictableCount === characters.length ? 15 : 100,\n );\n};\n","import $ from '@fr0st/query';\nimport { BaseComponent, generateId } from '@fr0st/ui';\nimport { getStrength } from './strength.js';\n\n/**\n * @typedef {object} PasswordStrengthLevel\n * @property {string} class The CSS class applied to the progress bar.\n * @property {number} score The minimum score for the level.\n * @property {string} [text] The label displayed in the progress bar.\n */\n\n/**\n * @callback PasswordStrengthScorer\n * @param {string} password The password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number} The password strength, from 0 to 100.\n */\n\n/**\n * @typedef {object} PasswordStrengthOptions\n * @property {string[]} [commonPasswords] Passwords to score as very weak.\n * @property {string|null} [container=null] The selector for the progress container.\n * @property {PasswordStrengthLevel[]} [levels] The ordered password-strength levels.\n * @property {PasswordStrengthScorer} [scorer] Calculates the password strength.\n * @property {boolean} [striped=false] Whether to display a striped progress bar.\n */\n\n/**\n * Displays the strength of a password input as a progress bar.\n * @augments {BaseComponent<PasswordStrengthOptions>}\n */\nexport default class PasswordStrength extends BaseComponent {\n static classes = {\n progress: 'progress mt-2',\n progressBar: 'progress-bar',\n progressBarStriped: 'progress-bar-striped',\n };\n /** @type {PasswordStrengthOptions} */\n static defaults = {\n commonPasswords: [\n '123456',\n '12345678',\n '123456789',\n '1234567890',\n '000000',\n '111111',\n 'abc123',\n 'admin',\n 'dragon',\n 'iloveyou',\n 'letmein',\n 'monkey',\n 'password',\n 'password1',\n 'password123',\n 'qwerty',\n 'qwerty123',\n 'welcome',\n ],\n levels: [\n {\n score: 0,\n class: 'text-bg-danger',\n text: 'Very Weak',\n },\n {\n score: 20,\n class: 'text-bg-danger',\n text: 'Weak',\n },\n {\n score: 40,\n class: 'text-bg-warning',\n text: 'Normal',\n },\n {\n score: 60,\n class: 'text-bg-success',\n text: 'Strong',\n },\n {\n score: 80,\n class: 'text-bg-success',\n text: 'Very Strong',\n },\n ],\n container: null,\n scorer: getStrength,\n striped: false,\n };\n\n #container = null;\n #describedBy;\n #form = null;\n #progress = null;\n #progressBar = null;\n\n /**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=this.defaults.commonPasswords] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\n static getStrength(password, commonPasswords = this.defaults.commonPasswords) {\n return getStrength(password, commonPasswords);\n }\n\n /**\n * Creates a PasswordStrength.\n * @param {HTMLInputElement} node The input node.\n * @param {PasswordStrengthOptions} [options] The PasswordStrength options.\n * @throws {TypeError} When the node is not an input element.\n */\n constructor(node, options) {\n if (!$._isElement(node) || !$.is(node, 'input')) {\n throw new TypeError('PasswordStrength must be created on an input element.');\n }\n\n super(node, options);\n\n try {\n this.#describedBy = $.getAttribute(this.node, 'aria-describedby');\n\n this.#form = $.getProperty(this.node, 'form');\n\n if (this.options.container) {\n this.#container = $.findOne(this.options.container);\n } else {\n this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();\n }\n\n this.#render();\n this.#refresh();\n this.#events();\n } catch (error) {\n this.dispose();\n throw error;\n }\n }\n\n /** @inheritdoc */\n dispose() {\n if (!this.node) {\n return;\n }\n\n $.remove(this.#progress);\n\n if (this.#describedBy === null) {\n $.removeAttribute(this.node, 'aria-describedby');\n } else {\n $.setAttribute(this.node, { 'aria-describedby': this.#describedBy });\n }\n\n this.#container = null;\n this.#form = null;\n this.#progress = null;\n this.#progressBar = null;\n\n super.dispose();\n }\n\n /**\n * Gets the password strength.\n * @returns {number} The password strength, from 0 to 100.\n */\n getStrength() {\n const value = $.getValue(this.node);\n const strength = Number(this.options.scorer(\n value,\n this.options.commonPasswords,\n ));\n\n return Number.isFinite(strength) ?\n $._clamp(strength, 0, 100) :\n 0;\n }\n\n /**\n * Attaches events for the PasswordStrength.\n */\n #events() {\n if (this.#form) {\n this.listen(this.#form, 'reset.ui.passwordstrength', (event) => {\n setTimeout(() => {\n if (this.node && !event.defaultPrevented) {\n this.#refresh();\n }\n }, 0);\n });\n }\n\n this.listen(this.node, 'input.ui.passwordstrength', () => {\n this.#refresh();\n });\n }\n\n /**\n * Refreshes the password strength.\n */\n #refresh() {\n const strength = this.getStrength();\n\n const nextLevel = this.options.levels.findLast(\n (level) => strength >= level.score,\n );\n\n $.setStyle(this.#progressBar, { width: `${strength}%` });\n $.setAttribute(this.#progressBar, {\n 'class': this.constructor.classes.progressBar,\n 'aria-valuenow': strength,\n });\n\n $.addClass(this.#progressBar, nextLevel.class);\n\n if (this.options.striped) {\n $.addClass(this.#progressBar, this.constructor.classes.progressBarStriped);\n }\n\n $.setText(this.#progressBar, nextLevel.text ?? '');\n }\n\n /**\n * Renders the password strength element.\n */\n #render() {\n this.#progress = $.create('div', {\n class: this.constructor.classes.progress,\n });\n\n const id = generateId('password-strength');\n\n this.#progressBar = $.create('div', {\n attributes: {\n id,\n 'role': 'progressbar',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n },\n });\n\n $.append(this.#progress, this.#progressBar);\n $.append(this.#container, this.#progress);\n\n const describedBy = [this.#describedBy, id]\n .filter(Boolean)\n .join(' ');\n\n $.setAttribute(this.node, { 'aria-describedby': describedBy });\n }\n}\n","import { initComponent } from '@fr0st/ui';\nimport PasswordStrength from './password-strength.js';\n\ninitComponent('passwordstrength', PasswordStrength);\n\nexport default PasswordStrength;\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fr0st/ui-passwordstrength",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "private": false,
5
5
  "description": "Password strength indicator component for Frost UI with customizable levels, labels, and striped progress styling.",
6
6
  "keywords": [
@@ -57,13 +57,13 @@
57
57
  "baseline newly available"
58
58
  ],
59
59
  "devDependencies": {
60
- "@fr0st/eslint-config": "^5.0.3",
61
- "@fr0st/query": "^5.0.0",
62
- "@fr0st/ui": "^4.0.0",
63
- "@playwright/test": "^1.63.0",
64
- "eslint": "^10.10.0",
65
- "monocart-reporter": "^2.13.1",
66
- "vite": "^8.3.0"
60
+ "@fr0st/eslint-config": "^6.0.1",
61
+ "@fr0st/query": "^5.0.1",
62
+ "@fr0st/ui": "^4.0.2",
63
+ "@playwright/test": "^1.64.1",
64
+ "eslint": "^10.13.0",
65
+ "monocart-reporter": "^2.14.0",
66
+ "vite": "^8.3.4"
67
67
  },
68
68
  "peerDependencies": {
69
69
  "@fr0st/query": "^5.0.0",
@@ -1,5 +1,5 @@
1
1
  import $ from '@fr0st/query';
2
- import { BaseComponent, generateId, getDataset } from '@fr0st/ui';
2
+ import { BaseComponent, generateId } from '@fr0st/ui';
3
3
  import { getStrength } from './strength.js';
4
4
 
5
5
  /**
@@ -89,12 +89,11 @@ export default class PasswordStrength extends BaseComponent {
89
89
  striped: false,
90
90
  };
91
91
 
92
- #container;
92
+ #container = null;
93
93
  #describedBy;
94
- #form;
95
- #progress;
96
- #progressBar;
97
- #resetHandler;
94
+ #form = null;
95
+ #progress = null;
96
+ #progressBar = null;
98
97
 
99
98
  /**
100
99
  * Calculates the strength of a password.
@@ -108,44 +107,45 @@ export default class PasswordStrength extends BaseComponent {
108
107
 
109
108
  /**
110
109
  * Creates a PasswordStrength.
111
- * @param {HTMLElement} node The input node.
110
+ * @param {HTMLInputElement} node The input node.
112
111
  * @param {PasswordStrengthOptions} [options] The PasswordStrength options.
112
+ * @throws {TypeError} When the node is not an input element.
113
113
  */
114
114
  constructor(node, options) {
115
+ if (!$._isElement(node) || !$.is(node, 'input')) {
116
+ throw new TypeError('PasswordStrength must be created on an input element.');
117
+ }
118
+
115
119
  super(node, options);
116
120
 
117
- const overrides = { ...getDataset(node), ...options };
121
+ try {
122
+ this.#describedBy = $.getAttribute(this.node, 'aria-describedby');
118
123
 
119
- // Replace supplied arrays instead of merging them with default entries.
120
- for (const key of ['levels', 'commonPasswords']) {
121
- if (Array.isArray(overrides[key])) {
122
- this.options[key].length = 0;
123
- $._extend(this.options[key], overrides[key]);
124
- }
125
- }
124
+ this.#form = $.getProperty(this.node, 'form');
126
125
 
127
- this.#form = this.node.form;
126
+ if (this.options.container) {
127
+ this.#container = $.findOne(this.options.container);
128
+ } else {
129
+ this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();
130
+ }
128
131
 
129
- if (this.options.container) {
130
- this.#container = $.findOne(this.options.container);
131
- } else {
132
- this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();
132
+ this.#render();
133
+ this.#refresh();
134
+ this.#events();
135
+ } catch (error) {
136
+ this.dispose();
137
+ throw error;
133
138
  }
134
-
135
- this.#render();
136
- this.#refresh();
137
- this.#events();
138
139
  }
139
140
 
140
141
  /** @inheritdoc */
141
142
  dispose() {
142
- $.remove(this.#progress);
143
- $.removeEvent(this.node, 'input.ui.passwordstrength');
144
-
145
- if (this.#form) {
146
- $.removeEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);
143
+ if (!this.node) {
144
+ return;
147
145
  }
148
146
 
147
+ $.remove(this.#progress);
148
+
149
149
  if (this.#describedBy === null) {
150
150
  $.removeAttribute(this.node, 'aria-describedby');
151
151
  } else {
@@ -156,7 +156,6 @@ export default class PasswordStrength extends BaseComponent {
156
156
  this.#form = null;
157
157
  this.#progress = null;
158
158
  this.#progressBar = null;
159
- this.#resetHandler = null;
160
159
 
161
160
  super.dispose();
162
161
  }
@@ -182,18 +181,16 @@ export default class PasswordStrength extends BaseComponent {
182
181
  */
183
182
  #events() {
184
183
  if (this.#form) {
185
- this.#resetHandler = (event) => {
184
+ this.listen(this.#form, 'reset.ui.passwordstrength', (event) => {
186
185
  setTimeout(() => {
187
186
  if (this.node && !event.defaultPrevented) {
188
187
  this.#refresh();
189
188
  }
190
189
  }, 0);
191
- };
192
-
193
- $.addEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);
190
+ });
194
191
  }
195
192
 
196
- $.addEvent(this.node, 'input.ui.passwordstrength', (_) => {
193
+ this.listen(this.node, 'input.ui.passwordstrength', () => {
197
194
  this.#refresh();
198
195
  });
199
196
  }
@@ -227,7 +224,6 @@ export default class PasswordStrength extends BaseComponent {
227
224
  * Renders the password strength element.
228
225
  */
229
226
  #render() {
230
- this.#describedBy = $.getAttribute(this.node, 'aria-describedby');
231
227
  this.#progress = $.create('div', {
232
228
  class: this.constructor.classes.progress,
233
229
  });
@@ -236,7 +232,7 @@ export default class PasswordStrength extends BaseComponent {
236
232
 
237
233
  this.#progressBar = $.create('div', {
238
234
  attributes: {
239
- 'id': id,
235
+ id,
240
236
  'role': 'progressbar',
241
237
  'aria-valuemin': 0,
242
238
  'aria-valuemax': 100,