CharDesk Docs

Cell Widget Behavior

State, input, scroll, and editor behavior contracts.

HEAD a63778e

Cell Widget 行为规范

返回事实白板 · UI 哲学 · Macintosh 标准 · Semantics

状态与主题

  • focused 是逻辑焦点,selected 是持久选择;二者可共存。
  • pressActive 是共享控制器通过 PressManager 管理的离散瞬时按压;activationFlash 是 command 已接受后的离散确认;manipulating 是 GestureManager 管理的连续 pointer 操控。三者均不进入业务 state。
  • Button、Checkbox、Select、ComboboxItem、Toggle、RadioItem、List、Menu、Tree、Grid 共用反色高亮:鼠标与 keyboard/AT 采用最近输入来源,逻辑 focus 独立保留;pointer 移出撤销高亮,键盘恢复同一套高亮。Slider 的单值与双端模式同样投影输入来源,但仅强调目标 thumb。Tabs 的选中态由 variant 独立控制。
  • Checkbox checked/mixed、Select selected、Toggle pressed、Radio checked、List/Tree/Grid selected 使用独立字符标记;Tabs 默认 underline,在当前标签文字下方绘制 ⎺;solid 保留单行反色选择背景。
  • TextInput、ComboboxInput 与 TextArea 的编辑活动由 Runtime 比对 focusedId === activeFocusId 后投影为 focusActive,不依赖输入来源、hover 或正文变化。TextInput 默认 surface,活动时整行 highlight;ghost 与 ComboboxInput 保留背景。三者仅在活动时以相对底色反差显示文字选区;选区 token 与底色撞色时反转当前前景/背景。原生 textarea 的 selectionchange 经 set-selection 同步到 Cell 状态,并保留反向选区。输入法组合文字保留下划线,插入点使用反色 Cell Cursor。TextArea 活动时 highlight 覆盖边框内的文字、padding 与空白,边框保持未激活样式;失焦撤去选区外观,并从内容起点展示,同时保留编辑视口与 selection。失焦期间滚动只改变展示视口;键盘/语义再聚焦恢复编辑视口,鼠标点入则从可见位置定位光标。Surface 空白只接管浏览器焦点并保留导航锚点,不激活编辑器。readOnly 可聚焦,disabled 无活动强调。
  • Cursor 默认反转所在 Cell 的最终颜色,不重新解释编辑状态;形状、fixed 模式、CSS token 与底图恢复由 cursor-appearance.ts 和测试验证。
  • Surface 失去实际焦点时保留逻辑 focusedId,但撤去焦点外观与 Canvas Cursor;selection 和编辑状态保留。
  • CellUiTheme 是颜色与字符主题入口;CellFeedbackConfig 是反馈入口,classic 默认闪烁 2 次,instant 为 0 次。两者共享 hover、focus、press 规则。
  • CellUiRecipe.defaultControlVariant 只为 Button、Select、Combobox、TextInput 提供省略 variant 时的默认值;显式 variant 优先。Box、ScrollArea、TextArea、Overlay、Dialog 保留各自内建默认。浏览器 CSS adapter 从 --cell-default-control-variant 读取同一配置,headless 不依赖 CSS。
  • 公开主题用法分别定义 variant 与 frame;后代状态消费最近的实际 surface substrate。背景不进入字符、语义或 Range Copy;边框会进入。
  • Select、Combobox 与 TextInput 使用共享的 surface | ghost recipe;默认 surface 使用 elevatedSurfaceStyle,ghost 沿用最近父层底色,无父层时使用 theme.background。Select/Combobox Content 的 frame 独立控制下拉层边框。ComboboxInput 与 ghost TextInput 活动时保留背景;SelectTrigger 的 focus 与 press 沿用反色规则,disabled 保留 recipe 并使用禁用文字色。
  • Select/Combobox 未指定宽度时从完整 Item 列表测量稳定的自然宽度,Trigger/Input 与 Content 共用该宽度;显式宽度优先。Content 的 open 只控制可见性,ComboboxItem 的 hidden 只控制过滤结果,两者都不移除 Item 的测量信息;SelectTrigger 的 placeholder 可为更长的空值文案预留宽度。
  • widget-capabilities.ts 拥有能力表;状态投影与外观配方分离,painter 只绘制解析结果。局部 textStyle 不重定义状态语言;底座契约拥有模块边界。
  • 统一控件的按压与高亮最多反色一次;确认阶段独占颜色,相对进入时的有效颜色对执行反色/恢复,disabled 拒绝全部临时强调。solid Button 使用 buttonSolidStyle,surface 使用 elevatedSurfaceStyle 与普通文字色,outline 保留字符边框,ghost 静止时填充当前层底色。
  • 解析后的状态背景覆盖 Widget 完整布局矩形,包括空白 Cell;透明状态不填充。
  • inline-control-chrome.ts 统一行内 spacing recipe:Button、Badge 与 TextInput 默认左右各留 1 个 content Cell,Button/Badge 可通过 style.paddingLeft/Right 覆盖;Checkbox、RadioItem、Toggle、Select 与 Combobox 提供固定 guard、状态标记和间隔;guard 是 owner-aware 空 Cell。border、collection disclosure、Tab underline 和 scrollbar 保持独立 chrome 职责。
  • Badge 默认是只读状态文字;interactive 才加入共享焦点、按压、确认和 activate 命令。tone 从主题的成对前景/背景色中取值,文字和语义不依赖颜色;不自动作为 live region 播报。
  • Field 可选地包裹一个 TextInput、TextArea、Select 或 Combobox,以同一行文契约显示 label 与 ! 错误文字。非空 error 将 invalid 传给实际获焦的控件,并以错误节点关联 aria-describedby;无错误时不改写输入状态。Button 的 tone="danger" 使用共享 danger token,variant 仍只控制外观。

