@fr0st/ui-passwordstrength 3.0.1 → 3.0.3

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
@@ -124,8 +124,6 @@ Calling `PasswordStrength.init()` again for the same input returns its existing
124
124
 
125
125
  The indicator renders the initial value and refreshes on `input` events. After changing the input's value in JavaScript, dispatch an `input` event to refresh the indicator; `getStrength()` only returns the current score.
126
126
 
127
- Form resets refresh the indicator after the browser restores the input's default value. This refresh is deferred until after the reset event finishes and is skipped if the reset is canceled. Inputs associated with a form through the `form` attribute are also supported.
128
-
129
127
  ## Options
130
128
 
131
129
  Options are resolved in this order:
@@ -309,10 +307,15 @@ Pass an options object to initialize every matched element, or pass a public met
309
307
  - A unique generated progress ID is appended to the input's existing `aria-describedby` value instead of replacing it.
310
308
  - Disposal restores the original `aria-describedby` state exactly, including absent and empty values.
311
309
  - Disposal also removes the generated progress markup, namespaced input and form-reset listeners, and registered component data.
312
- - The original password input remains the interactive and submitted form control.
313
310
 
314
311
  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
312
 
313
+ ## Forms
314
+
315
+ The original password input remains the interactive and submitted form control.
316
+
317
+ Form resets refresh the indicator after the browser restores the input's default value. This refresh is deferred until after the reset event finishes and is skipped if the reset is canceled. Inputs associated with a form through the `form` attribute are also supported.
318
+
316
319
  ## Themes and RTL
317
320
 
318
321
  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.
