树选择 tree-select
数据录入组件。三层同源:无头内核给出解剖与状态机,Vue 组件与自定义元素只是它的两层外壳,行为完全一致。
示例
基础用法
收起时整个控件只占触发器一个 Tab 位,展开那一刻焦点真的进树、落在已选中的那行上
已选:(无)
选中与展开双受控
两份集合都由宿主持有:组件只发事件,宿主写回它才动,回显的就是写回的那两份
已选:guide · 展开:docs
多选与表单
multiple 下确认键是切换、浮层不收起;写了 hidden-input 才随表单提交,多个值按逗号拼成一串
已选:index
形态
variant 只换触发框的描边与底色,浮层与树的长相不跟着变
语气
tone 决定用哪族颜色,与 variant 正交,这里统一用 subtle 形态
尺寸
size 换掉行高、内边距与字号,不写就是缺省档
禁用、只读与校验失败
disabled 连键盘入口都没有;readOnly 照常展开浏览但值改不动也清不掉;invalid 只报校验态,交互一切照旧
异步加载子节点
展开某个分支才去要它的子节点:先摆一行禁用占位,数据回来就地换掉,显示文本随之取到新 label
已选:(无)
浮层里的操作区
content 里除了树还能放别的:在浮层内点按钮不算点在外面,浮层不会因此收起
级联勾选与回显策略
multiple 加 cascade 内建父子传导:点分支整枝勾上、子全勾父勾、部分勾中半选;对外值按 checked-strategy 收敛,parent 档整组选满只报组名
对外值(parent 收敛):user:view
浮层内关键词过滤
输入框是树的兄弟节点,树的键盘处理器挂在 tree 上,打字不会被连打检索收走;换掉 collection 可见行与方向键顺序跟着重算
只挑文件不挑目录
选中值与展开态双受控:目录的值不写回,紧跟着那一次收起意图也一并吞掉,点目录就只剩展开收起
已选:(无)
产物
| 层 | 值 |
|---|---|
| 自定义元素 | <xh-tree-select> |
| Vue 组件 | XhTreeSelectBranch XhTreeSelectBranchContent XhTreeSelectBranchControl XhTreeSelectBranchIndicator XhTreeSelectBranchText XhTreeSelectBranchTrigger XhTreeSelectClearTrigger XhTreeSelectContent XhTreeSelectHiddenInput XhTreeSelectIndicator XhTreeSelectItem XhTreeSelectItemIndicator XhTreeSelectItemText XhTreeSelectLabel XhTreeSelectPositioner XhTreeSelectRoot XhTreeSelectTree XhTreeSelectTrigger XhTreeSelectValueText |
| 组合式函数 | useTreeSelect |
| 状态机 | treeSelectMachine |
| 皮肤 | @xihan-ui/styles/tree-select.css |
解剖
部件名即 data-part 属性值,也是皮肤的选择器。加粗的是必备部件,不渲染它组件不工作(Web Components 适配器会在诊断通道上报 wc.missing-part)。
data-scope="tree-select":root · label · trigger · value-text · indicator · clear-trigger · positioner · content · tree · item · item-text · item-indicator · branch · branch-control · branch-trigger · branch-indicator · branch-text · branch-content · hidden-input
Props
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
collection | TreeNode[] | 树数据,层级元信息与显示文本的唯一事实源。缺省为空树。 | |
value | string | string[] | 选中值。给定即受控:cell 直读 prop,写只发 onValueChange 不落内部值。 单选写成裸串是简写,内部一律归一成数组。 | |
defaultValue | string | string[] | ||
expandedValue | string[] | 展开集合。给定即受控,语义同上。 | |
defaultExpandedValue | string[] | ||
open | boolean | 展开态。给定即受控:内部不再自改,只发 onOpenChange。 | |
defaultOpen | boolean | ||
multiple | boolean | 多选:选中是集合,选中后浮层不收起、焦点留在树里以便接着挑。 | |
cascade | boolean | 多选下父子级联勾选:点分支整枝传导、子全勾父勾、部分勾中半选, 禁用子树整棵冻结。默认 false(朴素切换);单选下无效。 | |
checkedStrategy | CascadeStrategy | 级联下对外值的收敛策略,默认 child(只收叶);parent = 最高整枝,all = 全部勾中节点。 | |
disabled | boolean | 整个控件禁用:trigger 用原生 disabled,表单出口不参与提交。 | |
readOnly | boolean | 只读:浮层照常展开、树照常浏览与展开收起,但选中值改不动、也清不掉。 disabled 则连键盘入口都没有。 | |
invalid | boolean | 校验失败:trigger 报 aria-invalid,各角色节点带 data-invalid。 | |
variant | ControlVariant | 形态:outline / subtle / ghost,决定触发框的描边与底色怎么用。 | |
tone | Tone | 语气:brand / neutral / success / warning / danger / info,决定聚焦与选中用哪族颜色。 | |
size | Size | 尺寸:sm / md / lg,决定触发框与树节点行的几何档位。 | |
placeholder | string | 无选中时 value-text 显示的占位文字。 | |
placement | Placement | ||
offset | number | ||
loop | boolean | 上下键走到首尾是否回绕,默认 false。 | |
dir | Direction | 文字方向,默认 ltr;只对调左右方向键的「展开/收起」语义。 | |
name | string | 表单字段名。给定后表单出口才带 name,选中值随表单一并提交。 | |
onValueChange | (details: TreeSelectValueChangeDetails) => void | value 变化意图回调;受控时是唯一出口,非受控随内部写入一并通知。 | |
onExpandedChange | (details: TreeSelectExpandedChangeDetails) => void | 展开集合变化意图回调;语义同上。 | |
onOpenChange | (details: TreeSelectOpenChangeDetails) => void | open 变化意图回调;受控时是唯一出口,非受控时随内部转移一并通知。 |
状态机
状态:open · closed
事件:OPEN · TOGGLE · CLOSE · CONTROLLED.OPEN · CONTROLLED.CLOSE · NODE.FOCUS · NODE.LOST · NODE.SELECT · VALUE.SET · VALUE.CLEAR · EXPANDED.SET · BRANCH.EXPAND · BRANCH.COLLAPSE · BRANCH.TOGGLE · FORM.RESET
判据:isOpenControlled · isMultiple
connect API
useTreeSelect 产出的对象。getXxxProps() 铺到对应部件的宿主元素上,其余是可读状态与操作入口。
| 成员 | 类型 | 说明 |
|---|---|---|
open | boolean | |
collection | readonly TreeNode[] | 作者给的原始树数据。 |
visibleNodes | readonly TreeVisibleNode[] | 当前可见行序列(收起分支的子树不在其中)。 方向键、Home/End 与连打检索都在它上面走,不是在原始树上走。 |
value | string[] | 选中集合;单选下长度 ≤ 1,形状不随模式变。 |
expandedValue | string[] | |
valueText | string | null | 选中项的显示文本(多选用逗号加空格连接);无选中时为 null。取自 collection 的 label。 |
displayText | string | value-text 实际显示的文字:有选中取其文本,否则取 placeholder。 |
focusedValue | string | null | 焦点锚点;收起、或它已被收起而不可见时为 null。 |
multiple | boolean | |
disabled | boolean | |
readOnly | boolean | |
invalid | boolean | |
canClear | boolean | 清空按钮此刻可不可按。 |
isSelected | (value: string) => boolean | |
isIndeterminate | (value: string) => boolean | 级联模式下该分支是否半选(有效叶后代有勾有不勾);非级联恒 false。 |
isExpanded | (value: string) => boolean | |
setOpen | (next: boolean) => void | |
setValue | (next: string[]) => void | |
setExpandedValue | (next: string[]) => void | |
expand | (value: string) => void | |
collapse | (value: string) => void | |
select | (value: string) => void | 单选替换、多选切换,与点节点同一语义。 |
clear | () => void | |
getRootProps | () => T['element'] | |
getLabelProps | () => T['element'] | |
getTriggerProps | () => T['button'] | |
getValueTextProps | () => T['element'] | |
getIndicatorProps | () => T['element'] | |
getClearTriggerProps | () => T['button'] | |
getPositionerProps | () => T['element'] | |
getContentProps | () => T['element'] | |
getTreeProps | () => T['element'] | |
getItemProps | (props: TreeSelectNodeProps) => T['element'] | |
getItemTextProps | (props: TreeSelectNodeProps) => T['element'] | |
getItemIndicatorProps | (props: TreeSelectNodeProps) => T['element'] | |
getBranchProps | (props: TreeSelectNodeProps) => T['element'] | |
getBranchControlProps | (props: TreeSelectNodeProps) => T['element'] | |
getBranchTriggerProps | (props: TreeSelectNodeProps) => T['element'] | |
getBranchIndicatorProps | (props: TreeSelectNodeProps) => T['element'] | |
getBranchTextProps | (props: TreeSelectNodeProps) => T['element'] | |
getBranchContentProps | (props: TreeSelectNodeProps) => T['element'] | |
getHiddenInputProps | () => T['input'] | 表单出口:一份 type=hidden 的原生 input,选中值按逗号拼成一串随表单提交。 |
键盘
规格出处:W3C APG
| 按键 | 生效条件 | 行为 |
|---|---|---|
Enter / Space | closed, focus in trigger | 展开浮层并把焦点落到选中节点(无选中或它藏在收起的分支里则落首个可用行) |
ArrowDown | closed, focus in trigger | 展开浮层并把焦点落到选中节点的下一个可用行 |
ArrowUp | closed, focus in trigger | 展开浮层并把焦点落到选中节点的上一个可用行 |
ArrowDown | open, focus in tree | 焦点移到下一个可见行(禁用行跳过;loop 默认关,末行不回绕) |
ArrowUp | open, focus in tree | 焦点移到上一个可见行(禁用行跳过;loop 默认关,首行不回绕) |
Home | open, focus in tree | 焦点移到首个可见行 |
End | open, focus in tree | 焦点移到末个可见行(展开着的子树也算行) |
ArrowRight | open, focus on branch(dir=rtl 时改由 ArrowLeft 承担) | 收起的分支就地展开;已展开则把焦点移到首个子节点;叶子上什么都不做且不吞键 |
ArrowLeft | open, focus in tree(dir=rtl 时改由 ArrowRight 承担) | 展开的分支就地收起;收起的分支与叶子则把焦点移到父节点;根层的行什么都不做 |
Enter / Space | open, 焦点节点未禁用 | 选中焦点节点:单选替换并收起浮层、焦点归还 trigger;多选切换且浮层不收起 |
* | open, focus in tree | 展开与焦点行同一父级的全部分支(已展开与禁用的不动);同级没有可展开的分支时不吞这个键 |
单个可打印字符 | open, focus in tree | 连打检索在可见行上按 label 首字母搬焦点,不改选中值,也不展开任何分支 |
Escape | open | 收起浮层并把焦点归还 trigger,选中值与展开集合都不变 |
Tab / Shift+Tab | open | 收起浮层,焦点不归还 trigger,按 Tab 序列自然离开 |
