{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["export type RgbColor = readonly [number, number, number];\nexport type ColorInput = string | RgbColor;\nexport type WCAGLevel = 'AA' | 'AAA';\nexport type TextSize = 'normal' | 'large';\n\nexport interface ContrastRatioOptions {\n  precision?: number;\n}\n\nexport interface WCAGComplianceResult {\n  contrastRatio: number;\n  summary: string;\n  aa: {\n    normalText: boolean;\n    largeText: boolean;\n  };\n  aaa: {\n    normalText: boolean;\n    largeText: boolean;\n  };\n}\n\nconst HEX_COLOR_PATTERN = /^[0-9a-f]{3}([0-9a-f]{3})?$/i;\nconst RGB_CHANNEL_MIN = 0;\nconst RGB_CHANNEL_MAX = 255;\nconst AA_NORMAL_TEXT_RATIO = 4.5;\nconst AA_LARGE_TEXT_RATIO = 3;\nconst AAA_NORMAL_TEXT_RATIO = 7;\nconst AAA_LARGE_TEXT_RATIO = 4.5;\n\nexport const WCAG_CONTRAST_REQUIREMENTS = Object.freeze({\n  AA: Object.freeze({\n    normal: AA_NORMAL_TEXT_RATIO,\n    large: AA_LARGE_TEXT_RATIO,\n  }),\n  AAA: Object.freeze({\n    normal: AAA_NORMAL_TEXT_RATIO,\n    large: AAA_LARGE_TEXT_RATIO,\n  }),\n});\n\nfunction hexToRgb(hex: string): [number, number, number] {\n  const normalizedHex = hex.trim().replace(/^#/, '');\n\n  if (!HEX_COLOR_PATTERN.test(normalizedHex)) {\n    throw new Error(\n      'Invalid HEX color. Use a 3 or 6 digit hexadecimal string.'\n    );\n  }\n\n  const expandedHex =\n    normalizedHex.length === 3\n      ? normalizedHex\n          .split('')\n          .map(channel => channel + channel)\n          .join('')\n      : normalizedHex;\n\n  const numericValue = Number.parseInt(expandedHex, 16);\n\n  return [\n    (numericValue >> 16) & RGB_CHANNEL_MAX,\n    (numericValue >> 8) & RGB_CHANNEL_MAX,\n    numericValue & RGB_CHANNEL_MAX,\n  ];\n}\n\nfunction normalizeRgbChannel(channel: number, index: number): number {\n  if (\n    !Number.isInteger(channel) ||\n    channel < RGB_CHANNEL_MIN ||\n    channel > RGB_CHANNEL_MAX\n  ) {\n    throw new Error(\n      'Invalid RGB color. Each channel must be an integer between 0 and 255. ' +\n        `Received ${channel} at index ${index}.`\n    );\n  }\n\n  return channel;\n}\n\nfunction normalizeRgbColor(rgbColor: RgbColor): [number, number, number] {\n  if (rgbColor.length !== 3) {\n    throw new Error('Invalid RGB color. Expected exactly 3 channels.');\n  }\n\n  return [\n    normalizeRgbChannel(rgbColor[0], 0),\n    normalizeRgbChannel(rgbColor[1], 1),\n    normalizeRgbChannel(rgbColor[2], 2),\n  ];\n}\n\nfunction parseColor(color: ColorInput): [number, number, number] {\n  return typeof color === 'string' ? hexToRgb(color) : normalizeRgbColor(color);\n}\n\nfunction luminanceChannel(channel: number): number {\n  const normalizedChannel = channel / RGB_CHANNEL_MAX;\n\n  return normalizedChannel <= 0.03928\n    ? normalizedChannel / 12.92\n    : Math.pow((normalizedChannel + 0.055) / 1.055, 2.4);\n}\n\nfunction luminance([red, green, blue]: [number, number, number]): number {\n  return (\n    luminanceChannel(red) * 0.2126 +\n    luminanceChannel(green) * 0.7152 +\n    luminanceChannel(blue) * 0.0722\n  );\n}\n\nfunction validateContrastRatioValue(contrast: number): number {\n  if (!Number.isFinite(contrast) || contrast < 1) {\n    throw new Error(\n      'Invalid contrast ratio. Expected a finite number greater than or equal to 1.'\n    );\n  }\n\n  return contrast;\n}\n\nfunction applyPrecision(value: number, precision?: number): number {\n  if (precision === undefined) {\n    return value;\n  }\n\n  if (!Number.isInteger(precision) || precision < 0 || precision > 15) {\n    throw new Error('Invalid precision. Expected an integer between 0 and 15.');\n  }\n\n  return Number(value.toFixed(precision));\n}\n\nfunction getContrastRatioValue(color1: ColorInput, color2: ColorInput): number {\n  const luminance1 = luminance(parseColor(color1));\n  const luminance2 = luminance(parseColor(color2));\n  const brightest = Math.max(luminance1, luminance2);\n  const darkest = Math.min(luminance1, luminance2);\n\n  return (brightest + 0.05) / (darkest + 0.05);\n}\n\nfunction createComplianceSummary(contrast: number): string {\n  if (contrast >= AAA_NORMAL_TEXT_RATIO) {\n    return 'WCAG 2.1 AA and AAA compliant for normal and large text';\n  }\n\n  if (contrast >= AA_NORMAL_TEXT_RATIO) {\n    return 'WCAG 2.1 AA compliant for normal text and AAA compliant for large text';\n  }\n\n  if (contrast >= AA_LARGE_TEXT_RATIO) {\n    return 'WCAG 2.1 AA compliant for large text';\n  }\n\n  return 'Not WCAG 2.1 contrast compliant';\n}\n\nfunction resolveContrastRatio(\n  contrastOrColor1: number | ColorInput,\n  color2?: ColorInput\n): number {\n  if (typeof contrastOrColor1 === 'number') {\n    return validateContrastRatioValue(contrastOrColor1);\n  }\n\n  if (color2 === undefined) {\n    throw new Error(\n      'A second color is required when checking compliance from color inputs.'\n    );\n  }\n\n  return getContrastRatioValue(contrastOrColor1, color2);\n}\n\nexport function contrastRatio(\n  color1: ColorInput,\n  color2: ColorInput,\n  options: ContrastRatioOptions = {}\n): number {\n  return applyPrecision(\n    getContrastRatioValue(color1, color2),\n    options.precision\n  );\n}\n\nexport function getWCAGCompliance(contrast: number): WCAGComplianceResult;\nexport function getWCAGCompliance(\n  color1: ColorInput,\n  color2: ColorInput\n): WCAGComplianceResult;\nexport function getWCAGCompliance(\n  contrastOrColor1: number | ColorInput,\n  color2?: ColorInput\n): WCAGComplianceResult {\n  const ratio = resolveContrastRatio(contrastOrColor1, color2);\n\n  return {\n    contrastRatio: ratio,\n    summary: createComplianceSummary(ratio),\n    aa: {\n      normalText: ratio >= AA_NORMAL_TEXT_RATIO,\n      largeText: ratio >= AA_LARGE_TEXT_RATIO,\n    },\n    aaa: {\n      normalText: ratio >= AAA_NORMAL_TEXT_RATIO,\n      largeText: ratio >= AAA_LARGE_TEXT_RATIO,\n    },\n  };\n}\n\nexport function meetsWCAG(\n  contrast: number,\n  level: WCAGLevel = 'AA',\n  textSize: TextSize = 'normal'\n): boolean {\n  const compliance = getWCAGCompliance(contrast);\n\n  if (level === 'AAA') {\n    return textSize === 'large'\n      ? compliance.aaa.largeText\n      : compliance.aaa.normalText;\n  }\n\n  return textSize === 'large'\n    ? compliance.aa.largeText\n    : compliance.aa.normalText;\n}\n\nexport function passesWCAG(contrast: number): string {\n  return getWCAGCompliance(contrast).summary;\n}\n"],"mappings":";AAsBA,MAAM,oBAAoB;AAC1B,MAAM,kBAAkB;AACxB,MAAM,kBAAkB;AACxB,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB;AAC5B,MAAM,wBAAwB;AAC9B,MAAM,uBAAuB;AAE7B,MAAa,6BAA6B,OAAO,OAAO;CACtD,IAAI,OAAO,OAAO;EAChB,QAAQ;EACR,OAAO;EACR,CAAC;CACF,KAAK,OAAO,OAAO;EACjB,QAAQ;EACR,OAAO;EACR,CAAC;CACH,CAAC;AAEF,SAAS,SAAS,KAAuC;CACvD,MAAM,gBAAgB,IAAI,MAAM,CAAC,QAAQ,MAAM,GAAG;AAElD,KAAI,CAAC,kBAAkB,KAAK,cAAc,CACxC,OAAM,IAAI,MACR,4DACD;CAGH,MAAM,cACJ,cAAc,WAAW,IACrB,cACG,MAAM,GAAG,CACT,KAAI,YAAW,UAAU,QAAQ,CACjC,KAAK,GAAG,GACX;CAEN,MAAM,eAAe,OAAO,SAAS,aAAa,GAAG;AAErD,QAAO;EACJ,gBAAgB,KAAM;EACtB,gBAAgB,IAAK;EACtB,eAAe;EAChB;;AAGH,SAAS,oBAAoB,SAAiB,OAAuB;AACnE,KACE,CAAC,OAAO,UAAU,QAAQ,IAC1B,UAAU,mBACV,UAAU,gBAEV,OAAM,IAAI,MACR,kFACc,QAAQ,YAAY,MAAM,GACzC;AAGH,QAAO;;AAGT,SAAS,kBAAkB,UAA8C;AACvE,KAAI,SAAS,WAAW,EACtB,OAAM,IAAI,MAAM,kDAAkD;AAGpE,QAAO;EACL,oBAAoB,SAAS,IAAI,EAAE;EACnC,oBAAoB,SAAS,IAAI,EAAE;EACnC,oBAAoB,SAAS,IAAI,EAAE;EACpC;;AAGH,SAAS,WAAW,OAA6C;AAC/D,QAAO,OAAO,UAAU,WAAW,SAAS,MAAM,GAAG,kBAAkB,MAAM;;AAG/E,SAAS,iBAAiB,SAAyB;CACjD,MAAM,oBAAoB,UAAU;AAEpC,QAAO,qBAAqB,SACxB,oBAAoB,QACpB,KAAK,KAAK,oBAAoB,QAAS,OAAO,IAAI;;AAGxD,SAAS,UAAU,CAAC,KAAK,OAAO,OAAyC;AACvE,QACE,iBAAiB,IAAI,GAAG,QACxB,iBAAiB,MAAM,GAAG,QAC1B,iBAAiB,KAAK,GAAG;;AAI7B,SAAS,2BAA2B,UAA0B;AAC5D,KAAI,CAAC,OAAO,SAAS,SAAS,IAAI,WAAW,EAC3C,OAAM,IAAI,MACR,+EACD;AAGH,QAAO;;AAGT,SAAS,eAAe,OAAe,WAA4B;AACjE,KAAI,cAAc,KAAA,EAChB,QAAO;AAGT,KAAI,CAAC,OAAO,UAAU,UAAU,IAAI,YAAY,KAAK,YAAY,GAC/D,OAAM,IAAI,MAAM,2DAA2D;AAG7E,QAAO,OAAO,MAAM,QAAQ,UAAU,CAAC;;AAGzC,SAAS,sBAAsB,QAAoB,QAA4B;CAC7E,MAAM,aAAa,UAAU,WAAW,OAAO,CAAC;CAChD,MAAM,aAAa,UAAU,WAAW,OAAO,CAAC;CAChD,MAAM,YAAY,KAAK,IAAI,YAAY,WAAW;CAClD,MAAM,UAAU,KAAK,IAAI,YAAY,WAAW;AAEhD,SAAQ,YAAY,QAAS,UAAU;;AAGzC,SAAS,wBAAwB,UAA0B;AACzD,KAAI,YAAY,sBACd,QAAO;AAGT,KAAI,YAAY,qBACd,QAAO;AAGT,KAAI,YAAY,oBACd,QAAO;AAGT,QAAO;;AAGT,SAAS,qBACP,kBACA,QACQ;AACR,KAAI,OAAO,qBAAqB,SAC9B,QAAO,2BAA2B,iBAAiB;AAGrD,KAAI,WAAW,KAAA,EACb,OAAM,IAAI,MACR,yEACD;AAGH,QAAO,sBAAsB,kBAAkB,OAAO;;AAGxD,SAAgB,cACd,QACA,QACA,UAAgC,EAAE,EAC1B;AACR,QAAO,eACL,sBAAsB,QAAQ,OAAO,EACrC,QAAQ,UACT;;AAQH,SAAgB,kBACd,kBACA,QACsB;CACtB,MAAM,QAAQ,qBAAqB,kBAAkB,OAAO;AAE5D,QAAO;EACL,eAAe;EACf,SAAS,wBAAwB,MAAM;EACvC,IAAI;GACF,YAAY,SAAS;GACrB,WAAW,SAAS;GACrB;EACD,KAAK;GACH,YAAY,SAAS;GACrB,WAAW,SAAS;GACrB;EACF;;AAGH,SAAgB,UACd,UACA,QAAmB,MACnB,WAAqB,UACZ;CACT,MAAM,aAAa,kBAAkB,SAAS;AAE9C,KAAI,UAAU,MACZ,QAAO,aAAa,UAChB,WAAW,IAAI,YACf,WAAW,IAAI;AAGrB,QAAO,aAAa,UAChB,WAAW,GAAG,YACd,WAAW,GAAG;;AAGpB,SAAgB,WAAW,UAA0B;AACnD,QAAO,kBAAkB,SAAS,CAAC"}