UNPKG

@test-party/contrast-color-picker

Version:

🎨 @test-party/contrast-color-picker is a lightweight, customizable color contrast picker designed for React applications. Built with accessibility in mind, it helps developers ensure compliance with WCAG color contrast standards by offering real-time fee

1 lines 121 kB
{"version":3,"file":"index.module.js","sources":["../src/hooks/useEventCallback.ts","../src/utils/clamp.ts","../src/components/common/Interactive.tsx","../src/utils/format.ts","../src/components/common/Pointer.tsx","../src/utils/round.ts","../src/utils/convert.ts","../src/components/common/Hue.tsx","../src/components/common/Saturation.tsx","../src/utils/compare.ts","../src/hooks/useColorManipulation.ts","../src/hooks/useIsomorphicLayoutEffect.ts","../src/utils/nonce.ts","../src/hooks/useStyleSheet.ts","../src/components/common/ContrastWrapper.tsx","../src/components/common/ColorPicker.tsx","../src/utils/validate.ts","../src/components/common/ColorInput.tsx","../src/components/HexColorInput.tsx","../src/components/HexColorPicker.tsx","../src/components/common/Alpha.tsx","../src/components/common/AlphaColorPicker.tsx","../src/components/HexAlphaColorPicker.tsx","../src/components/HslaColorInput.tsx","../src/components/HslaColorPicker.tsx","../src/components/HslaStringColorInput.tsx","../src/components/HslaStringColorPicker.tsx","../src/components/HslColorInput.tsx","../src/components/HslColorPicker.tsx","../src/components/HslStringColorInput.tsx","../src/components/HslStringColorPicker.tsx","../src/components/HsvaColorInput.tsx","../src/components/HsvaColorPicker.tsx","../src/components/HsvaStringColorInput.tsx","../src/components/HsvaStringColorPicker.tsx","../src/components/HsvColorInput.tsx","../src/components/HsvColorPicker.tsx","../src/components/HsvStringColorInput.tsx","../src/components/HsvStringColorPicker.tsx","../src/components/RgbaColorInput.tsx","../src/components/RgbaColorPicker.tsx","../src/components/RgbaStringColorInput.tsx","../src/components/RgbaStringColorPicker.tsx","../src/components/RgbColorInput.tsx","../src/components/RgbColorPicker.tsx","../src/components/RgbStringColorInput.tsx","../src/components/RgbStringColorPicker.tsx"],"sourcesContent":["import { useRef } from \"react\";\n\n// Saves incoming handler to the ref in order to avoid \"useCallback hell\"\nexport function useEventCallback<T>(handler?: (value: T) => void): (value: T) => void {\n const callbackRef = useRef(handler);\n const fn = useRef((value: T) => {\n callbackRef.current && callbackRef.current(value);\n });\n callbackRef.current = handler;\n\n return fn.current;\n}\n","// Clamps a value between an upper and lower bound.\n// We use ternary operators because it makes the minified code\n// 2 times shorter then `Math.min(Math.max(a,b),c)`\nexport const clamp = (number: number, min = 0, max = 1): number => {\n return number > max ? max : number < min ? min : number;\n};\n","import React, { useRef, useMemo, useEffect } from \"react\";\n\nimport { useEventCallback } from \"../../hooks/useEventCallback\";\nimport { clamp } from \"../../utils/clamp\";\n\nexport interface Interaction {\n left: number;\n top: number;\n}\n\n// Check if an event was triggered by touch\nconst isTouch = (event: MouseEvent | TouchEvent): event is TouchEvent => \"touches\" in event;\n\n// Finds a proper touch point by its identifier\nconst getTouchPoint = (touches: TouchList, touchId: null | number): Touch => {\n for (let i = 0; i < touches.length; i++) {\n if (touches[i].identifier === touchId) return touches[i];\n }\n return touches[0];\n};\n\n// Finds the proper window object to fix iframe embedding issues\nconst getParentWindow = (node?: HTMLDivElement | null): Window => {\n return (node && node.ownerDocument.defaultView) || self;\n};\n\n// Returns a relative position of the pointer inside the node's bounding box\nconst getRelativePosition = (\n node: HTMLDivElement,\n event: MouseEvent | TouchEvent,\n touchId: null | number\n): Interaction => {\n const rect = node.getBoundingClientRect();\n\n // Get user's pointer position from `touches` array if it's a `TouchEvent`\n const pointer = isTouch(event) ? getTouchPoint(event.touches, touchId) : (event as MouseEvent);\n\n return {\n left: clamp((pointer.pageX - (rect.left + getParentWindow(node).pageXOffset)) / rect.width),\n top: clamp((pointer.pageY - (rect.top + getParentWindow(node).pageYOffset)) / rect.height),\n };\n};\n\n// Browsers introduced an intervention, making touch events passive by default.\n// This workaround removes `preventDefault` call from the touch handlers.\n// https://github.com/facebook/react/issues/19651\nconst preventDefaultMove = (event: MouseEvent | TouchEvent): void => {\n !isTouch(event) && event.preventDefault();\n};\n\n// Prevent mobile browsers from handling mouse events (conflicting with touch ones).\n// If we detected a touch interaction before, we prefer reacting to touch events only.\nconst isInvalid = (event: MouseEvent | TouchEvent, hasTouch: boolean): boolean => {\n return hasTouch && !isTouch(event);\n};\n\ninterface Props {\n onMove: (interaction: Interaction) => void;\n onKey: (offset: Interaction) => void;\n children: React.ReactNode;\n}\n\nconst InteractiveBase = ({ onMove, onKey, ...rest }: Props) => {\n const container = useRef<HTMLDivElement>(null);\n const onMoveCallback = useEventCallback<Interaction>(onMove);\n const onKeyCallback = useEventCallback<Interaction>(onKey);\n const touchId = useRef<null | number>(null);\n const hasTouch = useRef(false);\n\n const [handleMoveStart, handleKeyDown, toggleDocumentEvents] = useMemo(() => {\n const handleMoveStart = ({ nativeEvent }: React.MouseEvent | React.TouchEvent) => {\n const el = container.current;\n if (!el) return;\n\n // Prevent text selection\n preventDefaultMove(nativeEvent);\n\n if (isInvalid(nativeEvent, hasTouch.current) || !el) return;\n\n if (isTouch(nativeEvent)) {\n hasTouch.current = true;\n const changedTouches = nativeEvent.changedTouches || [];\n if (changedTouches.length) touchId.current = changedTouches[0].identifier;\n }\n\n el.focus();\n onMoveCallback(getRelativePosition(el, nativeEvent, touchId.current));\n toggleDocumentEvents(true);\n };\n\n const handleMove = (event: MouseEvent | TouchEvent) => {\n // Prevent text selection\n preventDefaultMove(event);\n\n // If user moves the pointer outside of the window or iframe bounds and release it there,\n // `mouseup`/`touchend` won't be fired. In order to stop the picker from following the cursor\n // after the user has moved the mouse/finger back to the document, we check `event.buttons`\n // and `event.touches`. It allows us to detect that the user is just moving his pointer\n // without pressing it down\n const isDown = isTouch(event) ? event.touches.length > 0 : event.buttons > 0;\n\n if (isDown && container.current) {\n onMoveCallback(getRelativePosition(container.current, event, touchId.current));\n } else {\n toggleDocumentEvents(false);\n }\n };\n\n const handleMoveEnd = () => toggleDocumentEvents(false);\n\n const handleKeyDown = (event: React.KeyboardEvent) => {\n const keyCode = event.which || event.keyCode;\n\n // Ignore all keys except arrow ones\n if (keyCode < 37 || keyCode > 40) return;\n // Do not scroll page by arrow keys when document is focused on the element\n event.preventDefault();\n // Send relative offset to the parent component.\n // We use codes (37←, 38↑, 39→, 40↓) instead of keys ('ArrowRight', 'ArrowDown', etc)\n // to reduce the size of the library\n onKeyCallback({\n left: keyCode === 39 ? 0.05 : keyCode === 37 ? -0.05 : 0,\n top: keyCode === 40 ? 0.05 : keyCode === 38 ? -0.05 : 0,\n });\n };\n\n function toggleDocumentEvents(state?: boolean) {\n const touch = hasTouch.current;\n const el = container.current;\n const parentWindow = getParentWindow(el);\n\n // Add or remove additional pointer event listeners\n const toggleEvent = state ? parentWindow.addEventListener : parentWindow.removeEventListener;\n toggleEvent(touch ? \"touchmove\" : \"mousemove\", handleMove);\n toggleEvent(touch ? \"touchend\" : \"mouseup\", handleMoveEnd);\n }\n\n return [handleMoveStart, handleKeyDown, toggleDocumentEvents];\n }, [onKeyCallback, onMoveCallback]);\n\n // Remove window event listeners before unmounting\n useEffect(() => toggleDocumentEvents, [toggleDocumentEvents]);\n\n return (\n <div\n {...rest}\n onTouchStart={handleMoveStart}\n onMouseDown={handleMoveStart}\n className=\"react-colorful__interactive\"\n ref={container}\n onKeyDown={handleKeyDown}\n tabIndex={0}\n role=\"slider\"\n />\n );\n};\n\nexport const Interactive = React.memo(InteractiveBase);\n","export const formatClassName = (names: unknown[]): string => names.filter(Boolean).join(\" \");\n","import React from \"react\";\nimport { formatClassName } from \"../../utils/format\";\n\ninterface Props {\n className?: string;\n top?: number;\n left: number;\n color: string;\n}\n\nexport const Pointer = ({ className, color, left, top = 0.5 }: Props): JSX.Element => {\n const nodeClassName = formatClassName([\"react-colorful__pointer\", className]);\n\n const style = {\n top: `${top * 100}%`,\n left: `${left * 100}%`,\n };\n\n return (\n <div className={nodeClassName} style={style}>\n <div className=\"react-colorful__pointer-fill\" style={{ backgroundColor: color }} />\n </div>\n );\n};\n","export const round = (number: number, digits = 0, base = Math.pow(10, digits)): number => {\n return Math.round(base * number) / base;\n};\n","import { round } from \"./round\";\nimport { RgbaColor, RgbColor, HslaColor, HslColor, HsvaColor, HsvColor } from \"../types\";\n\n/**\n * Valid CSS <angle> units.\n * https://developer.mozilla.org/en-US/docs/Web/CSS/angle\n */\nconst angleUnits: Record<string, number> = {\n grad: 360 / 400,\n turn: 360,\n rad: 360 / (Math.PI * 2),\n};\n\nexport const hexToHsva = (hex: string): HsvaColor => rgbaToHsva(hexToRgba(hex));\n\nexport const hexToRgba = (hex: string): RgbaColor => {\n if (hex[0] === \"#\") hex = hex.substring(1);\n\n if (hex.length < 6) {\n return {\n r: parseInt(hex[0] + hex[0], 16),\n g: parseInt(hex[1] + hex[1], 16),\n b: parseInt(hex[2] + hex[2], 16),\n a: hex.length === 4 ? round(parseInt(hex[3] + hex[3], 16) / 255, 2) : 1,\n };\n }\n\n return {\n r: parseInt(hex.substring(0, 2), 16),\n g: parseInt(hex.substring(2, 4), 16),\n b: parseInt(hex.substring(4, 6), 16),\n a: hex.length === 8 ? round(parseInt(hex.substring(6, 8), 16) / 255, 2) : 1,\n };\n};\n\nexport const parseHue = (value: string, unit = \"deg\"): number => {\n return Number(value) * (angleUnits[unit] || 1);\n};\n\nexport const hslaStringToHsva = (hslString: string): HsvaColor => {\n const matcher = /hsla?\\(?\\s*(-?\\d*\\.?\\d+)(deg|rad|grad|turn)?[,\\s]+(-?\\d*\\.?\\d+)%?[,\\s]+(-?\\d*\\.?\\d+)%?,?\\s*[/\\s]*(-?\\d*\\.?\\d+)?(%)?\\s*\\)?/i;\n const match = matcher.exec(hslString);\n\n if (!match) return { h: 0, s: 0, v: 0, a: 1 };\n\n return hslaToHsva({\n h: parseHue(match[1], match[2]),\n s: Number(match[3]),\n l: Number(match[4]),\n a: match[5] === undefined ? 1 : Number(match[5]) / (match[6] ? 100 : 1),\n });\n};\n\nexport const hslaStringToHsla = (hslaString: string): HslaColor => {\n const matcher = /hsla?\\(?\\s*(-?\\d*\\.?\\d+)(deg|rad|grad|turn)?[,\\s]+(-?\\d*\\.?\\d+)%?[,\\s]+(-?\\d*\\.?\\d+)%?,?\\s*[/\\s]*(-?\\d*\\.?\\d+)?(%)?\\s*\\)?/i;\n const match = matcher.exec(hslaString);\n\n if (!match) return { h: 0, s: 0, l: 0, a: 1 };\n\n return {\n h: parseHue(match[1], match[2]),\n s: Number(match[3]),\n l: Number(match[4]),\n a: match[5] === undefined ? 1 : Number(match[5]) / (match[6] ? 100 : 1),\n };\n};\n\nexport const hslaToHslaString = (hsla: HslaColor): string => {\n const { h, s, l, a } = hsla;\n return `hsla(${h}, ${s}%, ${l}%, ${a})`;\n};\n\nexport const hslToHslString = (hsl: HslColor): string => {\n const { h, s, l } = hsl;\n return `hsla(${h}, ${s}%, ${l}%)`;\n};\n\nexport const hslStringToHsva = hslaStringToHsva;\n\nexport const hslStringToHsl = hslaStringToHsla;\n\nexport const hslaToHsva = ({ h, s, l, a }: HslaColor): HsvaColor => {\n s *= (l < 50 ? l : 100 - l) / 100;\n\n return {\n h: h,\n s: s > 0 ? ((2 * s) / (l + s)) * 100 : 0,\n v: l + s,\n a,\n };\n};\n\nexport const hsvaToHex = (hsva: HsvaColor): string => rgbaToHex(hsvaToRgba(hsva));\n\nexport const hsvaToHsla = ({ h, s, v, a }: HsvaColor): HslaColor => {\n const hh = ((200 - s) * v) / 100;\n\n return {\n h: round(h),\n s: round(hh > 0 && hh < 200 ? ((s * v) / 100 / (hh <= 100 ? hh : 200 - hh)) * 100 : 0),\n l: round(hh / 2),\n a: round(a, 2),\n };\n};\n\nexport const hsvaToHslString = (hsva: HsvaColor): string => {\n const { h, s, l } = hsvaToHsla(hsva);\n return `hsl(${h}, ${s}%, ${l}%)`;\n};\n\nexport const hsvaToHsvString = (hsva: HsvaColor): string => {\n const { h, s, v } = roundHsva(hsva);\n return `hsv(${h}, ${s}%, ${v}%)`;\n};\n\nexport const hsvaToHsvaString = (hsva: HsvaColor): string => {\n const { h, s, v, a } = roundHsva(hsva);\n return `hsva(${h}, ${s}%, ${v}%, ${a})`;\n};\n\nexport const hsvaToHslaString = (hsva: HsvaColor): string => {\n const { h, s, l, a } = hsvaToHsla(hsva);\n return `hsla(${h}, ${s}%, ${l}%, ${a})`;\n};\n\nexport const hsvaToRgba = ({ h, s, v, a }: HsvaColor): RgbaColor => {\n h = (h / 360) * 6;\n s = s / 100;\n v = v / 100;\n\n const hh = Math.floor(h),\n b = v * (1 - s),\n c = v * (1 - (h - hh) * s),\n d = v * (1 - (1 - h + hh) * s),\n module = hh % 6;\n\n return {\n r: round([v, c, b, b, d, v][module] * 255),\n g: round([d, v, v, c, b, b][module] * 255),\n b: round([b, b, d, v, v, c][module] * 255),\n a: round(a, 2),\n };\n};\n\nexport const hsvaToRgbString = (hsva: HsvaColor): string => {\n const { r, g, b } = hsvaToRgba(hsva);\n return `rgb(${r}, ${g}, ${b})`;\n};\n\nexport const hsvaToRgbaString = (hsva: HsvaColor): string => {\n const { r, g, b, a } = hsvaToRgba(hsva);\n return `rgba(${r}, ${g}, ${b}, ${a})`;\n};\n\nexport const hsvaStringToHsva = (hsvString: string): HsvaColor => {\n const matcher = /hsva?\\(?\\s*(-?\\d*\\.?\\d+)(deg|rad|grad|turn)?[,\\s]+(-?\\d*\\.?\\d+)%?[,\\s]+(-?\\d*\\.?\\d+)%?,?\\s*[/\\s]*(-?\\d*\\.?\\d+)?(%)?\\s*\\)?/i;\n const match = matcher.exec(hsvString);\n\n if (!match) return { h: 0, s: 0, v: 0, a: 1 };\n\n return roundHsva({\n h: parseHue(match[1], match[2]),\n s: Number(match[3]),\n v: Number(match[4]),\n a: match[5] === undefined ? 1 : Number(match[5]) / (match[6] ? 100 : 1),\n });\n};\n\nexport const hsvStringToHsva = hsvaStringToHsva;\n\nexport const hsvStringToHsv = (hsvString: string): HsvColor => {\n const matcher = /hsva?\\(?\\s*(-?\\d*\\.?\\d+)(deg|rad|grad|turn)?[,\\s]+(-?\\d*\\.?\\d+)%?[,\\s]+(-?\\d*\\.?\\d+)%?,?\\s*[/\\s]*(-?\\d*\\.?\\d+)?(%)?\\s*\\)?/i;\n const match = matcher.exec(hsvString);\n\n if (!match) return { h: 0, s: 0, v: 0 };\n\n return roundHsv({\n h: parseHue(match[1], match[2]),\n s: Number(match[3]),\n v: Number(match[4])\n });\n};\n\nexport const hsvToHsvString = (hsv: HsvColor): string => {\n const { h, s, v } = roundHsv(hsv);\n return `hsv(${h}, ${s}%, ${v}%)`;\n};\n\nexport const rgbaStringToHsva = (rgbaString: string): HsvaColor => {\n const matcher = /rgba?\\(?\\s*(-?\\d*\\.?\\d+)(%)?[,\\s]+(-?\\d*\\.?\\d+)(%)?[,\\s]+(-?\\d*\\.?\\d+)(%)?,?\\s*[/\\s]*(-?\\d*\\.?\\d+)?(%)?\\s*\\)?/i;\n const match = matcher.exec(rgbaString);\n\n if (!match) return { h: 0, s: 0, v: 0, a: 1 };\n\n return rgbaToHsva({\n r: Number(match[1]) / (match[2] ? 100 / 255 : 1),\n g: Number(match[3]) / (match[4] ? 100 / 255 : 1),\n b: Number(match[5]) / (match[6] ? 100 / 255 : 1),\n a: match[7] === undefined ? 1 : Number(match[7]) / (match[8] ? 100 : 1),\n });\n};\n\nexport const rgbaStringToRgba = (rgbaString: string): RgbaColor => {\n const matcher = /rgba?\\(?\\s*(-?\\d*\\.?\\d+)(%)?[,\\s]+(-?\\d*\\.?\\d+)(%)?[,\\s]+(-?\\d*\\.?\\d+)(%)?,?\\s*[/\\s]*(-?\\d*\\.?\\d+)?(%)?\\s*\\)?/i;\n const match = matcher.exec(rgbaString);\n\n if (!match) return { r: 0, g: 0, b: 0, a: 1 };\n\n return {\n r: Number(match[1]) / (match[2] ? 100 / 255 : 1),\n g: Number(match[3]) / (match[4] ? 100 / 255 : 1),\n b: Number(match[5]) / (match[6] ? 100 / 255 : 1),\n a: match[7] === undefined ? 1 : Number(match[7]) / (match[8] ? 100 : 1),\n };\n};\n\nexport const rgbaToRgbaString = (rgba: RgbaColor): string => {\n const { r, g, b, a } = rgba;\n return `rgba(${r}, ${g}, ${b}, ${a})`;\n};\n\nexport const rgbStringToHsva = rgbaStringToHsva;\n\nexport const rgbStringToRgb = (rgbString: string): RgbColor => {\n const matcher = /rgba?\\(?\\s*(-?\\d*\\.?\\d+)(%)?[,\\s]+(-?\\d*\\.?\\d+)(%)?[,\\s]+(-?\\d*\\.?\\d+)(%)?,?\\s*[/\\s]*(-?\\d*\\.?\\d+)?(%)?\\s*\\)?/i;\n const match = matcher.exec(rgbString);\n\n if (!match) return { r: 0, g: 0, b: 0 };\n\n return {\n r: Number(match[1]) / (match[2] ? 100 / 255 : 1),\n g: Number(match[3]) / (match[4] ? 100 / 255 : 1),\n b: Number(match[5]) / (match[6] ? 100 / 255 : 1)\n };\n};\n\nexport const rgbToRgbString = (rgb: RgbColor): string => {\n const { r, g, b } = rgb;\n return `rgb(${r}, ${g}, ${b})`;\n};\n\nconst format = (number: number) => {\n const hex = number.toString(16);\n return hex.length < 2 ? \"0\" + hex : hex;\n};\n\nexport const rgbaToHex = ({ r, g, b, a }: RgbaColor): string => {\n const alphaHex = a < 1 ? format(round(a * 255)) : \"\";\n return \"#\" + format(r) + format(g) + format(b) + alphaHex;\n};\n\nexport const rgbaToHsva = ({ r, g, b, a }: RgbaColor): HsvaColor => {\n const max = Math.max(r, g, b);\n const delta = max - Math.min(r, g, b);\n\n // prettier-ignore\n const hh = delta\n ? max === r\n ? (g - b) / delta\n : max === g\n ? 2 + (b - r) / delta\n : 4 + (r - g) / delta\n : 0;\n\n return {\n h: round(60 * (hh < 0 ? hh + 6 : hh)),\n s: round(max ? (delta / max) * 100 : 0),\n v: round((max / 255) * 100),\n a,\n };\n};\n\nexport const roundHsva = (hsva: HsvaColor): HsvaColor => ({\n h: round(hsva.h),\n s: round(hsva.s),\n v: round(hsva.v),\n a: round(hsva.a, 2),\n});\n\nexport const roundHsv = (hsva: HsvColor): HsvColor => ({\n h: round(hsva.h),\n s: round(hsva.s),\n v: round(hsva.v)\n});\n\nexport const rgbaToRgb = ({ r, g, b }: RgbaColor): RgbColor => ({ r, g, b });\n\nexport const hslaToHsl = ({ h, s, l }: HslaColor): HslColor => ({ h, s, l });\n\nexport const hsvaToHsv = (hsva: HsvaColor): HsvColor => {\n const { h, s, v } = roundHsva(hsva);\n return { h, s, v };\n};\n","import React from \"react\";\n\nimport { Interactive, Interaction } from \"./Interactive\";\nimport { Pointer } from \"./Pointer\";\n\nimport { hsvaToHslString } from \"../../utils/convert\";\nimport { formatClassName } from \"../../utils/format\";\nimport { clamp } from \"../../utils/clamp\";\nimport { round } from \"../../utils/round\";\n\ninterface Props {\n className?: string;\n hue: number;\n onChange: (newHue: { h: number }) => void;\n}\n\nconst HueBase = ({ className, hue, onChange }: Props) => {\n const handleMove = (interaction: Interaction) => {\n onChange({ h: 360 * interaction.left });\n };\n\n const handleKey = (offset: Interaction) => {\n // Hue measured in degrees of the color circle ranging from 0 to 360\n onChange({\n h: clamp(hue + offset.left * 360, 0, 360),\n });\n };\n\n const nodeClassName = formatClassName([\"react-colorful__hue\", className]);\n\n return (\n <div className={nodeClassName}>\n <Interactive\n onMove={handleMove}\n onKey={handleKey}\n aria-label=\"Hue\"\n aria-valuenow={round(hue)}\n aria-valuemax=\"360\"\n aria-valuemin=\"0\"\n >\n <Pointer\n className=\"react-colorful__hue-pointer\"\n left={hue / 360}\n color={hsvaToHslString({ h: hue, s: 100, v: 100, a: 1 })}\n />\n </Interactive>\n </div>\n );\n};\n\nexport const Hue = React.memo(HueBase);\n","import React from \"react\";\nimport { Interactive, Interaction } from \"./Interactive\";\nimport { Pointer } from \"./Pointer\";\nimport { HsvaColor } from \"../../types\";\nimport { hsvaToHslString } from \"../../utils/convert\";\nimport { clamp } from \"../../utils/clamp\";\nimport { round } from \"../../utils/round\";\n\ninterface Props {\n hsva: HsvaColor;\n onChange: (newColor: { s: number; v: number }) => void;\n}\n\nconst SaturationBase = ({ hsva, onChange }: Props) => {\n const handleMove = (interaction: Interaction) => {\n onChange({\n s: interaction.left * 100,\n v: 100 - interaction.top * 100,\n });\n };\n\n const handleKey = (offset: Interaction) => {\n // Saturation and brightness always fit into [0, 100] range\n onChange({\n s: clamp(hsva.s + offset.left * 100, 0, 100),\n v: clamp(hsva.v - offset.top * 100, 0, 100),\n });\n };\n\n const containerStyle = {\n backgroundColor: hsvaToHslString({ h: hsva.h, s: 100, v: 100, a: 1 }),\n };\n\n return (\n <div className=\"react-colorful__saturation\" style={containerStyle}>\n <Interactive\n onMove={handleMove}\n onKey={handleKey}\n aria-label=\"Color\"\n aria-valuetext={`Saturation ${round(hsva.s)}%, Brightness ${round(hsva.v)}%`}\n >\n <Pointer\n className=\"react-colorful__saturation-pointer\"\n top={1 - hsva.v / 100}\n left={hsva.s / 100}\n color={hsvaToHslString(hsva)}\n />\n </Interactive>\n </div>\n );\n};\n\nexport const Saturation = React.memo(SaturationBase);\n","import { hexToRgba } from \"./convert\";\nimport { ObjectColor } from \"../types\";\n\nexport const equalColorObjects = (first: ObjectColor, second: ObjectColor): boolean => {\n if (first === second) return true;\n\n for (const prop in first) {\n // The following allows for a type-safe calling of this function (first & second have to be HSL, HSV, or RGB)\n // with type-unsafe iterating over object keys. TS does not allow this without an index (`[key: string]: number`)\n // on an object to define how iteration is normally done. To ensure extra keys are not allowed on our types,\n // we must cast our object to unknown (as RGB demands `r` be a key, while `Record<string, x>` does not care if\n // there is or not), and then as a type TS can iterate over.\n if (\n ((first as unknown) as Record<string, number>)[prop] !==\n ((second as unknown) as Record<string, number>)[prop]\n )\n return false;\n }\n\n return true;\n};\n\nexport const equalColorString = (first: string, second: string): boolean => {\n return first.replace(/\\s/g, \"\") === second.replace(/\\s/g, \"\");\n};\n\nexport const equalHex = (first: string, second: string): boolean => {\n if (first.toLowerCase() === second.toLowerCase()) return true;\n\n // To compare colors like `#FFF` and `ffffff` we convert them into RGB objects\n return equalColorObjects(hexToRgba(first), hexToRgba(second));\n};\n","import { useState, useEffect, useCallback, useRef } from \"react\";\nimport { ColorModel, AnyColor, HsvaColor } from \"../types\";\nimport { equalColorObjects } from \"../utils/compare\";\nimport { useEventCallback } from \"./useEventCallback\";\n\nexport function useColorManipulation<T extends AnyColor>(\n colorModel: ColorModel<T>,\n color: T,\n onChange?: (color: T) => void\n): [HsvaColor, (color: Partial<HsvaColor>) => void] {\n // Save onChange callback in the ref for avoiding \"useCallback hell\"\n const onChangeCallback = useEventCallback<T>(onChange);\n\n // No matter which color model is used (HEX, RGB(A) or HSL(A)),\n // all internal calculations are based on HSVA model\n const [hsva, updateHsva] = useState<HsvaColor>(() => colorModel.toHsva(color));\n\n // By using this ref we're able to prevent extra updates\n // and the effects recursion during the color conversion\n const cache = useRef({ color, hsva });\n\n // Update local HSVA-value if `color` property value is changed,\n // but only if that's not the same color that we just sent to the parent\n useEffect(() => {\n if (!colorModel.equal(color, cache.current.color)) {\n const newHsva = colorModel.toHsva(color);\n cache.current = { hsva: newHsva, color };\n updateHsva(newHsva);\n }\n }, [color, colorModel]);\n\n // Trigger `onChange` callback only if an updated color is different from cached one;\n // save the new color to the ref to prevent unnecessary updates\n useEffect(() => {\n let newColor;\n if (\n !equalColorObjects(hsva, cache.current.hsva) &&\n !colorModel.equal((newColor = colorModel.fromHsva(hsva)), cache.current.color)\n ) {\n cache.current = { hsva, color: newColor };\n onChangeCallback(newColor);\n }\n }, [hsva, colorModel, onChangeCallback]);\n\n // Merge the current HSVA color object with updated params.\n // For example, when a child component sends `h` or `s` only\n const handleChange = useCallback((params: Partial<HsvaColor>) => {\n updateHsva((current) => Object.assign({}, current, params));\n }, []);\n\n return [hsva, handleChange];\n}\n","import { useLayoutEffect, useEffect } from \"react\";\n\n// React currently throws a warning when using useLayoutEffect on the server.\n// To get around it, we can conditionally useEffect on the server (no-op) and\n// useLayoutEffect in the browser.\nexport const useIsomorphicLayoutEffect =\n typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n","declare const __webpack_nonce__: string | undefined;\nlet nonce: string | undefined;\n\n/**\n * Returns a nonce hash included by Webpack or the one defined manually by developer.\n * https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce\n * https://webpack.js.org/guides/csp/\n */\nexport const getNonce = (): string | undefined => {\n if (nonce) return nonce;\n if (typeof __webpack_nonce__ !== \"undefined\") return __webpack_nonce__;\n return undefined;\n};\n\n/**\n * Signs the style tag with a base64-encoded string (nonce) to conforms to Content Security Policies.\n * This function has to be invoked before any picker is rendered if you aren't using Webpack for CSP.\n */\nexport const setNonce = (hash: string): void => {\n nonce = hash;\n};\n","import { RefObject } from \"react\";\n\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect\";\nimport { getNonce } from \"../utils/nonce\";\n\n// Bundler is configured to load this as a processed minified CSS-string\nimport styles from \"../css/styles.css\";\n\nconst styleElementMap: Map<Document, HTMLStyleElement> = new Map();\n\n/**\n * Injects CSS code into the document's <head>\n */\nexport const useStyleSheet = (nodeRef: RefObject<HTMLDivElement>): void => {\n useIsomorphicLayoutEffect(() => {\n const parentDocument = nodeRef.current ? nodeRef.current.ownerDocument : document;\n\n if (typeof parentDocument !== \"undefined\" && !styleElementMap.has(parentDocument)) {\n const styleElement = parentDocument.createElement(\"style\");\n styleElement.innerHTML = styles;\n styleElementMap.set(parentDocument, styleElement);\n\n // Conform to CSP rules by setting `nonce` attribute to the inline styles\n const nonce = getNonce();\n if (nonce) styleElement.setAttribute(\"nonce\", nonce);\n\n parentDocument.head.appendChild(styleElement);\n }\n }, []);\n};\n","import React, { useState, useEffect } from \"react\";\n\nimport { HsvaColor } from \"../../types\";\nimport chroma from \"chroma-js\";\n\ninterface ContrastWrapperProps {\n foregroundColor: HsvaColor;\n backgroundColor: HsvaColor;\n children: React.ReactNode;\n}\n\nexport const ContrastWrapper = ({ children, foregroundColor, backgroundColor }: ContrastWrapperProps): JSX.Element => {\n const [contrastRatio, setContrastRatio] = useState<number>(0)\n const [pass, setPass] = useState<boolean>(false)\n\n const hsvaToChromaColor = (hsva: HsvaColor): chroma.Color => {\n const { h, s, v, a } = hsva\n return chroma.hsv(h, s / 100, v / 100).alpha(a)\n }\n\n const blendColors = (fg: chroma.Color, bg: chroma.Color): chroma.Color => {\n const alpha = fg.alpha()\n const fgRgb = fg.rgb()\n const bgRgb = bg.rgb()\n const blendedRgb = fgRgb.map((channel, i) => {\n return channel * alpha + bgRgb[i] * (1 - alpha)\n })\n return chroma(blendedRgb)\n }\n\n const getContrastCompliance = (ratio: number): { level: string; pass: boolean } => {\n if (ratio >= 7) return { level: 'AAA (Highest)', pass: true }\n if (ratio >= 4.5) return { level: 'AA (Good)', pass: true }\n // if (ratio >= 3) return { level: 'AA (Large Text)', pass: true }\n return { level: 'Fail', pass: false }\n }\n\n useEffect(() => {\n const foregroundColorChroma = hsvaToChromaColor(foregroundColor)\n const backgroundColorChroma = hsvaToChromaColor(backgroundColor)\n\n const blendedForeground = blendColors(foregroundColorChroma, backgroundColorChroma)\n const ratio = chroma.contrast(blendedForeground, backgroundColorChroma)\n const { level, pass } = getContrastCompliance(ratio)\n\n setContrastRatio(ratio)\n setPass(pass)\n\n // Debugging log\n console.log(\"updated version\")\n console.log('Foreground HSVA:', foregroundColor)\n console.log('Background HSVA:', backgroundColor)\n console.log('Contrast Ratio:', ratio)\n console.log('Compliance Level:', level)\n console.log('Pass:', pass)\n }, [foregroundColor, backgroundColor])\n\n return (\n <div className=\"contrast-color-picker\">\n <div className=\"color-picker-container\">\n {children}\n </div>\n\n <div className=\"contrast-analysis\">\n <div className={`contrast-analysis-section ${pass ? 'result-pass' : 'result-fail'}`}>\n <span>Contrast Ratio</span>\n <p>{pass ? 'Pass' : 'Fail'}</p>\n </div>\n </div>\n </div>\n )\n}","import React, { useRef } from \"react\";\n\nimport { Hue } from \"./Hue\";\nimport { Saturation } from \"./Saturation\";\n\nimport { ColorModel, ColorPickerBaseProps, AnyColor } from \"../../types\";\nimport { useColorManipulation } from \"../../hooks/useColorManipulation\";\nimport { useStyleSheet } from \"../../hooks/useStyleSheet\";\nimport { formatClassName } from \"../../utils/format\";\nimport { ContrastWrapper } from \"./ContrastWrapper\";\n\ninterface Props<T extends AnyColor> extends Partial<ColorPickerBaseProps<T>> {\n colorModel: ColorModel<T>;\n children?: React.ReactNode;\n}\n\nexport const ColorPicker = <T extends AnyColor>({\n className,\n colorModel,\n color = colorModel.defaultColor,\n backgroundColor = colorModel.defaultBackgroundColor,\n children,\n onChange,\n ...rest\n}: Props<T>): JSX.Element => {\n const nodeRef = useRef<HTMLDivElement>(null);\n useStyleSheet(nodeRef);\n\n const [hsva, updateHsva] = useColorManipulation<T>(colorModel, color, onChange);\n const [backgroundHsva, updateBackgroundHsva] = useColorManipulation<T>(colorModel, backgroundColor, onChange);\n\n const nodeClassName = formatClassName([\"react-colorful\", className]);\n\n return (\n <ContrastWrapper foregroundColor={hsva} backgroundColor={backgroundHsva}>\n <div {...rest} ref={nodeRef} className={nodeClassName}>\n <Saturation hsva={hsva} onChange={updateHsva} />\n <Hue hue={hsva.h} onChange={updateHsva} className=\"react-colorful__last-control\" />\n </div>\n { children }\n </ContrastWrapper>\n );\n};\n","const matcher = /^#?([0-9A-F]{3,8})$/i;\n\nexport const validHex = (value: string, alpha?: boolean): boolean => {\n const match = matcher.exec(value);\n const length = match ? match[1].length : 0;\n\n return (\n length === 3 || // '#rgb' format\n length === 6 || // '#rrggbb' format\n (!!alpha && length === 4) || // '#rgba' format\n (!!alpha && length === 8) // '#rrggbbaa' format\n );\n};\n","import React, { useState, useEffect, useCallback } from \"react\";\n\nimport { useEventCallback } from \"../../hooks/useEventCallback\";\nimport { ColorInputBaseProps } from \"../../types\";\n\ninterface Props extends ColorInputBaseProps {\n /** Blocks typing invalid characters and limits string length */\n escape: (value: string) => string;\n /** Checks that value is valid color string */\n validate: (value: string) => boolean;\n /** Processes value before displaying it in the input */\n format?: (value: string) => string;\n /** Processes value before sending it in `onChange` */\n process?: (value: string) => string;\n}\n\nexport const ColorInput = (props: Props): JSX.Element => {\n const { color = \"\", onChange, onBlur, escape, validate, format, process, ...rest } = props;\n const [value, setValue] = useState(() => escape(color));\n const onChangeCallback = useEventCallback<string>(onChange);\n const onBlurCallback = useEventCallback<React.FocusEvent<HTMLInputElement>>(onBlur);\n\n // Trigger `onChange` handler only if the input value is a valid color\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const inputValue = escape(e.target.value);\n setValue(inputValue);\n if (validate(inputValue)) onChangeCallback(process ? process(inputValue) : inputValue);\n },\n [escape, process, validate, onChangeCallback]\n );\n\n // Take the color from props if the last typed color (in local state) is not valid\n const handleBlur = useCallback(\n (e: React.FocusEvent<HTMLInputElement>) => {\n if (!validate(e.target.value)) setValue(escape(color));\n onBlurCallback(e);\n },\n [color, escape, validate, onBlurCallback]\n );\n\n // Update the local state when `color` property value is changed\n useEffect(() => {\n setValue(escape(color));\n }, [color, escape]);\n\n return (\n <input\n {...rest}\n className=\"color-input\"\n value={format ? format(value) : value}\n spellCheck=\"false\" // the element should not be checked for spelling errors\n onChange={handleChange}\n onBlur={handleBlur}\n />\n );\n};\n","import React, { useCallback } from \"react\";\nimport { ColorInputBaseProps } from \"../types\";\n\nimport { validHex } from \"../utils/validate\";\nimport { ColorInput } from \"./common/ColorInput\";\n\ninterface HexColorInputProps extends ColorInputBaseProps {\n /** Enables `#` prefix displaying */\n prefixed?: boolean;\n /** Allows `#rgba` and `#rrggbbaa` color formats */\n alpha?: boolean;\n label: string;\n}\n\n/** Adds \"#\" symbol to the beginning of the string */\nconst prefix = (value: string) => \"#\" + value;\n\nexport const HexColorInput = (props: HexColorInputProps): JSX.Element => {\n const { prefixed, alpha, ...rest } = props;\n\n /** Escapes all non-hexadecimal characters including \"#\" */\n const escape = useCallback(\n (value: string) => value.replace(/([^0-9A-F]+)/gi, \"\").substring(0, alpha ? 8 : 6),\n [alpha]\n );\n\n /** Validates hexadecimal strings */\n const validate = useCallback((value: string) => validHex(value, alpha), [alpha]);\n\n return (\n <div className=\"color-input-container\">\n <label htmlFor={props.label}>{props.label}</label>\n <ColorInput\n {...rest}\n id={props.label}\n escape={escape}\n format={prefixed ? prefix : undefined}\n process={prefix}\n validate={validate}\n />\n </div>\n \n );\n};\n","import React from \"react\";\n\nimport { ColorPicker } from \"./common/ColorPicker\";\nimport { HexColorInput } from \"./HexColorInput\";\nimport { ColorModel, ColorPickerBaseProps } from \"../types\";\nimport { equalHex } from \"../utils/compare\";\nimport { hexToHsva, hsvaToHex } from \"../utils/convert\";\n\nconst colorModel: ColorModel<string> = {\n defaultColor: \"000\",\n defaultBackgroundColor: \"fff\",\n toHsva: hexToHsva,\n fromHsva: ({ h, s, v }) => hsvaToHex({ h, s, v, a: 1 }),\n equal: equalHex,\n};\n\nexport const HexColorPicker = (props: Partial<ColorPickerBaseProps<string>>): JSX.Element => (\n <ColorPicker {...props} colorModel={colorModel}>\n <HexColorInput {...props} label=\"Hex\" prefixed />\n </ColorPicker>\n);\n","import React from \"react\";\n\nimport { Interactive, Interaction } from \"./Interactive\";\nimport { Pointer } from \"./Pointer\";\n\nimport { hsvaToHslaString } from \"../../utils/convert\";\nimport { formatClassName } from \"../../utils/format\";\nimport { clamp } from \"../../utils/clamp\";\nimport { round } from \"../../utils/round\";\nimport { HsvaColor } from \"../../types\";\n\ninterface Props {\n className?: string;\n hsva: HsvaColor;\n onChange: (newAlpha: { a: number }) => void;\n}\n\nexport const Alpha = ({ className, hsva, onChange }: Props): JSX.Element => {\n const handleMove = (interaction: Interaction) => {\n onChange({ a: interaction.left });\n };\n\n const handleKey = (offset: Interaction) => {\n // Alpha always fit into [0, 1] range\n onChange({ a: clamp(hsva.a + offset.left) });\n };\n\n // We use `Object.assign` instead of the spread operator\n // to prevent adding the polyfill (about 150 bytes gzipped)\n const colorFrom = hsvaToHslaString(Object.assign({}, hsva, { a: 0 }));\n const colorTo = hsvaToHslaString(Object.assign({}, hsva, { a: 1 }));\n\n const gradientStyle = {\n backgroundImage: `linear-gradient(90deg, ${colorFrom}, ${colorTo})`,\n };\n\n const nodeClassName = formatClassName([\"react-colorful__alpha\", className]);\n const ariaValue = round(hsva.a * 100);\n\n return (\n <div className={nodeClassName}>\n <div className=\"react-colorful__alpha-gradient\" style={gradientStyle} />\n <Interactive\n onMove={handleMove}\n onKey={handleKey}\n aria-label=\"Alpha\"\n aria-valuetext={`${ariaValue}%`}\n aria-valuenow={ariaValue}\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n >\n <Pointer\n className=\"react-colorful__alpha-pointer\"\n left={hsva.a}\n color={hsvaToHslaString(hsva)}\n />\n </Interactive>\n </div>\n );\n};\n","import React, { useRef } from \"react\";\n\nimport { Hue } from \"./Hue\";\nimport { Saturation } from \"./Saturation\";\nimport { Alpha } from \"./Alpha\";\n\nimport { ColorModel, ColorPickerBaseProps, AnyColor } from \"../../types\";\nimport { useColorManipulation } from \"../../hooks/useColorManipulation\";\nimport { useStyleSheet } from \"../../hooks/useStyleSheet\";\nimport { formatClassName } from \"../../utils/format\";\nimport { ContrastWrapper } from \"./ContrastWrapper\";\n\ninterface Props<T extends AnyColor> extends Partial<ColorPickerBaseProps<T>> {\n colorModel: ColorModel<T>;\n children?: React.ReactNode;\n}\n\nexport const AlphaColorPicker = <T extends AnyColor>({\n className,\n colorModel,\n color = colorModel.defaultColor,\n backgroundColor = colorModel.defaultBackgroundColor,\n children,\n onChange,\n ...rest\n}: Props<T>): JSX.Element => {\n const nodeRef = useRef<HTMLDivElement>(null);\n useStyleSheet(nodeRef);\n\n const [hsva, updateHsva] = useColorManipulation<T>(colorModel, color, onChange);\n const [backgroundHsva, updateBackgroundHsva] = useColorManipulation<T>(colorModel, backgroundColor, onChange);\n\n const nodeClassName = formatClassName([\"react-colorful\", className]);\n\n return (\n <ContrastWrapper foregroundColor={hsva} backgroundColor={backgroundHsva}>\n <div {...rest} ref={nodeRef} className={nodeClassName}>\n <Saturation hsva={hsva} onChange={updateHsva} />\n <Hue hue={hsva.h} onChange={updateHsva} />\n <Alpha hsva={hsva} onChange={updateHsva} className=\"react-colorful__last-control\" />\n </div>\n { children }\n </ContrastWrapper>\n );\n};\n","import React from \"react\";\n\nimport { AlphaColorPicker } from \"./common/AlphaColorPicker\";\nimport { HexColorInput } from \"./HexColorInput\";\nimport { ColorModel, ColorPickerBaseProps } from \"../types\";\nimport { equalHex } from \"../utils/compare\";\nimport { hexToHsva, hsvaToHex } from \"../utils/convert\";\n\nconst colorModel: ColorModel<string> = {\n defaultColor: \"0001\",\n defaultBackgroundColor: \"0000\",\n toHsva: hexToHsva,\n fromHsva: hsvaToHex,\n equal: equalHex,\n};\n\nexport const HexAlphaColorPicker = (props: Partial<ColorPickerBaseProps<string>>): JSX.Element => (\n <AlphaColorPicker {...props} colorModel={colorModel}>\n <HexColorInput {...props} label=\"Hex-Alpha\" prefixed alpha />\n </AlphaColorPicker>\n);\n","import React, { useState, useEffect } from \"react\";\nimport { HslaColor } from \"../types\";\nimport { ObjectColorInputBaseProps } from \"../types\";\n\ninterface HslaColorInputProps extends ObjectColorInputBaseProps<HslaColor> {\n label?: string;\n}\n\nexport const HslaColorInput = ({color, onChange, label=\"HSLA\"}: HslaColorInputProps): JSX.Element => {\n const [inputValues, setInputValues] = useState({\n h: color.h.toString(),\n s: color.s.toString(),\n l: color.l.toString(),\n a: color.a.toString(),\n })\n\n useEffect(() => {\n setInputValues(prev => {\n if (\n prev.h === color.h.toString() &&\n prev.s === color.s.toString() &&\n prev.l === color.l.toString() &&\n prev.a === color.a.toString()\n ) {\n return prev\n }\n return {\n h: color.h.toString(),\n s: color.s.toString(),\n l: color.l.toString(),\n a: color.a.toString(),\n }\n })\n }, [color])\n\n const handleInputChange = (field: keyof HslaColor) => (event: React.ChangeEvent<HTMLInputElement>) => {\n const value = event.target.value\n setInputValues(prevValues => ({\n ...prevValues,\n [field]: value,\n }))\n }\n\n const handleInputBlur = (field: keyof HslaColor) => () => {\n const value = inputValues[field]\n const parsedValue = parseFloat(value)\n if (value === '' || isNaN(parsedValue)) {\n setInputValues(prevValues => ({\n ...prevValues,\n [field]: color[field].toString(),\n }))\n return\n }\n\n let min = 0\n let max = 100\n if (field === 'h') max = 360\n if (field === 'a') max = 1\n\n const clampedValue = Math.max(min, Math.min(max, parsedValue))\n onChange({ ...color, [field]: clampedValue })\n }\n\n const handleKeyDown = (field: keyof HslaColor) => (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'Enter') {\n event.preventDefault()\n handleInputBlur(field)()\n }\n }\n\n return (\n <div className=\"input-fields\">\n <label id={`${label}-group-label`} htmlFor={`${label}-hue-input`}>{label}</label>\n <div className=\"input-fields-container\" role=\"group\" aria-labelledby={`${label}-group-label`}>\n <input\n id={`${label}-hue-input`}\n type=\"text\"\n value={inputValues.h}\n onChange={handleInputChange('h')}\n onBlur={handleInputBlur('h')}\n onKeyDown={handleKeyDown('h')}\n aria-label=\"Hue\"\n />\n <input\n type=\"text\"\n value={inputValues.s}\n onChange={handleInputChange('s')}\n onBlur={handleInputBlur('s')}\n onKeyDown={handleKeyDown('s')}\n aria-label=\"Saturation\"\n />\n <input\n type=\"text\"\n value={inputValues.l}\n onChange={handleInputChange('l')}\n onBlur={handleInputBlur('l')}\n onKeyDown={handleKeyDown('l')}\n aria-label=\"Lightness\"\n />\n <input\n type=\"text\"\n value={inputValues.a}\n onChange={handleInputChange('a')}\n onBlur={handleInputBlur('a')}\n onKeyDown={handleKeyDown('a')}\n aria-label=\"Alpha\"\n />\n </div>\n </div>\n )\n}","import React from \"react\";\n\nimport { AlphaColorPicker } from \"./common/AlphaColorPicker\";\nimport { ColorModel, ColorPickerBaseProps, HslaColor } from \"../types\";\nimport { equalColorObjects } from \"../utils/compare\";\nimport { hslaToHsva, hsvaToHsla } from \"../utils/convert\";\nimport { HslaColorInput } from \"./HslaColorInput\";\n\nconst colorModel: ColorModel<HslaColor> = {\n defaultColor: { h: 0, s: 0, l: 0, a: 1 },\n defaultBackgroundColor: { h: 0, s: 0, l: 100, a: 1 },\n toHsva: hslaToHsva,\n fromHsva: hsvaToHsla,\n equal: equalColorObjects,\n};\n\nexport const HslaColorPicker = (props: Partial<ColorPickerBaseProps<HslaColor>>): JSX.Element => (\n <AlphaColorPicker {...props} colorModel={colorModel}>\n {\n (props.color && props.onChange) \n &&\n <HslaColorInput color={props.color} onChange={props.onChange} />\n }\n </AlphaColorPicker>\n);\n","import React, { useState, useEffect } from \"react\";\nimport { ColorInputBaseProps } from \"../types\";\nimport { hslaStringToHsla, hslaToHslaString } from \"../utils/convert\";\n\ninterface HslaColorInputProps extends ColorInputBaseProps {\n label?: string;\n}\n\nexport const HslaStringColorInput = ({color=\"\", onChange, label}: HslaColorInputProps): JSX.Element => {\n const [inputValue, setInputValue] = useState<string>(color)\n\n useEffect(() => {\n setInputValue(color)\n }, [color])\n\n const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n const value = event.target.value\n setInputValue(value)\n }\n\n const handleInputBlur = () => {\n const parsed = hslaStringToHsla(inputValue)\n setInputValue(hslaToHslaString(parsed))\n if (onChange) {\n onChange(hslaToHslaString(parsed))\n }\n }\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (event.key === \"Enter\") {\n event.preventDefault()\n const parsed = hslaStringToHsla(inputValue)\n setInputValue(hslaToHslaString(parsed))\n if (onChange) {\n onChange(hslaToHslaString(parsed))\n }\n }\n }\n\n return (\n <div className=\"color-input-container\">\n <label htmlFor={label}>{label || 'HSLA'}</label>\n <input\n className=\"color-input\"\n id={label} \n type=\"text\"\n value={inputValue}\n onChange={handleInputChange}\n onBlur={handleInputBlur}\n onKeyDown={handleKeyDown}\n aria-label=\"HSLA Color\"\n />\n </div>\n )\n}","import React from \"react\";\n\nimport { AlphaColorPicker } from \"./common/AlphaColorPicker\";\nimport { ColorModel, ColorPickerBaseProps } from \"../types\";\nimport { equalColorString } from \"../utils/compare\";\nimport { hslaStringToHsva, hsvaToHslaString } from \"../utils/convert\";\nimport { HslaStringColorInput } from \"./HslaStringColorInput\";\n\nconst colorModel: ColorModel<string> = {\n defaultColor: \"hsla(0, 0%, 0%, 1)\",\n defaultBackgroundColor: \"hsla(0, 0%, 100%, 1)\",\n toHsva: hslaStringToHsva,\n fromHsva: hsvaToHslaString,\n equal: equalColorString,\n};\n\nexport const HslaStringColorPicker = (\n props: Partial<ColorPickerBaseProps<string>>\n): JSX.Element => ( \n <AlphaColorPicker {...props} colorModel={colorModel}>\n <HslaStringColorInput {...props} label=\"HSLA\"/>\n </AlphaColorPicker>\n);\n","import React, { useState, useEffect } from \"react\";\nimport { HslColor } from \"../types\";\nimport { ObjectColorInputBaseProps } from \"../types\";\n\ninterface HslColorInputProps extends ObjectColorInputBaseProps<HslColor> {\n label?: string;\n}\n\nexport const HslColorInput = ({color, onChange, label=\"HSL\"}: HslColorInputProps): JSX.Element => {\n const [inputValues, setInputValues] = useState({\n h: color.h.toString(),\n s: color.s.toString(),\n l: color.l.toString()\n })\n\n useEffect(() => {\n setInputValues(prev => {\n if (\n prev.h === color.h.toString() &&\n prev.s === color.s.toString() &&\n prev.l === color.l.toString()\n ) {\n return prev\n }\n return {\n h: color.h.toString(),\n s: color.s.toString(),\n l: color.l.toString()\n }\n })\n }, [color])\n\n const handleInputChange = (field: keyof HslColor) => (event: React.ChangeEvent<HTMLInputElement>) => {\n const value = event.target.value\n setInputValues(prevValues => ({\n ...prevValues,\n [field]: value,\n }))\n }\n\n const handleInputBlur = (field: keyof HslColor) => () => {\n const value = inputValues[field]\n const parsedValue = parseFloat(value)\n if (value === '' || isNaN(parsedValue)) {\n setInputValues(prevValues => ({\n ...prevValues,\n [field]: color[field].toString(),\n }))\n return\n }\n\n let min = 0\n let max = 100\n if (field === 'h') max = 360\n\n const clampedValue = Math.max(min, Math.min(max, parsedValue))\n onChange({ ...color, [field]: clampedValue })\n }\n\n const handleKeyDown = (field: keyof HslColor) => (event: React.KeyboardEvent<HTMLInputElement>) => {\n if (event.key === 'Enter') {\n event.preventDefault()\n handleInputBlur(field)()\n }\n }\n\n return (\n <div className=\"input-fields\">\n <label id={`${label}-group-label`} htmlFor={`${label}-hue-input`}>{label}</label>\n <div className=\"input-fields-container\" role=\"group\" aria-labelledby={`${label}-group-label`}>\n <input\n id={`${label}-hue-input`}\n type=\"text\"\n value={inputValues.h}\n onChange={handleInputChange('h')}\n onBlur={handleInputBlur('h')}\n onKeyDown={handleKeyDown('h')}\n aria-label=\"Hue\"\n />\n <input\n type=\"text\"\n value={inputValues.s}\n onChange={handleInputChange('s')}\n onBlur={handleInputBlur('s')}\n onKeyDown={handleKeyDown('s')}\n aria-label=\"Saturation\"\n />\n <input\n type=\"text\"\n value={inputValues.l