Skip to content

feat(color-picker): 支持吸色(EyeDropper API)#2585

Open
ceilf6 wants to merge 2 commits into
Tencent:developfrom
ceilf6:feature/issue-2568
Open

feat(color-picker): 支持吸色(EyeDropper API)#2585
ceilf6 wants to merge 2 commits into
Tencent:developfrom
ceilf6:feature/issue-2568

Conversation

@ceilf6

@ceilf6 ceilf6 commented Jul 1, 2026

Copy link
Copy Markdown

🤔 这个 PR 的性质是?

  • 新特性提交
  • 组件样式/交互改进
  • 文档改进
  • 测试用例

🔗 相关 Issue

ref #2568

💡 需求背景和解决方案

背景

Issue #2568 希望颜色选择器支持吸色:用户上传背景图后调整页面背景色时,希望能直接从页面中吸取颜色,而不是在面板里手动调整色值。浏览器已提供原生的 EyeDropper API(Chrome 95+、Edge 95+),本 PR 在公共库层面补齐该能力,供各框架实现消费。

方案

本仓库是各框架(vue-next / react / web-vue-next)共享的 JS 与样式层,组件渲染逻辑在各框架仓库实现,因此本 PR 只交付公共层:

  1. JS 能力封装 js/color-picker/eyedropper.ts

    • isEyedropperSupported():能力探测,组件据此决定是否渲染吸色按钮(不支持的环境不渲染入口,避免出现不可用按钮)。
    • openEyedropper(signal?):调用原生 EyeDropper.open(),返回 sRGBHex;支持透传 AbortSignal 以便取消。JSDoc 标注 reject 语义(不支持 vs 原生异常透传)。
    • getEyeDropperConstructor 通过 globalThis 读取并过滤非函数值,兼容浏览器 / Worker / Node 测试环境,并通过本地类型声明避免与 lib.dom 内置 EyeDropper 类型冲突。
    • 已从 js/color-picker/index.ts 导出。
  2. 样式 style/web/components/color-picker/

    • 新增 .t-color-picker__eyedropper 按钮样式(_index.less)与变量(_var.less),复用既有 __icon 视觉语言(次级文字色、hover 浅色容器底、圆角),点击热区略大于色板操作图标。
    • 按钮为独立成类,便于各框架实现按 enableEyedropper 在面板头部放置;不支持环境整体不渲染。
  3. 文档与设计规范

    • docs/web/api/color-picker.md(中 / 英)新增「支持吸色的颜色选择器」说明段,描述 enableEyedropper 的行为与浏览器兼容性(demo 由各框架仓库补充,未在本 PR 放占位符以免下游站点构建报错)。
    • docs/web/_design/颜色选择器 ColorPicker.md 新增「吸色」设计指南:何时使用、交互说明(系统级取色镜、Esc 取消、经 onChange/v-model 回填、渐变模式下回填到当前色点)、组成。
  4. 测试 test/unit/color-picker/eyedropper.test.ts:覆盖不支持 / 支持 / signal 透传 / open 异常 / 非函数 EyeDropper 边界与 isSupportedopenEyedropper 一致性,共 8 个用例。

  5. eslint 配置 .eslintrc.js:在 env 启用 es2020: true(自动以只读方式声明 globalThis 等标准全局),替代手动 globals 条目。

API 用法(各框架实现侧)

<ColorPicker v-model="color" :enableEyedropper="true" />
  • enableEyedropper(Boolean,默认 false):是否展示吸色按钮。仅在支持 EyeDropper API 的浏览器中渲染。
  • 吸到的颜色经既有 onChange / v-model 通道回填,与手动选色行为一致;渐变模式下回填到当前选中的渐变色点。

组件渲染层(按钮在面板中的放置与点击调用)在各框架仓库实现,随后以子模块形式消费本仓库的 JS 与样式。

📝 更新日志

  • feat(color-picker): 新增吸色能力(EyeDropper API),公共库提供 isEyedropperSupported / openEyedropper.t-color-picker__eyedropper 样式,供各框架实现按 enableEyedropper 渲染吸色按钮

☑️ 请求合并前的自查清单

  • 文档已补充或无须补充
  • 代码演示已提供或无须提供(demo 由各框架仓库补充)
  • TypeScript 定义已补充或无须补充
  • Changelog 已提供或无须补充

