@fr0st/ui-passwordstrength 3.0.1 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { BaseComponent, generateId, getDataset, initComponent } from "@fr0st/ui";
1
+ import { BaseComponent, generateId, initComponent } from "@fr0st/ui";
2
2
  import $ from "@fr0st/query";
3
3
 
4
4
  //#region src/strength.js
@@ -274,12 +274,11 @@ var PasswordStrength = class extends BaseComponent {
274
274
  scorer: getStrength,
275
275
  striped: false
276
276
  };
277
- #container;
277
+ #container = null;
278
278
  #describedBy;
279
- #form;
280
- #progress;
281
- #progressBar;
282
- #resetHandler;
279
+ #form = null;
280
+ #progress = null;
281
+ #progressBar = null;
283
282
  /**
284
283
  * Calculates the strength of a password.
285
284
  * @param {string} password The password.
@@ -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
+ 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
  }
@@ -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\n/**\n * @typedef {object} PasswordStrengthLevel\n * @property {string} class The CSS class applied to the progress bar.\n * @property {number} score The minimum score for the level.\n * @property {string} [text] The label displayed in the progress bar.\n */\n\n/**\n * @callback PasswordStrengthScorer\n * @param {string} password The password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number} The password strength, from 0 to 100.\n */\n\n/**\n * @typedef {object} PasswordStrengthOptions\n * @property {string[]} [commonPasswords] Passwords to score as very weak.\n * @property {string|null} [container=null] The selector for the progress container.\n * @property {PasswordStrengthLevel[]} [levels] The ordered password-strength levels.\n * @property {PasswordStrengthScorer} [scorer] Calculates the password strength.\n * @property {boolean} [striped=false] Whether to display a striped progress bar.\n */\n\n/**\n * Displays the strength of a password input as a progress bar.\n * @augments {BaseComponent<PasswordStrengthOptions>}\n */\nexport default class PasswordStrength extends BaseComponent {\n static classes = {\n progress: 'progress mt-2',\n progressBar: 'progress-bar',\n progressBarStriped: 'progress-bar-striped',\n };\n /** @type {PasswordStrengthOptions} */\n static defaults = {\n commonPasswords: [\n '123456',\n '12345678',\n '123456789',\n '1234567890',\n '000000',\n '111111',\n 'abc123',\n 'admin',\n 'dragon',\n 'iloveyou',\n 'letmein',\n 'monkey',\n 'password',\n 'password1',\n 'password123',\n 'qwerty',\n 'qwerty123',\n 'welcome',\n ],\n levels: [\n {\n score: 0,\n class: 'text-bg-danger',\n text: 'Very Weak',\n },\n {\n score: 20,\n class: 'text-bg-danger',\n text: 'Weak',\n },\n {\n score: 40,\n class: 'text-bg-warning',\n text: 'Normal',\n },\n {\n score: 60,\n class: 'text-bg-success',\n text: 'Strong',\n },\n {\n score: 80,\n class: 'text-bg-success',\n text: 'Very Strong',\n },\n ],\n container: null,\n scorer: getStrength,\n striped: false,\n };\n\n #container = null;\n #describedBy;\n #form = null;\n #progress = null;\n #progressBar = null;\n\n /**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=this.defaults.commonPasswords] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\n static getStrength(password, commonPasswords = this.defaults.commonPasswords) {\n return getStrength(password, commonPasswords);\n }\n\n /**\n * Creates a PasswordStrength.\n * @param {HTMLInputElement} node The input node.\n * @param {PasswordStrengthOptions} [options] The PasswordStrength options.\n * @throws {TypeError} When the node is not an input element.\n */\n constructor(node, options) {\n if (!$._isElement(node) || !$.is(node, 'input')) {\n throw new TypeError('PasswordStrength must be created on an input element.');\n }\n\n super(node, options);\n\n try {\n this.#describedBy = $.getAttribute(this.node, 'aria-describedby');\n\n this.#form = $.getProperty(this.node, 'form');\n\n if (this.options.container) {\n this.#container = $.findOne(this.options.container);\n } else {\n this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();\n }\n\n this.#render();\n this.#refresh();\n this.#events();\n } catch (error) {\n this.dispose();\n throw error;\n }\n }\n\n /** @inheritdoc */\n dispose() {\n if (!this.node) {\n return;\n }\n\n $.remove(this.#progress);\n\n if (this.#describedBy === null) {\n $.removeAttribute(this.node, 'aria-describedby');\n } else {\n $.setAttribute(this.node, { 'aria-describedby': this.#describedBy });\n }\n\n this.#container = null;\n this.#form = null;\n this.#progress = null;\n this.#progressBar = null;\n\n super.dispose();\n }\n\n /**\n * Gets the password strength.\n * @returns {number} The password strength, from 0 to 100.\n */\n getStrength() {\n const value = $.getValue(this.node);\n const strength = Number(this.options.scorer(\n value,\n this.options.commonPasswords,\n ));\n\n return Number.isFinite(strength) ?\n $._clamp(strength, 0, 100) :\n 0;\n }\n\n /**\n * Attaches events for the PasswordStrength.\n */\n #events() {\n if (this.#form) {\n this.listen(this.#form, 'reset.ui.passwordstrength', (event) => {\n setTimeout(() => {\n if (this.node && !event.defaultPrevented) {\n this.#refresh();\n }\n }, 0);\n });\n }\n\n this.listen(this.node, 'input.ui.passwordstrength', () => {\n this.#refresh();\n });\n }\n\n /**\n * Refreshes the password strength.\n */\n #refresh() {\n const strength = this.getStrength();\n\n const nextLevel = this.options.levels.findLast(\n (level) => strength >= level.score,\n );\n\n $.setStyle(this.#progressBar, { width: `${strength}%` });\n $.setAttribute(this.#progressBar, {\n 'class': this.constructor.classes.progressBar,\n 'aria-valuenow': strength,\n });\n\n $.addClass(this.#progressBar, nextLevel.class);\n\n if (this.options.striped) {\n $.addClass(this.#progressBar, this.constructor.classes.progressBarStriped);\n }\n\n $.setText(this.#progressBar, nextLevel.text ?? '');\n }\n\n /**\n * Renders the password strength element.\n */\n #render() {\n this.#progress = $.create('div', {\n class: this.constructor.classes.progress,\n });\n\n const id = generateId('password-strength');\n\n this.#progressBar = $.create('div', {\n attributes: {\n id,\n 'role': 'progressbar',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n },\n });\n\n $.append(this.#progress, this.#progressBar);\n $.append(this.#container, this.#progress);\n\n const describedBy = [this.#describedBy, id]\n .filter(Boolean)\n .join(' ');\n\n $.setAttribute(this.node, { 'aria-describedby': describedBy });\n }\n}\n","import { initComponent } from '@fr0st/ui';\nimport PasswordStrength from './password-strength.js';\n\ninitComponent('passwordstrength', PasswordStrength);\n\nexport default PasswordStrength;\n"],"mappings":";;;;AAEA,IAAM,iBAAiB;CACnB;EAAE,SAAS;EAAW,MAAM;CAAQ;CACpC;EAAE,SAAS;EAAW,MAAM;CAAQ;CACpC;EAAE,SAAS;EAAU,MAAM;CAAS;AACxC;AAEA,IAAM,oBAAoB;CACtB;CACA;CACA;CACA;AACJ,CAAC,CAAC,SACG,QAAQ,CACL,KACA,MAAM,KAAK,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,CACrC,CACJ;AAEA,IAAM,iBAAiB;CACnB,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;AACT;AAEA,IAAM,cAAc;CAChB;EAAE,KAAK;EAAG,KAAK;EAAI,KAAK;EAAG,OAAO;CAAE;CACpC;EAAE,KAAK;EAAG,KAAK;EAAI,KAAK;EAAI,OAAO;CAAE;CACrC;EAAE,KAAK;EAAI,KAAK;EAAI,KAAK;EAAI,OAAO;CAAE;CACtC;EAAE,KAAK;EAAI,KAAK;EAAI,KAAK;EAAI,OAAO;CAAG;CACvC;EAAE,KAAK;EAAI,KAAK;EAAK,KAAK;EAAI,OAAO;CAAG;AAC5C;;;;;;AAOA,IAAM,oBAAoB,cACtB,eAAe,MACV,EAAE,cAAc,QAAQ,KAAK,SAAS,CAC3C,CAAC,EAAE,QAAQ;;;;;;AAOf,IAAM,qBAAqB,eAAe;CACtC,MAAM,OAAO,YAAY,MACpB,EAAE,UAAU,WAAW,UAAU,GACtC,KAAK,YAAY,GAAG,EAAE;CACtB,MAAM,QAAQ,KAAK,MACf,EAAE,KACE,KAAK,IAAI,WAAW,QAAQ,KAAK,GAAG,GACpC,KAAK,OACL,KAAK,KACL,KAAK,KACL,KAAK,GACT,CACJ;CACA,MAAM,YAAY,IAAI,IAClB,WAAW,IAAI,gBAAgB,CACnC,CAAC,CAAC;CAEF,OAAO,KAAK,IACR,SACC,YAAY,KAAK,GAClB,KAAK,GACT;AACJ;;;;;;AAOA,IAAM,sBAAsB,eAAe;CACvC,MAAM,YAAY,KAAK,MAAM,WAAW,SAAS,CAAC;CAElD,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;CAIzC,OAAO;AACX;;;;;;AAOA,IAAM,qBAAqB,aACvB,OAAO,QAAQ,CAAC,CACX,UAAU,MAAM,CAAC,CACjB,YAAY;;;;;;AAOrB,IAAM,iBAAiB,aACnB,MAAM,KACF,WACC,cAAc,eAAe,cAAc,SAChD,CAAC,CAAC,KAAK,EAAE;;;;;;AAOb,IAAM,uBAAuB,aAAa;CAGtC,MAAM,WAAW,CAAC,UADE,SAAS,QAAQ,mBAAmB,aAClB,CAAC,CAAC,CAAC,SAAS,UAAU;EACxD,MAAM,cAAc,MAAM,QACtB,qCACA,EACJ;EAEA,OAAO,CACH,aACA,YAAY,QAAQ,cAAc,EAAE,CACxC;CACJ,CAAC;CAED,uBAAO,IAAI,IAAI;EACX,cAAc,QAAQ;EACtB,GAAG;EACH,GAAG,SAAS,IAAI,aAAa;CACjC,CAAC;AACL;;;;;;;AAQA,IAAM,6BAA6B,UAAU,oBAAoB;CAC7D,MAAM,SAAS,IAAI,IACf,gBAAgB,IAAI,iBAAiB,CACzC;CAEA,IAAI,OAAO,IAAI,QAAQ,GACnB,OAAO;CAGX,OAAO,OAAO,eAAe,oBAAoB,QAAQ,CAAC,IAAI,OAAO;AACzE;;;;;;AAOA,IAAM,cAAc,eAAe;CAC/B,MAAM,CAAC,OAAO,QAAQ,SAAS,WAAW,KACrC,cAAc,UAAU,YAAY,CACzC;CACA,MAAM,OAAO,iBAAiB,KAAK;CACnC,MAAM,YAAY,OAAO,YAAY,CAAC,IAAI,MAAM,YAAY,CAAC;CAE7D,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;AACzD;;;;;;AAOA,IAAM,sBAAsB,eAAe;CACvC,MAAM,WAAW,WAAW,KAAK,EAAE,CAAC,CAAC,YAAY;CAEjD,OAAO,kBAAkB,MACpB,qBAAqB,iBAAiB,SAAS,QAAQ,CAC5D;AACJ;;;;;;AAOA,IAAM,iBAAiB,eACnB,IAAI,IAAI,UAAU,CAAC,CAAC,SAAS,KAC7B,WAAW,UAAU,KACrB,mBAAmB,UAAU;;;;;;AAOjC,IAAM,uBAAuB,eAAe;CACxC,MAAM,0BAAU,IAAI,IAAI;CAExB,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,WAAW,QAAQ,KACvC,IAAI,cAAc,WAAW,MAAM,GAAG,IAAI,CAAC,CAAC,GAAG;EAC3C,QAAQ,IAAI,CAAC;EACb,QAAQ,IAAI,IAAI,CAAC;EACjB,QAAQ,IAAI,IAAI,CAAC;CACrB;CAGJ,OAAO,QAAQ;AACnB;;;;;;;AAQA,IAAa,eAAe,UAAU,kBAAkB,CAAC,MAAM;CAC3D,MAAM,qBAAqB,SAAS,UAAU,MAAM;CACpD,MAAM,aAAa,MAAM,KAAK,kBAAkB;CAEhD,IAAI,CAAC,WAAW,QACZ,OAAO;CAGX,MAAM,iBAAiB,0BACnB,mBAAmB,YAAY,GAC/B,eACJ;CAEA,IAAI,mBAAmB,MACnB,OAAO;CAGX,MAAM,QAAQ,kBAAkB,UAAU;CAC1C,MAAM,kBAAkB,mBAAmB,UAAU;CAErD,IAAI,iBAAiB,WAAW,GAC5B,OAAO;CAGX,IAAI,iBACA,OAAO,KAAK,IACR,OACA,kBAAkB,eAAe,GACjC,EACJ;CAGJ,MAAM,mBAAmB,oBAAoB,UAAU;CAEvD,OAAO,EAAE,OACL,QAAQ,kBACR,GACA,qBAAqB,WAAW,SAAS,KAAK,GAClD;AACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACjPA,IAAqB,mBAArB,cAA8C,cAAc;CACxD,OAAO,UAAU;EACb,UAAU;EACV,aAAa;EACb,oBAAoB;CACxB;;CAEA,OAAO,WAAW;EACd,iBAAiB;GACb;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACJ;EACA,QAAQ;GACJ;IACI,OAAO;IACP,OAAO;IACP,MAAM;GACV;GACA;IACI,OAAO;IACP,OAAO;IACP,MAAM;GACV;GACA;IACI,OAAO;IACP,OAAO;IACP,MAAM;GACV;GACA;IACI,OAAO;IACP,OAAO;IACP,MAAM;GACV;GACA;IACI,OAAO;IACP,OAAO;IACP,MAAM;GACV;EACJ;EACA,WAAW;EACX,QAAQ;EACR,SAAS;CACb;CAEA,aAAa;CACb;CACA,QAAQ;CACR,YAAY;CACZ,eAAe;;;;;;;CAQf,OAAO,YAAY,UAAU,kBAAkB,KAAK,SAAS,iBAAiB;EAC1E,OAAO,YAAY,UAAU,eAAe;CAChD;;;;;;;CAQA,YAAY,MAAM,SAAS;EACvB,IAAI,CAAC,EAAE,WAAW,IAAI,KAAK,CAAC,EAAE,GAAG,MAAM,OAAO,GAC1C,MAAM,IAAI,UAAU,uDAAuD;EAG/E,MAAM,MAAM,OAAO;EAEnB,IAAI;GACA,KAAK,eAAe,EAAE,aAAa,KAAK,MAAM,kBAAkB;GAEhE,KAAK,QAAQ,EAAE,YAAY,KAAK,MAAM,MAAM;GAE5C,IAAI,KAAK,QAAQ,WACb,KAAK,aAAa,EAAE,QAAQ,KAAK,QAAQ,SAAS;QAElD,KAAK,aAAa,EAAE,QAAQ,KAAK,MAAM,qCAAqC,CAAC,CAAC,MAAM;GAGxF,KAAK,QAAQ;GACb,KAAK,SAAS;GACd,KAAK,QAAQ;EACjB,SAAS,OAAO;GACZ,KAAK,QAAQ;GACb,MAAM;EACV;CACJ;;CAGA,UAAU;EACN,IAAI,CAAC,KAAK,MACN;EAGJ,EAAE,OAAO,KAAK,SAAS;EAEvB,IAAI,KAAK,iBAAiB,MACtB,EAAE,gBAAgB,KAAK,MAAM,kBAAkB;OAE/C,EAAE,aAAa,KAAK,MAAM,EAAE,oBAAoB,KAAK,aAAa,CAAC;EAGvE,KAAK,aAAa;EAClB,KAAK,QAAQ;EACb,KAAK,YAAY;EACjB,KAAK,eAAe;EAEpB,MAAM,QAAQ;CAClB;;;;;CAMA,cAAc;EACV,MAAM,QAAQ,EAAE,SAAS,KAAK,IAAI;EAClC,MAAM,WAAW,OAAO,KAAK,QAAQ,OACjC,OACA,KAAK,QAAQ,eACjB,CAAC;EAED,OAAO,OAAO,SAAS,QAAQ,IAC3B,EAAE,OAAO,UAAU,GAAG,GAAG,IACzB;CACR;;;;CAKA,UAAU;EACN,IAAI,KAAK,OACL,KAAK,OAAO,KAAK,OAAO,8BAA8B,UAAU;GAC5D,iBAAiB;IACb,IAAI,KAAK,QAAQ,CAAC,MAAM,kBACpB,KAAK,SAAS;GAEtB,GAAG,CAAC;EACR,CAAC;EAGL,KAAK,OAAO,KAAK,MAAM,mCAAmC;GACtD,KAAK,SAAS;EAClB,CAAC;CACL;;;;CAKA,WAAW;EACP,MAAM,WAAW,KAAK,YAAY;EAElC,MAAM,YAAY,KAAK,QAAQ,OAAO,UACjC,UAAU,YAAY,MAAM,KACjC;EAEA,EAAE,SAAS,KAAK,cAAc,EAAE,OAAO,GAAG,SAAS,GAAG,CAAC;EACvD,EAAE,aAAa,KAAK,cAAc;GAC9B,SAAS,KAAK,YAAY,QAAQ;GAClC,iBAAiB;EACrB,CAAC;EAED,EAAE,SAAS,KAAK,cAAc,UAAU,KAAK;EAE7C,IAAI,KAAK,QAAQ,SACb,EAAE,SAAS,KAAK,cAAc,KAAK,YAAY,QAAQ,kBAAkB;EAG7E,EAAE,QAAQ,KAAK,cAAc,UAAU,QAAQ,EAAE;CACrD;;;;CAKA,UAAU;EACN,KAAK,YAAY,EAAE,OAAO,OAAO,EAC7B,OAAO,KAAK,YAAY,QAAQ,SACpC,CAAC;EAED,MAAM,KAAK,WAAW,mBAAmB;EAEzC,KAAK,eAAe,EAAE,OAAO,OAAO,EAChC,YAAY;GACR;GACA,QAAQ;GACR,iBAAiB;GACjB,iBAAiB;EACrB,EACJ,CAAC;EAED,EAAE,OAAO,KAAK,WAAW,KAAK,YAAY;EAC1C,EAAE,OAAO,KAAK,YAAY,KAAK,SAAS;EAExC,MAAM,cAAc,CAAC,KAAK,cAAc,EAAE,CAAC,CACtC,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;EAEb,EAAE,aAAa,KAAK,MAAM,EAAE,oBAAoB,YAAY,CAAC;CACjE;AACJ;;;;ACvPA,cAAc,oBAAoB,gBAAgB;AAElD,kBAAe"}
@@ -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=class extends e{static classes={progress:`progress mt-2`,progressBar:`progress-bar`,progressBarStriped:`progress-bar-striped`};static defaults={commonPasswords:[`123456`,`12345678`,`123456789`,`1234567890`,`000000`,`111111`,`abc123`,`admin`,`dragon`,`iloveyou`,`letmein`,`monkey`,`password`,`password1`,`password123`,`qwerty`,`qwerty123`,`welcome`],levels:[{score:0,class:`text-bg-danger`,text:`Very Weak`},{score:20,class:`text-bg-danger`,text:`Weak`},{score:40,class:`text-bg-warning`,text:`Normal`},{score:60,class:`text-bg-success`,text:`Strong`},{score:80,class:`text-bg-success`,text:`Very Strong`}],container:null,scorer:y,striped:!1};#e=null;#t;#n=null;#r=null;#i=null;static getStrength(e,t=this.defaults.commonPasswords){return y(e,t)}constructor(e,t){if(!r._isElement(e)||!r.is(e,`input`))throw TypeError(`PasswordStrength must be created on an input element.`);super(e,t);try{this.#t=r.getAttribute(this.node,`aria-describedby`),this.#n=r.getProperty(this.node,`form`),this.#e=this.options.container?r.findOne(this.options.container):r.closest(this.node,`:not(.form-input):not(.input-group)`).shift(),this.#s(),this.#o(),this.#a()}catch(e){throw this.dispose(),e}}dispose(){this.node&&(r.remove(this.#r),this.#t===null?r.removeAttribute(this.node,`aria-describedby`):r.setAttribute(this.node,{"aria-describedby":this.#t}),this.#e=null,this.#n=null,this.#r=null,this.#i=null,super.dispose())}getStrength(){let e=r.getValue(this.node),t=Number(this.options.scorer(e,this.options.commonPasswords));return Number.isFinite(t)?r._clamp(t,0,100):0}#a(){this.#n&&this.listen(this.#n,`reset.ui.passwordstrength`,e=>{setTimeout(()=>{this.node&&!e.defaultPrevented&&this.#o()},0)}),this.listen(this.node,`input.ui.passwordstrength`,()=>{this.#o()})}#o(){let e=this.getStrength(),t=this.options.levels.findLast(t=>e>=t.score);r.setStyle(this.#i,{width:`${e}%`}),r.setAttribute(this.#i,{class:this.constructor.classes.progressBar,"aria-valuenow":e}),r.addClass(this.#i,t.class),this.options.striped&&r.addClass(this.#i,this.constructor.classes.progressBarStriped),r.setText(this.#i,t.text??``)}#s(){this.#r=r.create(`div`,{class:this.constructor.classes.progress});let e=t(`password-strength`);this.#i=r.create(`div`,{attributes:{id:e,role:`progressbar`,"aria-valuemin":0,"aria-valuemax":100}}),r.append(this.#r,this.#i),r.append(this.#e,this.#r);let n=[this.#t,e].filter(Boolean).join(` `);r.setAttribute(this.node,{"aria-describedby":n})}};n(`passwordstrength`,b);var x=b;export{x as default};
2
2
  //# sourceMappingURL=frost-ui-passwordstrength.esm.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"frost-ui-passwordstrength.esm.min.js","names":["$","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":"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"}
1
+ {"version":3,"file":"frost-ui-passwordstrength.esm.min.js","names":["$","characterTypes","keyboardSequences","row","leetCharacters","\"$\"","lengthBands","getCharacterType","pattern","character","getLengthStrength","band","characters","end","score","diversity","getRepeatedPattern","maxLength","length","index","normalizePassword","password","normalizeLeet","getPasswordVariants","variants","undecorated","value","getCommonPasswordStrength","common","commonPasswords","isSequence","first","second","third","type","direction","isKeyboardSequence","sequence","keyboardSequence","isPredictable","getPredictableCount","indices","i","getStrength","normalizedPassword","commonStrength","repeatedPattern","predictableCount","PasswordStrength","BaseComponent","node","options","#describedBy","#form","#container","#render","#refresh","#events","error","#progress","#progressBar","strength","event","nextLevel","level","\"class\"","id","generateId","\"role\"","describedBy","initComponent","src_default"],"sources":["../src/strength.js","../src/password-strength.js","../src/index.js"],"sourcesContent":["import $ from '@fr0st/query';\n\nconst characterTypes = [\n { pattern: /\\p{Ll}/u, type: 'lower' },\n { pattern: /\\p{Lu}/u, type: 'upper' },\n { pattern: /\\p{N}/u, type: 'number' },\n];\n\nconst keyboardSequences = [\n '1234567890',\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n].flatMap(\n (row) => [\n row,\n Array.from(row).reverse().join(''),\n ],\n);\n\nconst leetCharacters = {\n '$': 's',\n '0': 'o',\n '3': 'e',\n '4': 'a',\n '5': 's',\n '7': 't',\n '@': 'a',\n};\n\nconst lengthBands = [\n { end: 5, max: 19, min: 1, start: 1 },\n { end: 8, max: 39, min: 20, start: 6 },\n { end: 11, max: 59, min: 40, start: 9 },\n { end: 15, max: 79, min: 60, start: 12 },\n { end: 24, max: 100, min: 80, start: 16 },\n];\n\n/**\n * Gets the type of a password character.\n * @param {string} character The character.\n * @returns {string} The character type.\n */\nconst getCharacterType = (character) =>\n characterTypes.find(\n ({ pattern }) => pattern.test(character),\n )?.type || 'symbol';\n\n/**\n * Gets a length-based password strength.\n * @param {string[]} characters The password characters.\n * @returns {number} The password strength.\n */\nconst getLengthStrength = (characters) => {\n const band = lengthBands.find(\n ({ end }) => characters.length <= end,\n ) || lengthBands.at(-1);\n const score = Math.round(\n $._map(\n Math.min(characters.length, band.end),\n band.start,\n band.end,\n band.min,\n band.max,\n ),\n );\n const diversity = new Set(\n characters.map(getCharacterType),\n ).size;\n\n return Math.min(\n score +\n (diversity - 1) * 2,\n band.max,\n );\n};\n\n/**\n * Gets the smallest repeated pattern.\n * @param {string[]} characters The password characters.\n * @returns {string[]|null} The repeated pattern.\n */\nconst getRepeatedPattern = (characters) => {\n const maxLength = Math.floor(characters.length / 2);\n\n for (let length = 1; length <= maxLength; length++) {\n if (\n characters.length % length === 0 &&\n characters.every(\n (character, index) =>\n character === characters[index % length],\n )\n ) {\n return characters.slice(0, length);\n }\n }\n\n return null;\n};\n\n/**\n * Normalizes common password matching.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizePassword = (password) =>\n String(password)\n .normalize('NFKC')\n .toLowerCase();\n\n/**\n * Normalizes common leet substitutions.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizeLeet = (password) =>\n Array.from(\n password,\n (character) => leetCharacters[character] || character,\n ).join('');\n\n/**\n * Gets normalized variants used to match common passwords.\n * @param {string} password The normalized password.\n * @returns {Set<string>} The password variants.\n */\nconst getPasswordVariants = (password) => {\n // Preserve digits for decoration stripping while resolving symbol substitutions.\n const substituted = password.replace(/[^\\p{L}\\p{N}]/gu, normalizeLeet);\n const variants = [password, substituted].flatMap((value) => {\n const undecorated = value.replace(\n /^[^\\p{L}\\p{N}]+|[^\\p{L}\\p{N}]+$/gu,\n '',\n );\n\n return [\n undecorated,\n undecorated.replace(/^\\d+|\\d+$/g, ''),\n ];\n });\n\n return new Set([\n normalizeLeet(password),\n ...variants,\n ...variants.map(normalizeLeet),\n ]);\n};\n\n/**\n * Gets the score for a common password.\n * @param {string} password The normalized password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number|null} The common-password score.\n */\nconst getCommonPasswordStrength = (password, commonPasswords) => {\n const common = new Set(\n commonPasswords.map(normalizePassword),\n );\n\n if (common.has(password)) {\n return 0;\n }\n\n return common.isDisjointFrom(getPasswordVariants(password)) ? null : 5;\n};\n\n/**\n * Checks whether characters form an ascending or descending sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a sequence.\n */\nconst isSequence = (characters) => {\n const [first, second, third] = characters.map(\n (character) => character.toLowerCase(),\n );\n const type = getCharacterType(first);\n const direction = second.codePointAt(0) - first.codePointAt(0);\n\n return ['lower', 'number'].includes(type) &&\n getCharacterType(second) === type &&\n getCharacterType(third) === type &&\n Math.abs(direction) === 1 &&\n third.codePointAt(0) - second.codePointAt(0) === direction;\n};\n\n/**\n * Checks whether characters form a keyboard sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a keyboard sequence.\n */\nconst isKeyboardSequence = (characters) => {\n const sequence = characters.join('').toLowerCase();\n\n return keyboardSequences.some(\n (keyboardSequence) => keyboardSequence.includes(sequence),\n );\n};\n\n/**\n * Checks whether characters form a predictable pattern.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a predictable pattern.\n */\nconst isPredictable = (characters) =>\n new Set(characters).size === 1 ||\n isSequence(characters) ||\n isKeyboardSequence(characters);\n\n/**\n * Gets the number of characters in predictable patterns.\n * @param {string[]} characters The password characters.\n * @returns {number} The number of predictable characters.\n */\nconst getPredictableCount = (characters) => {\n const indices = new Set();\n\n for (let i = 0; i + 2 < characters.length; i++) {\n if (isPredictable(characters.slice(i, i + 3))) {\n indices.add(i);\n indices.add(i + 1);\n indices.add(i + 2);\n }\n }\n\n return indices.size;\n};\n\n/**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=[]] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\nexport const getStrength = (password, commonPasswords = []) => {\n const normalizedPassword = password.normalize('NFKC');\n const characters = Array.from(normalizedPassword);\n\n if (!characters.length) {\n return 0;\n }\n\n const commonStrength = getCommonPasswordStrength(\n normalizedPassword.toLowerCase(),\n commonPasswords,\n );\n\n if (commonStrength !== null) {\n return commonStrength;\n }\n\n const score = getLengthStrength(characters);\n const repeatedPattern = getRepeatedPattern(characters);\n\n if (repeatedPattern?.length === 1) {\n return 0;\n }\n\n if (repeatedPattern) {\n return Math.min(\n score,\n getLengthStrength(repeatedPattern),\n 15,\n );\n }\n\n const predictableCount = getPredictableCount(characters);\n\n return $._clamp(\n score - predictableCount,\n 0,\n predictableCount === characters.length ? 15 : 100,\n );\n};\n","import $ from '@fr0st/query';\nimport { BaseComponent, generateId } from '@fr0st/ui';\nimport { getStrength } from './strength.js';\n\n/**\n * @typedef {object} PasswordStrengthLevel\n * @property {string} class The CSS class applied to the progress bar.\n * @property {number} score The minimum score for the level.\n * @property {string} [text] The label displayed in the progress bar.\n */\n\n/**\n * @callback PasswordStrengthScorer\n * @param {string} password The password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number} The password strength, from 0 to 100.\n */\n\n/**\n * @typedef {object} PasswordStrengthOptions\n * @property {string[]} [commonPasswords] Passwords to score as very weak.\n * @property {string|null} [container=null] The selector for the progress container.\n * @property {PasswordStrengthLevel[]} [levels] The ordered password-strength levels.\n * @property {PasswordStrengthScorer} [scorer] Calculates the password strength.\n * @property {boolean} [striped=false] Whether to display a striped progress bar.\n */\n\n/**\n * Displays the strength of a password input as a progress bar.\n * @augments {BaseComponent<PasswordStrengthOptions>}\n */\nexport default class PasswordStrength extends BaseComponent {\n static classes = {\n progress: 'progress mt-2',\n progressBar: 'progress-bar',\n progressBarStriped: 'progress-bar-striped',\n };\n /** @type {PasswordStrengthOptions} */\n static defaults = {\n commonPasswords: [\n '123456',\n '12345678',\n '123456789',\n '1234567890',\n '000000',\n '111111',\n 'abc123',\n 'admin',\n 'dragon',\n 'iloveyou',\n 'letmein',\n 'monkey',\n 'password',\n 'password1',\n 'password123',\n 'qwerty',\n 'qwerty123',\n 'welcome',\n ],\n levels: [\n {\n score: 0,\n class: 'text-bg-danger',\n text: 'Very Weak',\n },\n {\n score: 20,\n class: 'text-bg-danger',\n text: 'Weak',\n },\n {\n score: 40,\n class: 'text-bg-warning',\n text: 'Normal',\n },\n {\n score: 60,\n class: 'text-bg-success',\n text: 'Strong',\n },\n {\n score: 80,\n class: 'text-bg-success',\n text: 'Very Strong',\n },\n ],\n container: null,\n scorer: getStrength,\n striped: false,\n };\n\n #container = null;\n #describedBy;\n #form = null;\n #progress = null;\n #progressBar = null;\n\n /**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=this.defaults.commonPasswords] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\n static getStrength(password, commonPasswords = this.defaults.commonPasswords) {\n return getStrength(password, commonPasswords);\n }\n\n /**\n * Creates a PasswordStrength.\n * @param {HTMLInputElement} node The input node.\n * @param {PasswordStrengthOptions} [options] The PasswordStrength options.\n * @throws {TypeError} When the node is not an input element.\n */\n constructor(node, options) {\n if (!$._isElement(node) || !$.is(node, 'input')) {\n throw new TypeError('PasswordStrength must be created on an input element.');\n }\n\n super(node, options);\n\n try {\n this.#describedBy = $.getAttribute(this.node, 'aria-describedby');\n\n this.#form = $.getProperty(this.node, 'form');\n\n if (this.options.container) {\n this.#container = $.findOne(this.options.container);\n } else {\n this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();\n }\n\n this.#render();\n this.#refresh();\n this.#events();\n } catch (error) {\n this.dispose();\n throw error;\n }\n }\n\n /** @inheritdoc */\n dispose() {\n if (!this.node) {\n return;\n }\n\n $.remove(this.#progress);\n\n if (this.#describedBy === null) {\n $.removeAttribute(this.node, 'aria-describedby');\n } else {\n $.setAttribute(this.node, { 'aria-describedby': this.#describedBy });\n }\n\n this.#container = null;\n this.#form = null;\n this.#progress = null;\n this.#progressBar = null;\n\n super.dispose();\n }\n\n /**\n * Gets the password strength.\n * @returns {number} The password strength, from 0 to 100.\n */\n getStrength() {\n const value = $.getValue(this.node);\n const strength = Number(this.options.scorer(\n value,\n this.options.commonPasswords,\n ));\n\n return Number.isFinite(strength) ?\n $._clamp(strength, 0, 100) :\n 0;\n }\n\n /**\n * Attaches events for the PasswordStrength.\n */\n #events() {\n if (this.#form) {\n this.listen(this.#form, 'reset.ui.passwordstrength', (event) => {\n setTimeout(() => {\n if (this.node && !event.defaultPrevented) {\n this.#refresh();\n }\n }, 0);\n });\n }\n\n this.listen(this.node, 'input.ui.passwordstrength', () => {\n this.#refresh();\n });\n }\n\n /**\n * Refreshes the password strength.\n */\n #refresh() {\n const strength = this.getStrength();\n\n const nextLevel = this.options.levels.findLast(\n (level) => strength >= level.score,\n );\n\n $.setStyle(this.#progressBar, { width: `${strength}%` });\n $.setAttribute(this.#progressBar, {\n 'class': this.constructor.classes.progressBar,\n 'aria-valuenow': strength,\n });\n\n $.addClass(this.#progressBar, nextLevel.class);\n\n if (this.options.striped) {\n $.addClass(this.#progressBar, this.constructor.classes.progressBarStriped);\n }\n\n $.setText(this.#progressBar, nextLevel.text ?? '');\n }\n\n /**\n * Renders the password strength element.\n */\n #render() {\n this.#progress = $.create('div', {\n class: this.constructor.classes.progress,\n });\n\n const id = generateId('password-strength');\n\n this.#progressBar = $.create('div', {\n attributes: {\n id,\n 'role': 'progressbar',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n },\n });\n\n $.append(this.#progress, this.#progressBar);\n $.append(this.#container, this.#progress);\n\n const describedBy = [this.#describedBy, id]\n .filter(Boolean)\n .join(' ');\n\n $.setAttribute(this.node, { 'aria-describedby': describedBy });\n }\n}\n","import { initComponent } from '@fr0st/ui';\nimport PasswordStrength from './password-strength.js';\n\ninitComponent('passwordstrength', PasswordStrength);\n\nexport default PasswordStrength;\n"],"mappings":"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"}
@@ -306,12 +306,11 @@ _fr0st_query = __toESM(_fr0st_query, 1);
306
306
  scorer: getStrength,
307
307
  striped: false
308
308
  };
309
- #container;
309
+ #container = null;
310
310
  #describedBy;
311
- #form;
312
- #progress;
313
- #progressBar;
314
- #resetHandler;
311
+ #form = null;
312
+ #progress = null;
313
+ #progressBar = null;
315
314
  /**
316
315
  * Calculates the strength of a password.
317
316
  * @param {string} password The password.
@@ -328,18 +327,10 @@ _fr0st_query = __toESM(_fr0st_query, 1);
328
327
  * @throws {TypeError} When the node is not an input element.
329
328
  */
330
329
  constructor(node, options) {
331
- if (!_fr0st_query.default.is(node, "input")) throw new TypeError("PasswordStrength must be created on an input element.");
330
+ if (!_fr0st_query.default._isElement(node) || !_fr0st_query.default.is(node, "input")) throw new TypeError("PasswordStrength must be created on an input element.");
332
331
  super(node, options);
333
332
  try {
334
333
  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
334
  this.#form = _fr0st_query.default.getProperty(this.node, "form");
344
335
  if (this.options.container) this.#container = _fr0st_query.default.findOne(this.options.container);
345
336
  else this.#container = _fr0st_query.default.closest(this.node, ":not(.form-input):not(.input-group)").shift();
@@ -355,15 +346,12 @@ _fr0st_query = __toESM(_fr0st_query, 1);
355
346
  dispose() {
356
347
  if (!this.node) return;
357
348
  _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
349
  if (this.#describedBy === null) _fr0st_query.default.removeAttribute(this.node, "aria-describedby");
361
350
  else _fr0st_query.default.setAttribute(this.node, { "aria-describedby": this.#describedBy });
362
351
  this.#container = null;
363
352
  this.#form = null;
364
353
  this.#progress = null;
365
354
  this.#progressBar = null;
366
- this.#resetHandler = null;
367
355
  super.dispose();
368
356
  }
369
357
  /**
@@ -379,15 +367,12 @@ _fr0st_query = __toESM(_fr0st_query, 1);
379
367
  * Attaches events for the PasswordStrength.
380
368
  */
381
369
  #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", () => {
370
+ if (this.#form) this.listen(this.#form, "reset.ui.passwordstrength", (event) => {
371
+ setTimeout(() => {
372
+ if (this.node && !event.defaultPrevented) this.#refresh();
373
+ }, 0);
374
+ });
375
+ this.listen(this.node, "input.ui.passwordstrength", () => {
391
376
  this.#refresh();
392
377
  });
393
378
  }
@@ -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\n/**\n * @typedef {object} PasswordStrengthLevel\n * @property {string} class The CSS class applied to the progress bar.\n * @property {number} score The minimum score for the level.\n * @property {string} [text] The label displayed in the progress bar.\n */\n\n/**\n * @callback PasswordStrengthScorer\n * @param {string} password The password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number} The password strength, from 0 to 100.\n */\n\n/**\n * @typedef {object} PasswordStrengthOptions\n * @property {string[]} [commonPasswords] Passwords to score as very weak.\n * @property {string|null} [container=null] The selector for the progress container.\n * @property {PasswordStrengthLevel[]} [levels] The ordered password-strength levels.\n * @property {PasswordStrengthScorer} [scorer] Calculates the password strength.\n * @property {boolean} [striped=false] Whether to display a striped progress bar.\n */\n\n/**\n * Displays the strength of a password input as a progress bar.\n * @augments {BaseComponent<PasswordStrengthOptions>}\n */\nexport default class PasswordStrength extends BaseComponent {\n static classes = {\n progress: 'progress mt-2',\n progressBar: 'progress-bar',\n progressBarStriped: 'progress-bar-striped',\n };\n /** @type {PasswordStrengthOptions} */\n static defaults = {\n commonPasswords: [\n '123456',\n '12345678',\n '123456789',\n '1234567890',\n '000000',\n '111111',\n 'abc123',\n 'admin',\n 'dragon',\n 'iloveyou',\n 'letmein',\n 'monkey',\n 'password',\n 'password1',\n 'password123',\n 'qwerty',\n 'qwerty123',\n 'welcome',\n ],\n levels: [\n {\n score: 0,\n class: 'text-bg-danger',\n text: 'Very Weak',\n },\n {\n score: 20,\n class: 'text-bg-danger',\n text: 'Weak',\n },\n {\n score: 40,\n class: 'text-bg-warning',\n text: 'Normal',\n },\n {\n score: 60,\n class: 'text-bg-success',\n text: 'Strong',\n },\n {\n score: 80,\n class: 'text-bg-success',\n text: 'Very Strong',\n },\n ],\n container: null,\n scorer: getStrength,\n striped: false,\n };\n\n #container = null;\n #describedBy;\n #form = null;\n #progress = null;\n #progressBar = null;\n\n /**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=this.defaults.commonPasswords] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\n static getStrength(password, commonPasswords = this.defaults.commonPasswords) {\n return getStrength(password, commonPasswords);\n }\n\n /**\n * Creates a PasswordStrength.\n * @param {HTMLInputElement} node The input node.\n * @param {PasswordStrengthOptions} [options] The PasswordStrength options.\n * @throws {TypeError} When the node is not an input element.\n */\n constructor(node, options) {\n if (!$._isElement(node) || !$.is(node, 'input')) {\n throw new TypeError('PasswordStrength must be created on an input element.');\n }\n\n super(node, options);\n\n try {\n this.#describedBy = $.getAttribute(this.node, 'aria-describedby');\n\n this.#form = $.getProperty(this.node, 'form');\n\n if (this.options.container) {\n this.#container = $.findOne(this.options.container);\n } else {\n this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();\n }\n\n this.#render();\n this.#refresh();\n this.#events();\n } catch (error) {\n this.dispose();\n throw error;\n }\n }\n\n /** @inheritdoc */\n dispose() {\n if (!this.node) {\n return;\n }\n\n $.remove(this.#progress);\n\n if (this.#describedBy === null) {\n $.removeAttribute(this.node, 'aria-describedby');\n } else {\n $.setAttribute(this.node, { 'aria-describedby': this.#describedBy });\n }\n\n this.#container = null;\n this.#form = null;\n this.#progress = null;\n this.#progressBar = null;\n\n super.dispose();\n }\n\n /**\n * Gets the password strength.\n * @returns {number} The password strength, from 0 to 100.\n */\n getStrength() {\n const value = $.getValue(this.node);\n const strength = Number(this.options.scorer(\n value,\n this.options.commonPasswords,\n ));\n\n return Number.isFinite(strength) ?\n $._clamp(strength, 0, 100) :\n 0;\n }\n\n /**\n * Attaches events for the PasswordStrength.\n */\n #events() {\n if (this.#form) {\n this.listen(this.#form, 'reset.ui.passwordstrength', (event) => {\n setTimeout(() => {\n if (this.node && !event.defaultPrevented) {\n this.#refresh();\n }\n }, 0);\n });\n }\n\n this.listen(this.node, 'input.ui.passwordstrength', () => {\n this.#refresh();\n });\n }\n\n /**\n * Refreshes the password strength.\n */\n #refresh() {\n const strength = this.getStrength();\n\n const nextLevel = this.options.levels.findLast(\n (level) => strength >= level.score,\n );\n\n $.setStyle(this.#progressBar, { width: `${strength}%` });\n $.setAttribute(this.#progressBar, {\n 'class': this.constructor.classes.progressBar,\n 'aria-valuenow': strength,\n });\n\n $.addClass(this.#progressBar, nextLevel.class);\n\n if (this.options.striped) {\n $.addClass(this.#progressBar, this.constructor.classes.progressBarStriped);\n }\n\n $.setText(this.#progressBar, nextLevel.text ?? '');\n }\n\n /**\n * Renders the password strength element.\n */\n #render() {\n this.#progress = $.create('div', {\n class: this.constructor.classes.progress,\n });\n\n const id = generateId('password-strength');\n\n this.#progressBar = $.create('div', {\n attributes: {\n id,\n 'role': 'progressbar',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n },\n });\n\n $.append(this.#progress, this.#progressBar);\n $.append(this.#container, this.#progress);\n\n const describedBy = [this.#describedBy, id]\n .filter(Boolean)\n .join(' ');\n\n $.setAttribute(this.node, { 'aria-describedby': describedBy });\n }\n}\n","import { initComponent } from '@fr0st/ui';\nimport PasswordStrength from './password-strength.js';\n\ninitComponent('passwordstrength', PasswordStrength);\n\nexport default PasswordStrength;\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("@fr0st/ui"),require("@fr0st/query")):typeof define==`function`&&define.amd?define([`exports`,`@fr0st/ui`,`@fr0st/query`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.UI=e.UI||{},e.UI,e.fQuery))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=Object.create,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,s=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty,l=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var s=o(t),l=0,u=s.length,d;l<u;l++)d=s[l],!c.call(e,d)&&d!==n&&i(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(r=a(t,d))||r.enumerable});return e};n=((e,t,n)=>(n=e==null?{}:r(s(e)),l(t||!e||!e.__esModule||!c.call(e,`default`)?i(n,`default`,{value:e,enumerable:!0}):n,e)))(n,1);var u=[{pattern:/\p{Ll}/u,type:`lower`},{pattern:/\p{Lu}/u,type:`upper`},{pattern:/\p{N}/u,type:`number`}],d=[`1234567890`,`qwertyuiop`,`asdfghjkl`,`zxcvbnm`].flatMap(e=>[e,Array.from(e).reverse().join(``)]),f={$:`s`,0:`o`,3:`e`,4:`a`,5:`s`,7:`t`,"@":`a`},p=[{end:5,max:19,min:1,start:1},{end:8,max:39,min:20,start:6},{end:11,max:59,min:40,start:9},{end:15,max:79,min:60,start:12},{end:24,max:100,min:80,start:16}],m=e=>u.find(({pattern:t})=>t.test(e))?.type||`symbol`,h=e=>{let t=p.find(({end:t})=>e.length<=t)||p.at(-1),r=Math.round(n.default._map(Math.min(e.length,t.end),t.start,t.end,t.min,t.max)),i=new Set(e.map(m)).size;return Math.min(r+(i-1)*2,t.max)},g=e=>{let t=Math.floor(e.length/2);for(let n=1;n<=t;n++)if(e.length%n===0&&e.every((t,r)=>t===e[r%n]))return e.slice(0,n);return null},_=e=>String(e).normalize(`NFKC`).toLowerCase(),v=e=>Array.from(e,e=>f[e]||e).join(``),y=e=>{let t=[e,e.replace(/[^\p{L}\p{N}]/gu,v)].flatMap(e=>{let t=e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,``);return[t,t.replace(/^\d+|\d+$/g,``)]});return/* @__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(t||!e||!e.__esModule||!c.call(e,`default`)?i(n,`default`,{value:e,enumerable:!0}):n,e)))(n,1);var u=[{pattern:/\p{Ll}/u,type:`lower`},{pattern:/\p{Lu}/u,type:`upper`},{pattern:/\p{N}/u,type:`number`}],d=[`1234567890`,`qwertyuiop`,`asdfghjkl`,`zxcvbnm`].flatMap(e=>[e,Array.from(e).reverse().join(``)]),f={$:`s`,0:`o`,3:`e`,4:`a`,5:`s`,7:`t`,"@":`a`},p=[{end:5,max:19,min:1,start:1},{end:8,max:39,min:20,start:6},{end:11,max:59,min:40,start:9},{end:15,max:79,min:60,start:12},{end:24,max:100,min:80,start:16}],m=e=>u.find(({pattern:t})=>t.test(e))?.type||`symbol`,h=e=>{let t=p.find(({end:t})=>e.length<=t)||p.at(-1),r=Math.round(n.default._map(Math.min(e.length,t.end),t.start,t.end,t.min,t.max)),i=new Set(e.map(m)).size;return Math.min(r+(i-1)*2,t.max)},g=e=>{let t=Math.floor(e.length/2);for(let n=1;n<=t;n++)if(e.length%n===0&&e.every((t,r)=>t===e[r%n]))return e.slice(0,n);return null},_=e=>String(e).normalize(`NFKC`).toLowerCase(),v=e=>Array.from(e,e=>f[e]||e).join(``),y=e=>{let t=[e,e.replace(/[^\p{L}\p{N}]/gu,v)].flatMap(e=>{let t=e.replace(/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu,``);return[t,t.replace(/^\d+|\d+$/g,``)]});return/* @__PURE__ */ new Set([v(e),...t,...t.map(v)])},b=(e,t)=>{let n=new Set(t.map(_));return n.has(e)?0:n.isDisjointFrom(y(e))?null:5},x=e=>{let[t,n,r]=e.map(e=>e.toLowerCase()),i=m(t),a=n.codePointAt(0)-t.codePointAt(0);return[`lower`,`number`].includes(i)&&m(n)===i&&m(r)===i&&Math.abs(a)===1&&r.codePointAt(0)-n.codePointAt(0)===a},S=e=>{let t=e.join(``).toLowerCase();return d.some(e=>e.includes(t))},C=e=>new Set(e).size===1||x(e)||S(e),w=e=>{let t=/* @__PURE__ */ new Set;for(let n=0;n+2<e.length;n++)C(e.slice(n,n+3))&&(t.add(n),t.add(n+1),t.add(n+2));return t.size},T=(e,t=[])=>{let r=e.normalize(`NFKC`),i=Array.from(r);if(!i.length)return 0;let a=b(r.toLowerCase(),t);if(a!==null)return a;let o=h(i),s=g(i);if(s?.length===1)return 0;if(s)return Math.min(o,h(s),15);let c=w(i);return n.default._clamp(o-c,0,c===i.length?15:100)},E=class extends t.BaseComponent{static classes={progress:`progress mt-2`,progressBar:`progress-bar`,progressBarStriped:`progress-bar-striped`};static defaults={commonPasswords:[`123456`,`12345678`,`123456789`,`1234567890`,`000000`,`111111`,`abc123`,`admin`,`dragon`,`iloveyou`,`letmein`,`monkey`,`password`,`password1`,`password123`,`qwerty`,`qwerty123`,`welcome`],levels:[{score:0,class:`text-bg-danger`,text:`Very Weak`},{score:20,class:`text-bg-danger`,text:`Weak`},{score:40,class:`text-bg-warning`,text:`Normal`},{score:60,class:`text-bg-success`,text:`Strong`},{score:80,class:`text-bg-success`,text:`Very Strong`}],container:null,scorer:T,striped:!1};#e=null;#t;#n=null;#r=null;#i=null;static getStrength(e,t=this.defaults.commonPasswords){return T(e,t)}constructor(e,t){if(!n.default._isElement(e)||!n.default.is(e,`input`))throw TypeError(`PasswordStrength must be created on an input element.`);super(e,t);try{this.#t=n.default.getAttribute(this.node,`aria-describedby`),this.#n=n.default.getProperty(this.node,`form`),this.#e=this.options.container?n.default.findOne(this.options.container):n.default.closest(this.node,`:not(.form-input):not(.input-group)`).shift(),this.#s(),this.#o(),this.#a()}catch(e){throw this.dispose(),e}}dispose(){this.node&&(n.default.remove(this.#r),this.#t===null?n.default.removeAttribute(this.node,`aria-describedby`):n.default.setAttribute(this.node,{"aria-describedby":this.#t}),this.#e=null,this.#n=null,this.#r=null,this.#i=null,super.dispose())}getStrength(){let e=n.default.getValue(this.node),t=Number(this.options.scorer(e,this.options.commonPasswords));return Number.isFinite(t)?n.default._clamp(t,0,100):0}#a(){this.#n&&this.listen(this.#n,`reset.ui.passwordstrength`,e=>{setTimeout(()=>{this.node&&!e.defaultPrevented&&this.#o()},0)}),this.listen(this.node,`input.ui.passwordstrength`,()=>{this.#o()})}#o(){let e=this.getStrength(),t=this.options.levels.findLast(t=>e>=t.score);n.default.setStyle(this.#i,{width:`${e}%`}),n.default.setAttribute(this.#i,{class:this.constructor.classes.progressBar,"aria-valuenow":e}),n.default.addClass(this.#i,t.class),this.options.striped&&n.default.addClass(this.#i,this.constructor.classes.progressBarStriped),n.default.setText(this.#i,t.text??``)}#s(){this.#r=n.default.create(`div`,{class:this.constructor.classes.progress});let e=(0,t.generateId)(`password-strength`);this.#i=n.default.create(`div`,{attributes:{id:e,role:`progressbar`,"aria-valuemin":0,"aria-valuemax":100}}),n.default.append(this.#r,this.#i),n.default.append(this.#e,this.#r);let r=[this.#t,e].filter(Boolean).join(` `);n.default.setAttribute(this.node,{"aria-describedby":r})}};(0,t.initComponent)(`passwordstrength`,E),e.PasswordStrength=E});
2
2
  //# sourceMappingURL=frost-ui-passwordstrength.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"frost-ui-passwordstrength.min.js","names":["$","BaseComponent","$","getDataset","generateId","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","isNodeMode","target","_fr0st_query","characterTypes","keyboardSequences","row","leetCharacters","\"$\"","lengthBands","getCharacterType","pattern","character","getLengthStrength","band","characters","end","score","diversity","getRepeatedPattern","maxLength","length","index","normalizePassword","password","normalizeLeet","getPasswordVariants","variants","undecorated","value","getCommonPasswordStrength","common","commonPasswords","isSequence","first","second","third","type","direction","isKeyboardSequence","sequence","keyboardSequence","isPredictable","getPredictableCount","indices","getStrength","normalizedPassword","commonStrength","repeatedPattern","predictableCount","PasswordStrength","_fr0st_ui","node","options","#describedBy","#form","#container","#render","#refresh","#events","error","#progress","#progressBar","strength","event","nextLevel","level","\"class\"","id","\"role\"","describedBy","src_default"],"sources":["../src/strength.js","../src/password-strength.js","../src/index.js"],"sourcesContent":["import $ from '@fr0st/query';\n\nconst characterTypes = [\n { pattern: /\\p{Ll}/u, type: 'lower' },\n { pattern: /\\p{Lu}/u, type: 'upper' },\n { pattern: /\\p{N}/u, type: 'number' },\n];\n\nconst keyboardSequences = [\n '1234567890',\n 'qwertyuiop',\n 'asdfghjkl',\n 'zxcvbnm',\n].flatMap(\n (row) => [\n row,\n Array.from(row).reverse().join(''),\n ],\n);\n\nconst leetCharacters = {\n '$': 's',\n '0': 'o',\n '3': 'e',\n '4': 'a',\n '5': 's',\n '7': 't',\n '@': 'a',\n};\n\nconst lengthBands = [\n { end: 5, max: 19, min: 1, start: 1 },\n { end: 8, max: 39, min: 20, start: 6 },\n { end: 11, max: 59, min: 40, start: 9 },\n { end: 15, max: 79, min: 60, start: 12 },\n { end: 24, max: 100, min: 80, start: 16 },\n];\n\n/**\n * Gets the type of a password character.\n * @param {string} character The character.\n * @returns {string} The character type.\n */\nconst getCharacterType = (character) =>\n characterTypes.find(\n ({ pattern }) => pattern.test(character),\n )?.type || 'symbol';\n\n/**\n * Gets a length-based password strength.\n * @param {string[]} characters The password characters.\n * @returns {number} The password strength.\n */\nconst getLengthStrength = (characters) => {\n const band = lengthBands.find(\n ({ end }) => characters.length <= end,\n ) || lengthBands.at(-1);\n const score = Math.round(\n $._map(\n Math.min(characters.length, band.end),\n band.start,\n band.end,\n band.min,\n band.max,\n ),\n );\n const diversity = new Set(\n characters.map(getCharacterType),\n ).size;\n\n return Math.min(\n score +\n (diversity - 1) * 2,\n band.max,\n );\n};\n\n/**\n * Gets the smallest repeated pattern.\n * @param {string[]} characters The password characters.\n * @returns {string[]|null} The repeated pattern.\n */\nconst getRepeatedPattern = (characters) => {\n const maxLength = Math.floor(characters.length / 2);\n\n for (let length = 1; length <= maxLength; length++) {\n if (\n characters.length % length === 0 &&\n characters.every(\n (character, index) =>\n character === characters[index % length],\n )\n ) {\n return characters.slice(0, length);\n }\n }\n\n return null;\n};\n\n/**\n * Normalizes common password matching.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizePassword = (password) =>\n String(password)\n .normalize('NFKC')\n .toLowerCase();\n\n/**\n * Normalizes common leet substitutions.\n * @param {string} password The password.\n * @returns {string} The normalized password.\n */\nconst normalizeLeet = (password) =>\n Array.from(\n password,\n (character) => leetCharacters[character] || character,\n ).join('');\n\n/**\n * Gets normalized variants used to match common passwords.\n * @param {string} password The normalized password.\n * @returns {Set<string>} The password variants.\n */\nconst getPasswordVariants = (password) => {\n // Preserve digits for decoration stripping while resolving symbol substitutions.\n const substituted = password.replace(/[^\\p{L}\\p{N}]/gu, normalizeLeet);\n const variants = [password, substituted].flatMap((value) => {\n const undecorated = value.replace(\n /^[^\\p{L}\\p{N}]+|[^\\p{L}\\p{N}]+$/gu,\n '',\n );\n\n return [\n undecorated,\n undecorated.replace(/^\\d+|\\d+$/g, ''),\n ];\n });\n\n return new Set([\n normalizeLeet(password),\n ...variants,\n ...variants.map(normalizeLeet),\n ]);\n};\n\n/**\n * Gets the score for a common password.\n * @param {string} password The normalized password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number|null} The common-password score.\n */\nconst getCommonPasswordStrength = (password, commonPasswords) => {\n const common = new Set(\n commonPasswords.map(normalizePassword),\n );\n\n if (common.has(password)) {\n return 0;\n }\n\n return common.isDisjointFrom(getPasswordVariants(password)) ? null : 5;\n};\n\n/**\n * Checks whether characters form an ascending or descending sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a sequence.\n */\nconst isSequence = (characters) => {\n const [first, second, third] = characters.map(\n (character) => character.toLowerCase(),\n );\n const type = getCharacterType(first);\n const direction = second.codePointAt(0) - first.codePointAt(0);\n\n return ['lower', 'number'].includes(type) &&\n getCharacterType(second) === type &&\n getCharacterType(third) === type &&\n Math.abs(direction) === 1 &&\n third.codePointAt(0) - second.codePointAt(0) === direction;\n};\n\n/**\n * Checks whether characters form a keyboard sequence.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a keyboard sequence.\n */\nconst isKeyboardSequence = (characters) => {\n const sequence = characters.join('').toLowerCase();\n\n return keyboardSequences.some(\n (keyboardSequence) => keyboardSequence.includes(sequence),\n );\n};\n\n/**\n * Checks whether characters form a predictable pattern.\n * @param {string[]} characters The password characters.\n * @returns {boolean} Whether the characters form a predictable pattern.\n */\nconst isPredictable = (characters) =>\n new Set(characters).size === 1 ||\n isSequence(characters) ||\n isKeyboardSequence(characters);\n\n/**\n * Gets the number of characters in predictable patterns.\n * @param {string[]} characters The password characters.\n * @returns {number} The number of predictable characters.\n */\nconst getPredictableCount = (characters) => {\n const indices = new Set();\n\n for (let i = 0; i + 2 < characters.length; i++) {\n if (isPredictable(characters.slice(i, i + 3))) {\n indices.add(i);\n indices.add(i + 1);\n indices.add(i + 2);\n }\n }\n\n return indices.size;\n};\n\n/**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=[]] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\nexport const getStrength = (password, commonPasswords = []) => {\n const normalizedPassword = password.normalize('NFKC');\n const characters = Array.from(normalizedPassword);\n\n if (!characters.length) {\n return 0;\n }\n\n const commonStrength = getCommonPasswordStrength(\n normalizedPassword.toLowerCase(),\n commonPasswords,\n );\n\n if (commonStrength !== null) {\n return commonStrength;\n }\n\n const score = getLengthStrength(characters);\n const repeatedPattern = getRepeatedPattern(characters);\n\n if (repeatedPattern?.length === 1) {\n return 0;\n }\n\n if (repeatedPattern) {\n return Math.min(\n score,\n getLengthStrength(repeatedPattern),\n 15,\n );\n }\n\n const predictableCount = getPredictableCount(characters);\n\n return $._clamp(\n score - predictableCount,\n 0,\n predictableCount === characters.length ? 15 : 100,\n );\n};\n","import $ from '@fr0st/query';\nimport { BaseComponent, generateId } from '@fr0st/ui';\nimport { getStrength } from './strength.js';\n\n/**\n * @typedef {object} PasswordStrengthLevel\n * @property {string} class The CSS class applied to the progress bar.\n * @property {number} score The minimum score for the level.\n * @property {string} [text] The label displayed in the progress bar.\n */\n\n/**\n * @callback PasswordStrengthScorer\n * @param {string} password The password.\n * @param {string[]} commonPasswords The common passwords.\n * @returns {number} The password strength, from 0 to 100.\n */\n\n/**\n * @typedef {object} PasswordStrengthOptions\n * @property {string[]} [commonPasswords] Passwords to score as very weak.\n * @property {string|null} [container=null] The selector for the progress container.\n * @property {PasswordStrengthLevel[]} [levels] The ordered password-strength levels.\n * @property {PasswordStrengthScorer} [scorer] Calculates the password strength.\n * @property {boolean} [striped=false] Whether to display a striped progress bar.\n */\n\n/**\n * Displays the strength of a password input as a progress bar.\n * @augments {BaseComponent<PasswordStrengthOptions>}\n */\nexport default class PasswordStrength extends BaseComponent {\n static classes = {\n progress: 'progress mt-2',\n progressBar: 'progress-bar',\n progressBarStriped: 'progress-bar-striped',\n };\n /** @type {PasswordStrengthOptions} */\n static defaults = {\n commonPasswords: [\n '123456',\n '12345678',\n '123456789',\n '1234567890',\n '000000',\n '111111',\n 'abc123',\n 'admin',\n 'dragon',\n 'iloveyou',\n 'letmein',\n 'monkey',\n 'password',\n 'password1',\n 'password123',\n 'qwerty',\n 'qwerty123',\n 'welcome',\n ],\n levels: [\n {\n score: 0,\n class: 'text-bg-danger',\n text: 'Very Weak',\n },\n {\n score: 20,\n class: 'text-bg-danger',\n text: 'Weak',\n },\n {\n score: 40,\n class: 'text-bg-warning',\n text: 'Normal',\n },\n {\n score: 60,\n class: 'text-bg-success',\n text: 'Strong',\n },\n {\n score: 80,\n class: 'text-bg-success',\n text: 'Very Strong',\n },\n ],\n container: null,\n scorer: getStrength,\n striped: false,\n };\n\n #container = null;\n #describedBy;\n #form = null;\n #progress = null;\n #progressBar = null;\n\n /**\n * Calculates the strength of a password.\n * @param {string} password The password.\n * @param {string[]} [commonPasswords=this.defaults.commonPasswords] Passwords to score as very weak.\n * @returns {number} The password strength, from 0 to 100.\n */\n static getStrength(password, commonPasswords = this.defaults.commonPasswords) {\n return getStrength(password, commonPasswords);\n }\n\n /**\n * Creates a PasswordStrength.\n * @param {HTMLInputElement} node The input node.\n * @param {PasswordStrengthOptions} [options] The PasswordStrength options.\n * @throws {TypeError} When the node is not an input element.\n */\n constructor(node, options) {\n if (!$._isElement(node) || !$.is(node, 'input')) {\n throw new TypeError('PasswordStrength must be created on an input element.');\n }\n\n super(node, options);\n\n try {\n this.#describedBy = $.getAttribute(this.node, 'aria-describedby');\n\n this.#form = $.getProperty(this.node, 'form');\n\n if (this.options.container) {\n this.#container = $.findOne(this.options.container);\n } else {\n this.#container = $.closest(this.node, ':not(.form-input):not(.input-group)').shift();\n }\n\n this.#render();\n this.#refresh();\n this.#events();\n } catch (error) {\n this.dispose();\n throw error;\n }\n }\n\n /** @inheritdoc */\n dispose() {\n if (!this.node) {\n return;\n }\n\n $.remove(this.#progress);\n\n if (this.#describedBy === null) {\n $.removeAttribute(this.node, 'aria-describedby');\n } else {\n $.setAttribute(this.node, { 'aria-describedby': this.#describedBy });\n }\n\n this.#container = null;\n this.#form = null;\n this.#progress = null;\n this.#progressBar = null;\n\n super.dispose();\n }\n\n /**\n * Gets the password strength.\n * @returns {number} The password strength, from 0 to 100.\n */\n getStrength() {\n const value = $.getValue(this.node);\n const strength = Number(this.options.scorer(\n value,\n this.options.commonPasswords,\n ));\n\n return Number.isFinite(strength) ?\n $._clamp(strength, 0, 100) :\n 0;\n }\n\n /**\n * Attaches events for the PasswordStrength.\n */\n #events() {\n if (this.#form) {\n this.listen(this.#form, 'reset.ui.passwordstrength', (event) => {\n setTimeout(() => {\n if (this.node && !event.defaultPrevented) {\n this.#refresh();\n }\n }, 0);\n });\n }\n\n this.listen(this.node, 'input.ui.passwordstrength', () => {\n this.#refresh();\n });\n }\n\n /**\n * Refreshes the password strength.\n */\n #refresh() {\n const strength = this.getStrength();\n\n const nextLevel = this.options.levels.findLast(\n (level) => strength >= level.score,\n );\n\n $.setStyle(this.#progressBar, { width: `${strength}%` });\n $.setAttribute(this.#progressBar, {\n 'class': this.constructor.classes.progressBar,\n 'aria-valuenow': strength,\n });\n\n $.addClass(this.#progressBar, nextLevel.class);\n\n if (this.options.striped) {\n $.addClass(this.#progressBar, this.constructor.classes.progressBarStriped);\n }\n\n $.setText(this.#progressBar, nextLevel.text ?? '');\n }\n\n /**\n * Renders the password strength element.\n */\n #render() {\n this.#progress = $.create('div', {\n class: this.constructor.classes.progress,\n });\n\n const id = generateId('password-strength');\n\n this.#progressBar = $.create('div', {\n attributes: {\n id,\n 'role': 'progressbar',\n 'aria-valuemin': 0,\n 'aria-valuemax': 100,\n },\n });\n\n $.append(this.#progress, this.#progressBar);\n $.append(this.#container, this.#progress);\n\n const describedBy = [this.#describedBy, id]\n .filter(Boolean)\n .join(' ');\n\n $.setAttribute(this.node, { 'aria-describedby': describedBy });\n }\n}\n","import { initComponent } from '@fr0st/ui';\nimport PasswordStrength from './password-strength.js';\n\ninitComponent('passwordstrength', PasswordStrength);\n\nexport default PasswordStrength;\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fr0st/ui-passwordstrength",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "private": false,
5
5
  "description": "Password strength indicator component for Frost UI with customizable levels, labels, and striped progress styling.",
6
6
  "keywords": [
@@ -59,11 +59,11 @@
59
59
  "devDependencies": {
60
60
  "@fr0st/eslint-config": "^6.0.1",
61
61
  "@fr0st/query": "^5.0.1",
62
- "@fr0st/ui": "^4.0.1",
63
- "@playwright/test": "^1.63.0",
64
- "eslint": "^10.11.0",
62
+ "@fr0st/ui": "^4.0.2",
63
+ "@playwright/test": "^1.64.1",
64
+ "eslint": "^10.13.0",
65
65
  "monocart-reporter": "^2.14.0",
66
- "vite": "^8.3.1"
66
+ "vite": "^8.3.4"
67
67
  },
68
68
  "peerDependencies": {
69
69
  "@fr0st/query": "^5.0.0",
@@ -1,5 +1,5 @@
1
1
  import $ from '@fr0st/query';
2
- import { BaseComponent, generateId, getDataset } from '@fr0st/ui';
2
+ import { BaseComponent, generateId } from '@fr0st/ui';
3
3
  import { getStrength } from './strength.js';
4
4
 
5
5
  /**
@@ -89,12 +89,11 @@ export default class PasswordStrength extends BaseComponent {
89
89
  striped: false,
90
90
  };
91
91
 
92
- #container;
92
+ #container = null;
93
93
  #describedBy;
94
- #form;
95
- #progress;
96
- #progressBar;
97
- #resetHandler;
94
+ #form = null;
95
+ #progress = null;
96
+ #progressBar = null;
98
97
 
99
98
  /**
100
99
  * Calculates the strength of a password.
@@ -113,7 +112,7 @@ export default class PasswordStrength extends BaseComponent {
113
112
  * @throws {TypeError} When the node is not an input element.
114
113
  */
115
114
  constructor(node, options) {
116
- if (!$.is(node, 'input')) {
115
+ if (!$._isElement(node) || !$.is(node, 'input')) {
117
116
  throw new TypeError('PasswordStrength must be created on an input element.');
118
117
  }
119
118
 
@@ -122,16 +121,6 @@ export default class PasswordStrength extends BaseComponent {
122
121
  try {
123
122
  this.#describedBy = $.getAttribute(this.node, 'aria-describedby');
124
123
 
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
124
  this.#form = $.getProperty(this.node, 'form');
136
125
 
137
126
  if (this.options.container) {
@@ -156,11 +145,6 @@ export default class PasswordStrength extends BaseComponent {
156
145
  }
157
146
 
158
147
  $.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
148
 
165
149
  if (this.#describedBy === null) {
166
150
  $.removeAttribute(this.node, 'aria-describedby');
@@ -172,7 +156,6 @@ export default class PasswordStrength extends BaseComponent {
172
156
  this.#form = null;
173
157
  this.#progress = null;
174
158
  this.#progressBar = null;
175
- this.#resetHandler = null;
176
159
 
177
160
  super.dispose();
178
161
  }
@@ -198,18 +181,16 @@ export default class PasswordStrength extends BaseComponent {
198
181
  */
199
182
  #events() {
200
183
  if (this.#form) {
201
- this.#resetHandler = (event) => {
184
+ this.listen(this.#form, 'reset.ui.passwordstrength', (event) => {
202
185
  setTimeout(() => {
203
186
  if (this.node && !event.defaultPrevented) {
204
187
  this.#refresh();
205
188
  }
206
189
  }, 0);
207
- };
208
-
209
- $.addEvent(this.#form, 'reset.ui.passwordstrength', this.#resetHandler);
190
+ });
210
191
  }
211
192
 
212
- $.addEvent(this.node, 'input.ui.passwordstrength', () => {
193
+ this.listen(this.node, 'input.ui.passwordstrength', () => {
213
194
  this.#refresh();
214
195
  });
215
196
  }