@@ -1,7 +1,7 @@
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
- //#region src/strength.js
4
+ // src/strength.js
5
5
  var characterTypes = [
6
6
  {
7
7
  pattern: /\p{Ll}/u,
@@ -189,8 +189,8 @@ var getStrength = (password, commonPasswords = []) => {
189
189
  return $._clamp(score - predictableCount, 0, predictableCount === characters.length ? 15 : 100);
190
190
  };
191
191
 
192
- //#endregion
193
- //#region src/password-strength.js
192
+ // src/password-strength.js
193
+ var window = $.getWindow();
194
194
  /**
195
195
  * @typedef {object} PasswordStrengthLevel
196
196
  * @property {string} class The CSS class applied to the progress bar.
@@ -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.
@@ -296,18 +295,10 @@ var PasswordStrength = class extends BaseComponent {
296
295
  * @throws {TypeError} When the node is not an input element.
297
296
  */
298
297
  constructor(node, options) {
299
- if (!$.is(node, "input")) throw new TypeError("PasswordStrength must be created on an input element.");
298
+ if (!$._isElement(node) || !$.is(node, "input")) throw new TypeError("PasswordStrength must be created on an input element.");
300
299
  super(node, options);
301
300
  try {
302
301
  this.#describedBy = $.getAttribute(this.node, "aria-describedby");
303
- const overrides = {
304
- ...getDataset(node),
305
- ...options
306
- };
307
- for (const key of ["levels", "commonPasswords"]) if (Array.isArray(overrides[key])) {
308
- this.options[key].length = 0;
309
- $._extend(this.options[key], overrides[key]);
310
- }
311
302
  this.#form = $.getProperty(this.node, "form");
312
303
  if (this.options.container) this.#container = $.findOne(this.options.container);
313
304
  else this.#container = $.closest(this.node, ":not(.form-input):not(.input-group)").shift();
@@ -323,15 +314,12 @@ var PasswordStrength = class extends BaseComponent {
323
314
  dispose() {
324
315
  if (!this.node) return;
325
316
  $.remove(this.#progress);
326
- $.removeEvent(this.node, "input.ui.passwordstrength");
327
- if (this.#form && this.#resetHandler) $.removeEvent(this.#form, "reset.ui.passwordstrength", this.#resetHandler);
328
317
  if (this.#describedBy === null) $.removeAttribute(this.node, "aria-describedby");
329
318
  else $.setAttribute(this.node, { "aria-describedby": this.#describedBy });
330
319
  this.#container = null;
331
320
  this.#form = null;
332
321
  this.#progress = null;
333
322
  this.#progressBar = null;
334
- this.#resetHandler = null;
335
323
  super.dispose();
336
324
  }
337
325
  /**
@@ -347,15 +335,12 @@ var PasswordStrength = class extends BaseComponent {
347
335
  * Attaches events for the PasswordStrength.
348
336
  */
349
337
  #events() {
350
- if (this.#form) {
351
- this.#resetHandler = (event) => {
352
- setTimeout(() => {
353
- if (this.node && !event.defaultPrevented) this.#refresh();
354
- }, 0);
355
- };
356
- $.addEvent(this.#form, "reset.ui.passwordstrength", this.#resetHandler);
357
- }
358
- $.addEvent(this.node, "input.ui.passwordstrength", () => {
338
+ if (this.#form) this.listen(this.#form, "reset.ui.passwordstrength", (event) => {
339
+ window.setTimeout(() => {
340
+ if (this.node && !event.defaultPrevented) this.#refresh();
341
+ }, 0);
342
+ });
343
+ this.listen(this.node, "input.ui.passwordstrength", () => {
359
344
  this.#refresh();
360
345
  });
361
346
  }
@@ -393,11 +378,9 @@ var PasswordStrength = class extends BaseComponent {
393
378
  }
394
379
  };
395
380
 
396
- //#endregion
397
- //#region src/index.js
381
+ // src/index.js
398
382
  initComponent("passwordstrength", PasswordStrength);
399
383
  var src_default = PasswordStrength;
400
384
 
401
- //#endregion
402
385
  export { src_default as default };
403
386
  //# sourceMappingURL=frost-ui-passwordstrength.esm.js.map
@@ -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 {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 (!$.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 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 = $.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 $.removeEvent(this.node, 'input.ui.passwordstrength');\n\n if (this.#form && this.#resetHandler) {\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.#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
+ {"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\nconst window = $.getWindow();\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 window.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/* @__PURE__ */ 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=/* @__PURE__ */ 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){if(!i.is(e,`input`))throw TypeError(`PasswordStrength must be created on an input element.`);super(e,t);try{this.#t=i.getAttribute(this.node,`aria-describedby`);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=i.getProperty(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()}catch(e){throw this.dispose(),e}}dispose(){this.node&&(i.remove(this.#r),i.removeEvent(this.node,`input.ui.passwordstrength`),this.#n&&this.#a&&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`,()=>{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.#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=r.getWindow(),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: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=>{b.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`,x);var S=x;export{S 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":["$","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","overrides","getDataset","key","#form","#container","#render","#refresh","#events","error","#progress","#resetHandler","#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, 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 {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 (!$.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 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 = $.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 $.removeEvent(this.node, 'input.ui.passwordstrength');\n\n if (this.#form && this.#resetHandler) {\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.#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":"yHAEA,IAAMC,EAAiB,CACnB,CAAE,QAAS,UAAW,KAAM,OAAQ,EACpC,CAAE,QAAS,UAAW,KAAM,OAAQ,EACpC,CAAE,QAAS,SAAU,KAAM,QAAS,CACxC,EAEMC,EAAoB,CACtB,aACA,aACA,YACA,SACJ,CAAC,CAAC,QACG,GAAQ,CACLC,EACA,MAAM,KAAKA,CAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,CACrC,CACJ,EAEMC,EAAiB,CACnBC,EAAK,IACL,EAAK,IACL,EAAK,IACL,EAAK,IACL,EAAK,IACL,EAAK,IACL,IAAK,GACT,EAEMC,EAAc,CAChB,CAAE,IAAK,EAAG,IAAK,GAAI,IAAK,EAAG,MAAO,CAAE,EACpC,CAAE,IAAK,EAAG,IAAK,GAAI,IAAK,GAAI,MAAO,CAAE,EACrC,CAAE,IAAK,GAAI,IAAK,GAAI,IAAK,GAAI,MAAO,CAAE,EACtC,CAAE,IAAK,GAAI,IAAK,GAAI,IAAK,GAAI,MAAO,EAAG,EACvC,CAAE,IAAK,GAAI,IAAK,IAAK,IAAK,GAAI,MAAO,EAAG,CAC5C,EAOMC,EAAoB,GACtBN,EAAe,MACV,CAAE,aAAcO,EAAQ,KAAKC,CAAS,CAC3C,CAAC,EAAE,MAAQ,SAOTC,EAAqB,GAAe,CACtC,IAAMC,EAAOL,EAAY,MACpB,CAAE,SAAUM,EAAW,QAAUC,CACtC,GAAKP,EAAY,GAAG,EAAE,EAChBQ,EAAQ,KAAK,MACfd,EAAE,KACE,KAAK,IAAIY,EAAW,OAAQD,EAAK,GAAG,EACpCA,EAAK,MACLA,EAAK,IACLA,EAAK,IACLA,EAAK,GACT,CACJ,EACMI,EAAY,IAAI,IAClBH,EAAW,IAAIL,CAAgB,CACnC,CAAC,CAAC,KAEF,OAAO,KAAK,IACRO,GACCC,EAAY,GAAK,EAClBJ,EAAK,GACT,CACJ,EAOMK,EAAsB,GAAe,CACvC,IAAMC,EAAY,KAAK,MAAML,EAAW,OAAS,CAAC,EAElD,IAAK,IAAIM,EAAS,EAAGA,GAAUD,EAAW,IACtC,GACIL,EAAW,OAASM,IAAW,GAC/BN,EAAW,OACN,EAAW,IACRH,IAAcG,EAAWO,EAAQD,EACzC,EAEA,OAAON,EAAW,MAAM,EAAGM,CAAM,EAIzC,OAAO,IACX,EAOME,EAAqB,GACvB,OAAOC,CAAQ,CAAC,CACX,UAAU,MAAM,CAAC,CACjB,YAAY,EAOfC,EAAiB,GACnB,MAAM,KACFD,EACC,GAAcjB,EAAeK,IAAcA,CAChD,CAAC,CAAC,KAAK,EAAE,EAOPc,EAAuB,GAAa,CAGtC,IAAMC,EAAW,CAACH,EADEA,EAAS,QAAQ,kBAAmBC,CAClB,CAAC,CAAC,CAAC,QAAS,GAAU,CACxD,IAAMG,EAAcC,EAAM,QACtB,oCACA,EACJ,EAEA,MAAO,CACHD,EACAA,EAAY,QAAQ,aAAc,EAAE,CACxC,CACJ,CAAC,EAED,sBAAO,IAAI,IAAI,CACXH,EAAcD,CAAQ,EACtB,GAAGG,EACH,GAAGA,EAAS,IAAIF,CAAa,CACjC,CAAC,CACL,EAQMK,GAA6B,EAAU,IAAoB,CAC7D,IAAMC,EAAS,IAAI,IACfC,EAAgB,IAAIT,CAAiB,CACzC,EAMA,OAJIQ,EAAO,IAAIP,CAAQ,EACZ,EAGJO,EAAO,eAAeL,EAAoBF,CAAQ,CAAC,EAAI,KAAO,CACzE,EAOMS,EAAc,GAAe,CAC/B,GAAM,CAACC,EAAOC,EAAQC,GAASrB,EAAW,IACrC,GAAcH,EAAU,YAAY,CACzC,EACMyB,EAAO3B,EAAiBwB,CAAK,EAC7BI,EAAYH,EAAO,YAAY,CAAC,EAAID,EAAM,YAAY,CAAC,EAE7D,MAAO,CAAC,QAAS,QAAQ,CAAC,CAAC,SAASG,CAAI,GACpC3B,EAAiByB,CAAM,IAAME,GAC7B3B,EAAiB0B,CAAK,IAAMC,GAC5B,KAAK,IAAIC,CAAS,IAAM,GACxBF,EAAM,YAAY,CAAC,EAAID,EAAO,YAAY,CAAC,IAAMG,CACzD,EAOMC,EAAsB,GAAe,CACvC,IAAMC,EAAWzB,EAAW,KAAK,EAAE,CAAC,CAAC,YAAY,EAEjD,OAAOV,EAAkB,KACpB,GAAqBoC,EAAiB,SAASD,CAAQ,CAC5D,CACJ,EAOME,EAAiB,GACnB,IAAI,IAAI3B,CAAU,CAAC,CAAC,OAAS,GAC7BkB,EAAWlB,CAAU,GACrBwB,EAAmBxB,CAAU,EAO3B4B,EAAuB,GAAe,CACxC,IAAMC,kBAAU,IAAI,IAEpB,IAAK,IAAIC,EAAI,EAAGA,EAAI,EAAI9B,EAAW,OAAQ,IACnC2B,EAAc3B,EAAW,MAAM8B,EAAGA,EAAI,CAAC,CAAC,IACxCD,EAAQ,IAAIC,CAAC,EACbD,EAAQ,IAAIC,EAAI,CAAC,EACjBD,EAAQ,IAAIC,EAAI,CAAC,GAIzB,OAAOD,EAAQ,IACnB,EAQaE,GAAe,EAAU,EAAkB,CAAC,IAAM,CAC3D,IAAMC,EAAqBvB,EAAS,UAAU,MAAM,EAC9CT,EAAa,MAAM,KAAKgC,CAAkB,EAEhD,GAAI,CAAChC,EAAW,OACZ,MAAO,GAGX,IAAMiC,EAAiBlB,EACnBiB,EAAmB,YAAY,EAC/Bf,CACJ,EAEA,GAAIgB,IAAmB,KACnB,OAAOA,EAGX,IAAM/B,EAAQJ,EAAkBE,CAAU,EACpCkC,EAAkB9B,EAAmBJ,CAAU,EAErD,GAAIkC,GAAiB,SAAW,EAC5B,MAAO,GAGX,GAAIA,EACA,OAAO,KAAK,IACRhC,EACAJ,EAAkBoC,CAAe,EACjC,EACJ,EAGJ,IAAMC,EAAmBP,EAAoB5B,CAAU,EAEvD,OAAOZ,EAAE,OACLc,EAAQiC,EACR,EACAA,IAAqBnC,EAAW,OAAS,GAAK,GAClD,CACJ,ECjPqBoC,EAArB,cAA8CC,CAAc,CACxD,OAAO,QAAU,CACb,SAAU,gBACV,YAAa,eACb,mBAAoB,sBACxB,EAEA,OAAO,SAAW,CACd,gBAAiB,CACb,SACA,WACA,YACA,aACA,SACA,SACA,SACA,QACA,SACA,WACA,UACA,SACA,WACA,YACA,cACA,SACA,YACA,SACJ,EACA,OAAQ,CACJ,CACI,MAAO,EACP,MAAO,iBACP,KAAM,WACV,EACA,CACI,MAAO,GACP,MAAO,iBACP,KAAM,MACV,EACA,CACI,MAAO,GACP,MAAO,kBACP,KAAM,QACV,EACA,CACI,MAAO,GACP,MAAO,kBACP,KAAM,QACV,EACA,CACI,MAAO,GACP,MAAO,kBACP,KAAM,aACV,CACJ,EACA,UAAW,KACX,OAAQN,EACR,QAAS,EACb,EAEA,GACA,GACA,GACA,GACA,GACA,GAQA,OAAO,YAAY,EAAU,EAAkB,KAAK,SAAS,gBAAiB,CAC1E,OAAOA,EAAYtB,EAAUQ,CAAe,CAChD,CAQA,YAAY,EAAM,EAAS,CACvB,GAAI,CAAC7B,EAAE,GAAGkD,EAAM,OAAO,EACnB,MAAU,UAAU,uDAAuD,EAG/E,MAAMA,EAAMC,CAAO,EAEnB,GAAI,CACA,KAAKC,GAAepD,EAAE,aAAa,KAAK,KAAM,kBAAkB,EAEhE,IAAMqD,EAAY,CAAE,GAAGC,EAAWJ,CAAI,EAAG,GAAGC,CAAQ,EAGpD,IAAK,IAAMI,IAAO,CAAC,SAAU,iBAAiB,EACtC,MAAM,QAAQF,EAAUE,EAAI,IAC5B,KAAK,QAAQA,EAAI,CAAC,OAAS,EAC3BvD,EAAE,QAAQ,KAAK,QAAQuD,GAAMF,EAAUE,EAAI,GAInD,KAAKC,GAAQxD,EAAE,YAAY,KAAK,KAAM,MAAM,EAE5C,AAGI,KAAKyD,GAHL,KAAK,QAAQ,UACKzD,EAAE,QAAQ,KAAK,QAAQ,SAAS,EAEhCA,EAAE,QAAQ,KAAK,KAAM,qCAAqC,CAAC,CAAC,MAAM,EAGxF,KAAK0D,GAAQ,EACb,KAAKC,GAAS,EACd,KAAKC,GAAQ,CACjB,OAASC,EAAO,CAEZ,MADA,KAAK,QAAQ,EACPA,CACV,CACJ,CAGA,SAAU,CACD,KAAK,OAIV7D,EAAE,OAAO,KAAK8D,EAAS,EACvB9D,EAAE,YAAY,KAAK,KAAM,2BAA2B,EAEhD,KAAKwD,IAAS,KAAKO,IACnB/D,EAAE,YAAY,KAAKwD,GAAO,4BAA6B,KAAKO,EAAa,EAGzE,KAAKX,KAAiB,KACtBpD,EAAE,gBAAgB,KAAK,KAAM,kBAAkB,EAE/CA,EAAE,aAAa,KAAK,KAAM,CAAE,mBAAoB,KAAKoD,EAAa,CAAC,EAGvE,KAAKK,GAAa,KAClB,KAAKD,GAAQ,KACb,KAAKM,GAAY,KACjB,KAAKE,GAAe,KACpB,KAAKD,GAAgB,KAErB,MAAM,QAAQ,EAClB,CAMA,aAAc,CACV,IAAMrC,EAAQ1B,EAAE,SAAS,KAAK,IAAI,EAC5BiE,EAAW,OAAO,KAAK,QAAQ,OACjCvC,EACA,KAAK,QAAQ,eACjB,CAAC,EAED,OAAO,OAAO,SAASuC,CAAQ,EAC3BjE,EAAE,OAAOiE,EAAU,EAAG,GAAG,EACzB,CACR,CAKA,IAAU,CACF,KAAKT,KACL,KAAKO,GAAiB,GAAU,CAC5B,eAAiB,CACT,KAAK,MAAQ,CAACG,EAAM,kBACpB,KAAKP,GAAS,CAEtB,EAAG,CAAC,CACR,EAEA3D,EAAE,SAAS,KAAKwD,GAAO,4BAA6B,KAAKO,EAAa,GAG1E/D,EAAE,SAAS,KAAK,KAAM,gCAAmC,CACrD,KAAK2D,GAAS,CAClB,CAAC,CACL,CAKA,IAAW,CACP,IAAMM,EAAW,KAAK,YAAY,EAE5BE,EAAY,KAAK,QAAQ,OAAO,SACjC,GAAUF,GAAYG,EAAM,KACjC,EAEApE,EAAE,SAAS,KAAKgE,GAAc,CAAE,MAAO,GAAGC,EAAS,EAAG,CAAC,EACvDjE,EAAE,aAAa,KAAKgE,GAAc,CAC9BK,MAAS,KAAK,YAAY,QAAQ,YAClC,gBAAiBJ,CACrB,CAAC,EAEDjE,EAAE,SAAS,KAAKgE,GAAcG,EAAU,KAAK,EAEzC,KAAK,QAAQ,SACbnE,EAAE,SAAS,KAAKgE,GAAc,KAAK,YAAY,QAAQ,kBAAkB,EAG7EhE,EAAE,QAAQ,KAAKgE,GAAcG,EAAU,MAAQ,EAAE,CACrD,CAKA,IAAU,CACN,KAAKL,GAAY9D,EAAE,OAAO,MAAO,CAC7B,MAAO,KAAK,YAAY,QAAQ,QACpC,CAAC,EAED,IAAMsE,EAAKC,EAAW,mBAAmB,EAEzC,KAAKP,GAAehE,EAAE,OAAO,MAAO,CAChC,WAAY,CACR,KACAwE,KAAQ,cACR,gBAAiB,EACjB,gBAAiB,GACrB,CACJ,CAAC,EAEDxE,EAAE,OAAO,KAAK8D,GAAW,KAAKE,EAAY,EAC1ChE,EAAE,OAAO,KAAKyD,GAAY,KAAKK,EAAS,EAExC,IAAMW,EAAc,CAAC,KAAKrB,GAAckB,CAAE,CAAC,CACtC,OAAO,OAAO,CAAC,CACf,KAAK,GAAG,EAEbtE,EAAE,aAAa,KAAK,KAAM,CAAE,mBAAoByE,CAAY,CAAC,CACjE,CACJ,EC1QAC,EAAc,mBAAoB1B,CAAgB,EAElD,IAAA2B,EAAe3B"}
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","window","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\nconst window = $.getWindow();\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 window.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,10 +1,10 @@
1
1
  (function(global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@fr0st/ui'), require('@fr0st/query')) :
3
- typeof define === 'function' && define.amd ? define(['exports', '@fr0st/ui', '@fr0st/query'], factory) :
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require("@fr0st/ui"), require("@fr0st/query")) :
3
+ typeof define === 'function' && define.amd ? define(['exports', "@fr0st/ui", "@fr0st/query"], factory) :
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.UI = global.UI || {}), global.UI,global.fQuery));
5
5
  })(this, function(exports, _fr0st_ui, _fr0st_query) {
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
7
- //#region \0rolldown/runtime.js
7
+ // \0rolldown/runtime.js
8
8
  var __create = Object.create;
9
9
  var __defProp = Object.defineProperty;
10
10
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -30,10 +30,9 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
30
30
  enumerable: true
31
31
  }) : target, mod));
32
32
 
33
- //#endregion
34
33
  _fr0st_query = __toESM(_fr0st_query, 1);
35
34
 
36
- //#region src/strength.js
35
+ // src/strength.js
37
36
  var characterTypes = [
38
37
  {
39
38
  pattern: /\p{Ll}/u,
@@ -221,9 +220,9 @@ _fr0st_query = __toESM(_fr0st_query, 1);
221
220
  return _fr0st_query.default._clamp(score - predictableCount, 0, predictableCount === characters.length ? 15 : 100);
222
221
  };
223
222
 
224
- //#endregion
225
- //#region src/password-strength.js
226
- /**
223
+ // src/password-strength.js
224
+ var window = _fr0st_query.default.getWindow();
225
+ /**
227
226
  * @typedef {object} PasswordStrengthLevel
228
227
  * @property {string} class The CSS class applied to the progress bar.
229
228
  * @property {number} score The minimum score for the level.
@@ -306,12 +305,11 @@ _fr0st_query = __toESM(_fr0st_query, 1);
306
305
  scorer: getStrength,
307
306
  striped: false
308
307
  };
309
- #container;
308
+ #container = null;
310
309
  #describedBy;
311
- #form;
312
- #progress;
313
- #progressBar;
314
- #resetHandler;
310
+ #form = null;
311
+ #progress = null;
312
+ #progressBar = null;
315
313
  /**
316
314
  * Calculates the strength of a password.
317
315
  * @param {string} password The password.
@@ -328,18 +326,10 @@ _fr0st_query = __toESM(_fr0st_query, 1);
328
326
  * @throws {TypeError} When the node is not an input element.
329
327
  */
330
328
  constructor(node, options) {
331
- if (!_fr0st_query.default.is(node, "input")) throw new TypeError("PasswordStrength must be created on an input element.");
329
+ if (!_fr0st_query.default._isElement(node) || !_fr0st_query.default.is(node, "input")) throw new TypeError("PasswordStrength must be created on an input element.");
332
330
  super(node, options);
333
331
  try {
334
332
  this.#describedBy = _fr0st_query.default.getAttribute(this.node, "aria-describedby");
335
- const overrides = {
336
- ...(0, _fr0st_ui.getDataset)(node),
337
- ...options
338
- };
339
- for (const key of ["levels", "commonPasswords"]) if (Array.isArray(overrides[key])) {
340
- this.options[key].length = 0;
341
- _fr0st_query.default._extend(this.options[key], overrides[key]);
342
- }
343
333
  this.#form = _fr0st_query.default.getProperty(this.node, "form");
344
334
  if (this.options.container) this.#container = _fr0st_query.default.findOne(this.options.container);
345
335
  else this.#container = _fr0st_query.default.closest(this.node, ":not(.form-input):not(.input-group)").shift();
@@ -355,15 +345,12 @@ _fr0st_query = __toESM(_fr0st_query, 1);
355
345
  dispose() {
356
346
  if (!this.node) return;
357
347
  _fr0st_query.default.remove(this.#progress);
358
- _fr0st_query.default.removeEvent(this.node, "input.ui.passwordstrength");
359
- if (this.#form && this.#resetHandler) _fr0st_query.default.removeEvent(this.#form, "reset.ui.passwordstrength", this.#resetHandler);
360
348
  if (this.#describedBy === null) _fr0st_query.default.removeAttribute(this.node, "aria-describedby");
361
349
  else _fr0st_query.default.setAttribute(this.node, { "aria-describedby": this.#describedBy });
362
350
  this.#container = null;
363
351
  this.#form = null;
364
352
  this.#progress = null;
365
353
  this.#progressBar = null;
366
- this.#resetHandler = null;
367
354
  super.dispose();
368
355
  }
369
356
  /**
@@ -379,15 +366,12 @@ _fr0st_query = __toESM(_fr0st_query, 1);
379
366
  * Attaches events for the PasswordStrength.
380
367
  */
381
368
  #events() {
382
- if (this.#form) {
383
- this.#resetHandler = (event) => {
384
- setTimeout(() => {
385
- if (this.node && !event.defaultPrevented) this.#refresh();
386
- }, 0);
387
- };
388
- _fr0st_query.default.addEvent(this.#form, "reset.ui.passwordstrength", this.#resetHandler);
389
- }
390
- _fr0st_query.default.addEvent(this.node, "input.ui.passwordstrength", () => {
369
+ if (this.#form) this.listen(this.#form, "reset.ui.passwordstrength", (event) => {
370
+ window.setTimeout(() => {
371
+ if (this.node && !event.defaultPrevented) this.#refresh();
372
+ }, 0);
373
+ });
374
+ this.listen(this.node, "input.ui.passwordstrength", () => {
391
375
  this.#refresh();
392
376
  });
393
377
  }
@@ -425,12 +409,10 @@ _fr0st_query = __toESM(_fr0st_query, 1);
425
409
  }
426
410
  };
427
411
 
428
- //#endregion
429
- //#region src/index.js
412
+ // src/index.js
430
413
  (0, _fr0st_ui.initComponent)("passwordstrength", PasswordStrength);
431
414
  var src_default = PasswordStrength;
432
415
 
433
- //#endregion
434
416
  exports.PasswordStrength = src_default;
435
417
  });
436
418
  //# sourceMappingURL=frost-ui-passwordstrength.js.map
@@ -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 {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 (!$.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 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 = $.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 $.removeEvent(this.node, 'input.ui.passwordstrength');\n\n if (this.#form && this.#resetHandler) {\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.#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
+ {"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\nconst window = $.getWindow();\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 window.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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAEA,IAAM,iBAAiB;EACnB;GAAE,SAAS;GAAW,MAAM;EAAQ;EACpC;GAAE,SAAS;GAAW,MAAM;EAAQ;EACpC;GAAE,SAAS;GAAU,MAAM;EAAS;CACxC;CAEA,IAAM,oBAAoB;EACtB;EACA;EACA;EACA;CACJ,CAAC,CAAC,SACG,QAAQ,CACL,KACA,MAAM,KAAK,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,CACrC,CACJ;CAEA,IAAM,iBAAiB;EACnB,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;CACT;CAEA,IAAM,cAAc;EAChB;GAAE,KAAK;GAAG,KAAK;GAAI,KAAK;GAAG,OAAO;EAAE;EACpC;GAAE,KAAK;GAAG,KAAK;GAAI,KAAK;GAAI,OAAO;EAAE;EACrC;GAAE,KAAK;GAAI,KAAK;GAAI,KAAK;GAAI,OAAO;EAAE;EACtC;GAAE,KAAK;GAAI,KAAK;GAAI,KAAK;GAAI,OAAO;EAAG;EACvC;GAAE,KAAK;GAAI,KAAK;GAAK,KAAK;GAAI,OAAO;EAAG;CAC5C;;;;;;CAOA,IAAM,oBAAoB,cACtB,eAAe,MACV,EAAE,cAAc,QAAQ,KAAK,SAAS,CAC3C,CAAC,EAAE,QAAQ;;;;;;CAOf,IAAM,qBAAqB,eAAe;EACtC,MAAM,OAAO,YAAY,MACpB,EAAE,UAAU,WAAW,UAAU,GACtC,KAAK,YAAY,GAAG,EAAE;EACtB,MAAM,QAAQ,KAAK,MACfA,qBAAE,KACE,KAAK,IAAI,WAAW,QAAQ,KAAK,GAAG,GACpC,KAAK,OACL,KAAK,KACL,KAAK,KACL,KAAK,GACT,CACJ;EACA,MAAM,YAAY,IAAI,IAClB,WAAW,IAAI,gBAAgB,CACnC,CAAC,CAAC;EAEF,OAAO,KAAK,IACR,SACC,YAAY,KAAK,GAClB,KAAK,GACT;CACJ;;;;;;CAOA,IAAM,sBAAsB,eAAe;EACvC,MAAM,YAAY,KAAK,MAAM,WAAW,SAAS,CAAC;EAElD,KAAK,IAAI,SAAS,GAAG,UAAU,WAAW,UACtC,IACI,WAAW,SAAS,WAAW,KAC/B,WAAW,OACN,WAAW,UACR,cAAc,WAAW,QAAQ,OACzC,GAEA,OAAO,WAAW,MAAM,GAAG,MAAM;EAIzC,OAAO;CACX;;;;;;CAOA,IAAM,qBAAqB,aACvB,OAAO,QAAQ,CAAC,CACX,UAAU,MAAM,CAAC,CACjB,YAAY;;;;;;CAOrB,IAAM,iBAAiB,aACnB,MAAM,KACF,WACC,cAAc,eAAe,cAAc,SAChD,CAAC,CAAC,KAAK,EAAE;;;;;;CAOb,IAAM,uBAAuB,aAAa;EAGtC,MAAM,WAAW,CAAC,UADE,SAAS,QAAQ,mBAAmB,aAClB,CAAC,CAAC,CAAC,SAAS,UAAU;GACxD,MAAM,cAAc,MAAM,QACtB,qCACA,EACJ;GAEA,OAAO,CACH,aACA,YAAY,QAAQ,cAAc,EAAE,CACxC;EACJ,CAAC;EAED,uBAAO,IAAI,IAAI;GACX,cAAc,QAAQ;GACtB,GAAG;GACH,GAAG,SAAS,IAAI,aAAa;EACjC,CAAC;CACL;;;;;;;CAQA,IAAM,6BAA6B,UAAU,oBAAoB;EAC7D,MAAM,SAAS,IAAI,IACf,gBAAgB,IAAI,iBAAiB,CACzC;EAEA,IAAI,OAAO,IAAI,QAAQ,GACnB,OAAO;EAGX,OAAO,OAAO,eAAe,oBAAoB,QAAQ,CAAC,IAAI,OAAO;CACzE;;;;;;CAOA,IAAM,cAAc,eAAe;EAC/B,MAAM,CAAC,OAAO,QAAQ,SAAS,WAAW,KACrC,cAAc,UAAU,YAAY,CACzC;EACA,MAAM,OAAO,iBAAiB,KAAK;EACnC,MAAM,YAAY,OAAO,YAAY,CAAC,IAAI,MAAM,YAAY,CAAC;EAE7D,OAAO,CAAC,SAAS,QAAQ,CAAC,CAAC,SAAS,IAAI,KACpC,iBAAiB,MAAM,MAAM,QAC7B,iBAAiB,KAAK,MAAM,QAC5B,KAAK,IAAI,SAAS,MAAM,KACxB,MAAM,YAAY,CAAC,IAAI,OAAO,YAAY,CAAC,MAAM;CACzD;;;;;;CAOA,IAAM,sBAAsB,eAAe;EACvC,MAAM,WAAW,WAAW,KAAK,EAAE,CAAC,CAAC,YAAY;EAEjD,OAAO,kBAAkB,MACpB,qBAAqB,iBAAiB,SAAS,QAAQ,CAC5D;CACJ;;;;;;CAOA,IAAM,iBAAiB,eACnB,IAAI,IAAI,UAAU,CAAC,CAAC,SAAS,KAC7B,WAAW,UAAU,KACrB,mBAAmB,UAAU;;;;;;CAOjC,IAAM,uBAAuB,eAAe;EACxC,MAAM,0BAAU,IAAI,IAAI;EAExB,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,WAAW,QAAQ,KACvC,IAAI,cAAc,WAAW,MAAM,GAAG,IAAI,CAAC,CAAC,GAAG;GAC3C,QAAQ,IAAI,CAAC;GACb,QAAQ,IAAI,IAAI,CAAC;GACjB,QAAQ,IAAI,IAAI,CAAC;EACrB;EAGJ,OAAO,QAAQ;CACnB;;;;;;;CAQA,IAAa,eAAe,UAAU,kBAAkB,CAAC,MAAM;EAC3D,MAAM,qBAAqB,SAAS,UAAU,MAAM;EACpD,MAAM,aAAa,MAAM,KAAK,kBAAkB;EAEhD,IAAI,CAAC,WAAW,QACZ,OAAO;EAGX,MAAM,iBAAiB,0BACnB,mBAAmB,YAAY,GAC/B,eACJ;EAEA,IAAI,mBAAmB,MACnB,OAAO;EAGX,MAAM,QAAQ,kBAAkB,UAAU;EAC1C,MAAM,kBAAkB,mBAAmB,UAAU;EAErD,IAAI,iBAAiB,WAAW,GAC5B,OAAO;EAGX,IAAI,iBACA,OAAO,KAAK,IACR,OACA,kBAAkB,eAAe,GACjC,EACJ;EAGJ,MAAM,mBAAmB,oBAAoB,UAAU;EAEvD,OAAOA,qBAAE,OACL,QAAQ,kBACR,GACA,qBAAqB,WAAW,SAAS,KAAK,GAClD;CACJ;;;CC5QA,IAAM,SAASC,qBAAE,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;CA6B3B,IAAqB,mBAArB,cAA8CC,wBAAc;EACxD,OAAO,UAAU;GACb,UAAU;GACV,aAAa;GACb,oBAAoB;EACxB;;EAEA,OAAO,WAAW;GACd,iBAAiB;IACb;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GACJ;GACA,QAAQ;IACJ;KACI,OAAO;KACP,OAAO;KACP,MAAM;IACV;IACA;KACI,OAAO;KACP,OAAO;KACP,MAAM;IACV;IACA;KACI,OAAO;KACP,OAAO;KACP,MAAM;IACV;IACA;KACI,OAAO;KACP,OAAO;KACP,MAAM;IACV;IACA;KACI,OAAO;KACP,OAAO;KACP,MAAM;IACV;GACJ;GACA,WAAW;GACX,QAAQ;GACR,SAAS;EACb;EAEA,aAAa;EACb;EACA,QAAQ;EACR,YAAY;EACZ,eAAe;;;;;;;EAQf,OAAO,YAAY,UAAU,kBAAkB,KAAK,SAAS,iBAAiB;GAC1E,OAAO,YAAY,UAAU,eAAe;EAChD;;;;;;;EAQA,YAAY,MAAM,SAAS;GACvB,IAAI,CAACD,qBAAE,WAAW,IAAI,KAAK,CAACA,qBAAE,GAAG,MAAM,OAAO,GAC1C,MAAM,IAAI,UAAU,uDAAuD;GAG/E,MAAM,MAAM,OAAO;GAEnB,IAAI;IACA,KAAK,eAAeA,qBAAE,aAAa,KAAK,MAAM,kBAAkB;IAEhE,KAAK,QAAQA,qBAAE,YAAY,KAAK,MAAM,MAAM;IAE5C,IAAI,KAAK,QAAQ,WACb,KAAK,aAAaA,qBAAE,QAAQ,KAAK,QAAQ,SAAS;SAElD,KAAK,aAAaA,qBAAE,QAAQ,KAAK,MAAM,qCAAqC,CAAC,CAAC,MAAM;IAGxF,KAAK,QAAQ;IACb,KAAK,SAAS;IACd,KAAK,QAAQ;GACjB,SAAS,OAAO;IACZ,KAAK,QAAQ;IACb,MAAM;GACV;EACJ;;EAGA,UAAU;GACN,IAAI,CAAC,KAAK,MACN;GAGJ,qBAAE,OAAO,KAAK,SAAS;GAEvB,IAAI,KAAK,iBAAiB,MACtB,qBAAE,gBAAgB,KAAK,MAAM,kBAAkB;QAE/C,qBAAE,aAAa,KAAK,MAAM,EAAE,oBAAoB,KAAK,aAAa,CAAC;GAGvE,KAAK,aAAa;GAClB,KAAK,QAAQ;GACb,KAAK,YAAY;GACjB,KAAK,eAAe;GAEpB,MAAM,QAAQ;EAClB;;;;;EAMA,cAAc;GACV,MAAM,QAAQA,qBAAE,SAAS,KAAK,IAAI;GAClC,MAAM,WAAW,OAAO,KAAK,QAAQ,OACjC,OACA,KAAK,QAAQ,eACjB,CAAC;GAED,OAAO,OAAO,SAAS,QAAQ,IAC3BA,qBAAE,OAAO,UAAU,GAAG,GAAG,IACzB;EACR;;;;EAKA,UAAU;GACN,IAAI,KAAK,OACL,KAAK,OAAO,KAAK,OAAO,8BAA8B,UAAU;IAC5D,OAAO,iBAAiB;KACpB,IAAI,KAAK,QAAQ,CAAC,MAAM,kBACpB,KAAK,SAAS;IAEtB,GAAG,CAAC;GACR,CAAC;GAGL,KAAK,OAAO,KAAK,MAAM,mCAAmC;IACtD,KAAK,SAAS;GAClB,CAAC;EACL;;;;EAKA,WAAW;GACP,MAAM,WAAW,KAAK,YAAY;GAElC,MAAM,YAAY,KAAK,QAAQ,OAAO,UACjC,UAAU,YAAY,MAAM,KACjC;GAEA,qBAAE,SAAS,KAAK,cAAc,EAAE,OAAO,GAAG,SAAS,GAAG,CAAC;GACvD,qBAAE,aAAa,KAAK,cAAc;IAC9B,SAAS,KAAK,YAAY,QAAQ;IAClC,iBAAiB;GACrB,CAAC;GAED,qBAAE,SAAS,KAAK,cAAc,UAAU,KAAK;GAE7C,IAAI,KAAK,QAAQ,SACb,qBAAE,SAAS,KAAK,cAAc,KAAK,YAAY,QAAQ,kBAAkB;GAG7E,qBAAE,QAAQ,KAAK,cAAc,UAAU,QAAQ,EAAE;EACrD;;;;EAKA,UAAU;GACN,KAAK,YAAYA,qBAAE,OAAO,OAAO,EAC7B,OAAO,KAAK,YAAY,QAAQ,SACpC,CAAC;GAED,MAAM,SAAKE,sBAAW,mBAAmB;GAEzC,KAAK,eAAeF,qBAAE,OAAO,OAAO,EAChC,YAAY;IACR;IACA,QAAQ;IACR,iBAAiB;IACjB,iBAAiB;GACrB,EACJ,CAAC;GAED,qBAAE,OAAO,KAAK,WAAW,KAAK,YAAY;GAC1C,qBAAE,OAAO,KAAK,YAAY,KAAK,SAAS;GAExC,MAAM,cAAc,CAAC,KAAK,cAAc,EAAE,CAAC,CACtC,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;GAEb,qBAAE,aAAa,KAAK,MAAM,EAAE,oBAAoB,YAAY,CAAC;EACjE;CACJ;;;CCzPA,6BAAc,oBAAoB,gBAAgB;CAElD,kBAAe"}
@@ -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/* @__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;#t;#n;#r;#i;#a;static getStrength(e,t=this.defaults.commonPasswords){return T(e,t)}constructor(e,r){if(!n.default.is(e,`input`))throw TypeError(`PasswordStrength must be created on an input element.`);super(e,r);try{this.#t=n.default.getAttribute(this.node,`aria-describedby`);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=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.#c(),this.#s(),this.#o()}catch(e){throw this.dispose(),e}}dispose(){this.node&&(n.default.remove(this.#r),n.default.removeEvent(this.node,`input.ui.passwordstrength`),this.#n&&this.#a&&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`,()=>{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.#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(i(n,`default`,{value:e,enumerable:!0}),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=n.default.getWindow(),D=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=>{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`,D),e.PasswordStrength=D});
2
2
  //# sourceMappingURL=frost-ui-passwordstrength.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"frost-ui-passwordstrength.min.js","names":["$","BaseComponent","$","getDataset","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","overrides","#form","#container","#render","#refresh","#events","error","#progress","#resetHandler","#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, 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 {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 (!$.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 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 = $.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 $.removeEvent(this.node, 'input.ui.passwordstrength');\n\n if (this.#form && this.#resetHandler) {\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.#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
+ {"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","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","window","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\nconst window = $.getWindow();\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 window.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.1",
3
+ "version": "3.0.3",
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": [
@@ -58,12 +58,17 @@
58
58
  ],
59
59
  "devDependencies": {
60
60
  "@fr0st/eslint-config": "^6.0.1",
61
- "@fr0st/query": "^5.0.1",
62
- "@fr0st/ui": "^4.0.1",
63
- "@playwright/test": "^1.63.0",
64
- "eslint": "^10.11.0",
65
- "monocart-reporter": "^2.14.0",
66
- "vite": "^8.3.1"
61
+ "@fr0st/query": "^5.0.2",
62
+ "@fr0st/ui": "^4.0.3",
63
+ "@playwright/test": "^1.64.1",
64
+ "eslint": "^10.13.0",
65
+ "monocart-reporter": "2.14.0",
66
+ "vite": "^8.3.5"
67
+ },
68
+ "overrides": {
69
+ "monocart-reporter": {
70
+ "monocart-coverage-reports": "2.13.0"
71
+ }
67
72
  },
68
73
  "peerDependencies": {
69
74
  "@fr0st/query": "^5.0.0",
@@ -1,7 +1,9 @@
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
+ const window = $.getWindow();
6
+
5
7
  /**
6
8
  * @typedef {object} PasswordStrengthLevel
7
9
  * @property {string} class The CSS class applied to the progress bar.
@@ -89,12 +91,11 @@ export default class PasswordStrength extends BaseComponent {
89
91
  striped: false,
90
92
  };
91
93
 
92
- #container;
94
+ #container = null;
93
95
  #describedBy;
94
- #form;
95
- #progress;
96
- #progressBar;
97
- #resetHandler;
96
+ #form = null;
97
+ #progress = null;
98
+ #progressBar = null;
98
99
 
99
100
  /**
100
101
  * Calculates the strength of a password.
@@ -113,7 +114,7 @@ export default class PasswordStrength extends BaseComponent {
113
114
  * @throws {TypeError} When the node is not an input element.
114
115
  */
115
116
  constructor(node, options) {
116
- if (!$.is(node, 'input')) {
117
+ if (!$._isElement(node) || !$.is(node, 'input')) {
117
118
  throw new TypeError('PasswordStrength must be created on an input element.');
118
119
  }
119
120
 
@@ -122,16 +123,6 @@ export default class PasswordStrength extends BaseComponent {
122
123
  try {
123
124
  this.#describedBy = $.getAttribute(this.node, 'aria-describedby');
124
125
 
125
- const overrides = { ...getDataset(node), ...options };
126
-
127
- // Replace supplied arrays instead of merging them with default entries.
128
- for (const key of ['levels', 'commonPasswords']) {
129
- if (Array.isArray(overrides[key])) {
130
- this.options[key].length = 0;
131
- $._extend(this.options[key], overrides[key]);
132
- }
133
- }
134
-
135
126
  this.#form = $.getProperty(this.node, 'form');
136
127
 
137
128
  if (this.options.container) {
@@ -156,11 +147,6 @@ export default class PasswordStrength extends BaseComponent {
156
147
  }
157
148
 
158
149
  $.remove(this.#progress);
159
- $.removeEvent(this.node, 'input.ui.passwordstrength');
160
-
161
- if (this.#form && this.#resetHandler) {
162
- $.removeEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);
163
- }
164
150
 
165
151
  if (this.#describedBy === null) {
166
152
  $.removeAttribute(this.node, 'aria-describedby');
@@ -172,7 +158,6 @@ export default class PasswordStrength extends BaseComponent {
172
158
  this.#form = null;
173
159
  this.#progress = null;
174
160
  this.#progressBar = null;
175
- this.#resetHandler = null;
176
161
 
177
162
  super.dispose();
178
163
  }
@@ -198,18 +183,16 @@ export default class PasswordStrength extends BaseComponent {
198
183
  */
199
184
  #events() {
200
185
  if (this.#form) {
201
- this.#resetHandler = (event) => {
202
- setTimeout(() => {
186
+ this.listen(this.#form, 'reset.ui.passwordstrength', (event) => {
187
+ window.setTimeout(() => {
203
188
  if (this.node && !event.defaultPrevented) {
204
189
  this.#refresh();
205
190
  }
206
191
  }, 0);
207
- };
208
-
209
- $.addEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);
192
+ });
210
193
  }
211
194
 
212
- $.addEvent(this.node, 'input.ui.passwordstrength', () => {
195
+ this.listen(this.node, 'input.ui.passwordstrength', () => {
213
196
  this.#refresh();
214
197
  });
215
198
  }