Tree 树
层级数据的展开与选择:分支可展开,节点可选。
用法
collection 是层级元信息的唯一事实源;不写结构时组件照它铺出标题与整棵树,缩进由子层容器自行撑开
组件结构
加粗的是必需部件。
data-scope="tree":root · label · tree · item · item-indicator · item-text · item-description · item-suffix · branch · branch-control · branch-trigger · branch-indicator · branch-text · branch-description · branch-content · branch-loading · branch-error · branch-retry-trigger · branch-empty · node-drag-trigger · empty · loading · live-region
示例
多选
选择模式缺省是单选,写 multiple 后点击与确认键都变为切换,选中集合形状不变仍是数组
受控
传入 expandedValue / selection 后由宿主决定,组件只发事件不落内部值,宿主写回后才变化
点击行展开与禁用节点
缺省点行只选中、展开归箭头与左右方向键,expandOnClick 打开后点行同时切换展开态;禁用节点仍可聚焦,只是确认键不响应它
关键词过滤
collection 换一份树即换一棵:标记跟随数据重新铺设,过滤后剩余的分支一并全部展开
异步加载子节点
节点写 hasChildren,首次展开时组件调 loadChildren 取子项:在途转圈并报告 aria-busy,取回后由组件暂存,收起再展开不重复请求;运维中心第一次会失败,点重试或在该行按 Enter 再取
前缀与行尾
行中放置什么由标记决定:文字前放图标、文字后放操作,展开箭头也可以移到行尾
只让叶子进入选中集合
选中受控后由宿主决定:分支的值直接不写回,点击目录只剩展开收起这一个效果
级联勾选
multiple 加 cascade 内建父子传导:点击分支整枝勾选、子全勾则父勾、部分勾选为半选;勾选与半选都画在行尾,半选是一道横杠,两态都由组件报告
选中值(默认只收叶):hz
拖拽移动
整个节点都是拖动源:按住拖到目标位置松手,也可以 Tab 进树后用 Alt + 上下键在同层移动、Alt + 左右键改变层级。三档落点(插在前 / 插在后 / 放进目录)连同指示线、自我后代守卫与读屏播报都归库;树仍不拥有数据,宿主只需按库报告的 value、parent、index 调整数组,外加一条 allowDrop 决定本次是否允许
末端横排
leaf-orientation 按结构判据横排子节点全为叶子的层;要指定哪一层横排就在节点上标注 childrenOrientation,它比树级值优先,标注 vertical 也可覆盖
范围选择
按住 Shift 点击某一项,选中锚点到它的一段;按可见序取值,折叠的子节点不会被选入
点「上海」,再按住 Shift 点「北京」 —— 中间整段一起选上。 再按住 Shift 点「南京」,选区往回收,起点不变。
变体
variant="ghost" 去掉外框与底色,树直接落在页面上;默认 outline 保持带框的外观
大树虚拟化
完整树数据负责层级与键盘语义,窗口只挂载当前可见行
连接线
lines 从父节点的展开箭头引出竖线,每个子节点横出一段接到行首、最后一个子节点拐成直角收住,层级深的时候一眼看得出谁挂在谁下面;只是外观,不改结构与键盘
尺寸
size 写到 root 的 data-size:行高、字号、展开箭头与对号盒、层级缩进一起随档;三档并排,缺省即 md
条目说明
一句名称说不清的解释写进说明,叶子与分支都可以带,落在名称下面一行;窄的时候截断,不把树撑宽
文件管理器式多选
选择模式写 extended:点击换成这一项,按住 Ctrl(macOS 为 ⌘)点击加选或取消,按住 Shift 点击从锚点选一段
点「上海」,再按住 Ctrl(macOS 为 ⌘)点「北京」把它加进来;按住 Shift 点「南京」,从「北京」到「南京」那一段换成新的选中;不按键直接点,只留这一项。
设计指引
何时使用
- 文件目录、组织架构、权限节点等任意深度的层级数据。
- 需要在树上多选并处理父子级联。
何时不用
特性
- 展开集合与选中集合两套值各自可受控。
variant决定外框形态,默认ghost,树直接落在页面或宿主面上;outline加一圈描边与底色,subtle换成淡底无描边。size取sm/md/lg,默认md,走集合家族的尺寸档:行高、行内内衬、字号、展开箭头与对号盒、拖拽把手与层级缩进一起随档。- 选择模式
selectionMode:缺省single单选;multiple点击与确认键切换,按住 Shift 点击把锚点到这一项那一段并进来;extended照文件管理器,点击与确认键换成这一项,按住 Ctrl / Cmd 点击或按 Ctrl / Cmd + Space 切换这一项,按住 Shift 点击选锚点到这一项那一段(同时按 Ctrl / Cmd 则并进)。锚点是最近一次点击或切换的那一项;选中从外面换了(受控时宿主另写了值、setSelection、初始的选中),锚点落到多出来的最后一项,清空了就没有锚点。级联勾选只在multiple下生效。 - 选中与树形选择器同一种读法:行不换面,单选、多选与级联都只在行尾画对号;分支行也摆
item-indicator,级联下的半选画横杠。 cascade与checkedStrategy决定勾选父节点是否带子节点,以及回显给哪一层。- 支持只让叶子进选中集合、关键词过滤、子节点异步加载、拖放换父。拖放与 Alt + 方向键落下之后,宿主写回
collection的那一次重排里,行从旧位置滑到新位置(换了父被重建的节点按节点值认回);落点线取品牌实心色,与表格、排序同一种。 - 展开与选中分开:点行只选中,展开归箭头与左右方向键;
expandOnClick打开后点整行才顺带展开。 - 节点可逐条声明语气,不向下传导;叶子行与分支行同样表达。
- 节点可写副文本,第 2 行放一句解释,不进连打检索串。
- 开着
nodeDraggable时,节点数据写draggable: false的那一个拖不起来:指针按住、Alt + 方向键都不搬它,行上不写data-draggable,它的拖拽把手报禁用;它照常能选、能展开,也照样是别的节点的落点。不向下传导,它的子节点各自照常可拖。大纲里的区域、页、页眉这类「能选能当容器、自己不能挪」的行用它,不必禁用。 - 节点行尾留一格给作者(计数、徽标),排在对号之前;行首那一格归展开箭头与拖拽把手。
- 空(
empty)与在途(loading)两个相位各有部件,都放在root内作为tree的兄弟;loading为真时树报告aria-busy,空态让位。在途分两种:还没有节点时在途占位露面,一枚加载环排在文案之前;已有节点时重新取数不换成占位,行保留上一帧淡下,取完再淡回。 leafOrientation按结构判据横排:子节点全是叶子的层跟随它,其余始终竖排。- 懒分支:节点写
hasChildren: true且不给children,首次展开时调loadChildren({ node, signal })取它的直接子项,取回的子项由组件暂存、不写回collection;在途时分支报告aria-busy、展开箭头换成转圈(减弱动效下停住)。收起会中止在途的一轮,收起再展开不重复请求。失败与「取回空数组」是两个终态:失败露出说明与重试钮,再次展开不自动重试,点重试钮或在分支上按 Enter / Space 才重发;空数组露出空结果。在途、失败说明、重试钮与空结果四个部件由组件在分支里自己补,手写结构时也一样。取数与树选择、级联选择同一套规则,事件是branch-load-start/branch-load/branch-load-error。 - 取数由作者自己编排时,把在途的分支写进
loadingValue:同样报告aria-busy并换成转圈,回来后写回collection并移出。 lines打开连接线:父节点展开箭头的中线向下引一道竖线,每个子节点从竖线横出一段接到行首(分支接到展开箭头,叶子穿过占位那一格接到正文前),最后一个子节点的竖线止于它那一行的中线;展开着的分支,竖线沿它的整棵子树接到下一个兄弟。颜色取内部分隔线(--xh-tree-line-color),只是外观,不改结构与键盘。顶层节点没有父节点可连,不画线;线画在嵌套的子层里,虚拟化平铺渲染的行不画。- 节点上标
childrenOrientation: 'horizontal' | 'vertical'指定该层子节点的排列方向,优先于leafOrientation;标vertical可以把树级的horizontal改回竖排。根层不受影响,始终竖排。
组合
- 不写结构时照标准结构铺:标题(给了
label时)与整棵树,分支是一行控制行(展开箭头、文字、副文本、对号)加一层子层,叶子是文字、副文本与对号;收起的分支照样铺出子层,节点按值复用。Vue / React 不写默认插槽与 Web Components 不写部件(或只写 root)时生成的节点出自同一个结构函数。行尾内容、自定义指示符、拖动把手与播报区、空态与在途占位不在标准结构里,要用时整棵手写。 - 前缀放图标,行尾放菜单;放入分栏的一侧。
- 大树把
visibleNodes的长度交给 Virtualizer,并把其collectionVirtualizer回传给根;展开收起后同步更新count。
最佳实践
- 大树必须虚拟化或按需加载,一次展开全部会卡顿。
- 级联勾选的策略与后端约定一致。
- 只需要某一层横排时标
childrenOrientation,不开启树级leafOrientation:后者按结构判断,其他恰好“子节点全是叶子”的层也会横排,并随数据增减变化。
反模式
- 展开状态不持久,用户每次进入都要重新展开。
- 拖放换父没有落点提示。
API 参考
产物
| 层 | 值 |
|---|---|
| 自定义元素 | <xh-tree> |
| Vue 组件 | XhTreeBranch XhTreeBranchContent XhTreeBranchControl XhTreeBranchDescription XhTreeBranchEmpty XhTreeBranchError XhTreeBranchIndicator XhTreeBranchLoading XhTreeBranchRetryTrigger XhTreeBranchText XhTreeBranchTrigger XhTreeEmpty XhTreeItem XhTreeItemDescription XhTreeItemIndicator XhTreeItemSuffix XhTreeItemText XhTreeLabel XhTreeLiveRegion XhTreeLoading XhTreeNodeDragTrigger XhTreeRoot XhTreeTree |
| 组合式函数 | useTree |
| 状态机 | treeMachine |
| 皮肤 | @xihan-ui/styles/tree.css |
Props
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
labelled | boolean | 作者渲染了 label 部件时置真,由适配器统计而不是判断标题文字是否有值。 为假时可聚焦部件不把 label 的 id 写进 aria-labelledby:指向未渲染的 id 是无效引用,作者写在控件上的 aria-label 也会被它压住。 | |
collection | TreeNode[] | 树数据,层级元信息的唯一事实源。默认为空树。 | |
virtualizer | CollectionVirtualizer | 完整 collection 与 Virtualizer 的焦点桥;count 必须等于当前 visibleNodes.length。 | |
variant | ControlVariant | 外框形态:ghost 去掉描边与底色只保留行(默认),outline 带描边与底色,subtle 淡底无描边。 | |
size | Size | 尺寸:sm / md / lg,默认 md。行高、行内内衬、字号、指示符盒(展开箭头、对号、拖拽把手)与层级缩进随档。 | |
leafOrientation | Orientation | 末端层的排布方式,默认 vertical(每行一个)。horizontal 使它们并排铺开。 只作用于子节点全是叶子的层:菜单授权中即按钮层: 一个菜单下十几个按钮,横向排成一行,省去纵向翻找。中间层与整棵树恒为纵向, 它们承载的是层级本身,横向排布会失去层级信息。 这是结构判据,逐层自动识别。需要精确指定哪一层横向排布时,在节点上标注 childrenOrientation,它优先于本项。 只影响排布,不改变键盘:方向键在树上是层级操作(左右收展、上下移动可见行), 这是 treeview 的规范语义,不随排布方向改写。 | |
expandedValue | string[] | 展开集合。提供即受控:cell 直读 prop,写入只发 onExpandedValueChange 不落内部值。 | |
defaultExpandedValue | string[] | ||
selection | string[] | 选中集合。提供即受控,语义同上。 | |
defaultSelection | string[] | ||
selectionMode | TreeSelectionMode | 选择模式,默认 single;multiple 与 extended 下 tree 带 aria-multiselectable=true。 | |
cascade | boolean | selectionMode 为 multiple 时父子级联勾选:点击分支整枝传导、子全勾父勾、部分勾选半选, 禁用子树整棵冻结。默认 false(朴素切换);single 与 extended 下无效。 | |
checkedStrategy | CascadeStrategy | 级联下对外值的收敛策略,默认 child(只收叶);parent = 最高整枝,all = 全部勾选节点。 | |
expandOnClick | boolean | 点击分支行(与确认键)是否同时展开 / 收起,默认 false:展开与选中分开,只有 branch-trigger 与左右方向键能改变展开态。 | |
disabled | boolean | 整棵树禁用:所有节点为 aria-disabled,键盘与点击都不再改变展开 / 选中。 | |
loading | boolean | 节点加载中:树报告 aria-busy、隐藏空态占位;还没有节点时显示在途占位,已有节点时行保留上一帧淡下。 | |
loadingValue | string[] | 正在取子节点的分支(节点 value)。在其中的分支报告 aria-busy,展开箭头换成转圈。 取数由作者自己编排时用它;交给 loadChildren 的懒分支由库自己标出在途,不必写在这里。 | |
loadChildren | BranchLoadChildren<TreeNode> | 取回 hasChildren: true 分支的直接子项。首次展开自动调用,失败后用 api.retryBranch 或 branch-retry-trigger 显式重试;收起分支会中止在途的那一轮。旧请求的兑现或拒绝不会覆盖更新的一轮, 也不会写回已移除的分支。取回的子项暂存在组件里,不写回 collection。 | |
lines | boolean | 连接线:父节点展开箭头的中线向下引一道竖线,每个子节点横出一段接到行首,最后一个子节点止于行中线,默认 false。只是外观,不改结构与键盘。 | |
loop | boolean | 上下键到达首尾是否回绕,默认 false。 | |
typeahead | boolean | 连打检索,默认开启。关闭后可打印字符一律放行给页面。 | |
dir | Direction | 文字方向,默认 ltr;只对调左右方向键的展开 / 收起语义。 | |
translations | Partial<TreeTranslations> | ||
nodeDraggable | boolean | 节点可以拖动移动。整个节点都是拖动源,不另设把手。 | |
allowDrop | (move: TreeMove) => boolean | 本次移动是否允许。收到的是折算后的落点(移到哪个父节点下的第几位)。 未提供时全部允许:落进自身后代与落在禁用节点上两条由库自行拦截。 | |
onNodeMove | (move: TreeMove) => void | ||
onExpandedValueChange | (details: TreeExpandedValueChangeDetails) => void | ||
onSelectionChange | (details: TreeSelectionChangeDetails) => void | ||
onBranchLoadStart | (details: TreeBranchLoadStartDetails) => void | 一轮有效分支请求开始;retry 与首次展开由 reason 区分。 | |
onBranchLoad | (details: TreeBranchLoadDetails) => void | 一轮有效分支请求成功;children 为空仍是成功,不转换为错误。 | |
onBranchLoadError | (details: TreeBranchLoadErrorDetails) => void | 一轮有效分支请求失败;保留 loadChildren 给出的原始 error。 |
TreeNode
collection 的元素。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
value | string | 是 | |
label | string | 展示名,也是连打检索与分支可及名的取字来源;默认回退为 value。 | |
disabled | boolean | 节点禁用:方向键与连打检索跳过它,但它仍可聚焦、仍是导航起点。不向下传导给子节点。 | |
draggable | boolean | 写 false 时这一个节点拖不起来(nodeDraggable 开着时指针按住、Alt + 方向键都不搬它), 照常能选、能展开、能当别的节点的落点。缺省可拖。不向下传导给子节点。 | |
tone | Tone | 该节点自身的性质:已失效的写 danger、需要留意的写 warning。不写即与其余节点同档, 也不向下传导给子节点——每一层各自声明。只换字色与悬停 / 按下的面,不改字重与缩进, 也不表达选中或校验;选中的标记与禁用都压过它。彩字不是唯一通道,要紧的差别仍要配图标。 | |
description | string | 副文本:叶子写入 item-description、分支写入 branch-description 部件;未提供时本条不铺该部件。 它是第 2 行的说明,跟着条目走 muted 档,不跟语气;放不下一行的解释才用它, 一句话能说清的写进 label。 | |
children | TreeNode[] | 子节点。提供数组即判定为分支,空数组也计入:暂时没有子项的目录仍要报告 aria-expanded。 | |
hasChildren | boolean | 懒分支:写 true 且不给 children 时,这一枝在首次展开时由 loadChildren 取子项。 已提供 children 时它没有额外作用。取回的子项由 headless 暂存,不需要宿主重写整棵 collection。 | |
childrenOrientation | Orientation | 该层子节点的排布方式,由作者在数据上标注。提供后以它为准,vertical 也优先于树级的 leafOrientation;未提供时才回退为 leafOrientation 加子节点全是叶子的结构判据。 标注在哪一层,横向排布就只落在哪一层:菜单授权中标注在按钮的父菜单上,其他目录不受影响, 也不随子节点增减漂移。只影响排布,不改变键盘。 |
事件
自定义元素将载荷放在 detail;Vue 使用同名 emit。
| 事件 | 载荷 | 说明 |
|---|---|---|
expanded-value-change | TreeExpandedValueChangeDetails | 展开集合变化;detail 为 { value: string[] } |
selection-change | TreeSelectionChangeDetails | 选中集合变化;detail 为 { value: string[] } |
node-move | TreeNodeMoveDetails | 节点已移动;detail 为 { value, parent, index },parent 为 null 即根层,index 是在该层的落位(已经过先移除后插入的修正) |
branch-load-start | TreeBranchLoadStartDetails | 分支请求开始;detail 为 { value, node, reason } |
branch-load | TreeBranchLoadDetails | 分支请求成功;detail 为 { value, node, children } |
branch-load-error | TreeBranchLoadErrorDetails | 分支请求失败;detail 为 { value, node, error } |
插槽
仅列出带载荷的插槽。
| Vue 组件 | 插槽 | 载荷 | 说明 |
|---|---|---|---|
XhTreeRoot | default | TreeRootSlotProps | |
XhTreeRoot | label | — | 标准结构里标题的内容;手写结构时不用它。 |
React 适配器 props
只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。
| React 组件 | 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
XhTreeBranch | value | string | 是 | |
XhTreeItem | value | string | 是 | |
XhTreeRoot | label | ReactNode | 标题:不写 children 时铺进标准结构的 label;手写结构时用 XhTreeLabel。 | |
XhTreeRoot | children | SlotChildren<TreeRootSlotProps> |
状态
公开状态写入 data-state。
| 部件 | 取值 |
|---|---|
branch | 'open' | 'closed' |
branch-control | 'open' | 'closed' |
branch-trigger | 'open' | 'closed' |
branch-indicator | 'open' | 'closed' |
branch-text | 'open' | 'closed' |
branch-description | 'open' | 'closed' |
branch-content | 'open' | 'closed' |
branch-loading | 'open' | 'closed' |
branch-error | 'open' | 'closed' |
branch-retry-trigger | 'open' | 'closed' |
branch-empty | 'open' | 'closed' |
以下名称仅用于内部状态机。
状态:idle
事件:EXPANDED.SET · BRANCH.EXPAND · BRANCH.COLLAPSE · BRANCH.TOGGLE · BRANCH.RETRY · SELECTION.SET · NODE.SELECT · NODE.FOCUS · TREE.BLUR · NODE_DRAG.START · NODE_DRAG.MOVE · NODE_DRAG.END · NODE_DRAG.CANCEL · NODE.MOVE_BY · PRESS.START · PRESS.END
判据:canPress
connect API
getXxxProps() 返回对应部件的宿主属性。
| 成员 | 类型 | 说明 |
|---|---|---|
collection | readonly TreeNode[] | 有效树数据:作者的 collection,懒分支补上已取回的子项(还没取回的补空数组)。 |
visibleNodes | readonly TreeVisibleNode[] | 当前可见行序列(收起分支的子树不在其中)。 方向键、Home/End 与连打检索都在它上面移动,不在原始树上移动。 |
dropTarget | DropTarget | null | 当前的落点;松手即落在此处。不合法或未落在任何节点上时为 null。 |
announcement | string | 读屏播报文本。渲染进 live-region,不进入视觉版面。 |
expandedValue | string[] | |
selection | string[] | |
focusedValue | string | null | 焦点锚点;焦点不在树内、或它已被收起而不可见时为 null。 |
selectionMode | TreeSelectionMode | 生效的选择模式。 |
disabled | boolean | |
translations | TreeTranslations | 生效的文案:内建英文与 translations 合并后的那一份。 |
isExpanded | (value: string) => boolean | |
isSelected | (value: string) => boolean | |
isIndeterminate | (value: string) => boolean | 级联模式下该分支是否半选(有效叶后代部分勾选);非级联恒为 false。 |
branchLoadState | (value: string) => TreeBranchLoadSnapshot | null | 非懒分支返回 null;懒分支即使尚未请求也返回 idle。 |
setExpandedValue | (next: string[]) => void | |
setSelection | (next: string[]) => void | |
expand | (value: string) => void | |
collapse | (value: string) => void | |
retryBranch | (value: string) => void | 失败后重新取该分支;非懒分支与未知 value 不产生副作用。 |
select | (value: string, options?: { extend?: boolean, additive?: boolean }) => void | 选中某个节点,与点击同一语义:extend 对应按住 Shift(选锚点到该节点的范围,单选与级联下不认), additive 对应按住 Ctrl/Cmd(extended 下切换这一项)。 |
getRootProps | () => T['element'] | |
getLabelProps | () => T['element'] | |
getTreeProps | () => T['element'] | |
getEmptyProps | () => T['element'] | 空态占位:放在 root 中、tree 的兄弟(role=tree 只允许拥有 treeitem 与 group)。 提供 collection 时由连接层按条数收放;节点手写时不写 hidden,是否显示由作者决定。 |
getLoadingProps | () => T['element'] | 在途占位:与空态占位同一位置,两者不同时显示:加载期间显示它,空态让位。 提供 collection 时由连接层按条数收放;节点手写时只按 loading 收放。 |
getNodeDragTriggerProps | (props: TreeNodeProps) => T['element'] | 节点拖动把手。触屏路径唯一的入口,不占 Tab 位。 常驻即可:nodeDraggable 关闭或该节点禁用时它声明 data-disabled、也不再让出滚动, 渲染不会出错。按是否可拖动决定是否渲染,会使 DOM 结构随状态变化。 |
getLiveRegionProps | () => T['element'] | |
getItemProps | (props: TreeNodeProps) => T['element'] | |
getItemTextProps | (props: TreeNodeProps) => T['element'] | |
getItemDescriptionProps | (props: TreeNodeProps) => T['element'] | |
getItemSuffixProps | (props: TreeNodeProps) => T['element'] | |
getItemIndicatorProps | (props: TreeNodeProps) => T['element'] | |
getBranchProps | (props: TreeNodeProps) => T['element'] | |
getBranchControlProps | (props: TreeNodeProps) => T['element'] | |
getBranchTriggerProps | (props: TreeNodeProps) => T['element'] | |
getBranchIndicatorProps | (props: TreeNodeProps) => T['element'] | |
getBranchTextProps | (props: TreeNodeProps) => T['element'] | |
getBranchDescriptionProps | (props: TreeNodeProps) => T['element'] | 分支行的第 2 行副文本,与叶子的 item-description 同一档。 |
getBranchContentProps | (props: TreeNodeProps) => T['element'] | |
getBranchLoadingProps | (props: TreeNodeProps) => T['element'] | 懒分支取数在途:组件或元素在分支里自己补出,展开且在途时露面。 |
getBranchErrorProps | (props: TreeNodeProps) => T['element'] | 懒分支取数失败的说明:展开且失败时露面。 |
getBranchRetryTriggerProps | (props: TreeNodeProps) => T['button'] | 懒分支失败后的重试按钮:不占 Tab 位,键盘在分支上按确认键即重试。 |
getBranchEmptyProps | (props: TreeNodeProps) => T['element'] | 懒分支取回了空数组:展开时露面。 |
无障碍
键盘
规格出处:W3C APG
| 按键 | 生效条件 | 行为 |
|---|---|---|
Tab / Shift+Tab | focus outside the tree | 整棵树只占一个 Tab 位:焦点进入锚点节点,无锚点时先落容器再由它转投 |
ArrowDown | focus in tree | 焦点移到下一个可见行(禁用行跳过;loop 默认关,末行不回绕) |
ArrowUp | focus in tree | 焦点移到上一个可见行(禁用行跳过;loop 默认关,首行不回绕) |
Home | focus in tree | 焦点移到首个可见行 |
End | focus in tree | 焦点移到末个可见行(展开着的子树也算行) |
ArrowRight | focus on branch(dir=rtl 时改由 ArrowLeft 承担) | 收起的分支就地展开;已展开则把焦点移到首个子节点;叶子上什么都不做且不吞键 |
ArrowLeft | focus in tree(dir=rtl 时改由 ArrowRight 承担) | 展开的分支就地收起;收起的分支与叶子则把焦点移到父节点;根层的行什么都不做 |
Enter / Space | focus on node, 节点未禁用 | 选中焦点节点:selectionMode 为 single 或 extended 时换成这一项,multiple 时切换;焦点在分支上且 expandOnClick 打开时顺带切换展开态 |
Enter / Space | focus on branch, 懒分支取数失败 | 重试该分支,焦点留在分支行;不改变选中值与展开集合 |
Ctrl+Space / Cmd+Space | focus on node, selectionMode 为 multiple 或 extended、节点未禁用 | 只切换焦点节点的选中态,其余选中不动,不改展开态 |
Enter / Space | held on node, 树未禁用、未加载且节点未禁用 | 按住期间叶子行或分支行(branch-control)投影 data-pressed,与指针 :active 同一副按压面;抬起或失焦撤下。选中与展开语义照旧由这一次按键承担 |
* | focus in tree | 展开与焦点行同一父级的全部分支(已展开与禁用的不动);同级没有可展开的分支时不吞这个键 |
单个可打印字符 | focus in tree, typeahead 未关 | 连打检索在可见行上按 label 首字母搬焦点,不改选中值,也不展开任何分支 |
Alt+ArrowUp / Alt+ArrowDown | focus in tree, draggable 开启 | 把焦点节点在同一层的兄弟里往前 / 往后挪一位,按一下就是一次完整提交,不进拖动态;纵轴与文字方向无关,rtl 下两键不对调;已是同层首位 / 末位就不动,也不回绕;落点节点禁用或 allowDrop 不许就不搬。裸方向键仍是走可见行、确认键仍是选中 |
Alt+ArrowLeft / Alt+ArrowRight | focus in tree, draggable 开启 | 改焦点节点的缩进层级:往里去是认上一个兄弟当父、落进它子层末位,往外去是变成父节点的下一个兄弟;rtl 下两键对调,「往里去」的那个方向恒是缩进。没有上一个兄弟就缩不进去,已在根层就退不出去,两种情形都不动;落点节点禁用或 allowDrop 不许就不搬 |
ARIA
以下属性由 connect 生成。
| 部件 | 属性 | 值 |
|---|---|---|
tree | aria-busy | 'true' | undefined |
tree | aria-disabled | 'true' | 'false' |
tree | aria-labelledby | label 部件的 id | undefined |
tree | aria-multiselectable | 'true' | 'false' |
tree | aria-orientation | 'vertical' |
tree | role | 'tree' |
item | aria-checked | 'true' | 'mixed' | 'false' | undefined |
item | aria-disabled | 'true' | 'false' |
item | aria-level | meta?.level |
item | aria-posinset | meta?.posInSet |
item | aria-selected | 'true' | 'false' |
item | aria-setsize | meta?.setSize |
item | role | 'treeitem' |
item-indicator | aria-hidden | 'true' |
branch | aria-busy | 'true' | undefined |
branch | aria-checked | 'true' | 'mixed' | 'false' | undefined |
branch | aria-disabled | 'true' | 'false' |
branch | aria-expanded | 'true' | 'false' |
branch | aria-level | meta?.level |
branch | aria-posinset | meta?.posInSet |
branch | aria-selected | 'true' | 'false' |
branch | aria-setsize | meta?.setSize |
branch | role | 'treeitem' |
branch-trigger | aria-hidden | 'true' |
branch-indicator | aria-hidden | 'true' |
branch-content | role | 'group' |
branch-loading | role | 'status' |
branch-error | role | 'alert' |
branch-retry-trigger | aria-label | translations.retry |
branch-empty | role | 'status' |
node-drag-trigger | aria-hidden | 'true' |
live-region | aria-atomic | 'true' |
live-region | aria-live | 'polite' |
live-region | role | 'status' |
样式参考
皮肤
@xihan-ui/styles/tree.css 按 [data-scope="tree"][data-part="root"] 部件选择器书写,发布产物以挂载类 .xh-scope-tree 代替其中的 data-scope(特异性相同),位于 xihan.components 层。覆盖样式使用 xihan.overrides,部件选择器照常可用。
forced-colors: active 下另有一套规则:颜色交给系统,边框与状态标记改用系统色关键字。
数据属性
由 connect 生成;条件不成立时不输出无值属性。
| 部件 | 属性 | 值 |
|---|---|---|
root | data-disabled | ''(条件成立时才出现) |
root | data-loading | ''(条件成立时才出现) |
root | data-orientation | 'vertical' |
root | data-size | props.size |
root | data-variant | props.variant |
label | data-disabled | ''(条件成立时才出现) |
tree | data-disabled | ''(条件成立时才出现) |
tree | data-lines | ''(条件成立时才出现) |
tree | data-loading | ''(条件成立时才出现) |
tree | data-orientation | 'vertical' |
item | data-disabled | ''(条件成立时才出现) |
item | data-draggable | ''(条件成立时才出现) |
item | data-dragging | ''(条件成立时才出现) |
item | data-drop | 'before' | 'after' | 'inside' |
item | data-highlighted | ''(条件成立时才出现) |
item | data-indeterminate | ''(条件成立时才出现) |
item | data-pressed | ''(条件成立时才出现) |
item | data-selected | ''(条件成立时才出现) |
item | data-tone | metaOf(value)?.tone |
item | data-xh-collection-context | 'overlay' |
item | data-xh-collection-item | '' |
item | data-xh-collection-size | props.size |
item-indicator | data-disabled | ''(条件成立时才出现) |
item-indicator | data-highlighted | ''(条件成立时才出现) |
item-indicator | data-indeterminate | ''(条件成立时才出现) |
item-indicator | data-selected | ''(条件成立时才出现) |
item-indicator | data-xh-collection-slot | 'indicator' |
item-text | data-disabled | ''(条件成立时才出现) |
item-text | data-highlighted | ''(条件成立时才出现) |
item-text | data-indeterminate | ''(条件成立时才出现) |
item-text | data-selected | ''(条件成立时才出现) |
item-text | data-xh-collection-slot | 'text' |
item-description | data-disabled | ''(条件成立时才出现) |
item-description | data-highlighted | ''(条件成立时才出现) |
item-description | data-indeterminate | ''(条件成立时才出现) |
item-description | data-selected | ''(条件成立时才出现) |
item-description | data-xh-collection-slot | 'description' |
item-suffix | data-disabled | ''(条件成立时才出现) |
item-suffix | data-highlighted | ''(条件成立时才出现) |
item-suffix | data-indeterminate | ''(条件成立时才出现) |
item-suffix | data-selected | ''(条件成立时才出现) |
item-suffix | data-xh-collection-slot | 'suffix' |
branch | data-disabled | ''(条件成立时才出现) |
branch | data-empty | ''(条件成立时才出现) |
branch | data-error | ''(条件成立时才出现) |
branch | data-highlighted | ''(条件成立时才出现) |
branch | data-indeterminate | ''(条件成立时才出现) |
branch | data-load-state | branchLoadState(value)?.status |
branch | data-loading | ''(条件成立时才出现) |
branch | data-selected | ''(条件成立时才出现) |
branch | data-state | 'open' | 'closed' |
branch-control | data-disabled | ''(条件成立时才出现) |
branch-control | data-draggable | ''(条件成立时才出现) |
branch-control | data-dragging | ''(条件成立时才出现) |
branch-control | data-drop | 'before' | 'after' | 'inside' |
branch-control | data-highlighted | ''(条件成立时才出现) |
branch-control | data-indeterminate | ''(条件成立时才出现) |
branch-control | data-pressed | ''(条件成立时才出现) |
branch-control | data-selected | ''(条件成立时才出现) |
branch-control | data-state | 'open' | 'closed' |
branch-control | data-tone | metaOf(value)?.tone |
branch-control | data-xh-collection-context | 'overlay' |
branch-control | data-xh-collection-item | '' |
branch-control | data-xh-collection-size | props.size |
branch-trigger | data-disabled | ''(条件成立时才出现) |
branch-trigger | data-highlighted | ''(条件成立时才出现) |
branch-trigger | data-indeterminate | ''(条件成立时才出现) |
branch-trigger | data-selected | ''(条件成立时才出现) |
branch-trigger | data-state | 'open' | 'closed' |
branch-trigger | data-xh-collection-slot | 'prefix' |
branch-indicator | data-disabled | ''(条件成立时才出现) |
branch-indicator | data-highlighted | ''(条件成立时才出现) |
branch-indicator | data-indeterminate | ''(条件成立时才出现) |
branch-indicator | data-selected | ''(条件成立时才出现) |
branch-indicator | data-state | 'open' | 'closed' |
branch-indicator | data-xh-collection-slot | 'prefix' |
branch-text | data-disabled | ''(条件成立时才出现) |
branch-text | data-highlighted | ''(条件成立时才出现) |
branch-text | data-indeterminate | ''(条件成立时才出现) |
branch-text | data-selected | ''(条件成立时才出现) |
branch-text | data-state | 'open' | 'closed' |
branch-text | data-xh-collection-slot | 'text' |
branch-description | data-disabled | ''(条件成立时才出现) |
branch-description | data-highlighted | ''(条件成立时才出现) |
branch-description | data-indeterminate | ''(条件成立时才出现) |
branch-description | data-selected | ''(条件成立时才出现) |
branch-description | data-state | 'open' | 'closed' |
branch-description | data-xh-collection-slot | 'description' |
branch-content | data-disabled | ''(条件成立时才出现) |
branch-content | data-highlighted | ''(条件成立时才出现) |
branch-content | data-indeterminate | ''(条件成立时才出现) |
branch-content | data-orientation | 'horizontal' | 'vertical' |
branch-content | data-selected | ''(条件成立时才出现) |
branch-content | data-state | 'open' | 'closed' |
branch-loading | data-disabled | ''(条件成立时才出现) |
branch-loading | data-empty | ''(条件成立时才出现) |
branch-loading | data-error | ''(条件成立时才出现) |
branch-loading | data-highlighted | ''(条件成立时才出现) |
branch-loading | data-indeterminate | ''(条件成立时才出现) |
branch-loading | data-load-state | branchLoadState(value)?.status |
branch-loading | data-loading | ''(条件成立时才出现) |
branch-loading | data-selected | ''(条件成立时才出现) |
branch-loading | data-state | 'open' | 'closed' |
branch-loading | data-xh-loading-ring | '' |
branch-error | data-disabled | ''(条件成立时才出现) |
branch-error | data-empty | ''(条件成立时才出现) |
branch-error | data-error | ''(条件成立时才出现) |
branch-error | data-highlighted | ''(条件成立时才出现) |
branch-error | data-indeterminate | ''(条件成立时才出现) |
branch-error | data-load-state | branchLoadState(value)?.status |
branch-error | data-selected | ''(条件成立时才出现) |
branch-error | data-state | 'open' | 'closed' |
branch-retry-trigger | data-disabled | ''(条件成立时才出现) |
branch-retry-trigger | data-empty | ''(条件成立时才出现) |
branch-retry-trigger | data-error | ''(条件成立时才出现) |
branch-retry-trigger | data-highlighted | ''(条件成立时才出现) |
branch-retry-trigger | data-indeterminate | ''(条件成立时才出现) |
branch-retry-trigger | data-load-state | branchLoadState(value)?.status |
branch-retry-trigger | data-selected | ''(条件成立时才出现) |
branch-retry-trigger | data-state | 'open' | 'closed' |
branch-empty | data-disabled | ''(条件成立时才出现) |
branch-empty | data-empty | ''(条件成立时才出现) |
branch-empty | data-error | ''(条件成立时才出现) |
branch-empty | data-highlighted | ''(条件成立时才出现) |
branch-empty | data-indeterminate | ''(条件成立时才出现) |
branch-empty | data-load-state | branchLoadState(value)?.status |
branch-empty | data-selected | ''(条件成立时才出现) |
branch-empty | data-state | 'open' | 'closed' |
node-drag-trigger | data-disabled | ''(条件成立时才出现) |
node-drag-trigger | data-dragging | ''(条件成立时才出现) |
node-drag-trigger | data-xh-collection-slot | 'prefix' |
empty | data-disabled | ''(条件成立时才出现) |
loading | data-disabled | ''(条件成立时才出现) |
loading | data-loading | ''(条件成立时才出现) |
loading | data-xh-loading-ring | '' |
CSS 变量
本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。
| 变量 | 部件 | CSS 属性 | 状态 | 默认来源 | 说明 |
|---|---|---|---|---|---|
--xh-tree-bg | roottree | background | defaultvariant=subtle | --xh-bg-subtle--xh-bg-surface | tree 的 root、tree 部件 background 覆盖槽。 |
--xh-tree-border | tree | border | default | --xh-border-default | tree 的 tree 部件 border 覆盖槽。 |
--xh-tree-branch-content-gap | branchbranch-contentbranch-controlitemtree | backgroundgapinset-blockrow-gap | defaultlinesnot(:last-child)orientation=horizontalorientation=vertical | --xh-space-0 | tree 的 branch、branch-content、branch-control、item、tree 部件 background、gap、inset-block、row-gap 覆盖槽。 |
--xh-tree-branch-error-fg | branch-error | background-color | default | --xh-fg-danger | tree 的 branch-error 部件 background-color 覆盖槽。 |
--xh-tree-branch-gap | branchbranch-contentbranch-controlitemtree | backgroundgapinset-block | defaultfirst-childlinesorientation=verticalstate=open | --xh-space-0 | tree 的 branch、branch-content、branch-control、item、tree 部件 background、gap、inset-block 覆盖槽。 |
--xh-tree-branch-indicator-fg | branch-indicatorbranch-trigger | color | default | --xh-fg-muted | tree 的 branch-indicator、branch-trigger 部件 color 覆盖槽。 |
--xh-tree-branch-loading-fg | branchbranch-controlbranch-indicatorbranch-trigger | color | loading | --xh-fg-brand | tree 的 branch、branch-control、branch-indicator、branch-trigger 部件 color 覆盖槽。 |
--xh-tree-branch-retry-fg | branch-retry-trigger | color | default | --xh-fg-brand | tree 的 branch-retry-trigger 部件 color 覆盖槽。 |
--xh-tree-branch-status-fg | branch-emptybranch-errorbranch-loading | color | is([data-part='branch-loading'], [data-part='branch-empty'], [data-part='branch-error']) | --xh-fg-muted | tree 的 branch-empty、branch-error、branch-loading 部件 color 覆盖槽。 |
--xh-tree-branch-status-font-size | branch-emptybranch-errorbranch-loadingbranch-retry-trigger | font-size | defaultis([data-part='branch-loading'], [data-part='branch-empty'], [data-part='branch-error']) | --xh-_tree-font-size | tree 的 branch-empty、branch-error、branch-loading、branch-retry-trigger 部件 font-size 覆盖槽。 |
--xh-tree-branch-status-py | branch-emptybranch-errorbranch-loading | padding-block | is([data-part='branch-loading'], [data-part='branch-empty'], [data-part='branch-error']) | --xh-_tree-row-py | tree 的 branch-empty、branch-error、branch-loading 部件 padding-block 覆盖槽。 |
--xh-tree-drag-fg | node-drag-trigger | color | default | --xh-fg-subtle | tree 的 node-drag-trigger 部件 color 覆盖槽。 |
--xh-tree-drag-fg-active | node-drag-trigger | color | disableddragginghovernot([data-disabled]) | --xh-fg-default | tree 的 node-drag-trigger 部件 color 覆盖槽。 |
--xh-tree-drag-fg-disabled | node-drag-trigger | color | disabled | --xh-fg-disabled | tree 的 node-drag-trigger 部件 color 覆盖槽。 |
--xh-tree-drag-grip-long | node-drag-trigger | inline-size | empty | --xh-space-2 | tree 的 node-drag-trigger 部件 inline-size 覆盖槽。 |
--xh-tree-drag-grip-short | node-drag-trigger | block-size | empty | --xh-space-1 | tree 的 node-drag-trigger 部件 block-size 覆盖槽。 |
--xh-tree-drag-radius | node-drag-trigger | border-radius | default | --xh-shape-control | tree 的 node-drag-trigger 部件 border-radius 覆盖槽。 |
--xh-tree-drag-size | node-drag-trigger | block-sizeinline-size | default | --xh-_tree-indicator | tree 的 node-drag-trigger 部件 block-size、inline-size 覆盖槽。 |
--xh-tree-dragging-opacity | branch-controlitem | opacity | dragging | --xh-state-dragging-opacity | tree 的 branch-control、item 部件 opacity 覆盖槽。 |
--xh-tree-drop-fg | branch-controlitem | backgroundbox-shadow | disableddrop=afterdrop=beforedrop=insideis([data-drop='before'], [data-drop='after'])not([data-disabled]) | --xh-bg-brand | tree 的 branch-control、item 部件 background、box-shadow 覆盖槽。 |
--xh-tree-drop-inside-bg | branch-controlitem | background | disableddrop=insidenot([data-disabled]) | --xh-bg-subtle-hover | tree 的 branch-control、item 部件 background 覆盖槽。 |
--xh-tree-drop-line | branch-controlitem | block-sizebox-shadow | disableddrop=afterdrop=beforedrop=insideis([data-drop='before'], [data-drop='after'])not([data-disabled]) | --xh-stroke-thick | tree 的 branch-control、item 部件 block-size、box-shadow 覆盖槽。 |
--xh-tree-empty-fg | empty | color | default | --xh-fg-muted | tree 的 empty 部件 color 覆盖槽。 |
--xh-tree-empty-font-size | empty | font-size | default | --xh-_tree-font-size | tree 的 empty 部件 font-size 覆盖槽。 |
--xh-tree-empty-px | empty | padding-inline | default | --xh-_tree-row-px | tree 的 empty 部件 padding-inline 覆盖槽。 |
--xh-tree-empty-py | empty | padding-block | default | --xh-space-3 | tree 的 empty 部件 padding-block 覆盖槽。 |
--xh-tree-fg | tree | color | default | --xh-fg-default | tree 的 tree 部件 color 覆盖槽。 |
--xh-tree-gap | root | gap | default | --xh-space-2 | tree 的 root 部件 gap 覆盖槽。 |
--xh-tree-icon-size | branch-controlitemroot | --xh-icon-size | defaultsize=lgsize=sm | --xh-_collection-glyph-size--xh-glyph-size-lg--xh-glyph-size-md--xh-glyph-size-sm | tree 的 branch-control、item、root 部件 --xh-icon-size 覆盖槽。 |
--xh-tree-indent | branchbranch-contentbranch-controlbranch-emptybranch-errorbranch-loadingbranch-retry-triggeritemtree | inline-sizeinset-inline-startmargin-inline-startpadding-inlinepadding-inline-start | defaultis([data-part='branch-loading'], [data-part='branch-empty'], [data-part='branch-error'])linesnot(:last-child)orientation=vertical | --xh-_tree-indent | tree 的 branch、branch-content、branch-control、branch-empty、branch-error、branch-loading、branch-retry-trigger、item、tree 部件 inline-size、inset-inline-start、margin-inline-start、padding-inline、padding-inline-start 覆盖槽。 |
--xh-tree-indicator-size | branchbranch-contentbranch-controlbranch-descriptionbranch-indicatorbranch-triggeritemitem-indicatortree | --xh-icon-sizeinline-sizeinset-inline-startpadding-inline-start | defaulthas(> [data-scope='tree'][data-part='branch-trigger']:first-child)linesnot(:last-child)orientation=vertical | --xh-_tree-indicator | tree 的 branch、branch-content、branch-control、branch-description、branch-indicator、branch-trigger、item、item-indicator、tree 部件 --xh-icon-size、inline-size、inset-inline-start、padding-inline-start 覆盖槽。 |
--xh-tree-item-check-fg | branch-controlitem | color | disablederrorhighlightedhoveris(:active, [data-pressed])is(:focus-visible, [data-highlighted])is([aria-selected='true'], [data-selected])not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])pressedselectedstate=checkedxh-collection-context=overlayxh-collection-slot=indicator | --xh-tree-item-indicator-fg | tree 的 branch-control、item 部件 color 覆盖槽。 |
--xh-tree-item-indicator-fg | branch-controlitem | color | disablederrorhighlightedhoveris(:active, [data-pressed])is(:focus-visible, [data-highlighted])is([aria-selected='true'], [data-selected])not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])pressedselectedstate=checkedxh-collection-context=overlayxh-collection-slot=indicator | --xh-fg-brand | tree 的 branch-control、item 部件 color 覆盖槽。 |
--xh-tree-label-fg | label | color | default | --xh-fg-muted | tree 的 label 部件 color 覆盖槽。 |
--xh-tree-label-font-size | label | font-size | default | --xh-text-label-size | tree 的 label 部件 font-size 覆盖槽。 |
--xh-tree-label-font-weight | label | font-weight | default | --xh-text-label-weight | tree 的 label 部件 font-weight 覆盖槽。 |
--xh-tree-leaf-row-gap | branch-content | column-gap | orientation=horizontal | --xh-space-3 | tree 的 branch-content 部件 column-gap 覆盖槽。 |
--xh-tree-line-color | branchbranch-contentbranch-controlitemtree | backgroundborder-block-endborder-inline-start | last-childlinesnot(:last-child)orientation=vertical | --xh-border-default | tree 的 branch、branch-content、branch-control、item、tree 部件 background、border-block-end、border-inline-start 覆盖槽。 |
--xh-tree-loading-fg | loading | color | default | --xh-fg-muted | tree 的 loading 部件 color 覆盖槽。 |
--xh-tree-loading-font-size | loading | font-size | default | --xh-_tree-font-size | tree 的 loading 部件 font-size 覆盖槽。 |
--xh-tree-loading-gap | loading | gap | default | --xh-_tree-row-gap | tree 的 loading 部件 gap 覆盖槽。 |
--xh-tree-loading-px | loading | padding-inline | default | --xh-_tree-row-px | tree 的 loading 部件 padding-inline 覆盖槽。 |
--xh-tree-loading-py | loading | padding-block | default | --xh-space-3 | tree 的 loading 部件 padding-block 覆盖槽。 |
--xh-tree-max-h | tree | max-block-size | default | --xh-viewport-h-lg | tree 的 tree 部件 max-block-size 覆盖槽。 |
--xh-tree-px | tree | padding-inline | default | --xh-space-1 | tree 的 tree 部件 padding-inline 覆盖槽。 |
--xh-tree-py | tree | padding-block | default | --xh-space-1 | tree 的 tree 部件 padding-block 覆盖槽。 |
--xh-tree-radius | tree | border-radius | default | --xh-shape-surface | tree 的 tree 部件 border-radius 覆盖槽。 |
--xh-tree-row-bg-hover | branch-controlitem | background-color | disablederrorhighlightedhoveris(:focus-visible, [data-highlighted])is([aria-selected='true'], [data-selected])not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])selectedxh-collection-context=overlay | --xh-bg-subtle | tree 的 branch-control、item 部件 background-color 覆盖槽。 |
--xh-tree-row-bg-pressed | branch-controlitem | background-color | disablederroris(:active, [data-pressed])is([aria-selected='true'], [data-selected])not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])pressedselectedxh-collection-context=overlay | --xh-bg-subtle-hover | tree 的 branch-control、item 部件 background-color 覆盖槽。 |
--xh-tree-row-fg | branch-controlitem | color | defaultdisablederrorhighlightedhoveris(:active, [data-pressed])is(:focus-visible, [data-highlighted])is([aria-selected='true'], [data-selected])not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])pressedselectedxh-collection-context=overlay | --xh-fg-default | tree 的 branch-control、item 部件 color 覆盖槽。 |
--xh-tree-row-fg-selected | branch-controlitem | color | disablederrorhighlightedhoveris(:active, [data-pressed])is(:focus-visible, [data-highlighted])is([aria-selected='true'], [data-selected])not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])pressedselectedxh-collection-context=overlay | --xh-tree-row-fg | tree 的 branch-control、item 部件 color 覆盖槽。 |
--xh-tree-row-font-size | branch-controlitem | font-size | default | --xh-_tree-font-size | tree 的 branch-control、item 部件 font-size 覆盖槽。 |
--xh-tree-row-gap | branch-controlbranch-descriptionitem | gappadding-inline-start | defaulthas(> [data-scope='tree'][data-part='branch-trigger']:first-child)orientation=vertical | --xh-_tree-row-gap | tree 的 branch-control、branch-description、item 部件 gap、padding-inline-start 覆盖槽。 |
--xh-tree-row-leading | branch-controlitem | line-height | default | --xh-leading-normal | tree 的 branch-control、item 部件 line-height 覆盖槽。 |
--xh-tree-row-loading-opacity | branch-controlitemtree | opacity | is([data-scope='tree'][data-part='item'], [data-scope='tree'][data-part='branch-control'])loading | --xh-state-disabled-opacity | tree 的 branch-control、item、tree 部件 opacity 覆盖槽。 |
--xh-tree-row-px | branchbranch-contentbranch-controlitemtree | inline-sizeinset-inline-startpadding-inlinepadding-inline-start | defaultlinesnot(:last-child)orientation=vertical | --xh-_tree-row-px | tree 的 branch、branch-content、branch-control、item、tree 部件 inline-size、inset-inline-start、padding-inline、padding-inline-start 覆盖槽。 |
--xh-tree-row-py | branch-controlitem | padding-block | default | --xh-_tree-row-py | tree 的 branch-control、item 部件 padding-block 覆盖槽。 |
--xh-tree-row-radius | branch-controlitem | border-radius | default | --xh-shape-inset | tree 的 branch-control、item 部件 border-radius 覆盖槽。 |
--xh-tree-row-selected-font-weight | branch-controlitem | font-weight | disablederrorhighlightedhoveris(:active, [data-pressed])is(:focus-visible, [data-highlighted])is([aria-selected='true'], [data-selected])not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])pressedselectedxh-collection-context=overlay | --xh-font-weight-regular | tree 的 branch-control、item 部件 font-weight 覆盖槽。 |
--xh-tree-tree-gap | tree | gap | default | --xh-space-0 | tree 的 tree 部件 gap 覆盖槽。 |
动效
动效角色:按压 · 状态 · 指示与换位 · 披露 · 出现 · 循环(见动效规范)。
共享关键帧 xh-fade-in · xh-spin 由 family/motion.css 提供,皮肤 @import 它,单独引入仍成立;-webkit-mask-size · background-color · box-shadow · color · mask-size · opacity · rotate · text-decoration-color · translate 走 transition 过渡。时长与缓动读动效令牌,改令牌即改全局节奏。
prefers-reduced-motion: reduce 下本组件另有降级规则。
RTL
皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像;只认物理方向的量乘 --xh-direction-sign 换向,按就近的 dir 走。