双呈现

presentation 在 CellSurface/Runtime 级别选择,默认 rich;切换不改变值、焦点、命令或语义。text 仍消费共享 hover、focus、press、blink 和编辑光标,不等于无样式快照。Button 使用 [ Label ];Alert、Dialog、Tooltip、TextArea、Select 弹层、Table 和 surface Box/ScrollArea 用方角字符边框;Progress 使用 [//--];Tabs 使用 ⎺。TextArea 省略 variant/frame 时,Rich 为无框 surface,Text 保留同一底色并使用方角字符边框;显式 variant 仍决定底色,Rich 的显式 frame/borderShape 决定边框,Text 始终使用方角字符框。Select/Combobox、List/Menu/Tree/Grid 的 > 表示当前导航项,✓ 表示已提交或持久选中。其余字符原生组件沿用现有 glyph。显式边框不叠加第二层;ghost Box/ScrollArea 填充当前层底色。公开 variant 保持兼容,text 字符配方优先于纯视觉 variant;旧 Button outline 在 rich 中继续可用。

组件 Playground 独立切换呈现。Config 在 text 下隐藏被固定字符配方覆盖的外观项,保留仍影响颜色或字符的选项(包括 surface/ghost);隐藏项的 Rich 选择不丢失,切回 Rich 后恢复。配置弹层若在切换时失效会关闭,焦点回到 presentation 控件。

Toast 内容由 Toast descriptor 持有 tone、variant 和 Rich 的 frame/borderShape;通知队列与位置仍由 browser host 持有。tone 复用 Badge 语义色,非 neutral 显示对应字符;Text 固定方角字符框。Toast 本身不创建第二个 live region,外层通知容器以 status 播报。

Primitive 快照

此表包含内部 Widget 契约,不等同于公开组件目录。List、Menu、Tree、Overlay 和双端 Slider 的底层 descriptor 仍服务内部 fixture 与共享机制,但不再作为独立组件分发。

PrimitiveCell 表现输入与语义
Root / Box / TextRoot 只建立画布;Box 用背景 variant 和独立 frame 表达边界;Text 提供字符不产生独立 command;Text 与边框字符可被 Cell Range 复制
Alert / Title / Description页面内持久提示,info/success/warning/error 复用状态色与单 Cell 标记;`border="none""square"
Overlayroot layer、clip,默认 ElevatedEscape/outside down dismiss;modal dialog
Dialog / Title / Description / Footer复用 Overlay,默认居中、36 Cell 宽;surface 使用 elevated surface token,ghost 使用当前层底色,弹窗均不透明;`border="none""square"
Button单行填充矩形;默认左右各 1 Cell,style.paddingLeft/Right 可分别覆盖全局 focus traversal;完整 tap、Enter、Space 或 AT 激活
Badge单行内容宽度,文字左右各 1 Cell;neutral/info/success/warning/error 由主题着色,无边框字符默认只读、无焦点和动作;interactive 要求稳定 id,成为 button 并沿用完整 tap、Enter、Space、AT 激活;disabled 不激活
Accordion / Item / Trigger / Content▸ / ▾ 显示展开状态;标题消费共享反色与 press,无确认闪烁多项独立展开;tap、Enter、Space 输出 Item 的 set-expanded;上下/Home/End 在同组可用标题间导航,不循环
Checkbox[ ] Label / [x] Label / [-] Label;两侧 guard、三 Cell 标记及内容间隔由 renderer 拥有checked 与 mixed 进入 Semantic DOM;完整行 tap、Enter、Space 或 AT 激活,disabled 跳过
Toggle○ Bold / ● Bold;两侧 guard、状态灯及内容间隔由 renderer 拥有,状态灯取自全局主题button + aria-pressed;整行 tap/Enter/Space 使用统一 press 与确认反馈;状态灯和 guard 保留在字符快照及复制中
Progresssolid 使用透明的 █/░;outline 使用 [//--] 且括号包含在声明宽度内;number 在声明宽度内为确定进度显示百分比,窄宽度隐藏数字;value={null} 的 thumb 逐 Cell 环绕且不显示数字只读 progressbar;确定值限制到 0…max,indeterminate 不暴露数值 ARIA 属性;headless host 以显式动画时间驱动投影
Spinnerwheel 为 ◐◓◑◒,dots 为 ⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏;始终 1×1 Cell,每 120 ms 换帧;可见文字由相邻 Text 组合只读、无数值的 progressbar;与 Progress 共用显式动画时间,隐藏页和 reduced motion 下停在首帧
Tooltip与 Dialog 共用不透明的 `surfaceghostvariant 和border="none"
Separatorline/slash/double/dots 由全局 glyph map 投影,横纵方向保持固定 Cell 几何无焦点与动作;separator + orientation;variant 只触发 paint,消费全局 border 颜色
RadioGroup / RadioItem( )/(●);Group value 派生直接子项 checked,value 唯一且非空radiogroup/radio;组内一个 Tab 入口,方向键循环选择并跳过 disabled;select-radio 即时导航,activate 使用统一确认反馈
Sliderrenderer-owned ━/─/┃ 单行轨道;pointer hover、keyboard focus 或 manipulating 只将 thumb Cell 切换为 █,轨道不反色、不增加 bold;可见 label/value 由外部 Cell 组合单值 value: number 以 Slider ID 接收 set-value;双端 value: [number, number] 与 thumbs 以各端点 ID 接收命令;方向键一步、Page 十步、Home/End 端点;精确 tap/drag,无确认闪烁
双端 Slider 内部 descriptor共享轨道的外侧 ─、双 thumb 之间 ━;两个端点独立 focus点击轨道选择最近端点,平局优先已 focus 端点再 lower;端点不交叉,区间不整体拖动;Semantic DOM 投影 named group 与两个受彼此约束的 numeric slider
Select填充 Trigger+▾/▴,Content 锚定并按 viewport 上下翻转,selected item 使用 ✓;共享 inline chrome 固定左右 guard 及右侧间隔/标记focus 与 committed selection 分离;Trigger 只展开,item 的 Enter/click 立即提交并确认闪烁,确认结束后 dismiss;Escape/outside/blur 可提前关闭并保留提交值
Combobox单行填充 Input+▾/▴,Content 复用 Select 的浮层、scroll、✓ 和右侧保护 Cell;active item 临时反色关闭时点击输入行任意位置展开;展开后文字区保留编辑,箭头及其尾部保护 Cell 切换关闭。DOM focus 始终留在 Input,aria-activedescendant 指向 active item;本地稳定子串过滤,Enter/click 提交,Escape/Tab/outside 恢复已选 label;IME 只在 composition commit 后过滤
ListItem 左右各 1 Cell guard;guard 内侧为 2 Cell 选择列,selected 使用全局 collectionSelectedIndicator(默认 ✓);用户 padding 在 chrome 内侧叠加,整行临时反色Up/Down/Home/End 仅定位;完整 tap、Enter/Space 即时激活,无确认闪烁
Menu整行临时反色,无持久选择标记hover 更新临时导航目标;Up/Down/Home/End 导航;完整 tap、Enter/Space 在确认完成后执行,取消丢弃待执行动作
TreeItem 左右各 1 Cell guard;内侧每层缩进 2 Cell,独立 ▾/▸ 展开列、✓ 选择列及 label;用户 padding 叠加于 chrome 内侧Left/Right 层级导航;branch expand/collapse,leaf activate;无确认闪烁;折叠移除焦点后代时收敛到仍可用祖先
Tabs默认 underline:第二行仅在 selected 标签文字宽度内绘制 ⎺,使用 highlight 色;第二行不参与 pointer hover/命中,临时 hover 仅填充第一行。solid:单行反色。两者都保留左右各 1 Cell guard 与标签间 2 Cell 间隔Left/Right wrap、Home/End 即时聚焦并切换可用页面,无确认闪烁;tab 与 tabpanel relations
GridGridCell 左右各 1 Cell guard;内侧 2 Cell 选择列使用全局 collectionSelectedIndicator;声明宽度不变,用户 padding 叠加于 chrome 内侧;临时强调覆盖整个格子一个 Grid 一个 Tab 入口,优先上次可用焦点、已选可用格子、行列排序首项;方向键沿同一行/列跳过 disabled 与缺格,边界停止;Home/End 定位当前行端点;点击、Enter/Space 即时激活,无确认闪烁;row/column semantics
Table只读定宽列;plain 用表头分隔线,outline 用方角边框与列线,surface 用表头及隔行背景、无分隔字符;背景复用 light/dark surface tokentable/row/columnheader/cell 语义;无焦点、选择或激活命令,长文本截断仅影响画面,不改写语义标签
TextInput / ComboboxInput固定 1 Cell 高、无边框;TextInput 默认 surface,实际焦点驱动整行反色,ghost TextInput 与 ComboboxInput 保留背景;选区用反差色、输入法组合用下划线,插入点用反色 Cell Cursor,并保留 selection、composition真实 textarea;textbox/combobox value/readOnly/disabled;style 仅允许宽度与 flex 约束
TextArea多行 content、Block 背景与边框、terminal Cell Cursor、selection、composition;Rich 无框 surface 默认左右各留 1 Cell content inset,显式 padding 优先;实际焦点仅反色边框内区(含 padding 与空白),边框保留静态主题样式真实 textarea;multiline textbox value/readOnly/disabled
ScrollAreaoverflow 时使用内部 rail 和 cornerwheel、page、track、thumb drag;不建立 DOM scrollbar

Button、Badge、Checkbox、Toggle、RadioItem、Select Trigger/Item、Combobox Item 和 Tab 的标签共用单行文本规则:可用 Cell 不足时在内容区以 … 截断,先保护组件 chrome;原文与语义标签不变。普通 Text 仍按 Cell 换行。

Pointer 与 Scroll

  • Surface 的基础 Canvas 与 Overlay Canvas 由同一呈现节点注册表拥有;hover、pointer、wheel 共用归属判断。Canvas 事件只接受当前 Surface 的呈现节点;真实编辑 textarea 的 pointer down 经同一 Scene 命中校验后进入 Canvas 的 Cell 拖选流程,不交给原生指针选区;其他 DOM 目标不进入 Cell 指针命令。textarea 仍负责键盘、输入法、复制粘贴与无障碍焦点,非指针的原生选区变化同步回 Cell 状态。Widget、clip 与 modal scope 由 Scene 判定。所有层使用基础 Canvas 原点和共享 Cell metrics,浮层可以超出基础 viewport。浮层提交、scroll 与 resize 会重新计算静止鼠标的命中。

  • pointer down 定位焦点;Button、SelectTrigger、SelectItem、ComboboxItem、Checkbox、Toggle、RadioItem、ListItem、MenuItem、TreeItem、Tab、GridCell 移出时撤去按压、移回时恢复,pointer up 仍命中原目标才激活。drag winner、cancel、capture loss 或 blur 永久取消本轮 press。

  • Button、Checkbox、Toggle、RadioItem 与 SelectItem 接受 activate 后,在物理释放后播放 activationBlinkCount 次完整确认;AT 直接激活立即播放,0 立即完成。SelectTrigger 展开与 Radio select-radio 导航不闪烁。SelectItem 等待释放及播放期间锁定导航、选择和滚动;控制器完成后执行一次 dismiss。反馈会话契约拥有阶段时长和呈现确认。

  • SelectItem/MenuItem pointer hover 同步临时焦点;ComboboxItem hover 同步 active descendant;均不提交选择或执行动作。随后 Enter 确认临时目标。其他组件的 hover 不派发业务命令。

  • Tab/Shift+Tab 在当前焦点 scope 内遍历可用控件;RadioGroup 折叠为已选可用项或首个可用项一个入口,到 Surface 边界交还浏览器。

  • Grid 导航与入口选择由 grid-navigation.ts 拥有,FocusManager 仅保存每个 Grid 的目标 ID,卸载清理。禁用/删除焦点按入口规则收敛,不改写业务选择;全部 disabled 时跳过。Semantic DOM 保留 Surface 管理的程序化焦点,不维护另一套 Tab 顺序。Cell Range 与 Grid selection 独立,范围拖动不激活格子,复制包含可见 ✓ 和边框。

  • 嵌套 ScrollArea 的 wheel 从内向外寻找可滚动的 viewport;最外层到边界后仍消费 wheel。区域外页面可滚动,Ctrl+wheel 留给浏览器。父级只把内层 ScrollArea 的 viewport 计入自身内容尺寸;内容收缩后钳制 offset 并发出 scroll 命令同步受控状态。焦点命令的 reveal 保留最近一层,跨层时 reveals 按内到外列出全部目标和 offset。useCellScrollState 按 ID 保存双轴 offset 与全部焦点 reveal;固定高度区域仍须处理真实溢出。

  • 横纵 scrollbar 可相互缩小 viewport;轨道占位随布局重算,自然高度 ScrollArea 只在需要横向轨道时增高。测量和 Runtime 共用此布局结果。rail 始终保留 Cell 与命中区域,默认不绘制;悬停最近滚动容器、键盘焦点、滚动或拖拽时显现,滚动结束 1.2 秒后隐藏。内容 viewport 保留 padding,rail 独立延伸到边框内侧(无框时为表面边缘),端点 thumb 不留空 Cell;双轴交角由 rail 共享。thumb 使用半 Cell 精度,drag 以按下时 offset/range/track 为锚。

  • text 的竖向 rail 显现时使用 Unicode 🮐 轨道、🮑/🮒 半格 thumb 边缘,轨道复用 thumb 解析后的前景色;横向 rail 在 rich/text 均为空白 owner Cell,仅以 ━ 和半格 ╺/╸ 绘制 thumb,空白轨道仍可点击翻页。共享 painter 按实际 Cell 背景解析颜色,焦点 TextArea 的竖向轨道和 thumb 同随反色文字。显现时 thumb 与边缘保留为可复制的 Cell.text。

  • scrolling 不改变 selection;Virtual List 只在 focused row 离开 viewport 时把 focus 收敛到可见可用项。

编辑视口与 Canvas 边界

  • CellSurface.viewport 始终表示逻辑内容区;浏览器 Canvas 四周额外保留 1 Cell 绘制 guard。guard 不进入 Cell buffer、Probe 文本或复制结果;基础层、浮层、光标、隐藏 textarea 和 pointer 坐标共用同一 guard 偏移。hosted Surface 的 guard 透明,浮层以可见 Cell 边界定位、贴合和判断外部点击;字形仍可跨 Cell 绘制,物理画布边界不裁掉内容区首尾 Cell 的墨迹。
  • TextArea 可显示双轴 rail;TextInput 与 ComboboxInput 固定为 1 Cell 高且隐藏 rail,但保留横向 offset。编辑器和 ScrollArea 共用 scroll geometry,不复制 offset state。
  • 文本 layout、clip、Cursor、selection、hit 与隐藏 textarea 定位消费同一个 Scene viewport;手动 scroll 不修改 document/history。
  • Cell Range 修饰键优先于文本 selection gesture;只读编辑器可滚动,disabled 不新增交互。
  • 所有 border、block、thumb 与内容都保留为 Cell.text 字符;Canvas 字体路径、Probe 和复制结果一致。

证据

Dialog 的 Title 必须非空且为唯一直接子项,Description 至多一个直接子项;自动关联 aria-labelledby / aria-describedby。initialFocusId 不可用时进入首个可用内容控件,无控件则聚焦弹窗自身。外部点击关闭不穿透;嵌套 Select 先处理 Escape/外部点击。普通 blur 不关闭 Dialog;非模态允许 Tab 离开,已移到外部的焦点不被关闭操作夺回。长正文组合 ScrollArea;宿主须分配可容纳外壳的高度(Gallery 提供扩展浮层区域)。契约测试、双浏览器验证。

AccordionItem 必须有稳定 id,直接包含 Trigger、Content(依此顺序);expanded 受控,默认折叠。Group/Item 的 disabled 向内容控件传播。折叠保留 Widget Tree 与外部受控值,但退出布局、命中、Tab 和读屏;焦点从隐藏内容恢复至自身标题,再尝试同组后项/前项,且不逃离焦点域。Trigger 的 button/expanded/controls 与 Content 的具名 region 自动关联;内容内部控件保留自身键盘行为。契约测试、浏览器验证。

Combobox 必须以一个 Input 开头,后接可选 Content;Content 只接受 Item 或空结果 Text。Input 与 Content 自动建立 controls/label 关系,至多一个 active Item 且必须匹配 activeDescendantId。关闭态显示并全选 committed label,使下一次输入替换而非追加;输入、候选 active、选择和浮层开关可分别受控。Headless 契约、浏览器契约。

Public components · Widget tests · Browser tests · Editor scroll E2E