Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions script/test/mocks/ResizeObserverMock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { vi } from 'vitest';

class ResizeObserverMock {
constructor(callback) {
this.callback = callback;
this.observe = vi.fn();
this.unobserve = vi.fn();
this.disconnect = vi.fn();
}

trigger(entries) {
this.callback(entries);
}
}

export default ResizeObserverMock;
18 changes: 18 additions & 0 deletions script/test/mocks/Touch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class Touch {
constructor(init) {
this.identifier = init.identifier;
this.target = init.target;
this.clientX = init.clientX;
this.clientY = init.clientY;
this.pageX = init.pageX;
this.pageY = init.pageY;
this.screenX = init.screenX;
this.screenY = init.screenY;
this.radiusX = init.radiusX;
this.radiusY = init.radiusY;
this.rotationAngle = init.rotationAngle;
this.force = init.force;
}
}

export default Touch;
8 changes: 8 additions & 0 deletions script/test/setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { vi } from 'vitest';
import ResizeObserverMock from './mocks/ResizeObserverMock.js';
import Touch from './mocks/Touch.js';

global.Touch = Touch;

vi.stubGlobal('ResizeObserver', ResizeObserverMock);
vi.stubGlobal('Touch', Touch);
43 changes: 5 additions & 38 deletions src/color-picker/ColorPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@ export interface ColorPickerProps extends TdColorPickerProps, StyledProps {}
const ColorPicker: FC<ColorPickerProps> = (props) => {
const { format, type, enableAlpha, swatchColors, style, value, defaultValue, fixed, onChange, onPaletteBarChange } =
useDefaultProps(props, colorPickerDefaultProps);
const [formatList, setFormatList] = useState([]);
const [formatList, setFormatList] = useState<[string, Array<string | number>]>(['', []]);
const [innerSwatchList, setInnerSwatchList] = useState([]);
const [showPrimaryColorPreview] = useState(false);
const [previewColor, setPreviewColor] = useState('');
const [sliderInfo, setSliderInfo] = useState(0);
const [panelRect, setPanelRect] = useState<PanelRectType>({
width: SATURATION_PANEL_DEFAULT_WIDTH,
Expand Down Expand Up @@ -60,9 +58,7 @@ const ColorPicker: FC<ColorPickerProps> = (props) => {
const getSliderThumbStyle = useCallback(
({ value, maxValue }) => {
const { width } = sliderRect;
if (!width) {
return;
}
if (!width) return;
const left = Math.round((value / maxValue) * 100);
return {
left: `${left}%`,
Expand Down Expand Up @@ -96,7 +92,6 @@ const ColorPicker: FC<ColorPickerProps> = (props) => {
value: color.current.value,
}),
);
setPreviewColor(color.current.rgba);
setFormatList(getFormatList(format, color.current));
},
[getSaturationThumbStyle, getSliderThumbStyle],
Expand Down Expand Up @@ -162,10 +157,6 @@ const ColorPicker: FC<ColorPickerProps> = (props) => {
color.current = new Color(value || DEFAULT_COLOR);
}, [value]);

useEffect(() => {
setPreviewColor(value);
}, [value]);

useEffect(() => {
setCoreStyle(format);
}, [format, setCoreStyle]);
Expand Down Expand Up @@ -210,13 +201,7 @@ const ColorPicker: FC<ColorPickerProps> = (props) => {
const { x } = coordinate;
const maxValue = isAlpha ? ALPHA_MAX : HUE_MAX;

let value = Math.round((x / width) * maxValue * 100) / 100;
if (value < 0) {
value = 0;
}
if (value > maxValue) {
value = maxValue;
}
const value = Math.min(maxValue, Math.max(0, Math.round((x / width) * maxValue * 100) / 100));
handleChangeSlider({ value, isAlpha });
}

Expand Down Expand Up @@ -249,8 +234,6 @@ const ColorPicker: FC<ColorPickerProps> = (props) => {
case 'alpha-slider':
handleSliderDrag(e, true);
break;
default:
break;
}
}

Expand Down Expand Up @@ -286,12 +269,6 @@ const ColorPicker: FC<ColorPickerProps> = (props) => {
};

const renderPicker = () => {
const renderPreviewColorContent = () => (
<div className={classNames(`${rootClassName}__sliders-preview`, `${rootClassName}--bg-alpha`)}>
<div className={`${rootClassName}__sliders-preview-inner`} style={{ background: previewColor }}></div>
</div>
);

const renderAlphaContent = () => (
<div className={classNames(`${rootClassName}__slider-wrapper`, `${rootClassName}__slider-wrapper--alpha-type`)}>
<div
Expand Down Expand Up @@ -336,15 +313,6 @@ const ColorPicker: FC<ColorPickerProps> = (props) => {
}
: { top: `${saturationThumbStyle.top}`, left: `${saturationThumbStyle.left}` }
}
onTouchStart={(e) => {
e.stopPropagation();
}}
onTouchMove={(e) => {
e.stopPropagation();
}}
onTouchEnd={(e) => {
e.stopPropagation();
}}
/>
</div>
<div className={`${rootClassName}__sliders-wrapper`}>
Expand All @@ -368,15 +336,14 @@ const ColorPicker: FC<ColorPickerProps> = (props) => {
</div>
{enableAlpha ? renderAlphaContent() : null}
</div>
{showPrimaryColorPreview ? renderPreviewColorContent() : null}
</div>
<div className={`${rootClassName}__format`}>
<div className={classNames(`${rootClassName}__format-item`, `${rootClassName}__format-item--first`)}>
{format}
{formatList[0]}
</div>
<div className={classNames(`${rootClassName}__format-item`, `${rootClassName}__format-item--second`)}>
<div className={`${rootClassName}__format-inputs`}>
{formatList.map((item, index) => (
{formatList[1]?.map((item, index) => (
<div
key={index}
className={classNames(
Expand Down
193 changes: 193 additions & 0 deletions src/color-picker/__tests__/color-picker.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import { act, describe, expect, fireEvent, it, render, vi } from '@test/utils';
import React from 'react';

import { Color, getColorObject } from '../../_common/js/color-picker';
import ColorPicker, { ColorPickerProps } from '../index';
import { ColorFormat, TypeEnum } from '../type';

const prefix = 't';
const name = `.${prefix}-color-picker`;

const makeTouch = (
el: Element,
eventName: string,
touchPosition?: { clientX?: number; clientY?: number; pageX?: number; pageY?: number },
) => {
const touchInit = {
changedTouches: [
new Touch({
identifier: 0,
target: el,
clientX: touchPosition?.clientX || 0,
clientY: touchPosition?.clientY || 0,
pageX: touchPosition?.pageX || 0,
pageY: touchPosition?.pageY || 0,
}),
],
bubbles: true,
cancelable: true,
};

const event = new TouchEvent(eventName, touchInit);
el.dispatchEvent(event);
};

const mockBoundingClientRect = (info) => {
window.HTMLElement.prototype.getBoundingClientRect = () => info;
};

const renderColorPicker = (props: ColorPickerProps) => render(<ColorPicker {...props} />);

describe('ColorPicker', () => {
describe('props', () => {
it(': multiple', () => {
const testCurrentProp = (type: TypeEnum, target: number) => {
const { container } = renderColorPicker({ type });
const dom = container.querySelectorAll(`${name}__saturation`);
expect(dom).toHaveLength(target);
};
testCurrentProp(undefined, 0);
testCurrentProp('base', 0);
testCurrentProp('multiple', 1);
});

it(': enableAlpha', () => {
const testEnableAlpha = (enableAlpha: boolean) => {
const { container } = renderColorPicker({ enableAlpha, type: 'multiple' });
const alphaDom = container.querySelectorAll(`${name}__slider-wrapper--alpha-type`);
expect(alphaDom).toHaveLength(enableAlpha ? 1 : 0);
};
testEnableAlpha(false);
testEnableAlpha(true);
});

it(': swatchColors', () => {
const testSwatchColors = (swatchColors: Array<string> | null, target: number) => {
const { container } = renderColorPicker({ swatchColors });
const dom = container.querySelectorAll(`${name}__swatches-item`);
expect(dom).toHaveLength(target);
};
testSwatchColors(null, 0);
testSwatchColors([], 0);
testSwatchColors(undefined, 10);
testSwatchColors(['red', 'blur'], 2);
});

it(': format', () => {
const testFormat = (format: string, target: ColorFormat) => {
const { container } = renderColorPicker({ format: format as ColorFormat, type: 'multiple' });
const dom = container.querySelector(`${name}__format-item--first`);
expect(dom.innerHTML).toBe(target);
};
testFormat('RGB', 'RGB');
testFormat('123', 'RGB');
testFormat('HEX', 'HEX');
});
});

describe('events', () => {
it(': preset change', async () => {
const onChange = vi.fn();
const { container } = renderColorPicker({ onChange });
const swatch = container.querySelector(`${name}__swatches-item`);

fireEvent.click(swatch);
const result = 'rgb(236, 242, 254)';

expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange).toHaveBeenLastCalledWith(result, {
trigger: 'preset',
color: getColorObject(new Color(result)),
});
});

it(': saturation change', async () => {
const testSaturation = async (fixed = false) => {
const onPaletteBarChange = vi.fn();
const { container } = renderColorPicker({ onPaletteBarChange, type: 'multiple', fixed });
const el = container.querySelector('.t-color-picker__saturation');

mockBoundingClientRect({
left: 0,
top: 0,
width: 300,
height: 50,
});

act(() => {
makeTouch(el, 'touchstart');
makeTouch(el, 'touchmove', { pageY: 40, pageX: 0, clientY: 40 });
makeTouch(el, 'touchmove', { pageY: 40, pageX: 0, clientY: 40 });
makeTouch(el, 'touchmove', { pageY: 30, pageX: 0, clientY: 30 });
makeTouch(el, 'touchend', { pageY: 30, pageX: 30, clientY: 20 });
});

expect(onPaletteBarChange).toHaveBeenCalledTimes(3);
const result = 'rgba(80, 80, 80, 1)';
const color = new Color(result);
color.saturation = 0;
color.value = fixed ? 0.4 : 0.8214285714285714;

expect(onPaletteBarChange).toHaveBeenLastCalledWith({
color: getColorObject(color),
});
};

await testSaturation();
await testSaturation(true);
});

it(': hue slider change', async () => {
const onChange = vi.fn();
const { container } = renderColorPicker({ onChange, type: 'multiple' });
const el = container.querySelector('.t-color-picker__slider');

mockBoundingClientRect({
left: 0,
top: 0,
width: 300,
height: 50,
});

act(() => {
makeTouch(el, 'touchstart', { pageY: 0, pageX: 0, clientY: 30 });
makeTouch(el, 'touchmove', { pageY: 0, pageX: 30, clientY: 30 });
makeTouch(el, 'touchend', { pageY: 30, pageX: 40, clientY: 30 });
});

expect(onChange).toHaveBeenCalledTimes(2);
const result = 'rgb(151, 91, 0)';
expect(onChange).toHaveBeenLastCalledWith(result, {
trigger: 'palette-hue-bar',
color: getColorObject(new Color(result)),
});
});

it(': alpha slider change', async () => {
const onChange = vi.fn();
const { container } = renderColorPicker({ onChange, type: 'multiple', enableAlpha: true });
const el = container.querySelector('.t-color-picker__slider-wrapper--alpha-type .t-color-picker__slider');

mockBoundingClientRect({
left: 0,
top: 0,
width: 300,
height: 50,
});
act(() => {
makeTouch(el, 'touchstart', { pageY: 0, pageX: 0, clientY: 30 });
makeTouch(el, 'touchmove', { pageY: 0, pageX: 40, clientY: 30 });
makeTouch(el, 'touchend', { pageY: 30, pageX: 40, clientY: 30 });
});

expect(onChange).toHaveBeenCalledTimes(2);
const result = 'rgb(0, 31, 151)';
const color = new Color(result);
color.alpha = 0.13;
expect(onChange).toHaveBeenLastCalledWith(result, {
trigger: 'palette-alpha-bar',
color: getColorObject(color),
});
});
});
});
6 changes: 3 additions & 3 deletions src/color-picker/helper/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const getCoordinate = (e: TouchEvent, rect: PanelRectType, isFixed?: bool
};
};

export const getFormatList = (format: ColorPickerProps['format'], color: Color) => {
export const getFormatList = (format: ColorPickerProps['format'], color: Color): [string, Array<string | number>] => {
const FORMAT_MAP = {
HSV: Object.values(color.getHsva()),
HSVA: Object.values(color.getHsva()),
Expand All @@ -32,9 +32,9 @@ export const getFormatList = (format: ColorPickerProps['format'], color: Color)

const cur = FORMAT_MAP[format];
if (cur) {
return [...cur.slice(0, cur.length - 1), `${Math.round(color.alpha * 100)}%`];
return [format, [...cur.slice(0, cur.length - 1), `${Math.round(color.alpha * 100)}%`]];
}
return FORMAT_MAP.RGB;
return ['RGB', FORMAT_MAP.RGB];
};

export const genSwatchList = (prop: ColorPickerProps['swatchColors']) => {
Expand Down
2 changes: 2 additions & 0 deletions src/color-picker/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ export type TypeEnum = 'base' | 'multiple';

export type ColorPickerChangeTrigger = 'palette-hue-bar' | 'palette-alpha-bar' | 'preset';

export type ColorFormat = 'RGB' | 'RGBA' | 'HSL' | 'HSLA' | 'HSB' | 'HSV' | 'HSVA' | 'HEX' | 'CMYK' | 'CSS';

export type ColorPickerTrigger = 'overlay';

export interface ColorObject {
Expand Down
Loading
Loading