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
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ exports[`ColorPickerPanel > :props > :colorModes[monochrome,linear-gradient] 1`]
/>
</div>
</div>
<!---->
</div>
<div
class="t-color-picker__body"
Expand Down Expand Up @@ -1342,6 +1343,7 @@ exports[`ColorPickerPanel > :props > :colorModes[monochrome,linear-gradient] 3`]
/>
</div>
</div>
<!---->
</div>
<div
class="t-color-picker__body"
Expand Down Expand Up @@ -2318,6 +2320,7 @@ exports[`ColorPickerPanel > :ui > :mount 1`] = `
/>
</div>
</div>
<!---->
</div>
<div
class="t-color-picker__body"
Expand Down
121 changes: 120 additions & 1 deletion packages/components/color-picker/__tests__/color-picker.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
import { describe, expect, it } from 'vitest';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { ref, nextTick } from 'vue';
import { ColorPicker } from '@tdesign/components';
import type { ColorObject } from '@tdesign/components/color-picker';
Expand Down Expand Up @@ -93,6 +93,125 @@ describe('ColorPicker', () => {
expect(sliderNode2.element.children.length).toBe(2);
});

describe(':eyeDropper[boolean]', () => {
const stubEyeDropper = (hex = '#ff6600') => {
vi.stubGlobal(
'EyeDropper',
class {
// eslint-disable-next-line class-methods-use-this
open() {
return Promise.resolve({ sRGBHex: hex });
}
},
);
};

afterEach(() => {
vi.unstubAllGlobals();
// 清理本组用例挂载的 Popup 浮层,避免残留 DOM 遮挡后续用例的 trigger
document.body.innerHTML = '';
});

it('does not render eyedropper button by default', async () => {
stubEyeDropper();
const { panel } = await mountColorPickerAndTriggerPanel({ props: { value: '#0052d9' } });
expect(panel.find('.t-color-picker__eyedropper').exists()).toBeFalsy();
});

it('picks color and emits change with trigger eyedropper', async () => {
stubEyeDropper('#ff6600');
const onChange = vi.fn();
const { panel } = await mountColorPickerAndTriggerPanel({
props: { value: '#0052d9', eyeDropper: true, onChange },
});
const btn = panel.find('.t-color-picker__eyedropper');
expect(btn.exists()).toBeTruthy();
expect(btn.classes()).not.toContain('t-is-disabled');
await btn.trigger('click');
await nextTick();
expect(onChange).toHaveBeenCalledTimes(1);
const [, context] = onChange.mock.calls[0];
expect(context.trigger).toBe('eyedropper');
expect(context.color.hex).toBe('#ff6600');
});

it('renders disabled button when EyeDropper API is not supported', async () => {
// jsdom 默认无 EyeDropper 全局对象
const onChange = vi.fn();
const { panel } = await mountColorPickerAndTriggerPanel({
props: { value: '#0052d9', eyeDropper: true, onChange },
});
const btn = panel.find('.t-color-picker__eyedropper');
expect(btn.exists()).toBeTruthy();
expect(btn.classes()).toContain('t-is-disabled');
expect((btn.element as HTMLButtonElement).disabled).toBe(true);
await btn.trigger('click');
await nextTick();
expect(onChange).not.toHaveBeenCalled();
});

it('keeps current alpha when enableAlpha is true', async () => {
stubEyeDropper('#ff6600');
const onChange = vi.fn();
const { panel } = await mountColorPickerAndTriggerPanel({
props: { value: 'rgba(0, 82, 217, 0.5)', format: 'RGBA', enableAlpha: true, eyeDropper: true, onChange },
});
await panel.find('.t-color-picker__eyedropper').trigger('click');
await nextTick();
const [, context] = onChange.mock.calls[0];
expect(context.color.hex).toBe('#ff6600');
expect(context.color.alpha).toBe(0.5);
});

it('renders button even when only one color mode', async () => {
stubEyeDropper();
const { panel } = await mountColorPickerAndTriggerPanel({
props: { value: '#0052d9', colorModes: ['monochrome'], eyeDropper: true },
});
expect(panel.find('.t-radio-group').exists()).toBeFalsy();
expect(panel.find('.t-color-picker__eyedropper').exists()).toBeTruthy();
});

it('does not emit change when user cancels picking', async () => {
vi.stubGlobal(
'EyeDropper',
class {
// eslint-disable-next-line class-methods-use-this
open() {
return Promise.reject(new DOMException('User aborted', 'AbortError'));
}
},
);
const onChange = vi.fn();
const { panel } = await mountColorPickerAndTriggerPanel({
props: { value: '#0052d9', eyeDropper: true, onChange },
});
await panel.find('.t-color-picker__eyedropper').trigger('click');
await nextTick();
expect(onChange).not.toHaveBeenCalled();
});

it('updates only the selected gradient stop in gradient mode', async () => {
stubEyeDropper('#ff6600');
const onChange = vi.fn();
const { panel } = await mountColorPickerAndTriggerPanel({
props: {
value: 'linear-gradient(45deg, #4facfe 0%, #00f2fe 100%)',
colorModes: ['linear-gradient'],
eyeDropper: true,
onChange,
},
});
await panel.find('.t-color-picker__eyedropper').trigger('click');
await nextTick();
const [value, context] = onChange.mock.calls[0];
expect(context.trigger).toBe('eyedropper');
// 仍为渐变值,仅更新选中的渐变节点,而非把整体颜色替换为单色
expect(value).toContain('linear-gradient');
expect(value).toContain('255, 102, 0');
});
});

describe(':format[HEX/HEX8/RGB/RGBA/HSL/HSLA/HSV/HSVA/CMYK/CSS]', async () => {
// ps: enableAlpha true: HEX8/RGBA/HSLA/HSVA
it('format:validator', () => {
Expand Down
15 changes: 15 additions & 0 deletions packages/components/color-picker/_example/eye-dropper.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<template>
<t-space direction="vertical">
<t-color-picker v-model="color" :eye-dropper="true" @change="handleChange" />
<t-color-picker-panel v-model="color" :eye-dropper="true" @change="handleChange" />
</t-space>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import type { ColorPickerProps } from 'tdesign-vue-next';

const color = ref('#0052d9');
const handleChange: ColorPickerProps['onChange'] = (value, context) => {
console.log(value, context.trigger);
};
</script>
6 changes: 6 additions & 0 deletions packages/components/color-picker/_usage/props.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
"defaultValue": true,
"options": []
},
{
"name": "eyeDropper",
"type": "Boolean",
"defaultValue": false,
"options": []
},
{
"name": "size",
"type": "enum",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export default {
type: Boolean,
default: true,
},
/** 是否开启吸色(屏幕取色)功能。开启后面板顶部显示吸色按钮,基于浏览器原生 EyeDropper API 实现,浏览器不支持时按钮呈禁用状态 */
eyeDropper: Boolean,
/** 格式化色值。`enableAlpha` 为真时,`HEX8/RGBA/HSLA/HSVA` 有效 */
format: {
type: String as PropType<TdColorPickerPanelProps['format']>,
Expand Down
10 changes: 6 additions & 4 deletions packages/components/color-picker/color-picker.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ colorModes | Array | ["monochrome", "linear-gradient"] | Typescript:`Array<'mo
disabled | Boolean | undefined | \- | N
enableAlpha | Boolean | false | \- | N
enableMultipleGradient | Boolean | true | \- | N
eyeDropper | Boolean | false | enable screen color sampling. When enabled, an eyedropper button appears in the panel header, implemented with the browser's native EyeDropper API. The button is disabled when the browser does not support the API | N
format | String | RGB | When `enableAlpha` is true, `HEX8/RGBA/HSLA/HSVA` are valid。options: HEX/HEX8/RGB/RGBA/HSL/HSLA/HSV/HSVA/CMYK/CSS | N
inputProps | Object | - | Typescript:`InputProps`,[Input API Documents](./input?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/packages/components/color-picker/type.ts) | N
popupProps | Object | - | Typescript:`PopupProps`,[Popup API Documents](./popup?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/packages/components/color-picker/type.ts) | N
Expand All @@ -23,7 +24,7 @@ size | String | medium | options: small/medium/large。Typescript:`SizeEnum`
swatchColors | Array | undefined | swatch colors。Typescript:`Array<string> \| null \| undefined` | N
value | String | - | color value。`v-model` and `v-model:value` is supported | N
defaultValue | String | - | color value。uncontrolled property | N
onChange | Function | | Typescript:`(value: string, context: { color: ColorObject; trigger: ColorPickerChangeTrigger }) => void`<br/>[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/packages/components/color-picker/type.ts)。<br/>`type ColorPickerChangeTrigger = 'palette-saturation-brightness' \| 'palette-saturation' \| 'palette-brightness' \| 'palette-hue-bar' \| 'palette-alpha-bar' \| 'input' \| 'preset' \| 'recent' `<br/> | N
onChange | Function | | Typescript:`(value: string, context: { color: ColorObject; trigger: ColorPickerChangeTrigger }) => void`<br/>[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/packages/components/color-picker/type.ts)。<br/>`type ColorPickerChangeTrigger = 'palette-saturation-brightness' \| 'palette-saturation' \| 'palette-brightness' \| 'palette-hue-bar' \| 'palette-alpha-bar' \| 'input' \| 'preset' \| 'recent' \| 'eyedropper' `<br/> | N
onClear | Function | | Typescript:`(context: { e: MouseEvent }) => void`<br/> | N
onPaletteBarChange | Function | | Typescript:`(context: { color: ColorObject }) => void`<br/>[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/packages/components/color-picker/type.ts)。<br/>`interface ColorObject { alpha: number; css: string; hex: string; hex8: string; hsl: string; hsla: string; hsv: string; hsva: string; rgb: string; rgba: string; saturation: number; value: number; isGradient: boolean; linearGradient?: string; }`<br/> | N
onRecentColorsChange | Function | | Typescript:`(value: Array<string>) => void`<br/> | N
Expand All @@ -32,7 +33,7 @@ onRecentColorsChange | Function | | Typescript:`(value: Array<string>) => voi

name | params | description
-- | -- | --
change | `(value: string, context: { color: ColorObject; trigger: ColorPickerChangeTrigger })` | [see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/packages/components/color-picker/type.ts)。<br/>`type ColorPickerChangeTrigger = 'palette-saturation-brightness' \| 'palette-saturation' \| 'palette-brightness' \| 'palette-hue-bar' \| 'palette-alpha-bar' \| 'input' \| 'preset' \| 'recent' `<br/>
change | `(value: string, context: { color: ColorObject; trigger: ColorPickerChangeTrigger })` | [see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/packages/components/color-picker/type.ts)。<br/>`type ColorPickerChangeTrigger = 'palette-saturation-brightness' \| 'palette-saturation' \| 'palette-brightness' \| 'palette-hue-bar' \| 'palette-alpha-bar' \| 'input' \| 'preset' \| 'recent' \| 'eyedropper' `<br/>
clear | `(context: { e: MouseEvent })` | \-
palette-bar-change | `(context: { color: ColorObject })` | [see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/packages/components/color-picker/type.ts)。<br/>`interface ColorObject { alpha: number; css: string; hex: string; hex8: string; hsl: string; hsla: string; hsv: string; hsva: string; rgb: string; rgba: string; saturation: number; value: number; isGradient: boolean; linearGradient?: string; }`<br/>
recent-colors-change | `(value: Array<string>)` | \-
Expand All @@ -46,21 +47,22 @@ colorModes | Array | ["monochrome", "linear-gradient"] | Typescript:`Array<'mo
disabled | Boolean | - | \- | N
enableAlpha | Boolean | false | \- | N
enableMultipleGradient | Boolean | true | \- | N
eyeDropper | Boolean | false | enable screen color sampling. When enabled, an eyedropper button appears in the panel header, implemented with the browser's native EyeDropper API. The button is disabled when the browser does not support the API | N
format | String | RGB | When `enableAlpha` is true, `HEX8/RGBA/HSLA/HSVA` are valid。options: HEX/HEX8/RGB/RGBA/HSL/HSLA/HSV/HSVA/CMYK/CSS | N
recentColors | Array | [] | used color recently。`v-model:recentColors` is supported。Typescript:`Array<string> \| boolean \| null` | N
defaultRecentColors | Array | [] | used color recently。uncontrolled property。Typescript:`Array<string> \| boolean \| null` | N
showPrimaryColorPreview | Boolean | true | \- | N
swatchColors | Array | - | swatch colors。Typescript:`Array<string> \| null \| undefined` | N
value | String | - | color value。`v-model` and `v-model:value` is supported | N
defaultValue | String | - | color value。uncontrolled property | N
onChange | Function | | Typescript:`(value: string, context: { color: ColorObject; trigger: ColorPickerChangeTrigger }) => void`<br/>[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/packages/components/color-picker-panel/type.ts)。<br/>`type ColorPickerChangeTrigger = 'palette-saturation-brightness' \| 'palette-saturation' \| 'palette-brightness' \| 'palette-hue-bar' \| 'palette-alpha-bar' \| 'input' \| 'preset' \| 'recent' `<br/> | N
onChange | Function | | Typescript:`(value: string, context: { color: ColorObject; trigger: ColorPickerChangeTrigger }) => void`<br/>[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/packages/components/color-picker-panel/type.ts)。<br/>`type ColorPickerChangeTrigger = 'palette-saturation-brightness' \| 'palette-saturation' \| 'palette-brightness' \| 'palette-hue-bar' \| 'palette-alpha-bar' \| 'input' \| 'preset' \| 'recent' \| 'eyedropper' `<br/> | N
onPaletteBarChange | Function | | Typescript:`(context: { color: ColorObject }) => void`<br/>[see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/packages/components/color-picker-panel/type.ts)。<br/>`interface ColorObject { alpha: number; css: string; hex: string; hex8: string; hsl: string; hsla: string; hsv: string; hsva: string; rgb: string; rgba: string; saturation: number; value: number; isGradient: boolean; linearGradient?: string; }`<br/> | N
onRecentColorsChange | Function | | Typescript:`(value: Array<string>) => void`<br/> | N