验证

  • npm run lint(stylelint + prettier + eslint + typecheck)通过
  • npm run test:unit 通过(28 文件 / 554 用例,含本次新增 8 个)

🤖 Generated with Claude Code

在颜色选择器面板中提供浏览器原生吸色能力,允许用户从页面任意位置取色,
无需在面板中手动调整色值。

- js/color-picker/eyedropper.ts:封装 EyeDropper API,提供 isEyedropperSupported
  能力探测与 openEyedropper 调用方法(支持 AbortSignal 取消),通过 globalThis
  读取构造函数以兼容浏览器/Worker/Node 测试环境,并避免与 lib.dom 内置类型冲突
- style/web:新增 .t-color-picker__eyedropper 按钮样式与变量,复用 __icon 视觉语言
- docs:补充 enableEyedropper 属性文档(中英文)与吸色交互设计说明
- test:新增单元测试覆盖支持/不支持/signal 透传/异常等路径
- .eslintrc.js:补充 globalThis 全局声明

各框架(vue-next/react/web-vue-next)随后以子模块形式消费本仓库的 JS 与样式,
在面板头部按 enableEyedropper 渲染吸色按钮,吸到的颜色经既有 onChange / v-model 回填。

Closes Tencent#2568

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 1, 2026 14:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

为 ColorPicker 在公共层补齐浏览器原生 EyeDropper API(吸色)能力封装,并提供对应样式与文档/测试支撑,供各框架仓库在渲染层按 enableEyedropper 开关接入。

Changes:

  • 新增 isEyedropperSupported / openEyedropper 封装并从 js/color-picker 导出
  • 新增吸色按钮样式与 Less 变量
  • 增补中英文文档示例段与单元测试覆盖

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
js/color-picker/eyedropper.ts 新增 EyeDropper API 封装与能力探测/调用方法
js/color-picker/index.ts 导出 eyedropper 能力供外部消费
style/web/components/color-picker/_index.less 新增 __eyedropper 吸色按钮样式
style/web/components/color-picker/_var.less 新增吸色按钮相关设计变量
docs/web/api/color-picker.md 中文 API 文档新增“支持吸色”示例说明与占位 demo
docs/web/api/color-picker.en-US.md 英文 API 文档新增“Color Picker with Eyedropper”示例说明与占位 demo
test/unit/color-picker/eyedropper.test.ts 新增 eyedropper 封装的单元测试用例
.eslintrc.js ESLint globals 增加 globalThis 以避免 no-undef

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +34 to +46
const getEyeDropperConstructor = (): EyeDropperConstructor | undefined => {
if (typeof globalThis === 'undefined') {
return undefined;
}
return (globalThis as unknown as { EyeDropper?: EyeDropperConstructor }).EyeDropper;
};

/**
* 判断当前环境是否支持吸色(EyeDropper API)
* @returns
*/
export const isEyedropperSupported = (): boolean => typeof getEyeDropperConstructor() === 'function';

Comment on lines +60 to +63
flex-shrink: 0;
width: @color-picker-eyedropper-size;
height: @color-picker-eyedropper-size;
font-size: @color-picker-icon-font-size;
Comment thread .eslintrc.js Outdated
Component: true,
getRegExp: true,
NodeJS: true,
globalThis: true,
@ceilf6

ceilf6 commented Jul 1, 2026

Copy link
Copy Markdown
Author

@ceilf6/repo-guard CR

@ceilf6

ceilf6 commented Jul 1, 2026

Copy link
Copy Markdown
Author

🛡️ ceilf6/repo-guard

代码评审报告: feat(color-picker): 支持吸色(EyeDropper API)

风险等级:
处理建议: 评论
决策摘要: 公共层能力封装本身正确、隔离、有测试覆盖,可以合并;但存在一处示例占位符可能导致文档构建失败,以及未使用变量、类型冲突风险等小问题需要关注。

级联分析

  • 变更符号: isEyedropperSupported, openEyedropper, EyeDropper/EyeDropperResult/EyeDropperOpenOptions/EyeDropperConstructor 类型, js/color-picker/index.ts re-export
  • 受影响流程: 各框架仓库(vue-next/react/web-vue-next)后续消费该 API 渲染吸色按钮;文档站点构建({{ eyedropper }} demo 占位符)
  • 变更集外调用方: 无(本仓库为纯公共库,没有组件渲染层直接调用这些导出)(text search)
  • 置信度: medium — 本仓库内部无消费者,无法验证各框架仓库最终如何接入;文档 demo 占位符是否已在对应站点仓库注册无法确认

