组件状态标准
组件状态用于表达组件是否可操作、被选中、获得焦点、正在加载或处于异常。状态必须具有稳定语义,并允许真实业务中常见的组合;不能把组件压缩成互斥的单一视觉枚举。
状态总表
| 状态 | 语义 | 典型视觉 | 交互要求 |
|---|---|---|---|
| normal | 默认可操作。 | 默认背景、文本和边框。 | 可按组件语义响应输入。 |
| hovered | 指针位于可交互区域。 | stateHoverOverlay 或弱边框变化。 |
不改变布局,不抢焦点。 |
| pressed | 指针或键盘正在激活。 | statePressedOverlay 或明确按下反馈。 |
取消、离开或禁用后恢复。 |
| focused | 键盘焦点或文本输入焦点。 | focus ring、caret 或 focused border。 | 可通过键盘继续操作。 |
| selected | 当前项被选中或激活。 | selection 背景或强调条。 | 必须可与 hover、pressed、focus 同时表达。 |
| disabled | 整体不可交互。 | 弱化文本和背景。 | 不响应 pointer、keyboard、shortcut、popup。 |
| readonly | 值可看但不可改。 | 保持可读,弱化编辑入口。 | 复制、选择和滚动由组件语义决定。 |
| loading | 正在加载或处理。 | spinner、遮罩、骨架或等待状态。 | 明确是否阻断输入。 |
| empty | 无数据但非错误。 | 空状态文案或占位。 | 可保留新增、刷新等操作。 |
| error | 数据或操作失败。 | error 边框、文本或图标。 | 保留重试或修正入口。 |
| warning | 有风险但仍可继续。 | warning 色或提示。 | 不默认阻断操作。 |
| success | 操作成功或状态正常。 | success 色或图标。 | 不长期占据主要视觉层级。 |
visible 是所有 RenderObject 的基础状态,语义见组件基础状态契约。它会决定组件是否参与布局、绘制、hit test 和焦点,不属于普通交互状态。
组合模型
状态按不同职责分层,而不是使用一个总优先级互相覆盖:
能力门控:visible / disabled / blocking loading
持续语义:selected / readonly / error / warning / success
瞬时输入:pressed / hovered
独立指示:focused
合成顺序:
visible=false退出布局、绘制、hit test 和焦点。disabled或阻断式 loading 关闭输入能力并清理临时状态。- 选择基础 surface:normal、selected 或业务状态面。
- 在基础 surface 上合成 hovered;只有组件真实维护 pointer/key down 到 release/cancel 的 pressed 生命周期时,才继续合成 pressed,并让 pressed 优先于 hovered。
- 绘制 error/warning 等验证边框或提示。
- 最后绘制 focus ring,focus 不替换 selection 或 validation。
因此:
- selected + hovered:保留 selection 主视觉,再叠加
stateHoverOverlay。 - selected + pressed:仅适用于真实维护 pressed 生命周期的组件;保留 selection 主视觉,再叠加
statePressedOverlay。 - selected + focused:保留 selection,并额外绘制 focus ring。
- error + focused:保留 error 语义,并额外绘制 focus ring。
- disabled + any:清理 hover、pressed、drag、popup 和焦点,绘制 disabled 状态。
框架内部样式配方可以使用状态 flags 表达组合;业务代码不应导入或复制内部 derive*Style() 来手工模拟这套规则。
状态视觉强度
一般强度顺序:
normal < hovered < selected;具有 pressed 生命周期时,pressed 提供最强的瞬时反馈
这只是视觉强度,不表示 selected 会覆盖 focused。focus 是独立边框/焦点环,validation 是独立语义层。
stateHoverOverlay 和 statePressedOverlay 是透明覆盖色,适合叠加在普通 surface 或 selection 上。普通按钮也可以使用完整的 surfaceControlHover / surfaceControlActive。二者选择由组件内部配方决定,页面代码不要混用。
Hover
- 只在可交互命中区域内出现。
- 指针离开,或组件被隐藏、禁用时必须消失。
- 不改变组件尺寸、位置或周围布局。
- 高密数据行保持轻量,不能因为 hover 重建数据源或触发整页 layout。
- selected 项被 hover 时继续保留 selected 主视觉。
Pressed
- 来自明确激活动作:pointer down、Space、Enter 或拖拽 handle。
- 操作取消、指针释放、组件隐藏或禁用时必须恢复。
- pressed 不改变尺寸。
- click 通常在 release 且仍命中时触发,除非组件语义另有说明。
- selected 项被按下时仍是 selected,只增加更明确的瞬时反馈。
Focused
- 可键盘操作组件必须能获得焦点并绘制可见 focus ring。
- focus ring 不占布局尺寸。
- focus 不等于 selected;当前表格行、当前 tab 和输入焦点是不同语义。
- pointer 点击是否聚焦由组件契约决定。
- 组件禁用、隐藏或移出有效交互树后不能继续持有焦点。
- focus ring 使用
focusBorder或字段的fieldFocusBorder,不能遮掉 error border。
Selected
- 表示业务选择或当前激活项,不等于 focused。
- 多选组件必须区分 selected 集合、active/focused item 和 selection anchor。
- 当前文档 tab、当前导航项、当前表格行都属于 selected/active 语义,但可由不同组件配方表达。
selectionMuted用于弱激活,selectionBg用于明确选择,selectionStrong用于需要更强识别的选择。- 实际选择背景经过组合后,文字颜色使用
resolveSelectionText()保证可读。
Disabled
禁用后必须:
- 阻断 pointer、keyboard、shortcut、popup 和 drag。
- 从焦点顺序中移除,并释放已有焦点。
- 关闭该组件已经打开的 popup、tooltip、context menu 或 drag preview。
- 清理 hover、pressed 和拖拽临时状态。
- 使用 disabled token 绘制,但保持必要内容可读。
disabled 是能力状态,不只是把 alpha 降低。组件不能保留“看起来可操作但事件被吞掉”的视觉。
Readonly
readonly 不等于 disabled:
- 输入类控件通常保留文本选择、复制和滚动。
- 选择类控件不打开修改值的 popup。
- 命令类控件通常不提供 readonly,应使用 disabled。
- readonly 不应使内容难以阅读。
- readonly 可以和 focused、selected、error 同时存在,具体交互按组件契约处理。
Loading、Empty、Error
- loading 要说明是否阻断输入;非阻断 loading 不应禁用整个组件。
- empty 是正常状态,不使用 error 色。
- error 需要可读信息或可定位视觉,并保留修正入口。
- warning 表示风险,不默认阻断操作。
- success 是结果反馈,不应长期占据页面主要层级。
- Grid、Tree、List、Chart 等数据组件应把 loading、empty、error 放进组件状态层,不让每个业务页面重复绘制。
字段编辑状态
普通输入和表格内编辑使用字段语义:
- 默认编辑背景:
fieldBg。 - 编辑焦点:
fieldFocusBorder。 - 字段内文本选择:
fieldSelectionBg。
PlainTextEditor、CodeEditor、Markdown 等文档编辑器使用 editor.selectionBg / editor.selectionText。文档编辑器选择范围可能很大,不能与普通字段 token 混为一类。
验收清单
- hover、pressed、selected、focused 四种状态能否分别识别?
- selected + hover、selected + focus 是否仍保留两种语义?
- error + focus 是否同时可见?
- disabled 是否真正关闭所有输入、popup 和 drag?
- readonly 是否仍可读,并保留契约允许的复制/滚动?
- 状态变化是否不改变尺寸、不触发无关布局?
- 数据行 hover 是否只更新必要绘制区域?
- 浅色、深色和紧凑主题下,状态顺序与文本对比度是否成立?