### ColorPickerPanel Events

name | params | description
-- | -- | --
change | `(value: string, context: { color: ColorObject; trigger: ColorPickerChangeTrigger })` | [see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/packages/components/color-picker-panel/type.ts)。<br/>`type ColorPickerChangeTrigger = 'palette-saturation-brightness' \| 'palette-saturation' \| 'palette-brightness' \| 'palette-hue-bar' \| 'palette-alpha-bar' \| 'input' \| 'preset' \| 'recent' `<br/>
change | `(value: string, context: { color: ColorObject; trigger: ColorPickerChangeTrigger })` | [see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/packages/components/color-picker-panel/type.ts)。<br/>`type ColorPickerChangeTrigger = 'palette-saturation-brightness' \| 'palette-saturation' \| 'palette-brightness' \| 'palette-hue-bar' \| 'palette-alpha-bar' \| 'input' \| 'preset' \| 'recent' \| 'eyedropper' `<br/>
palette-bar-change | `(context: { color: ColorObject })` | [see more ts definition](https://github.com/Tencent/tdesign-vue-next/blob/develop/packages/components/color-picker-panel/type.ts)。<br/>`interface ColorObject { alpha: number; css: string; hex: string; hex8: string; hsl: string; hsla: string; hsv: string; hsva: string; rgb: string; rgba: string; saturation: number; value: number; isGradient: boolean; linearGradient?: string; }`<br/>
recent-colors-change | `(value: Array<string>)` | \-
Loading