问题发现

  1. [中] 文档新增 {{ eyedropper }} demo 占位符但本 PR 未提供对应 demo 文件

    • 证据: docs/web/api/color-picker.md:48.en-US.md:48 新增 {{ eyedropper }},PR 描述也承认“示例占位待各框架仓库补充演示”
    • 受影响调用方/流程: 文档站点构建流程(tdesign-common 本身通常不构建站点,但下游站点仓库引用该 md 时会尝试解析该占位符)
    • 最小可行修复: 如果本仓库的文档不会被直接构建成站点(仅作为文本被各框架仓库复制/引用),可以合并;否则应确认该占位符不会导致下游 build 报“demo not found”。建议在 PR 描述或 commit 中明确本仓库文档 md 与实际构建的关系,避免下游合并子模块后站点构建立即失败。
  2. [低] EyeDropperConstructor 未使用 new.target / 未处理构造函数抛错路径

    • 证据: js/color-picker/eyedropper.ts:52-55openEyedroppernew EyeDropperConstructor() 未 try/catch)
    • 受影响调用方/流程: 各框架实现调用 openEyedropper 时,如果 new EyeDropper() 本身抛错(理论上罕见,但用户已有 EyeDropper 会话未结束时规范允许抛错),异常会向上抛出但没有专门包装,调用方需要自行区分“不支持”和“调用失败”两种 reject。
    • 最小可行修复: 非阻塞。当前直接透传 reject 即可满足契约,属于合理最小实现,仅建议在类型注释里说明 reject 语义供各框架实现参考。

