ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

open-pencil Vue SDK 渐变编辑器原语解析:GradientEditorStop 的状态模型、键盘交互与无障碍实现

open-pencil Vue SDK 渐变编辑器原语解析:GradientEditorStop 的状态模型、键盘交互与无障碍实现 前端桌面应用AI 应用MCP 服务【免费下载链接】open-pencilAI-native design editor. Open-source Figma alternative.项目地址https://gitcode.com/gh_mirrors/op/open-pencil点击查看免费下载GradientEditorStop是 open-pencil 的open-pencil/vue包提供的无头headless渐变停靠点gradient stop原语用于在自定义渐变编辑器中渲染单个可选中、可拖拽、可删除的渐变点。本文以 packages/docs/it/programmable/sdk/api/components/gradient-editor-stop.md 为主线结合packages/vue/src/primitives/GradientEditor/下的真实实现与 open-pencil 编辑器内的实际集成src/components/fill-picker/GradientEditor.vue完整讲解该组件的 Props、事件、插槽协议、ARIA 暴露与键盘交互并给出可直接运行的 Vue 示例。读完本文你将能够基于该原语搭建具备完整可访问性屏幕阅读器 键盘的自定义渐变编辑器或在其上扩展出样式化的停靠点行stop row。GradientEditorStop 在渐变编辑器中的地位在 open-pencil 的渐变编辑能力中组件被拆分为三个可组合的无头原语GradientEditorStop是其中最细粒度的点原语GradientEditorRoot无头根原语负责活动停靠点状态、渐变子类型切换、停靠点增删改逻辑、活动颜色编辑与派生出的渐变条背景GradientEditorBar无头可拖拽条原语处理停靠点在条上的拖拽与选中GradientEditorStop渲染一个多态polymorphic停靠点记录并暴露选中与拖拽状态。该原语源码位于 packages/vue/src/primitives/GradientEditor/GradientEditorStop.vue类型定义在 packages/vue/src/primitives/GradientEditor/types.ts并统一从 packages/vue/src/primitives/GradientEditor/index.ts 与 packages/vue/src/index.ts 对外导出供open-pencil/vue消费者直接import。底层数据模型GradientStop 与 Fill在深入组件之前先明确它操作的数据类型。GradientEditorStop接受一个GradientStop对象其结构定义在 packages/scene-graph/src/types.tsexport interface GradientStop { color: Color position: number // 0 ~ 1 的小数表示位于渐变条上的比例位置 }停靠点stops挂在渐变填充Fill的gradientStops字段上见同文件Fill接口type: FillType支持GRADIENT_LINEAR、GRADIENT_RADIAL、GRADIENT_ANGULAR、GRADIENT_DIAMOND等渐变子类型。GradientEditorStop本身只负责渲染与交互一个点而点的增删改由根原语通过useGradientStops组合式函数统一管理。Props 全量解析组件的 Props 定义来自GradientEditorStopProps继承 reka-ui 的PrimitiveProps源码见 types.tsProp类型默认值说明stopGradientStop必填当前停靠点的颜色与位置indexnumber必填停靠点在数组中的索引事件回调与无障碍标签会用到它activeboolean必填是否选中选中状态通过data-selected属性暴露供样式化draggingbooleanfalse是否正在拖拽通过data-dragging属性暴露interactivebooleantrue是否为可交互停靠点见下文两种渲染模式removablebooleantrue是否允许通过 Delete/Backspace 删除positionStepnumber1方向键微调步长单位百分比按住 Shift 时放大 10 倍labelstring自动生成ARIA 标签不传时自动生成Gradient stop {index 1}as/asChild继承自PrimitivePropsdiv/false多态渲染目标元素其中as/asChild来自 reka-ui 的Primitive组件见 GradientEditorStop.vue 中的import { Primitive } from reka-ui意味着同一个原语既可以渲染成div也可以通过asChild把交互行为注入到你自己的根元素上——这正是无头原语多态能力的来源。事件协议GradientEditorStop通过以下事件向上传递变更意图payload 一律携带index便于父级在数组中找到目标停靠点源码见 GradientEditorStop.vue事件payload触发时机selectindex: number点击或聚焦时选中该停靠点updatePositionindex, position键盘微调或外部调用actions.updatePosition后位置变化updateColorindex, hex更新停靠点颜色hex 字符串updateOpacityindex, opacity更新停靠点透明度百分比removeindexDelete/Backspace 或外部调用actions.remove时删除注意updatePosition携带的position是百分比0–100而非底层模型中的小数0–1转换逻辑发生在useGradientStops中见下文。插槽协议与 actionsGradientEditorStop是典型的渲染内容完全交由消费者的原语它自身不绘制任何视觉元素而是通过默认插槽把状态与动作暴露出来。插槽 props 定义于GradientEditorStopSlotProps见 types.ts{ stop: GradientStop // 原始数据 index: number active: boolean // 与 selected 等价 selected: boolean dragging: boolean positionPercent: number // 位置百分比0–100已取整 opacityPercent: number // 透明度百分比0–100已取整 hex: string // 颜色的 hex 原始串 css: string // 颜色的 CSS 表示可直接用于 background actions: GradientEditorStopActions }actions是GradientEditorStopActions接口的实例见 types.tsexport interface GradientEditorStopActions { select: () void updatePosition: (position: number) void updateColor: (hex: string) void updateOpacity: (opacity: number) void remove: () void }这些 action 已经绑定了index因此插槽内部调用时无需再传索引。派生值positionPercent、opacityPercent、hex、css在 GradientEditorStop.vue 中通过computed生成颜色转换复用open-pencil/scene-graph/color的colorToHexRaw与colorToCSS。两种渲染模式interactive 与 composite row原语设计了两个使用场景由interactive属性区分交互式停靠点默认interactivetrue用于渐变条bar上的手柄。它会以roleslider进入 Tab 键顺序并通过 ARIA 暴露百分比位置。方向键以positionStep微调、按住 Shift 使用 10 倍步长、Home/End 跳转到边界、Delete/Backspace 在removable时触发remove。被处理过的按键会同时preventDefault()与stopPropagation()从而阻断编辑器层面的删除与移动快捷键避免双重触发见 GradientEditorStop.vue。非交互式interactivefalse用于停靠点列表行这种复合结构。行内的每个字段位置输入、色板、hex 输入、透明度、删除按钮各自独立聚焦此时整个行不进入 slider 的 Tab 顺序但插槽 actions 与data-selected/data-dragging属性照常暴露。两种模式都通过data-slotstop、data-selected、data-dragging属性暴露状态配合 Tailwind CSS 属性选择器即可完成样式化。无障碍细节ARIA 暴露交互式模式下原语会设置完整的 slider 无障碍契约见 GradientEditorStop.vueroleslider tabindex0 aria-labellabel 或 Gradient stop N aria-valuemin0 aria-valuemax100 aria-valuenowpositionPercent aria-valuetextpositionPercent%其中aria-valuenow直接使用Math.round(stop.position * 100)计算的位置百分比。这让屏幕阅读器用户能够准确感知停靠点当前所处位置同时原生 Tab 顺序在停靠点之间循环的设计每个交互式停靠点都是tabindex0的独立可聚焦元素保证键盘用户无需鼠标即可逐一访问所有停靠点。官方示例最小可用用法英文权威文档 gradient-editor-stop.md 给出了可直接运行的 twoslash 示例展示了非受控渲染 事件监听的最小用法script setup langts import type { GradientStop } from open-pencil/scene-graph import { GradientEditorStop } from open-pencil/vue const stop: GradientStop { color: { r: 0.4, g: 0.2, b: 0.9, a: 1 }, position: 0.5 } /script template GradientEditorStop :stopstop :index0 active labelMiddle gradient stop update-position(_index, position) console.log(position) / /template真实项目中GradientEditorStop通常与GradientEditorRoot配合使用由根原语统一提供停靠点数组与各update*动作见 GradientEditorRoot.vue它内部通过useGradientStops组合函数管理全部状态。编辑器内真实集成两种模式的完整拼装open-pencil 编辑器自带的渐变面板 src/components/fill-picker/GradientEditor.vue 是GradientEditorStop的完整生产级用例演示了如何在一条代码里同时使用两种模式渐变条上的交互式停靠点拖拽手柄GradientEditorBar :stopsroot.stops :active-stop-indexroot.activeStopIndex :bar-backgroundroot.barBackground select-stoproot.actions.selectStop drag-stoproot.actions.dragStop v-slotbar GradientEditorStop v-for(stop, idx) in bar.stops :keyidx :stopstop :indexidx :activeidx bar.activeStopIndex :draggingidx bar.draggingIndex :removablebar.stops.length 2 :style{ left: ${stop.position * 100}%, background: colorToCSS(stop.color) } selectroot.actions.selectStop update-positionroot.actions.updateStopPosition removeroot.actions.removeStop pointerdown.stopbar.actions.stopPointerDown(idx, $event) / /GradientEditorBar注意这里的细节left定位由父级样式负责原语不强制视觉布局pointerdown.stop防止点击手柄时与条形拖拽事件冲突removable与当前停靠点数量联动少于等于 2 个时不允许删除。停靠点列表行interactivefalse复合行GradientEditorStop v-for(stop, idx) in root.stops :keyidx :stopstop :indexidx :activeidx root.activeStopIndex :removableroot.stops.length 2 :interactivefalse v-slots NumberField :model-values.positionPercent :min0 :max100 update:model-values.actions.updatePosition(Number($event)) / button :style{ background: s.css } click.stops.actions.select / input :values.hex maxlength6 changes.actions.updateColor(inputValue($event)) / NumberField :model-values.opacityPercent :min0 :max100 update:model-values.actions.updateOpacity(Number($event)) / IconButton v-ifroot.stops.length 2 click.stops.actions.remove / /GradientEditorStop这正是行内各字段各自聚焦、整行不进入 slider Tab 顺序的典型拼装位置、颜色、透明度分别用独立输入控件编辑直接消费插槽暴露的positionPercent、css、hex、opacityPercent与actions。底层逻辑useGradientStops 如何驱动这些动作GradientEditorRoot的所有插槽属性与动作均来自 useGradientStops源码见 packages/vue/src/primitives/GradientEditor/useGradientStops.ts。理解它有助于把GradientEditorStop的事件正确接回到数据模型updateStopPosition(index, position)接收GradientEditorStop传来的百分比除以 100 后夹取到[0, 1]写回GradientStop.positionupdateStopOpacity(index, opacity)同样以百分比输入、夹取到[0, 1]后写入color.aupdateStopColor(index, hex)内部先selectStop(index)选中目标点再通过useColorModel的updateHex走统一颜色模型链路addStop()在最后两个停靠点位置的中点插入新点不足两个点时取 0.5并排序后自动选中新点removeStop(index)当停靠点数量 2时才允许删除删除后活动索引回落到安全范围内——这正是面板里:removablestops.length 2约束的根源barBackground派生自所有停靠点的linear-gradient(to right, ...)字符串作为渐变条的 CSS 背景。GradientEditorStop自身只做渲染 意图传递不持有任何数据——数据始终单向流动Fill.gradientStops→ Root 派生 → Stop 展示 → 事件回调 →useGradientStops更新 → 通过update事件把新的Fill对象交还给应用。这与 GradientEditorRoot 文档中接收fill、发出update的约定完全一致。测试验证键盘微调与删除行为open-pencil 的端到端测试 tests/e2e/color-picker/basic.spec.ts 直接验证了本原语的键盘契约可作为行为规格参考// 添加一个停靠点后条上应出现 3 个 slider 角色元素 const stops page.getByTestId(fill-picker-gradient-bar).getByRole(slider) await expect(stops).toHaveCount(3) // 聚焦第一个停靠点按方向键 → aria-valuenow 1 const first stops.first() await first.focus() const before Number(await first.getAttribute(aria-valuenow)) await first.press(ArrowRight) await expect(first).toHaveAttribute(aria-valuenow, String(Math.min(100, before 1))) // 按 Delete → 停靠点数量减一 await first.press(Delete) await expect(stops).toHaveCount(2)这条测试同时印证了三件事交互式停靠点确实以slider角色暴露、方向键以positionStep1微调并夹取到 100 上限、removable时 Delete 键确实触发删除。配套的fill-picker-add-stop与fill-picker-gradient-bar测试标识分别对应面板中的添加停靠点按钮与渐变条见 GradientEditor.vue 中的data-test-id。相关 API 索引GradientEditorRoot渐变编辑根原语负责状态与动作GradientEditorBar可拖拽渐变条原语useGradientStops停靠点状态与变更逻辑的组合式函数useColorModel颜色编辑所复用的统一颜色模型类型与导出types.ts、index.ts赞分享前端桌面应用AI 应用MCP 服务【免费下载链接】open-pencilAI-native design editor. Open-source Figma alternative.项目地址https://gitcode.com/gh_mirrors/op/open-pencil点击查看免费下载相关推荐Open-Pencil Vue SDK 的 GradientEditorStop渐变停止点原语的可访问性与键盘交互全解析Open Pencil Vue SDK 的 GradientEditorStop渐变停止点原语的可访问性与键盘交互全解析 GradientEditorStop前端桌面应用AI 应用MCP 服务open-pencil SDK 的 GradientEditorRoot无头渐变编辑器根原语的状态契约与实现剖析open pencil SDK 的 GradientEditorRoot无头渐变编辑器根原语的状态契约与实现剖析 GradientEditorRoot 是 o前端桌面应用AI 应用MCP 服务OpenPencil 渐变编辑器 Stop 原语实战GradientEditorStop 的交互状态、可访问性与键盘操作全解OpenPencil 渐变编辑器 Stop 原语实战GradientEditorStop 的交互状态、可访问性与键盘操作全解 GradientEditorSt前端桌面应用AI 应用MCP 服务上一篇用 WeChatMsg 三步把微信聊天记录导出成可搜索的私人档案下一篇B站大会员4K视频免费下载指南一个开源工具完整搞定充电专属内容创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表