diff --git a/packages/common b/packages/common index 5aedfdf7ab..dc7710dbf8 160000 --- a/packages/common +++ b/packages/common @@ -1 +1 @@ -Subproject commit 5aedfdf7ab0457141b0a886d2126e22bbdb42cae +Subproject commit dc7710dbf8808b2bb9dbf74715f3eec0124a0837 diff --git a/packages/components/color-picker/_example/eye-dropper.tsx b/packages/components/color-picker/_example/eye-dropper.tsx new file mode 100644 index 0000000000..fcfcfc1db6 --- /dev/null +++ b/packages/components/color-picker/_example/eye-dropper.tsx @@ -0,0 +1,8 @@ +import { defineComponent } from 'vue'; +import { ColorPicker } from 'tdesign-vue-next'; + +export default defineComponent({ + setup() { + return () => ; + }, +}); diff --git a/packages/components/color-picker/color-picker-panel-props.ts b/packages/components/color-picker/color-picker-panel-props.ts index db17d95655..d2f449e232 100644 --- a/packages/components/color-picker/color-picker-panel-props.ts +++ b/packages/components/color-picker/color-picker-panel-props.ts @@ -22,6 +22,8 @@ export default { type: Boolean, default: true, }, + /** 是否开启吸色功能,启用后面板顶部出现吸色按钮,仅支持 Chrome/Edge 等现代浏览器 */ + eyeDropper: Boolean, /** 格式化色值。`enableAlpha` 为真时,`HEX8/RGBA/HSLA/HSVA` 有效 */ format: { type: String as PropType, diff --git a/packages/components/color-picker/components/panel/header.tsx b/packages/components/color-picker/components/panel/header.tsx index 90426793a8..88db6d070d 100644 --- a/packages/components/color-picker/components/panel/header.tsx +++ b/packages/components/color-picker/components/panel/header.tsx @@ -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'; @@ -17,35 +17,75 @@ export default defineComponent({ }, 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 (
-
- - {Object.keys(COLOR_MODES).map((key) => ( - - {Reflect.get(globalConfig.value, COLOR_MODES[key as keyof typeof COLOR_MODES])} - - ))} - -
+ {showModeSwitch && ( +
+ + {Object.keys(COLOR_MODES).map((key) => ( + + {Reflect.get(globalConfig.value, COLOR_MODES[key as keyof typeof COLOR_MODES])} + + ))} + +
+ )} + {showEyeDropper && ( + + )}
); }; diff --git a/packages/components/color-picker/components/panel/index.tsx b/packages/components/color-picker/components/panel/index.tsx index 9688bab301..af1af38985 100644 --- a/packages/components/color-picker/components/panel/index.tsx +++ b/packages/components/color-picker/components/panel/index.tsx @@ -111,6 +111,11 @@ export default defineComponent({ }, ); + const handleEyeDropperPick = (hex: string) => { + color.value.update(hex); + emitColorChange('eyedropper'); + }; + /** * mode change * @param newMode @@ -289,7 +294,7 @@ export default defineComponent({ return (
- +
{isGradient.value ? ( | boolean | null; @@ -198,7 +208,8 @@ export type ColorPickerChangeTrigger = | 'palette-alpha-bar' | 'input' | 'preset' - | 'recent'; + | 'recent' + | 'eyedropper'; export interface ColorObject { alpha: number; diff --git a/packages/components/color-picker/utils/eyedropper.ts b/packages/components/color-picker/utils/eyedropper.ts new file mode 100644 index 0000000000..04e8c1e623 --- /dev/null +++ b/packages/components/color-picker/utils/eyedropper.ts @@ -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 => { + const Ctor = getEyeDropperCtor(); + if (!Ctor) return null; + try { + const result = await new Ctor().open(signal ? { signal } : undefined); + return result.sRGBHex; + } catch { + return null; + } +};