行级发现

  • [js/color-picker/eyedropper.ts:24] getEyeDropperConstructortypeof globalThis === 'undefined' 分支在目标运行环境(ES2020+/Node 12+/所有现代浏览器)几乎不可能为真,属于防御性冗余判断,非阻塞但可以去掉简化。
  • [js/color-picker/eyedropper.ts:52] openEyedroppernew EyeDropperConstructor() 未做 try/catch,构造失败会以未包装的原生异常向上抛,建议在类型注释中说明 reject 语义(见 Findings chore(deps-dev): bump jest from 26.6.3 to 27.4.5 #2),非阻塞。
  • [test/unit/color-picker/eyedropper.test.ts:6] stubEyeDropperfunction MockEyeDropper() { return { open }; } 模拟构造函数返回对象覆盖 this,这是合法 JS 行为但依赖隐晦语义,建议加一行注释说明"函数作为构造器时若显式 return 对象会覆盖默认 this",便于后续维护者理解为何不用 class。
  • [style/web/components/color-picker/_var.less:76] 新增变量 @color-picker-eyedropper-margin-left 在同批次 diff 中未见任何 .less 或框架侧使用,若确认各框架仓库会消费该变量可以保留,否则建议确认是否为过早引入的未使用变量。
  • [.eslintrc.js:26] 新增 globalThis: true 全局声明合理,但更规范的做法是在 env 中启用 es2020: true(该 env 会自动包含 globalThis 全局),而不是逐个手动列全局变量;非阻塞,当前写法可行。

Karpathy 评审

  • 假设: PR 假设"公共库只需提供能力探测 + 调用封装,UI 渲染完全交给下游框架仓库",这个假设与仓库定位(tdesign-common 是共享 JS/样式层)一致,且 issue [ColorPicker] 支持吸色 #2568 本身要求"UI 设计 + 任意一侧组件代码",本 PR 只完成了 JS/样式基础设施部分,未完成"任意一侧组件代码"这一验收要求的完整闭环——但这符合 monorepo 架构下的合理分工,PR 描述已明确说明。
  • 简洁性: 实现克制,没有过度抽象,isEyedropperSupported + openEyedropper 两个函数职责单一,类型定义精简,无猜测性配置项。
  • 结构质量: 良好。新文件独立、无侵入性修改,index.ts 只新增一行 export,样式复用 __icon 视觉语言并抽出变量,未见对现有代码的侵入式改动。
  • 变更范围: 精准。9 个文件变更均直接服务于吸色能力目标,无夹带的无关重构或格式噪声。
  • 验证: 测试覆盖四条核心路径(不支持/支持/signal 透传/异常抛出),共 7 个用例,覆盖了 openEyedropper 的主要分支。但没有测试覆盖 getEyeDropperConstructorEyeDropper 存在但非函数类型(如 undefined 与 object 之外的其他类型)的边界,以及没有验证 isEyedropperSupportedopenEyedropper 之间状态一致性(理论上两者应该总是同步判断,测试对此隐含验证但未显式断言两者互斥/一致)。

缺失覆盖

  • 需要确认各框架仓库(vue-next/react/web-vue-next)在消费本次新增导出时,是否已有对应的组件渲染 PR 或跟踪 issue,避免公共层先行合并后长期无人消费,造成"能力已声明支持但实际组件不可用"的用户预期落差。
  • docs/web/api/*.md 中的 {{ eyedropper }} demo 占位符在各框架仓库(真正构建文档站点的地方)落地前,建议确认不会造成站点构建 CI 报错(取决于该占位符解析机制是否要求 demo 文件必须存在)。

- getEyeDropperConstructor 过滤非函数值,避免 new 抛通用 TypeError;
  isEyedropperSupported 简化为判断构造函数是否存在,两者状态一致
- openEyedropper 补充 reject 语义说明(不支持 vs 原生异常透传)
- 删除未使用的 @color-picker-eyedropper-margin-left 变量
- .eslintrc.js 改用 env.es2020 声明 globalThis(只读),替代手动 writable 全局
- 移除文档中 {{ eyedropper }} demo 占位符,避免下游站点构建报 demo not found
- 测试补充:非函数 EyeDropper 边界 + isSupported/openEyedropper 一致性用例;
  stubEyeDropper 增加注释说明为何用 function 而非 class

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ceilf6

ceilf6 commented Jul 1, 2026

Copy link
Copy Markdown
Author

感谢 @ceilf6/repo-guard 与 @copilot 的评审,已按意见在 e86985f 逐项处理:

Copilot

  1. getEyeDropperConstructor 无类型守卫 → 改为内部 typeof EyeDropper === 'function' 过滤,非函数值统一回退为 undefinedisEyedropperSupported 简化为 getEyeDropperConstructor() !== undefined,两者状态一致,openEyedropper 不再出现 new 抛通用 TypeError 的路径。
  2. @color-picker-eyedropper-margin-left 未使用 → 已删除(间距交由各框架实现按放置上下文处理,不在基类强加 margin)。
  3. globalThis: true(可写)→ 改为 env.es2020: true,以只读方式声明 globalThis 等标准全局,更规范。

repo-guard

  • [中] {{ eyedropper }} 占位符 → 已从中文/英文 API 文档移除,避免下游站点仓库解析未注册占位符报 "demo not found";示例段说明文字保留,demo 由各框架仓库补充。
  • [低] openEyedropper reject 语义 → 已在 JSDoc 说明:不支持环境抛 Error,其余 reject(用户取消、已有吸色会话等)原样透传,由调用方区分。
  • 行级 stubEyeDropper → 已加注释说明用 function 而非 class 的原因(构造器显式 return 对象覆盖 this,同时避开 class-methods-use-this / max-classes-per-file)。
  • 行级 typeof globalThis === 'undefined' 防御冗余 → 保留并加注释。理由:本仓库 browserslistie >= 11 / android >= 4.4,这些旧环境无 globalThis,去掉会触发 ReferenceError;该兜底保证 isEyedropperSupported 在旧环境安全返回 false 而非抛错。
  • 行级 .eslintrc.js → 已按建议改用 env.es2020(见上)。
  • Karpathy 测试缺口 → 补充用例:EyeDropper 为非函数值时 isEyedropperSupported=falseopenEyedropper 抛"不支持",显式断言两者一致性。

关于"缺失覆盖"

  • 各框架仓库的渲染层 PR:本 PR 为公共层先行,后续会在 vue-next / react / web-vue-next 各仓库跟进组件侧实现,届时补充 demo 与 enableEyedropper 的渲染逻辑。
  • 占位符构建风险:已通过移除 {{ eyedropper }} 消除。

验证:npm run lint 通过,npm run test:unit 28 文件 / 554 用例全过。

@ceilf6

ceilf6 commented Jul 1, 2026

Copy link
Copy Markdown
Author

🛡️ ceilf6/repo-guard

代码评审报告: feat(color-picker): 支持吸色(EyeDropper API)

风险等级:
处理建议: 批准
决策摘要: 上一轮 Copilot 与 repo-guard 意见均已逐项落实,typeof EyeDropper === 'function' 守卫消除了非函数值路径,{{ eyedropper }} 占位符已移除,测试补充了一致性断言;公共层本身可合并,跨框架渲染层留待后续 PR 是预期内的分阶段交付。

级联分析

  • 变更符号: isEyedropperSupportedopenEyedroppergetEyeDropperConstructor(新增,本仓库内无既有调用方);js/color-picker/index.ts 新增一行 export * from './eyedropper'(纯增量导出,不改变已有导出)
  • 受影响流程: 无——本 PR 未修改任何现有组件渲染逻辑,enableEyedropper 消费方在下游框架仓库(vue-next / react / web-vue-next),不在本变更集范围内
  • 变更集外调用方: 无。新增的 .less 类名 __eyedropper 与变量在本仓库内未被任何组件模板引用,属于"先行交付、后续消费"的公共层模式,与 issue [ColorPicker] 支持吸色 #2568 "需同时完成 UI 设计和任意一侧组件代码"的要求相符(本 PR 明确定位为其中的公共库前置部分)
  • 置信度: high(无代码图谱可用,采用 diff 文本审查;变更文件数少、边界清晰,新增符号在本仓库内无消费者可通过全文搜索确认,破坏性风险低)

问题发现

无 blocking findings。

行级发现

  • [js/color-picker/eyedropper.ts:31] typeof globalThis === 'undefined' 分支在多数现代目标环境下不可达,但已加注释说明是为兼容 browserslist 中 ie >= 11 等旧环境,判断合理,非阻塞。
  • [js/color-picker/eyedropper.ts:34] getEyeDropperConstructor 已通过 typeof EyeDropper === 'function' 过滤非函数值,与 isEyedropperSupported 状态一致,此前 Copilot 指出的类型守卫问题已解决。
  • [test/unit/color-picker/eyedropper.test.ts:38] 新增用例显式断言 EyeDropper 为非函数值时 isEyedropperSupported() === falseopenEyedropper() 抛"不支持"错误,覆盖了此前缺失的一致性场景。
  • [docs/web/api/color-picker.md:45] {{ eyedropper }} 占位符已移除,仅保留文字说明,不会导致下游站点构建时因未注册 demo 占位符报错。
  • [.eslintrc.js:8] 已改为 es2020: true,以只读方式声明标准全局变量,替代此前可写的 globalThis: true,符合建议。

Karpathy 评审

  • 假设: 唯一残留假设是"下游框架仓库会在后续 PR 中消费这些导出并实现 enableEyedropper 渲染逻辑",PR 描述已明确说明分阶段交付计划,风险可接受。
  • 简洁性: getEyeDropperConstructor 单一职责、无冗余分支;样式变量精简(已删除未使用的 margin 变量),未见过度设计。
  • 结构质量: 新文件小而聚焦(66 行),无跨层泄漏;样式类 __eyedropper 复用 __icon 视觉语言变量,未重复造轮子;未触及任何超过 1000 行的文件。
  • 变更范围: 9 个文件均围绕吸色能力展开,无无关重构或格式噪声;.eslintrc.js 单行 env 配置调整属于必要的构建期修复,范围克制。
  • 验证: 新增 8 个单测覆盖不支持/支持/signal 透传/异常透传/非函数值边界,且补充了一致性断言;linttest:unit(28 文件/554 用例)均通过,验证强度与变更风险匹配。

缺失覆盖

  • 无本仓库内阻塞项。后续需在 vue-next / react / web-vue-next 各框架仓库的跟进 PR 中验证:enableEyedropper 渲染逻辑是否正确调用 openEyedropper、能力探测失败时按钮是否确实不渲染、渐变模式下颜色是否正确回填到当前色点——这些应作为下游 PR 的独立验收项,而非本 PR 的合并阻塞条件。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants