From 701eae2f2effa0aabb06de61d2dcdec1b58dc7b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tetsuaki=20Hamano=20/=20=E6=B5=9C=E9=87=8E=20=E5=93=B2?= =?UTF-8?q?=E6=98=8E?= Date: Mon, 18 Jul 2022 12:00:28 +0000 Subject: [PATCH 1/4] Fix RGBA calc error when change alpha --- src/hooks/useColorManipulation.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/hooks/useColorManipulation.ts b/src/hooks/useColorManipulation.ts index f0fc38fb..6915ade2 100644 --- a/src/hooks/useColorManipulation.ts +++ b/src/hooks/useColorManipulation.ts @@ -32,10 +32,18 @@ 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) + ? Object.assign({}, cache.current.color, { a }) + : 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); From ce3b5464ea4022da95b4994fe629b48a11ab4a0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tetsuaki=20Hamano=20/=20=E6=B5=9C=E9=87=8E=20=E5=93=B2?= =?UTF-8?q?=E6=98=8E?= Date: Fri, 29 Jul 2022 16:49:21 +0000 Subject: [PATCH 2/4] Add updateAlpha prop to colorModel --- src/components/HslaColorPicker.tsx | 3 ++- src/components/HslaStringColorPicker.tsx | 3 ++- src/components/HsvaColorPicker.tsx | 3 ++- src/components/HsvaStringColorPicker.tsx | 3 ++- src/components/RgbaColorPicker.tsx | 3 ++- src/components/RgbaStringColorPicker.tsx | 3 ++- src/hooks/useColorManipulation.ts | 6 +++++- src/types.ts | 3 +++ src/utils/convert.ts | 18 +++++++++++++++++- 9 files changed, 37 insertions(+), 8 deletions(-) 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 6915ade2..a6a9ddc5 100644 --- a/src/hooks/useColorManipulation.ts +++ b/src/hooks/useColorManipulation.ts @@ -36,9 +36,13 @@ export function useColorManipulation( // eslint-disable-next-line @typescript-eslint/no-unused-vars const { a: _, ...cacheHsv } = cache.current.hsva; + const isSupportAlpha = "updateAlpha" in colorModel; + // When alpha channel is changed, use cached RGB values to prevent rounding errors const newColor = equalColorObjects(hsv, cacheHsv) - ? Object.assign({}, cache.current.color, { a }) + ? isSupportAlpha + ? colorModel.updateAlpha(cache.current.color, a) + : cache.current.color : colorModel.fromHsva(hsva); if ( diff --git a/src/types.ts b/src/types.ts index b1833384..352075e8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -39,6 +39,9 @@ export interface ColorModel { toHsva: (defaultColor: T) => HsvaColor; fromHsva: (hsva: HsvaColor) => T; equal: (first: T, second: T) => boolean; + updateAlpha?: + | ((color: string, alpha: number) => string) + | ((color: ObjectColor, alpha: number) => ObjectColor); } type ColorPickerHTMLAttributes = Omit< diff --git a/src/utils/convert.ts b/src/utils/convert.ts index 116aec03..6ca45987 100644 --- a/src/utils/convert.ts +++ b/src/utils/convert.ts @@ -1,5 +1,13 @@ import { round } from "./round"; -import { RgbaColor, RgbColor, HslaColor, HslColor, HsvaColor, HsvColor } from "../types"; +import { + RgbaColor, + RgbColor, + HslaColor, + HslColor, + HsvaColor, + HsvColor, + ObjectColor, +} from "../types"; /** * Valid CSS units. @@ -203,3 +211,11 @@ 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: ObjectColor, alpha: number): ObjectColor => { + return Object.assign({}, color, { a: alpha }); +}; From 31cde596c1c7235174492cf522ef24a142c02ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tetsuaki=20Hamano=20/=20=E6=B5=9C=E9=87=8E=20=E5=93=B2?= =?UTF-8?q?=E6=98=8E?= Date: Tue, 16 Aug 2022 04:12:54 +0000 Subject: [PATCH 3/4] Fix type error --- src/components/HexAlphaColorPicker.tsx | 3 ++- src/components/HslaColorPicker.tsx | 4 ++-- src/components/HsvaColorPicker.tsx | 4 ++-- src/components/RgbaColorPicker.tsx | 4 ++-- src/hooks/useColorManipulation.ts | 4 +--- src/types.ts | 4 +--- src/utils/convert.ts | 20 ++++++++++---------- 7 files changed, 20 insertions(+), 23 deletions(-) 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 5340acc5..456570bf 100644 --- a/src/components/HslaColorPicker.tsx +++ b/src/components/HslaColorPicker.tsx @@ -3,14 +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, updateAlphaFromObject } from "../utils/convert"; +import { hslaToHsva, hsvaToHsla, updateAlphaFromHsla } from "../utils/convert"; const colorModel: ColorModel = { defaultColor: { h: 0, s: 0, l: 0, a: 1 }, toHsva: hslaToHsva, fromHsva: hsvaToHsla, equal: equalColorObjects, - updateAlpha: updateAlphaFromObject, + updateAlpha: updateAlphaFromHsla, }; export const HslaColorPicker = (props: Partial>): JSX.Element => ( diff --git a/src/components/HsvaColorPicker.tsx b/src/components/HsvaColorPicker.tsx index 2e78074d..d72e7ebd 100644 --- a/src/components/HsvaColorPicker.tsx +++ b/src/components/HsvaColorPicker.tsx @@ -3,14 +3,14 @@ import React from "react"; import { AlphaColorPicker } from "./common/AlphaColorPicker"; import { ColorModel, ColorPickerBaseProps, HsvaColor } from "../types"; import { equalColorObjects } from "../utils/compare"; -import { roundHsva, updateAlphaFromObject } from "../utils/convert"; +import { roundHsva, updateAlphaFromHsva } from "../utils/convert"; const colorModel: ColorModel = { defaultColor: { h: 0, s: 0, v: 0, a: 1 }, toHsva: (hsva) => hsva, fromHsva: roundHsva, equal: equalColorObjects, - updateAlpha: updateAlphaFromObject, + updateAlpha: updateAlphaFromHsva, }; export const HsvaColorPicker = (props: Partial>): JSX.Element => ( diff --git a/src/components/RgbaColorPicker.tsx b/src/components/RgbaColorPicker.tsx index 9e28e7d3..716dac25 100644 --- a/src/components/RgbaColorPicker.tsx +++ b/src/components/RgbaColorPicker.tsx @@ -3,14 +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, updateAlphaFromObject } from "../utils/convert"; +import { rgbaToHsva, hsvaToRgba, updateAlphaFromRgba } from "../utils/convert"; const colorModel: ColorModel = { defaultColor: { r: 0, g: 0, b: 0, a: 1 }, toHsva: rgbaToHsva, fromHsva: hsvaToRgba, equal: equalColorObjects, - updateAlpha: updateAlphaFromObject, + updateAlpha: updateAlphaFromRgba, }; export const RgbaColorPicker = (props: Partial>): JSX.Element => ( diff --git a/src/hooks/useColorManipulation.ts b/src/hooks/useColorManipulation.ts index a6a9ddc5..6a73a64d 100644 --- a/src/hooks/useColorManipulation.ts +++ b/src/hooks/useColorManipulation.ts @@ -36,11 +36,9 @@ export function useColorManipulation( // eslint-disable-next-line @typescript-eslint/no-unused-vars const { a: _, ...cacheHsv } = cache.current.hsva; - const isSupportAlpha = "updateAlpha" in colorModel; - // When alpha channel is changed, use cached RGB values to prevent rounding errors const newColor = equalColorObjects(hsv, cacheHsv) - ? isSupportAlpha + ? colorModel.updateAlpha ? colorModel.updateAlpha(cache.current.color, a) : cache.current.color : colorModel.fromHsva(hsva); diff --git a/src/types.ts b/src/types.ts index 352075e8..a03d9888 100644 --- a/src/types.ts +++ b/src/types.ts @@ -39,9 +39,7 @@ export interface ColorModel { toHsva: (defaultColor: T) => HsvaColor; fromHsva: (hsva: HsvaColor) => T; equal: (first: T, second: T) => boolean; - updateAlpha?: - | ((color: string, alpha: number) => string) - | ((color: ObjectColor, alpha: number) => ObjectColor); + updateAlpha?: (color: T, alpha: number) => T; } type ColorPickerHTMLAttributes = Omit< diff --git a/src/utils/convert.ts b/src/utils/convert.ts index 813c4f06..89a0c899 100644 --- a/src/utils/convert.ts +++ b/src/utils/convert.ts @@ -1,13 +1,5 @@ import { round } from "./round"; -import { - RgbaColor, - RgbColor, - HslaColor, - HslColor, - HsvaColor, - HsvColor, - ObjectColor, -} from "../types"; +import { RgbaColor, RgbColor, HslaColor, HslColor, HsvaColor, HsvColor } from "../types"; /** * Valid CSS units. @@ -217,6 +209,14 @@ export const updateAlphaFromString = (color: string, alpha: number): string => { return color.replace(/\d+\.\d+\)/, `${alpha})`); }; -export const updateAlphaFromObject = (color: ObjectColor, alpha: number): ObjectColor => { +export const updateAlphaFromHsla = (color: HslaColor, alpha: number): HslaColor => { + return Object.assign({}, color, { a: alpha }); +}; + +export const updateAlphaFromHsva = (color: HsvaColor, alpha: number): HsvaColor => { + return Object.assign({}, color, { a: alpha }); +}; + +export const updateAlphaFromRgba = (color: RgbaColor, alpha: number): RgbaColor => { return Object.assign({}, color, { a: alpha }); }; From f68766ef5a54e3465286c064493454512ee99504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tetsuaki=20Hamano=20/=20=E6=B5=9C=E9=87=8E=20=E5=93=B2?= =?UTF-8?q?=E6=98=8E?= Date: Tue, 16 Aug 2022 11:40:23 +0000 Subject: [PATCH 4/4] Refactoring with Generics --- src/components/HslaColorPicker.tsx | 4 ++-- src/components/HsvaColorPicker.tsx | 4 ++-- src/components/RgbaColorPicker.tsx | 4 ++-- src/utils/convert.ts | 13 ++++--------- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/components/HslaColorPicker.tsx b/src/components/HslaColorPicker.tsx index 456570bf..5340acc5 100644 --- a/src/components/HslaColorPicker.tsx +++ b/src/components/HslaColorPicker.tsx @@ -3,14 +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, updateAlphaFromHsla } 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: updateAlphaFromHsla, + updateAlpha: updateAlphaFromObject, }; export const HslaColorPicker = (props: Partial>): JSX.Element => ( diff --git a/src/components/HsvaColorPicker.tsx b/src/components/HsvaColorPicker.tsx index d72e7ebd..2e78074d 100644 --- a/src/components/HsvaColorPicker.tsx +++ b/src/components/HsvaColorPicker.tsx @@ -3,14 +3,14 @@ import React from "react"; import { AlphaColorPicker } from "./common/AlphaColorPicker"; import { ColorModel, ColorPickerBaseProps, HsvaColor } from "../types"; import { equalColorObjects } from "../utils/compare"; -import { roundHsva, updateAlphaFromHsva } 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: updateAlphaFromHsva, + updateAlpha: updateAlphaFromObject, }; export const HsvaColorPicker = (props: Partial>): JSX.Element => ( diff --git a/src/components/RgbaColorPicker.tsx b/src/components/RgbaColorPicker.tsx index 716dac25..9e28e7d3 100644 --- a/src/components/RgbaColorPicker.tsx +++ b/src/components/RgbaColorPicker.tsx @@ -3,14 +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, updateAlphaFromRgba } 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: updateAlphaFromRgba, + updateAlpha: updateAlphaFromObject, }; export const RgbaColorPicker = (props: Partial>): JSX.Element => ( diff --git a/src/utils/convert.ts b/src/utils/convert.ts index 89a0c899..cf218cc9 100644 --- a/src/utils/convert.ts +++ b/src/utils/convert.ts @@ -209,14 +209,9 @@ export const updateAlphaFromString = (color: string, alpha: number): string => { return color.replace(/\d+\.\d+\)/, `${alpha})`); }; -export const updateAlphaFromHsla = (color: HslaColor, alpha: number): HslaColor => { - return Object.assign({}, color, { a: alpha }); -}; - -export const updateAlphaFromHsva = (color: HsvaColor, alpha: number): HsvaColor => { - return Object.assign({}, color, { a: alpha }); -}; - -export const updateAlphaFromRgba = (color: RgbaColor, alpha: number): RgbaColor => { +export const updateAlphaFromObject = ( + color: T, + alpha: number +): T => { return Object.assign({}, color, { a: alpha }); };