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
8 changes: 8 additions & 0 deletions packages/components/color-picker/_example/eye-dropper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineComponent } from 'vue';
import { ColorPicker } from 'tdesign-vue-next';

export default defineComponent({
setup() {
return () => <ColorPicker eye-dropper defaultValue="#0052d9" />;
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export default {
type: Boolean,
default: true,
},
/** 是否开启吸色功能,启用后面板顶部出现吸色按钮,仅支持 Chrome/Edge 等现代浏览器 */
eyeDropper: Boolean,
/** 格式化色值。`enableAlpha` 为真时,`HEX8/RGBA/HSLA/HSVA` 有效 */
format: {
type: String as PropType<TdColorPickerPanelProps['format']>,
Expand Down
76 changes: 58 additions & 18 deletions packages/components/color-picker/components/panel/header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { defineComponent, PropType, ref, watch } from 'vue';

import { defineComponent, PropType, ref, watch, onUnmounted } from 'vue';
import { COLOR_MODES } from '@tdesign/common-js/color-picker/constants';
import { isEyeDropperSupported, openEyeDropper } from '../../utils/eyedropper';
import { useConfig } from '@tdesign/shared-hooks';
import { RadioButton as TRadioButton, RadioGroup as TRadioGroup } from '../../../radio';
import props from '../../color-picker-panel-props';
Expand All @@ -17,35 +17,75 @@
},
onModeChange: {
type: Function,
default: () => {
return () => {};
},
default: () => () => {},
},
onEyeDropperPick: {
type: Function as PropType<(hex: string) => void>,
default: () => () => {},
},
},
setup(props) {
const { globalConfig } = useConfig('colorPicker');
const baseClassName = useBaseClassName();
const modeValue = ref(props.mode);
const handleModeChange = (v: string) => props.onModeChange(v);
const picking = ref(false);
const supported = isEyeDropperSupported();
let abortCtrl: AbortController | null = null;

watch(
() => props.mode,
(v) => (modeValue.value = v),
);
return () => {
if (props.colorModes?.length === 1) {
return null;

onUnmounted(() => {
abortCtrl?.abort();
});

const handleModeChange = (v: string) => props.onModeChange(v);

const handleEyeDropperClick = async () => {
if (picking.value || !supported) return;
abortCtrl = new AbortController();
picking.value = true;
try {
const hex = await openEyeDropper(abortCtrl.signal);
if (hex) props.onEyeDropperPick(hex);
} finally {
picking.value = false;
abortCtrl = null;
}
};

return () => {
const showModeSwitch = (props.colorModes?.length ?? 0) !== 1;
const showEyeDropper = props.eyeDropper;
if (!showModeSwitch && !showEyeDropper) return null;

return (
<div class={`${baseClassName.value}__head`}>
<div class={`${baseClassName.value}__mode`}>
<TRadioGroup variant="default-filled" size="small" v-model={modeValue.value} onChange={handleModeChange}>
{Object.keys(COLOR_MODES).map((key) => (
<TRadioButton key={key} value={key}>
{Reflect.get(globalConfig.value, COLOR_MODES[key as keyof typeof COLOR_MODES])}
</TRadioButton>
))}
</TRadioGroup>
</div>
{showModeSwitch && (
<div class={`${baseClassName.value}__mode`}>
<TRadioGroup variant="default-filled" size="small" v-model={modeValue.value} onChange={handleModeChange}>
{Object.keys(COLOR_MODES).map((key) => (
<TRadioButton key={key} value={key}>
{Reflect.get(globalConfig.value, COLOR_MODES[key as keyof typeof COLOR_MODES])}
</TRadioButton>
))}
</TRadioGroup>
</div>
)}
{showEyeDropper && (
<button
class={[`${baseClassName.value}__eyedropper`, !supported && 't-is-disabled']}
title={globalConfig.value.eyeDropper ?? '吸色'}

Check failure on line 80 in packages/components/color-picker/components/panel/header.tsx

View workflow job for this annotation

GitHub Actions / tdesign-vue-next-chat

Property 'eyeDropper' does not exist on type '{} & (ColorPickerConfig | ({ readonly swatchColorTitle: "系统预设颜色"; readonly recentColorTitle: "最近使用颜色"; readonly clearConfirmText: "确定清空最近使用的颜色吗?"; readonly singleColor: "单色"; readonly gradientColor: "渐变"; } & ColorPickerConfig))'.

Check failure on line 80 in packages/components/color-picker/components/panel/header.tsx

View workflow job for this annotation

GitHub Actions / lint

Property 'eyeDropper' does not exist on type '{} & (ColorPickerConfig | ({ readonly swatchColorTitle: "系统预设颜色"; readonly recentColorTitle: "最近使用颜色"; readonly clearConfirmText: "确定清空最近使用的颜色吗?"; readonly singleColor: "单色"; readonly gradientColor: "渐变"; } & ColorPickerConfig))'.

Check failure on line 80 in packages/components/color-picker/components/panel/header.tsx

View workflow job for this annotation

GitHub Actions / build-chat

Property 'eyeDropper' does not exist on type '{} & (ColorPickerConfig | ({ readonly swatchColorTitle: "系统预设颜色"; readonly recentColorTitle: "最近使用颜色"; readonly clearConfirmText: "确定清空最近使用的颜色吗?"; readonly singleColor: "单色"; readonly gradientColor: "渐变"; } & ColorPickerConfig))'.
disabled={!supported || picking.value}
onClick={handleEyeDropperClick}
>
<svg viewBox="0 0 16 16" width="1em" height="1em" fill="currentColor" aria-hidden="true">
<path d="M13.354.646a2.207 2.207 0 0 0-3.121 0L8.75 2.129l-.604-.604a1 1 0 1 0-1.414 1.414l.25.25L1.47 9.2A2.5 2.5 0 0 0 .75 11v.25H.5a.5.5 0 0 0 0 1h.25V12.5a.5.5 0 0 0 1 0v-.25H2a2.5 2.5 0 0 0 1.8-.72l5.982-5.982.25.25a1 1 0 1 0 1.414-1.414l-.604-.604 1.483-1.483a2.207 2.207 0 0 0 0-3.121zM3.094 11.78A1.5 1.5 0 0 1 2 12.25H1.75v-.25A1.5 1.5 0 0 1 2.22 10.906L8 5.121l.879.879-5.785 5.78z"/>
</svg>
</button>
)}
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ export default defineComponent({
},
);

const handleEyeDropperPick = (hex: string) => {
color.value.update(hex);
emitColorChange('eyedropper');
};

/**
* mode change
* @param newMode
Expand Down Expand Up @@ -289,7 +294,7 @@ export default defineComponent({

return (
<div class={[`${baseClassName.value}__panel`, props.disabled ? statusClassNames.disabled : false]}>
<PanelHeader {...props} mode={mode.value} onModeChange={handleModeChange} />
<PanelHeader {...props} mode={mode.value} onModeChange={handleModeChange} onEyeDropperPick={handleEyeDropperPick} />
<div class={[`${baseClassName.value}__body`]}>
{isGradient.value ? (
<LinearGradient
Expand Down
15 changes: 13 additions & 2 deletions packages/components/color-picker/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ export interface TdColorPickerProps {
* @default true
*/
enableMultipleGradient?: boolean;
/**
* 是否开启吸色功能,启用后面板顶部出现吸色按钮,仅支持 Chrome/Edge 等现代浏览器
* @default false
*/
eyeDropper?: boolean;
/**
* 格式化色值。`enableAlpha` 为真时,`HEX8/RGBA/HSLA/HSVA` 有效
* @default RGB
Expand Down Expand Up @@ -133,13 +138,18 @@ export interface TdColorPickerPanelProps {
* @default true
*/
enableMultipleGradient?: boolean;
/**
* 是否开启吸色功能,启用后面板顶部出现吸色按钮,仅支持 Chrome/Edge 等现代浏览器
* @default false
*/
eyeDropper?: boolean;
/**
* 格式化色值。`enableAlpha` 为真时,`HEX8/RGBA/HSLA/HSVA` 有效
* @default RGB
*/
format?: 'HEX' | 'HEX8' | 'RGB' | 'RGBA' | 'HSL' | 'HSLA' | 'HSV' | 'HSVA' | 'CMYK' | 'CSS';
/**
* 最近使用的颜色。值为 [] 表示以组件内部的最近使用颜色”为准,值长度大于 0 则以该值为准显示最近使用颜色”。值为 false 或 null 则完全不显示最近使用颜色”
* 最近使用的颜色。值为 [] 表示以组件内部的最近使用颜色”为准,值长度大于 0 则以该值为准显示最近使用颜色”。值为 false 或 null 则完全不显示最近使用颜色”
* @default []
*/
recentColors?: Array<string> | boolean | null;
Expand Down Expand Up @@ -198,7 +208,8 @@ export type ColorPickerChangeTrigger =
| 'palette-alpha-bar'
| 'input'
| 'preset'
| 'recent';
| 'recent'
| 'eyedropper';

export interface ColorObject {
alpha: number;
Expand Down
26 changes: 26 additions & 0 deletions packages/components/color-picker/utils/eyedropper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
interface EyeDropperInstance {
open(options?: { signal?: AbortSignal }): Promise<{ sRGBHex: string }>;
}

interface EyeDropperConstructor {
new (): EyeDropperInstance;
}

function getEyeDropperCtor(): EyeDropperConstructor | undefined {
if (typeof window === 'undefined') return undefined;
const { EyeDropper } = window as Window & { EyeDropper?: unknown };
return typeof EyeDropper === 'function' ? (EyeDropper as EyeDropperConstructor) : undefined;
}

export const isEyeDropperSupported = (): boolean => getEyeDropperCtor() !== undefined;

export const openEyeDropper = async (signal?: AbortSignal): Promise<string | null> => {
const Ctor = getEyeDropperCtor();
if (!Ctor) return null;
try {
const result = await new Ctor().open(signal ? { signal } : undefined);
return result.sRGBHex;
} catch {
return null;
}
};
Loading