diff --git a/src/components/HexAlphaColorPicker.tsx b/src/components/HexAlphaColorPicker.tsx index cc373002..cb58a29b 100644 --- a/src/components/HexAlphaColorPicker.tsx +++ b/src/components/HexAlphaColorPicker.tsx @@ -3,13 +3,14 @@ import React from "react"; import { AlphaColorPicker } from "./common/AlphaColorPicker"; import { ColorModel, ColorPickerBaseProps } from "../types"; import { equalHex } from "../utils/compare"; -import { hexToHsva, hsvaToHex } from "../utils/convert"; +import { hexToHsva, hsvaToHex, updateAlphaFromString } from "../utils/convert"; const colorModel: ColorModel = { defaultColor: "0001", toHsva: hexToHsva, fromHsva: hsvaToHex, equal: equalHex, + updateAlpha: updateAlphaFromString, }; export const HexAlphaColorPicker = (props: Partial>): JSX.Element => ( diff --git a/src/components/HslaColorPicker.tsx b/src/components/HslaColorPicker.tsx index 9ec4fdf9..5340acc5 100644 --- a/src/components/HslaColorPicker.tsx +++ b/src/components/HslaColorPicker.tsx @@ -3,13 +3,14 @@ import React from "react"; import { AlphaColorPicker } from "./common/AlphaColorPicker"; import { ColorModel, ColorPickerBaseProps, HslaColor } from "../types"; import { equalColorObjects } from "../utils/compare"; -import { hslaToHsva, hsvaToHsla } from "../utils/convert"; +import { hslaToHsva, hsvaToHsla, updateAlphaFromObject } from "../utils/convert"; const colorModel: ColorModel = { defaultColor: { h: 0, s: 0, l: 0, a: 1 }, toHsva: hslaToHsva, fromHsva: hsvaToHsla, equal: equalColorObjects, + updateAlpha: updateAlphaFromObject, }; export const HslaColorPicker = (props: Partial>): JSX.Element => ( diff --git a/src/components/HslaStringColorPicker.tsx b/src/components/HslaStringColorPicker.tsx index ac813b67..607abb28 100644 --- a/src/components/HslaStringColorPicker.tsx +++ b/src/components/HslaStringColorPicker.tsx @@ -3,13 +3,14 @@ import React from "react"; import { AlphaColorPicker } from "./common/AlphaColorPicker"; import { ColorModel, ColorPickerBaseProps } from "../types"; import { equalColorString } from "../utils/compare"; -import { hslaStringToHsva, hsvaToHslaString } from "../utils/convert"; +import { hslaStringToHsva, hsvaToHslaString, updateAlphaFromString } from "../utils/convert"; const colorModel: ColorModel = { defaultColor: "hsla(0, 0%, 0%, 1)", toHsva: hslaStringToHsva, fromHsva: hsvaToHslaString, equal: equalColorString, + updateAlpha: updateAlphaFromString, }; export const HslaStringColorPicker = ( diff --git a/src/components/HsvaColorPicker.tsx b/src/components/HsvaColorPicker.tsx index 47e4eb4c..2e78074d 100644 --- a/src/components/HsvaColorPicker.tsx +++ b/src/components/HsvaColorPicker.tsx @@ -3,13 +3,14 @@ import React from "react"; import { AlphaColorPicker } from "./common/AlphaColorPicker"; import { ColorModel, ColorPickerBaseProps, HsvaColor } from "../types"; import { equalColorObjects } from "../utils/compare"; -import { roundHsva } from "../utils/convert"; +import { roundHsva, updateAlphaFromObject } from "../utils/convert"; const colorModel: ColorModel = { defaultColor: { h: 0, s: 0, v: 0, a: 1 }, toHsva: (hsva) => hsva, fromHsva: roundHsva, equal: equalColorObjects, + updateAlpha: updateAlphaFromObject, }; export const HsvaColorPicker = (props: Partial>): JSX.Element => ( diff --git a/src/components/HsvaStringColorPicker.tsx b/src/components/HsvaStringColorPicker.tsx index 7355f94d..8e9651fe 100644 --- a/src/components/HsvaStringColorPicker.tsx +++ b/src/components/HsvaStringColorPicker.tsx @@ -3,13 +3,14 @@ import React from "react"; import { AlphaColorPicker } from "./common/AlphaColorPicker"; import { ColorModel, ColorPickerBaseProps } from "../types"; import { equalColorString } from "../utils/compare"; -import { hsvaStringToHsva, hsvaToHsvaString } from "../utils/convert"; +import { hsvaStringToHsva, hsvaToHsvaString, updateAlphaFromString } from "../utils/convert"; const colorModel: ColorModel = { defaultColor: "hsva(0, 0%, 0%, 1)", toHsva: hsvaStringToHsva, fromHsva: hsvaToHsvaString, equal: equalColorString, + updateAlpha: updateAlphaFromString, }; export const HsvaStringColorPicker = ( diff --git a/src/components/RgbaColorPicker.tsx b/src/components/RgbaColorPicker.tsx index 6bdc2db1..9e28e7d3 100644 --- a/src/components/RgbaColorPicker.tsx +++ b/src/components/RgbaColorPicker.tsx @@ -3,13 +3,14 @@ import React from "react"; import { AlphaColorPicker } from "./common/AlphaColorPicker"; import { ColorModel, ColorPickerBaseProps, RgbaColor } from "../types"; import { equalColorObjects } from "../utils/compare"; -import { rgbaToHsva, hsvaToRgba } from "../utils/convert"; +import { rgbaToHsva, hsvaToRgba, updateAlphaFromObject } from "../utils/convert"; const colorModel: ColorModel = { defaultColor: { r: 0, g: 0, b: 0, a: 1 }, toHsva: rgbaToHsva, fromHsva: hsvaToRgba, equal: equalColorObjects, + updateAlpha: updateAlphaFromObject, }; export const RgbaColorPicker = (props: Partial>): JSX.Element => ( diff --git a/src/components/RgbaStringColorPicker.tsx b/src/components/RgbaStringColorPicker.tsx index c6e3c4ff..e70dbcb1 100644 --- a/src/components/RgbaStringColorPicker.tsx +++ b/src/components/RgbaStringColorPicker.tsx @@ -3,13 +3,14 @@ import React from "react"; import { AlphaColorPicker } from "./common/AlphaColorPicker"; import { ColorModel, ColorPickerBaseProps } from "../types"; import { equalColorString } from "../utils/compare"; -import { rgbaStringToHsva, hsvaToRgbaString } from "../utils/convert"; +import { rgbaStringToHsva, hsvaToRgbaString, updateAlphaFromString } from "../utils/convert"; const colorModel: ColorModel = { defaultColor: "rgba(0, 0, 0, 1)", toHsva: rgbaStringToHsva, fromHsva: hsvaToRgbaString, equal: equalColorString, + updateAlpha: updateAlphaFromString, }; export const RgbaStringColorPicker = ( diff --git a/src/hooks/useColorManipulation.ts b/src/hooks/useColorManipulation.ts index f0fc38fb..6a73a64d 100644 --- a/src/hooks/useColorManipulation.ts +++ b/src/hooks/useColorManipulation.ts @@ -32,10 +32,20 @@ export function useColorManipulation( // Trigger `onChange` callback only if an updated color is different from cached one; // save the new color to the ref to prevent unnecessary updates useEffect(() => { - let newColor; + const { a, ...hsv } = hsva; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { a: _, ...cacheHsv } = cache.current.hsva; + + // When alpha channel is changed, use cached RGB values to prevent rounding errors + const newColor = equalColorObjects(hsv, cacheHsv) + ? colorModel.updateAlpha + ? colorModel.updateAlpha(cache.current.color, a) + : cache.current.color + : colorModel.fromHsva(hsva); + if ( !equalColorObjects(hsva, cache.current.hsva) && - !colorModel.equal((newColor = colorModel.fromHsva(hsva)), cache.current.color) + !colorModel.equal(newColor, cache.current.color) ) { cache.current = { hsva, color: newColor }; onChangeCallback(newColor); diff --git a/src/types.ts b/src/types.ts index b1833384..a03d9888 100644 --- a/src/types.ts +++ b/src/types.ts @@ -39,6 +39,7 @@ export interface ColorModel { toHsva: (defaultColor: T) => HsvaColor; fromHsva: (hsva: HsvaColor) => T; equal: (first: T, second: T) => boolean; + updateAlpha?: (color: T, alpha: number) => T; } type ColorPickerHTMLAttributes = Omit< diff --git a/src/utils/convert.ts b/src/utils/convert.ts index 2d35b38e..cf218cc9 100644 --- a/src/utils/convert.ts +++ b/src/utils/convert.ts @@ -204,3 +204,14 @@ export const hsvaToHsv = (hsva: HsvaColor): HsvColor => { const { h, s, v } = roundHsva(hsva); return { h, s, v }; }; + +export const updateAlphaFromString = (color: string, alpha: number): string => { + return color.replace(/\d+\.\d+\)/, `${alpha})`); +}; + +export const updateAlphaFromObject = ( + color: T, + alpha: number +): T => { + return Object.assign({}, color, { a: alpha }); +};