{"version":3,"file":"password-strength.cjs","names":[],"sources":["../src/feedback/password-strength/password-strength.ts"],"sourcesContent":["import { bind, define, html, prop } from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\n\nimport { reducedMotionMixin } from '../../styles';\nimport componentStyles from './password-strength.css?inline';\n\n/** Scoring levels for password strength. */\nexport type PasswordStrengthLevel = 'empty' | 'weak' | 'fair' | 'good' | 'strong';\n\n/** Props accepted by <ore-password-strength>. */\nexport type OrePasswordStrengthProps = {\n  /** Accessible name for assistive technology. Default: 'Password strength'. */\n  label?: string;\n  /**\n   * Optional level labels in order: empty, weak, fair, good, strong.\n   * If omitted or invalid length, defaults are used.\n   */\n  labels?: string[];\n  /**\n   * Optional score override (0..4). Use this to integrate external scorers\n   * such as zxcvbn while keeping block rendering and accessibility behavior.\n   * -1 means no override (default).\n   */\n  score?: number;\n  /** Whether to render visible textual feedback. Default: true. */\n  'show-label'?: boolean;\n  /** Password string to evaluate. */\n  value?: string;\n};\n\n/**\n * Strong password meter with segmented progress visualization.\n *\n * Built-in scoring is heuristic and conservative:\n * - length < 6 => weak\n * - length >= 8 with mixed case => fair\n * - + digit or symbol => good\n * - length >= 12 with mixed case, digit and symbol => strong\n *\n * @element ore-password-strength\n *\n * @attr {string} value - Password string to evaluate\n * @attr {number} score - Optional score override (0..4). Use -1 for no override (default: -1)\n * @attr {boolean} show-label - Show visible feedback label (default: true)\n * @attr {string} label - Accessible name (default: 'Password strength')\n *\n * @cssprop --password-strength-height       Segment bar height\n * @cssprop --password-strength-gap          Gap between segments\n * @cssprop --password-strength-radius       Segment corner radius\n * @cssprop --password-strength-track-bg     Inactive segment color\n * @cssprop --password-strength-label-size   Visible label font size\n * @cssprop --password-strength-label-color  Visible label color\n * @cssprop --password-strength-weak-color   Weak state segment color\n * @cssprop --password-strength-fair-color   Fair state segment color\n * @cssprop --password-strength-good-color   Good state segment color\n * @cssprop --password-strength-strong-color Strong state segment color\n *\n * @example\n * ```html\n * <!-- Pair with an ore-input to evaluate in real time -->\n * <ore-input type=\"password\" label=\"Password\" name=\"password\" id=\"pwd\"></ore-input>\n * <ore-password-strength id=\"meter\"></ore-password-strength>\n * <script type=\"module\">\n *   document.getElementById('pwd').addEventListener('input', (e) => {\n *     document.getElementById('meter').value = e.target.value;\n *   });\n * </script>\n *\n * <!-- Fixed score display (e.g. from a server-side score) -->\n * <ore-password-strength score=\"3\"></ore-password-strength>\n * ```\n */\nexport const PASSWORD_STRENGTH_TAG = 'ore-password-strength' as const;\ndefine<OrePasswordStrengthProps>(PASSWORD_STRENGTH_TAG, {\n  props: {\n    label: prop.string('Password strength'),\n    labels: prop.json(undefined as string[] | undefined),\n    score: prop.number(-1),\n    'show-label': prop.bool(true),\n    value: prop.string(),\n  },\n\n  setup(props) {\n    const defaultLabels: Record<PasswordStrengthLevel, string> = {\n      empty: '',\n      fair: 'Fair',\n      good: 'Good',\n      strong: 'Strong',\n      weak: 'Weak',\n    };\n\n    const levels: PasswordStrengthLevel[] = ['empty', 'weak', 'fair', 'good', 'strong'];\n\n    const computeScore = (password: string): 0 | 1 | 2 | 3 | 4 => {\n      if (!password) return 0;\n\n      if (password.length < 6) return 1;\n\n      const hasLower = /[a-z]/.test(password);\n      const hasUpper = /[A-Z]/.test(password);\n      const hasDigit = /\\d/.test(password);\n      const hasSymbol = /[^a-zA-Z0-9]/.test(password);\n      const long = password.length >= 12;\n\n      if (long && hasLower && hasUpper && hasDigit && hasSymbol) return 4;\n\n      if ((hasLower || hasUpper) && (hasDigit || hasSymbol) && password.length >= 8) return 3;\n\n      if ((hasLower || hasUpper) && password.length >= 8) return 2;\n\n      return 1;\n    };\n\n    const computeLevel = (): PasswordStrengthLevel => {\n      const external = props.score.value ?? -1;\n      const finalScore =\n        external >= 0 ? Math.max(0, Math.min(4, Math.trunc(external))) : computeScore(props.value.value ?? '');\n\n      return levels[finalScore];\n    };\n\n    const score = computed<0 | 1 | 2 | 3 | 4>(() => {\n      // score >= 0 means an external override was provided\n      const external = props.score.value ?? -1;\n\n      if (external >= 0) {\n        return Math.max(0, Math.min(4, Math.trunc(external))) as 0 | 1 | 2 | 3 | 4;\n      }\n\n      return computeScore(props.value.value ?? '');\n    });\n\n    const levelLabel = computed<string>(() => {\n      const custom = props.labels.value;\n\n      if (Array.isArray(custom) && custom.length === 5) return String(custom[score.value] ?? '');\n\n      return defaultLabels[computeLevel()];\n    });\n\n    const ariaValueText = computed<string | null>(() => {\n      if (score.value === 0) return null;\n\n      return levelLabel.value || null;\n    });\n\n    // Sync level change to data-level attribute reactively\n    bind({\n      attr: {\n        'data-level': () => computeLevel(),\n      },\n    });\n\n    const segClass = (threshold: number) => () => `segment${score.value >= threshold ? ' active' : ''}`;\n\n    return html`\n      <div\n        class=\"meter\"\n        role=\"meter\"\n        aria-label=\"${props.label}\"\n        aria-valuemin=\"0\"\n        aria-valuemax=\"4\"\n        aria-valuenow=\"${() => String(score.value)}\"\n        aria-valuetext=\"${() => ariaValueText.value}\">\n        <div class=\"segments\" aria-hidden=\"true\">\n          <div class=\"${segClass(1)}\"></div>\n          <div class=\"${segClass(2)}\"></div>\n          <div class=\"${segClass(3)}\"></div>\n          <div class=\"${segClass(4)}\"></div>\n        </div>\n      </div>\n      ${() =>\n        props['show-label'].value\n          ? html`\n              <span class=\"level-label\" aria-live=\"polite\" aria-atomic=\"true\">${() => levelLabel.value}</span>\n            `\n          : ''}\n    `;\n  },\n\n  styles: [reducedMotionMixin, componentStyles],\n});\n"],"mappings":"6PAwEA,IAAa,EAAwB,yBACrC,EAAA,EAAA,OAAA,CAAiC,EAAuB,CACtD,MAAO,CACL,MAAO,EAAA,KAAK,OAAO,mBAAmB,EACtC,OAAQ,EAAA,KAAK,KAAK,IAAA,EAAiC,EACnD,MAAO,EAAA,KAAK,OAAO,EAAE,EACrB,aAAc,EAAA,KAAK,KAAK,EAAI,EAC5B,MAAO,EAAA,KAAK,OAAO,CACrB,EAEA,MAAM,EAAO,CACX,IAAM,EAAuD,CAC3D,MAAO,GACP,KAAM,OACN,KAAM,OACN,OAAQ,SACR,KAAM,MACR,EAEM,EAAkC,CAAC,QAAS,OAAQ,OAAQ,OAAQ,QAAQ,EAE5E,EAAgB,GAAwC,CAC5D,GAAI,CAAC,EAAU,MAAO,GAEtB,GAAI,EAAS,OAAS,EAAG,MAAO,GAEhC,IAAM,EAAW,QAAQ,KAAK,CAAQ,EAChC,EAAW,QAAQ,KAAK,CAAQ,EAChC,EAAW,KAAK,KAAK,CAAQ,EAC7B,EAAY,eAAe,KAAK,CAAQ,EAS9C,OARa,EAAS,QAAU,IAEpB,GAAY,GAAY,GAAY,EAAkB,GAE7D,GAAY,KAAc,GAAY,IAAc,EAAS,QAAU,EAAU,GAEjF,GAAY,IAAa,EAAS,QAAU,EAAU,EAEpD,CACT,EAEM,MAA4C,CAChD,IAAM,EAAW,EAAM,MAAM,OAAS,GAChC,EACJ,GAAY,EAAI,KAAK,IAAI,EAAG,KAAK,IAAI,EAAG,KAAK,MAAM,CAAQ,CAAC,CAAC,EAAI,EAAa,EAAM,MAAM,OAAS,EAAE,EAEvG,OAAO,EAAO,EAChB,EAEM,GAAA,EAAQ,EAAA,SAAA,KAAkC,CAE9C,IAAM,EAAW,EAAM,MAAM,OAAS,GAMtC,OAJI,GAAY,EACP,KAAK,IAAI,EAAG,KAAK,IAAI,EAAG,KAAK,MAAM,CAAQ,CAAC,CAAC,EAG/C,EAAa,EAAM,MAAM,OAAS,EAAE,CAC7C,CAAC,EAEK,GAAA,EAAa,EAAA,SAAA,KAAuB,CACxC,IAAM,EAAS,EAAM,OAAO,MAI5B,OAFI,MAAM,QAAQ,CAAM,GAAK,EAAO,SAAW,EAAU,OAAO,EAAO,EAAM,QAAU,EAAE,EAElF,EAAc,EAAa,EACpC,CAAC,EAEK,GAAA,EAAgB,EAAA,SAAA,KAChB,EAAM,QAAU,EAAU,KAEvB,EAAW,OAAS,IAC5B,GAGD,EAAA,EAAA,KAAA,CAAK,CACH,KAAM,CACJ,iBAAoB,EAAa,CACnC,CACF,CAAC,EAED,IAAM,EAAY,OAA4B,UAAU,EAAM,OAAS,EAAY,UAAY,KAE/F,MAAO,GAAA,IAAI;;;;sBAIO,EAAM,MAAM;;;6BAGH,OAAO,EAAM,KAAK,EAAE;8BACnB,EAAc,MAAM;;wBAE5B,EAAS,CAAC,EAAE;wBACZ,EAAS,CAAC,EAAE;wBACZ,EAAS,CAAC,EAAE;wBACZ,EAAS,CAAC,EAAE;;;YAI5B,EAAM,aAAa,CAAC,MAChB,EAAA,IAAI;oFACsE,EAAW,MAAM;cAE3F,GAAG;KAEb,EAEA,OAAQ,CAAC,EAAA,mBAAoB,EAAA,OAAe,CAC9C,CAAC"}