生态产品多个设计器现已发布正式版

查看生态总览
跳转到内容

Table 表格 ​

多行同构记录按列排开,支持排序、选择、展开与吸顶。

用法 ​

columns 是列号与列宽的唯一事实源,rows 是行序与行号的唯一事实源,标记只负责外观;列名装在 column-label 里,它是列头里唯一可收窄的一格,排序与列宽把手写在它旁边

团队成员
姓名
部门
职级
赵一
平台研发
P6
钱二
前端体验
P7
孙三
基础架构
P6
李四
质量保障
P5

组件结构 ​

加粗的是必需部件。

data-scope="table":root · header · body · footer · row · column-header · column-label · cell · caption · toolbar · column-list · column-visibility-trigger · select-all-trigger · row-select-trigger · sort-trigger · column-resize-trigger · column-drag-trigger · row-drag-trigger · expand-trigger · expanded-row · empty · loading · load-more-trigger · live-region

示例 ​

排序 ​

列上标注 sortable 才识别排序按钮;按住 Shift 点击是追加到排序链,直接点击是整条链换为该列

姓名
部门
职级
赵一
平台研发
P6
钱二
前端体验
P7
孙三
基础架构
P6
李四
前端体验
P5
排序链:(无)

多选 ​

selectionMode 默认 none,声明 multiple 才有选择机制;选择列也要在 columns 中占一条,否则右侧列号错位

姓名
部门
赵一
平台研发
钱二
前端体验
孙三
基础架构
李四(禁用)
质量保障
选中:u2

行展开 ​

行上标注 expandable 才识别展开按钮与左右方向键;详情行占一个真实行号,收起只加 hidden 不卸载内部节点

订单号
金额
XH-2026-0001
¥ 1,280
明细:键盘 ×1、鼠标 ×2
明细:显示器支架 ×1
明细:线材若干
展开:o1

密度 ​

size 只写为 root 的 data-size,改变的是单元格纵向内边距与字号;三档并排,差别在行高上

sm 紧凑
姓名
职级
赵一
P6
钱二
P7
孙三
P6
李四
P5
周五
P7
缺省
姓名
职级
赵一
P6
钱二
P7
孙三
P6
李四
P5
周五
P7
lg 宽松
姓名
职级
赵一
P6
钱二
P7
孙三
P6
李四
P5
周五
P7

空态与加载态 ​

两个占位节点常驻只依靠 hidden 显隐:表体为空且在取数时显示加载环,取完没有行才显示空态;已有行时重新取数不换占位,表体保留上一帧淡下

任务
负责人
还没有任务,点「取数」拉一份。

多行表尾 ​

footerRows 把行号空间的最后几行留给表尾,每一行表尾写明 level;表尾单元格不属于任何数据行,也就没有选中与禁用

采购清单
条目
数量
金额
键盘
2
¥ 1280
鼠标
3
¥ 447
显示器支架
1
¥ 320
合计
6
¥ 2047
均价
¥ 341

表头吸顶、表尾吸底与列吸附 ​

root 自身就是滚动容器:stickyHeader 固定表头,stickyFooter 固定表尾,列上标注 sticky 的固定该列

姓名
部门
城市
分机
邮箱
员工 1
平台研发
杭州
8100
member1@example.com
员工 2
前端体验
上海
8101
member2@example.com
员工 3
基础架构
北京
8102
member3@example.com
员工 4
质量保障
成都
8103
member4@example.com
员工 5
平台研发
杭州
8104
member5@example.com
员工 6
前端体验
上海
8105
member6@example.com
员工 7
基础架构
北京
8106
member7@example.com
员工 8
质量保障
成都
8107
member8@example.com
员工 9
平台研发
杭州
8108
member9@example.com
员工 10
前端体验
上海
8109
member10@example.com
员工 11
基础架构
北京
8110
member11@example.com
员工 12
质量保障
成都
8111
member12@example.com
员工 13
平台研发
杭州
8112
member13@example.com
员工 14
前端体验
上海
8113
member14@example.com
员工 15
基础架构
北京
8114
member15@example.com
员工 16
质量保障
成都
8115
member16@example.com
共 16 人

单选 ​

selectionMode 设为 single:选中集合最多一个元素,再次点击已选中的行即清空,焦点行按空格同理

套餐
价格
入门版
¥ 0 / 月
团队版
¥ 99 / 月
企业版
¥ 399 / 月
已选:p2

跨列单元格 ​

colspan 从它自己的列向后计算,报告为 aria-colspan;1 与省略同义,因此只在真正跨列时写

交付单量
小组
上半年
下半年
平台研发
42
51
前端体验
36
39
全年
168

单元格就地编辑 ​

表体的方向键与 Home/End 是挂在 body 上的冒泡监听,可编辑控件上阻止冒泡后这些键就回归输入框自身

采购清单
条目
数量
备注
键盘
鼠标
显示器支架
合计 6 件

多级表头与表头分组 ​

列给出 children 即为分组:表头按 headerRows 逐层渲染,分组格的跨列数、行号与较浅列头的纵向跨行都由表格算出

季度交付单量
小组
上半年
下半年
Q1
Q2
Q3
Q4
平台研发
12
15
18
21
前端体验
9
11
14
16
基础架构
7
8
10
12

列过滤 ​

过滤按钮是列标题中的一段内容,过滤结果就是宿主计算后传入的 rows;表头是表体的兄弟,按钮上的按键不会被表体接管

姓名
部门
城市
赵一
平台研发
杭州
钱二
前端体验
上海
孙三
基础架构
北京
李四
前端体验
杭州
周五
质量保障
成都
吴六
平台研发
上海
命中 6 / 6 人 · 部门:不限 · 城市:不限

树形表格 ​

rows 按契约就是一条已摊平的可见行序列:层级三项逐行声明,缩进落在首格的内边距上

组织
负责人
研发中心
赵一
服务端组
周五
展开:rd

拖拽调整列宽 ​

列上标注 resizable 才识别改宽把手;拖出表头仍跟随,方向键一次 8px、按住 Shift 一次 40px

拖动列标题右侧那条竖线;也可以 Tab 到它用方向键调
姓名
部门
城市
赵一
平台研发
杭州
钱二
前端体验
上海
孙三
基础架构
北京
李四
质量保障
成都
列宽偏好:{}

只渲染窗口内的行 ​

一万行交给 Virtualizer:表格经 virtualizer 接上它的 collectionVirtualizer,行号与方向键仍按完整行序走,DOM 里只有窗口那十几行

编号
姓名
部门

放进滚动区 ​

表格交给滚动区的视口滚动,两条自绘滚动条与吸顶表头、吸附列一起工作;表格自身不再定高

姓名
部门
城市
分机
邮箱
员工 1
平台研发
杭州
8100
member1@example.com
员工 2
前端体验
上海
8101
member2@example.com
员工 3
基础架构
北京
8102
member3@example.com
员工 4
质量保障
成都
8103
member4@example.com
员工 5
平台研发
杭州
8104
member5@example.com
员工 6
前端体验
上海
8105
member6@example.com
员工 7
基础架构
北京
8106
member7@example.com
员工 8
质量保障
成都
8107
member8@example.com
员工 9
平台研发
杭州
8108
member9@example.com
员工 10
前端体验
上海
8109
member10@example.com
员工 11
基础架构
北京
8110
member11@example.com
员工 12
质量保障
成都
8111
member12@example.com
员工 13
平台研发
杭州
8112
member13@example.com
员工 14
前端体验
上海
8113
member14@example.com
员工 15
基础架构
北京
8114
member15@example.com
员工 16
质量保障
成都
8115
member16@example.com
员工 17
平台研发
杭州
8116
member17@example.com
员工 18
前端体验
上海
8117
member18@example.com
员工 19
基础架构
北京
8118
member19@example.com
员工 20
质量保障
成都
8119
member20@example.com
员工 21
平台研发
杭州
8120
member21@example.com
员工 22
前端体验
上海
8121
member22@example.com
员工 23
基础架构
北京
8122
member23@example.com
员工 24
质量保障
成都
8123
member24@example.com

前缀列与分页序号 ​

prefix-columns 让库把序号/多选列插在最前面并占用列号;序号是分页全局序号,翻到第二页不会重新从 1 开始

#
名称
负责人
状态
1
资源 1
曦寒
停用
2
资源 2
碧落
启用
3
资源 3
葳蕤
启用
4
资源 4
曦寒
启用
5
资源 5
碧落
停用
6
资源 6
葳蕤
启用
7
资源 7
曦寒
启用
8
资源 8
碧落
启用
9
资源 9
葳蕤
停用
10
资源 10
曦寒
启用
已选 0 项 · 序号跨页连续

范围选择 ​

按住 Shift 点击勾选框选中一段;焦点落在表体中按 Ctrl/Cmd + A 全选。禁用行占据顺序位置但不被选入

点第一行的勾选框,再按住 Shift 点第五行 —— 中间整段一起选上(禁用那行跳过)。 再按住 Shift 点第三行,选区会往回收,起点不变。 焦点落在表体里按 Ctrl/Cmd + A 全选。

文件名
大小
报告.docx
1.2 MB
预算.xlsx
480 KB
会议纪要.md
12 KB
归档.zip(禁用)
88 MB
封面.png
2.4 MB
演示.pptx
5.1 MB
选中:(无)

拖拽调整列位 ​

列上标注 reorderable 才识别拖拽把手;也可以 Tab 到它用方向键移动,Home / End 到两端

拖列标题左侧的抓手换位;「操作」列没标 reorderable,拖不动也拖不过去
姓名
部门
城市
操作
赵一
平台研发
杭州
编辑
钱二
前端体验
上海
编辑
孙三
基础架构
北京
编辑
列序:(还没改过)

拖拽调整行位 ​

整行都是拖动源,按住拖到目标位置松手;也可以 Tab 进表体后按 Alt + 上下键移动。库只报告新行序,写回归使用者

按住任意一行拖动换位,落点画在两行之间;也可以 Tab 进表体,用 Alt + 上下键挪
序号
环节
负责人
1
需求评审
赵一
2
方案设计
钱二
3
开发实现
孙三
4
测试验收
李四
5
发布上线
周五
当前顺序:需求评审 → 方案设计 → 开发实现 → 测试验收 → 发布上线

触屏拖动把手 ​

整行拖动只识别鼠标与笔;触屏要按住行首的把手才能拖动,代价是该小块区域不再随表格滚动。键盘路径照常:Tab 进表体后 Alt + 上下键

鼠标按住整行就能拖;手机上按住行首的抓手拖,按下即走,不用先拖一段距离
序号
环节
负责人
1
需求评审
赵一
2
方案设计
钱二
3
开发实现
孙三
4
测试验收
李四
5
发布上线
周五
当前顺序:需求评审 → 方案设计 → 开发实现 → 测试验收 → 发布上线

树形表拖拽 ​

行声明了 parentId 即为树:拖到一行中段是放进该行(更换父节点),拖到上下两端仍是插在它前后;键盘使用 Alt + 上下键同层移动、Alt + 左右键改变缩进。库报告的是移动到哪个父节点下的第几位外加重排后的整份行序,写回归宿主:按 ids 重排、再把该行的 parentId 设为 parent,两件都做才一致。是否允许移动由 allowRowDrop 决定

拖到一行中段是放进这一行,拖到上下两端是插在它前后;也可以 Tab 进表体,用「Alt + 上下键」同层挪、「Alt + 左右键」改缩进。已归档那一组只出不进
任务
负责人
客户端
赵一
登录页改版
钱二
离线缓存
孙三
服务端
李四
限流中间件
周五
已归档
吴六
旧版导出
郑七
按住任意一行拖走,或 Tab 进表体按 Alt + 方向键搬

列设置与工具条 ​

工具条渲染为表的兄弟排在表前(root 是 grid,工具条不能进入其中);列设置区按 columnSettings 渲染,隐藏的列也在其中,只剩最后一列显示时该按钮转为禁用

成员 4 人
姓名
部门
城市
职级
赵一
平台研发
杭州
P6
钱二
前端体验
上海
P7
孙三
基础架构
北京
P6
李四
前端体验
杭州
P5
存下来的列偏好:尚未改过

树形表级联勾选 ​

cascade 与树的级联同一套算法:勾父行整枝带上,子行勾满父行跟着勾中、勾了一部分显示半选,禁用行的子树不动;对外值缺省只收叶行

组织
负责人
研发中心
赵一
前端组
钱二
服务端组
孙三
实验室(冻结)
李四
运维中心
周五
稳定性组
吴六
选中:(无)

单元格合并 ​

cellSpan 逐格询问合并区的大小:部门列按连续相同的值纵向合并,汇总行的两个季度横向合并;作者照常逐格渲染,被合并掉的格子由表格收起或留成占位

季度交付单量
部门
姓名
Q1
Q2
研发
赵一
12
15
钱二
9
11
孙三
7
10
运维
李四
5
6
周五
4
8
合计
—
上半年 104

导出 CSV ​

工具条里放一个下载按钮:点击时按当前的排序与列头现拼 CSV,Excel 打开不乱码要带 BOM,字段里的逗号、引号与换行按规则转义

成员 4 人
姓名
部门
备注
赵一
平台研发
负责网关,兼管发布
钱二
前端体验
组件库, 设计系统
孙三
基础架构
口头禅是"先压测"
李四
前端体验
控制台

行主操作 ​

点一行报告行与点中的那一列,焦点停在行上按 Enter 报告行:据此下钻或联动

大区
订单
金额
华北
1,204
¥ 386,200
华东
2,318
¥ 742,900
华南
1,876
¥ 590,400
点一行,或焦点停在行上按 Enter

单元格导航 ​

navigation="cell" 照 treegrid 在格子之间走:Tab 进表体落在行上,右键进到格子,方向键在格子之间走、上下键走同一列;Enter 或 F2 进格内的输入框编辑,编辑时方向键归输入框,Esc 退回格子

显示名
取值

数据驱动 ​

给 data 与 columns 就是一张会排序、会筛选、会分页的表:列 id 即字段名,排序、筛选与切页都由表格自己算,表后自动铺一组分页

姓名
部门
职级
入职
员工 1
平台研发
P5
2016 年
员工 2
前端体验
P8
2021 年
员工 3
基础架构
P7
2017 年
员工 4
质量保障
P6
2022 年
员工 5
平台研发
P5
2018 年
员工 6
前端体验
P8
2023 年
员工 7
基础架构
P7
2019 年
员工 8
质量保障
P6
2024 年
第 1 页

格子插槽 ​

数据驱动下要在格子里放标签、链接这类内容时接管那一格:参数里有记录、字段值与本来要显示的文字,别的列照常交出文字;不读字段的列写 field: null,文字由 format 从整条记录拼

任务
负责人
状态
进度
接入单点登录
赵一
进行中
3/5
导出报表
钱二
待办
0/4
表格虚拟滚动
孙三
已完成
6/6
暗色主题
李四
进行中
1/3

数据驱动的虚拟滚动 ​

一万行只加一个 virtual:表体只铺滚到的那一屏,滚的是表格根节点,吸顶表头、排序与方向键仍按完整的一万行算;Virtualizer 那一块开了才加载

编号
姓名
部门

设计指引 ​

何时使用 ​

  • 每条记录有多个字段需要按列对照。
  • 需要排序、筛选、批量选择。

何时不用 ​

  • 每条只有标题和一句描述时,使用列表,表格的列头是额外负担。
  • 移动端窄屏上横向滚动的表格难以使用,考虑换成卡片列表。

特性 ​

  • 排序、选择、展开三套状态各自可受控。
  • 列名放在 column-label 里,它是列头里唯一可收窄的一格:列名太长时由它出省略号。排序钮、列宽把手与列拖拽把手都是它的兄弟,不装进它里面。列头是 flex 行,裸写的文本是匿名 flex item、缩不下去,窄列上会把定尺的把手连同外边距一起挤出列头盒,所以不可排序、不可改宽的列也要用它。
  • 列定义中的 rowHeader 把这一列标成行表头:表体与脚注里这一列的格子写 role="rowheader",读屏报某一格时连着念出它所在行的表头。交叉表把行维度的各列标上;分组标题行用 cellSpan 横跨时起点格在第一列,也就是行表头;合计行的标题格同理。列头仍是 columnheader,外观不变。rowHeader 也可以给函数 (row, rowIndex) => boolean,按行决定这一格是不是行表头:分组报表只让组头行与小计行的分组格报行表头,明细行里空着的分组格照旧是 gridcell,读屏不会念出一串空表头。rowIndex 是这一行在可见数据行里的序号,与 cellSpan 的相同;表尾格子没有行定义,函数不调用、按行表头处理。
  • 列定义中的 align 控制该列所有格子(列头与数据格)的内容对齐方向:start(默认)、center、end,对应列头与单元格上的 data-align 属性。
  • 排序钮(sort-trigger)是列头里独立的定尺图标钮,不包列名:列名留在 column-label 上,钮写在列名之后、被推到行尾侧与列宽把手并排;点列头文字不排序,点钮才排序。
  • 选中行铺品牌淡底行面并由行首的勾选方框标记;悬停与按下只换面,行的几何与吸附列不动。
  • 表头吸顶与列吸附、条纹、密度、边框都是开关。
  • 支持多行表头与表头分组、跨列单元格、树形表格、单元格就地编辑、列过滤、拖拽调列宽。
  • 列宽:width 是伸缩的基准,表格比各列之和宽时各列一起分剩余空间、窄时一起收;minWidth / maxWidth 同时管布局与拖动改宽,伸缩时不越过这两条,三者写成同一个数就是一列定宽。没写下限的列收到皮肤的 --xh-table-cell-min-w 为止;拖动调过的列钉在拖出的宽度上,不再参与分配。
  • 表头分组:列给出 children 即为分组,只在表头占一格、横跨它的全部叶子列,不进列号空间,也不承载数据、排序、改宽与冻结。嵌套几层就有几行表头,按 headerRows 逐层渲染:表头行写明 level,列头按所在的行定位自己是哪一格;较浅的叶子列(含前缀列)在它起始的那一行出列头、纵向跨到最后一行(aria-rowspan),下面各行那一格是对读屏隐藏的占位,照样渲染以保住列宽。行号空间把各层表头都算进去,数据行从表头之后起算。分组内的叶子列要给出宽度,分组格的宽度按它们相加;列偏好把一个分组的叶子列拆开时,分组格按连续的段各出一格。
  • 单元格合并用 cellSpan,与 antd 的 spanMethod 同一种写法:逐格询问合并区的大小(rowSpan / colSpan)。表格按它算出起点格的 aria-rowspan / aria-colspan:与起点同一行、被横向跨过的格子不渲染(hidden);下面行里被纵向跨过的,在合并区最左那一列留一格占位(data-covered,对读屏隐藏、只保住宽度),其余不渲染。作者照常逐格渲染,谁显谁藏由表格决定;cellSpanOf(行, 列) 可查某一格的合并情形。合并只在可见数据行之间,遇到展开的详情行截断。
  • 纵向合并的起点格(data-row-span)挂载后按实测行位铺满合并的几行,压在下面几行之上、底色随起点行;量到之前按普通格子排。
  • 只渲了一段行时(接了虚拟滚动,或宿主只渲了一页),合并区只按在场的行铺开;起点行滚出窗口时,合并区里第一条在场的行接替起点:它的那一格露出内容、报剩下的行数作 aria-rowspan,cellSpanOf 对它报 covered: false。所以被合并掉的格子也照常写内容,接替时才有东西可显示。
  • 横向滚过之后,冻结列与滚动区之间只在确有内容被压住时出现一道描边:行首冻结列滚离起始端后画在紧挨滚动区那一列的行尾侧,行尾冻结列没滚到末端时画在它的行首侧(data-frozen-edge 标出那一列,root 以 data-at-min-horizontal / data-at-max-horizontal 报两端)。滚动容器是 root 自己;放进滚动区的表格不画这道边界。
  • 行换位(拖放与 Alt + 方向键)提交之后,宿主按 ids 重排的那一次里行从旧位置滑到新位置,详情行随它的数据行一起走;落点线取品牌实心色,与树、排序同一种。键盘换位时宿主写回若挪动了持有焦点的行节点(Web Components 下把行节点按新次序排回表体就是这样),焦点仍回到搬走的那一行,可以接着再挪。
  • 冻结列不必都写数字宽度:同侧多列冻结时,数字宽度直接累加,其余(没写、百分比、fr)取挂载后实测的列头宽度;量到之前那一侧从该列起暂时贴边。
  • 行数很大时只渲染窗口内的行:把虚拟滚动的 collectionVirtualizer 交给 virtualizer,虚拟滚动写 scrollContainer="ancestor",表体里放它的视口(不定高),每个虚拟条目装一行数据行(count 等于可见数据行的条数,展开的详情行跟在同一个条目里)。行间线与普通表体一样画在行与行之间:按完整行序判,第一行不画,滚动换批时每一行的高度不变。行号与 aria-rowcount 照旧按完整行序报;上下键与 Home / End 按完整行序求落点,落点不在窗口里时先把它滚进来再交焦点。窗口外的行没有落点,接上后行拖动换位不可用(rowReorderDisabledReason 为 virtualized)。Web Components 下行隔着一层 xh-virtualizer,行节点写 data-xh-part-owner="table" 归表格。表格根节点保留皮肤给的限高与滚动,表头吸顶、冻结列、横向溢出与行都在这一个滚动面里,列宽之和超过容器时表头与行照样对齐;视口不滚动,也就不占 Tab 位。表体里的视口自己滚时,表头与冻结列落在另一个滚动面上,开发模式下会提醒一次。
  • 数据驱动模式:给了 data(连同 columns),表格自己按排序链、筛选值与页码算出当前页的记录,行定义随之推出,rows 不再读。列 id 即字段名,行 id 缺省取 id 字段(rowKey 可换成别的字段或取值函数)。排序按字段值比:数字与日期按大小,字符串按数值序(第 10 项排在第 9 项之后),空值无论升降都排最后;列给了 compare 就用它。列给了 filterOptions 时列头铺一颗筛选钮,点开是一张勾选型菜单,钮里只有漏斗,名字取 translations.filter(列名);勾了几项就只留下命中的行,一项都不勾即不筛,勾选值存在 filterValue 里(可受控),列的 filter 可以换掉缺省的比法(字段值转字符串后在勾选值里,字段是数组时有一项在里面就算)。pagination 打开切页:按 page 与 pageSize(缺省 10)切出当前页,表后铺一组分页,按筛过的总条数算页数;筛选值一变回到第 1 页,数据变短时页码夹到最后一页,序号跨页连续。格子里的文字取列的 format,没有就是字段值;要放标签、链接这类内容时用 cell 插槽接管那一格(React 是 renderCell,Web Components 在宿主的直接子节点上写 slot="cell:<行 id>:<列 id>"),参数里有记录、字段值与本来要显示的文字。每一行是各自伸缩的一行,没写宽度的列按内容定基准时列头(多了排序钮与筛选钮)与格子对不齐,所以数据驱动模式下没写宽度的数据列以 8rem、序号列以 4rem 为伸缩基准,各行同一个,再照常一起分剩余空间。远程排序、远程分页这类只交一页数据进来的表格仍用 rows 手写结构。
  • 列的 field:数据驱动模式下列读记录里的哪个字段,缺省即列 id。列 id 想另起名字(同一个字段铺两列)时写 field: "字段名";写 field: null 是不读字段的列(操作列、由几个字段拼出来的列),值恒为 undefined,格子的文字靠 format 或 cell 插槽,排序与筛选靠 compare 与 filter。排序链与筛选值仍按列 id 记。
  • 行类型泛型:根组件按 data 的元素类型推断行类型,数据列的 id 与 field、rowKey 只能写它的字段,写错编译即报错;format 的 value 是那个字段的类型,compare、filter、format 与 cell 插槽(React 是 renderCell)的记录都是那一行的类型,插槽参数按 field 判出字段后 value 也跟着收窄。列定义另写成常量时标成 TableColumnDef<行类型>[]。行类型只从 data 推断,没给 data 的手写结构表格是宽松的 TableRecord,什么列名都收。Vue 在模板里由 Volar 推断,h() 推断不了泛型,那里按宽松的记录检查;Web Components 写 querySelector<XhTableElement<行类型>>("xh-table")。
  • 不写结构(Vue 不写默认插槽、React 不给 children、Web Components 只写 root 或什么都不写)时按标准结构铺出表头、表体、在途与空态占位,三端出自同一个结构函数。前缀列照样铺:多选时序号列与选择列的列头分别是 # 与全选把手,格子是序号与行选把手;可排序的列铺排序钮,声明了 resizable 与 reorderable 的列铺改宽与拖拽把手。空态的字取 translations.empty,empty 插槽(React 是 renderEmpty)可以接管。标准结构只铺数据行:展开详情、表尾、标题、工具条与列设置区跟着手写结构走。
  • 标准结构的虚拟化开关 virtual:表体只铺滚到的那一屏行,滚的是表格根节点,吸顶表头、冻结列与行在同一个滚动面里;行号、aria-rowcount 与方向键仍按完整行序,落点不在窗口里时先滚进来再交焦点。行高按 estimateSize(缺省 40,Web Components 为 estimate-size)起算,渲出后按实测校正。Virtualizer 那一块开了才加载,手写结构的表格不为它付体积;手写结构时照旧自己套 Virtualizer(见上一条)。
  • 树形表(行声明了 parentId)按展开展平后的可见行渲染:Vue 与 React 从插槽载荷的 visibleRows 读,Web Components 从元素的只读属性 visibleRows 读,收起的父行底下的子行不在其中;rowNumber(行 id) 给出大纲编号,isExpanded(行 id) 查展开态,展开集合变化(expanded-value-change)后按新的可见行重铺表体。
  • tree 声明这是一棵树,不必等某一行带上 parentId:一开始平铺的选项表也报 treegrid、每行写 aria-level,Alt + 左右键改层级归表格(平表不开时这两个键放行给页面),任何一行都能接受「放进去」(指针拖到一行中段,仍经 allowRowDrop 把关),叶子行不写 aria-expanded,读屏不会把它念成「已收起」。不开时照旧按行有没有 parentId 推断。用户先平铺加了几项、再把后面几项挪到第一项下面,开着它就做得到。
  • 树形表(行声明了 parentId)在 multiple 下可以打开 cascade 级联勾选,与树的 cascade 同一套算法:勾父行整枝传导,子行全勾上父行跟着勾中,勾了一部分的父行把手显示半选(data-indeterminate),禁用行的子树整棵冻结。对外值按 checkedStrategy 收敛,缺省 child 只收叶行;parent 收到最高的整枝,all 收全部勾中的行。全选的基数是够得着的叶行,禁用子树冻结着的父行不妨碍全选把手勾满。级联下不接 Shift 范围选。
  • 工具条(toolbar)与列设置区(column-list + column-visibility-trigger)把排序、列宽与显隐接出:设置区按 columnSettings 渲染,隐藏的列也包含在内。两块都放在 root 之外:root 是 grid 系角色,子节点只能是行与行组。
  • 行主操作 onAction(事件名 action):指针主键点在数据行上时报 { value: 行 id, column: 点中那一格的列 id }(纵向合并格算它的起点行),焦点停在行上按 Enter 时 column 为 null,用来下钻或把表格当联动源。点在行内的交互控件与表格自己的把手上、行拖动松手的那一次点击、禁用行、详情行、表头与脚注都不触发;选中、展开与 Space 不受影响,点行本身不选中。
  • 单元格级导航:navigation="cell" 让表体的格子也成为焦点落点,照 WAI-ARIA treegrid 的键盘。焦点在行上时左右键照旧展开 / 收起,已展开或不可展开的行再按右键进到首格,已收起的行或叶子行按左键回到父行,Ctrl + Home / End 到首末行;焦点在格子上时左右键在格子之间走(首格再按左键回到行),上下键在可见数据行之间走同一列(禁用行跳过),Home / End 到本行首末格,Ctrl + Home / End 到本列首末行。Enter 或 F2 进到格内第一个可聚焦的控件,之后按键都归控件,Esc 或 F2 退回格子(控件自己用掉了 Esc 的不退);格内没有控件时 Enter 触发行主操作,column 报这一格的列 id。选中(Space)、Alt + 方向键换位与改层级、拖动照常可用,换位之后焦点仍在原来那一格。整张表只有一个 Tab 位,落在锚点所在的行或格子上:表体数据行格子里原本能 Tab 到的控件平时写 tabindex="-1",由表格记下原值,焦点进到哪一格的控件里(Enter、F2,或指针点进去)才放开那一格,焦点离开那一格就收回;后来插入的控件、宿主改写的 tabindex 同样接得住,改写成什么就记成什么。编辑中 Tab / Shift + Tab 照 APG 允许的「在一格之内回绕」只在这一格的控件之间走,不离开这一格,要离开先按 Esc 回到格子;控件自己用掉了 Tab 的不管。表头、表尾与详情行里的控件不归它管。被合并掉的格子不是落点,上下键走到那一行时落在行上。缺省 row 是行级焦点。
  • 表尾可以有多行:footerRows 声明表尾行数,行号空间的最后几行留给它们,每一行表尾写明 level(与多级表头同一写法,只有一行时可省);aria-rowcount 把它们都计入。表尾的格子合并用 footerCellSpan,与 cellSpan 同一种写法,逐格询问第几行表尾(level)的哪一格:总计的标题格可以横跨行表头的几列、纵跨几行表尾,被跨过的格子同样由表格藏起或留占位,表尾格子照常逐格渲染。纵向只在表尾行之间。stickyFooter 把表尾钉在滚动容器的下沿(data-fixed),与 stickyHeader 对称,滚动之后总计行仍看得见;接了虚拟滚动、滚动面在根节点上时同样钉在根的下沿。
  • 三种非条目相位各有部件:空(empty)、在途(loading)、还有更多(load-more-trigger)。取下一页按钮的行为由作者决定,取数在途时自动停用。
  • 在途分两种:表体为空时 loading 占位露面,一枚加载环排在文案之前;已有行时重新取数(排序、翻页、筛选)不换成占位,表体与表尾原样保留、照常可操作,不淡化(淡到禁用透明度会把文字压到 4.5:1 以下)。两种都由 root 报告 aria-busy。

组合 ​

最佳实践 ​

  • 列宽尽量固定,不让内容长度决定列宽,否则翻页时整张表会重排。
  • 批量选择要显示已选条数,并在跨页时说明选中范围。

反模式 ​

  • 列多到必须横向滚动却不吸附首列,滚动后无法分辨行。
  • 用表格做页面布局。

API 参考 ​

产物 ​

层值
自定义元素<xh-table>
Vue 组件XhTableBody XhTableCaption XhTableCell XhTableColumnDragTrigger XhTableColumnHeader XhTableColumnLabel XhTableColumnList XhTableColumnResizeTrigger XhTableColumnVisibilityTrigger XhTableEmpty XhTableExpandTrigger XhTableExpandedRow XhTableFooter XhTableHeader XhTableLoadMoreTrigger XhTableLoading XhTableRoot XhTableRow XhTableRowDragTrigger XhTableRowSelectTrigger XhTableSelectAllTrigger XhTableSortTrigger XhTableToolbar
组合式函数useTable
状态机tableMachine
皮肤@xihan-ui/styles/table.css

Props ​

属性类型必填说明
captionedboolean作者渲染了 caption 部件时置真,由适配器统计而不是判断标题文字是否有值。 为假时根不输出 aria-labelledby:指向未渲染的 id 是无效引用,作者写在根上的 aria-label / aria-labelledby 也会被它压住。
columnsTableColumnDef<TRow>[]列定义,列号与列总数的唯一事实源。默认为空表。
rowsTableRowDef[]行定义,行序与行号的唯一事实源。默认为空表。给了 data 时不再读它,行定义由 data 推出。
datareadonly TRow[]数据驱动模式:给了它,表格自己按排序链、筛选值与页码算出当前页的记录,行定义随之推出(rows 不再读)。 列 id 即记录里的字段名(或列的 field);不写结构时按标准结构铺出表头、表体与空态,格子的文字取 format 或字段值。 远程分页、远程排序这类只交一页数据进来的表格仍用 rows 手写结构。
rowKeyTableField<TRow> | ((record: TableRecordOf<TRow>) => string)数据驱动模式下记录的行 id:字段名或取值函数,缺省取 id 字段;记录里没有时退回它在 data 里的下标。
filterValueTableFilterValue筛选值(列 id → 勾选的值)。提供即受控:写入只发 onFilterValueChange。只在数据驱动模式下参与计算。
defaultFilterValueTableFilterValue
paginationboolean数据驱动模式下分页:按 page 与 pageSize 切出当前页,表后铺一组分页按筛过的总条数算页数。 筛选值一变回到第 1 页;数据变短时页码夹到最后一页。
defaultPagenumber非受控初始页码,默认 1。
sortTableSortDescriptor[]排序链。提供即受控:cell 直读 prop,写入只发 onSortChange 不落内部值。
defaultSortTableSortDescriptor[]
selectionTableSelection选中集合。提供即受控,语义同上。
defaultSelectionTableSelection
expandedValuestring[]展开集合。提供即受控,语义同上。
defaultExpandedValuestring[]
selectionModeTableSelectionMode默认 none:未声明则没有选择机制,行也不报告 aria-selected。
virtualizerCollectionVirtualizer与 Virtualizer 的正式接线口(Virtualizer 的 collectionVirtualizer):count 必须等于可见数据行的条数, 每个虚拟条目装一行数据行(展开的详情行跟在同一个条目里)。接上后上下键与 Home / End 按完整行序计算, 落点不在窗口里时先把它滚进来再交焦点;行号照旧按完整行序报;行拖动换位不可用(窗口外的行没有落点)。
cellSpan(details: TableCellSpanDetails) => TableCellSpan | null | undefined单元格合并(与 antd 的 spanMethod 同一种写法):逐格询问,返回合并区的大小。 表格按它算出起点格的 aria-rowspan / aria-colspan:同一行里被横向合并的格子不渲染(hidden), 下面被纵向跨过的行在那一列留一格占位保住列宽、对读屏隐藏。作者照常逐格渲染,由连接层决定谁显谁藏。 合并只在可见数据行之间,遇到展开的详情行截断。焦点仍是行级:上下键逐行走,合并格随它的起点行读出。
footerCellSpan(details: TableFooterCellSpanDetails) => TableCellSpan | null | undefined表尾单元格合并,与 cellSpan 同一种写法:逐格询问第几行表尾的哪一格,返回合并区的大小。 起点格报 aria-colspan / aria-rowspan,同一行里被横向合并的格子不渲染(hidden),下面的表尾行里被纵向跨过的 在那一列留一格占位、对读屏隐藏。纵向只在表尾行之间,不跨进表体。表尾格子须写明所在的第几行(getCellProps 的 footer)。
cascadeboolean树形表(行声明了 parentId)在 multiple 下父子级联勾选,与 Tree 的 cascade 同一套算法: 勾父整枝传导、子全勾父勾、部分勾选的父行把手显示半选,禁用行的子树整棵冻结。 级联下不接 Shift 范围选。默认 false;平表与 single 下无效。
treeboolean声明这是一棵树,不必等某一行带上 parentId:根节点报 treegrid、行写 aria-level, Alt + 左右键改层级归表格,任何一行都能接受「放进去」(仍经 allowRowDrop 把关), 叶子行不写 aria-expanded。缺省 false:按行有没有 parentId 推断。
checkedStrategyCascadeStrategy级联下对外选中值的收敛策略,默认 child(只收叶行);parent = 最高整枝,all = 全部勾中的行。与 Tree 同义。
prefixColumnsTableColumnKind[]需要的前缀列,按给定顺序插在最前面,默认不插入任何列。 它们由库插入并占用列号:不占用时右侧所有列的 aria-colindex 会整体错位, 这正是使用者手工向 columns 中添加假列的原因。作者按 api.columns 渲染即可, 每一项都声明 kind。
columnPreferenceTableColumnPreference列偏好。提供即受控:内部不自行修改,写入只发 onColumnPreferenceChange。 持久化归使用者:库只负责把它计算进生效列。
defaultColumnPreferenceTableColumnPreference
pagenumber当前页码与每页条数。手写结构时只用于计算序号,不参与切片:切片归调用方(或分页组件的 api.slice), 都未提供时序号回退为可见序。数据驱动模式开了 pagination 时表格按它们切片,pageSize 缺省 10; page 提供即受控,写入只发 onPageChange。
pageSizenumber
loadingboolean数据加载中:root 报告 aria-busy;表体为空时加载态节点显示,已有行时表体原样保留、照常可操作。
emptyboolean显式声明表体为空;未提供时按 rows 是否为空推导。
stickyHeaderboolean表头吸顶:只写 data-fixed(布尔),固定的实现归皮肤。列冻结使用 data-frozen,两者不同名。
stripedboolean斑马纹:表体偶数行换一层浅底。
variantControlVariant形态:outline 画外框与圆角(默认),ghost 去掉外框只留行间横线,subtle 淡底。默认 outline。
ruledboolean列与列之间加竖分隔线,写为 root 上的 data-split。
footerRowsnumber表尾行数,缺省 0。行号空间的最后几行留给它们,aria-rowcount 也把它们计入; 每一行表尾经 getFooterRowProps 写明第几行(level)。
stickyFooterboolean表尾吸底:只写 data-fixed(布尔),钉在滚动容器下沿的实现归皮肤,与 stickyHeader 对称。
loopboolean上下键到达首尾是否回绕,默认 false。
navigationTableNavigation表体的键盘导航粒度,缺省 row(只落在行上);cell 时格子也是焦点落点,见 TableNavigation。
dirDirection文字方向,默认 ltr;只对调左右方向键的展开 / 收起语义。
sizeSize密度:sm / md / lg。只影响单元格的纵向内边距与字号,列宽算法不受影响。
translationsPartial<TableTranslations>
rowReorderableboolean行可以拖动换位。整行都是拖动源;另有一个不占 Tab 位的拖动把手, 触屏路径只经它(见 getRowDragTriggerProps)。
onRowMove(details: TableRowMoveDetails) => void
allowRowDrop(move: TableRowMoveDetails) => boolean本次移动是否允许。收到的是折算后的落点。 未提供时全部允许:落进自身后代与落在禁用行上两条由库自行拦截。
onColumnPreferenceChange(details: TableColumnPreferenceChangeDetails) => void
onSortChange(details: TableSortChangeDetails) => void
onFilterValueChange(details: TableFilterValueChangeDetails) => void
onPageChange(details: TablePageChangeDetails) => void
onSelectionChange(details: TableSelectionChangeDetails) => void
onExpandedValueChange(details: TableExpandedValueChangeDetails) => void
onAction(details: TableActionDetails) => void行主操作:指针主键点在数据行上(column 是点中那一格的列 id),或焦点停在行上按 Enter(column 为 null)。 点在行内的交互控件与表格自己的把手上、行拖动松手的那一次点击、禁用行、详情行、表头与脚注都不触发; 选中、展开与 Space 的语义不变。

TableColumnDef ​

columns 的元素。

字段类型必填说明
idstring是全表唯一:既是 DOM 身份(data-value),也是排序链与列号索引的键。 数据驱动模式下没写 field 时它也是记录里的字段名:给了行类型时只能写它的字段,写错编译即报错。
fieldTableField<TRow> | null数据驱动模式下这一列读记录里的哪个字段,缺省即列 id。列 id 想另起名字(同一个字段铺两列)时写在这里; 写 null 是不读字段的列(操作列、由几个字段拼出来的列),格子的文字靠 format 或 cell 插槽,排序与筛选靠 compare / filter。
labelstring展示名。只供调用方渲染,不作为可及名。
sortableboolean可排序:提供后才产出 aria-sort,排序把手也才响应按键与点击。
stickyboolean | 'start' | 'end'横向冻结(左右滚动时该列固定),写为条目上的 data-frozen。true 等于 'start'(固定在行首侧),'end' 固定在行尾侧。 与表头吸顶的 data-fixed 是两件事:那是布尔,这个带方向,同名会使 [data-fixed] 一条选择器命中两种语义。 同侧有多列吸附时,连接层按前面各列的宽度累加出偏移,写入 --xh-table-sticky-inset: 数字列宽直接累加,不是数字(没写、百分比、fr 这类)的列取挂载后实测的列头宽度,量到之前该侧从该列起暂时贴边。
widthstring | number列宽。数字按 px 处理,字符串原样写入内联 inline-size。它是伸缩的基准:表格比各列之和宽时 与其余列一起分剩余空间,窄时一起收;要定宽就把 minWidth / maxWidth 写成与它同一个数。
minWidthnumber列宽下限(px):布局里伸缩时不窄过它,拖动改宽也不窄过它。 未提供时布局下限是皮肤的 --xh-table-cell-min-w,拖动下限是 TABLE_COLUMN_MIN_WIDTH。
maxWidthnumber列宽上限(px):布局里伸缩时不宽过它,拖动改宽也不宽过它。未提供时不封顶。
resizableboolean该列的宽度可以拖动修改。提供后才产出改宽把手。
childrenTableColumnDef<TRow>[]表头分组:给了 children 即为分组列,只在表头占一格、横跨它全部叶子列,不进列号空间, 也不承载数据、排序、改宽与冻结。叶子列才是生效列;嵌套几层表头就有几行, 较浅的叶子列(含前缀列)的列头纵向跨到最后一行。分组内的叶子列都要给出宽度, 分组那一格的宽度才能按叶子列之和算准。
reorderableboolean该列可以拖动换位。提供后才产出拖拽把手:每个把手都是一个 Tab 位, 未声明的表格不承担该代价。 不可拖动的列与冻结列一样是屏障:跨过它落下会把它挤走,而作者已声明该列不动。
align'start' | 'center' | 'end'列内容的对齐方向,写为列头与数据格上的 data-align。 不提供时由皮肤决定(默认 start)。
rowHeaderboolean | ((row: TableRowDef, rowIndex: number) => boolean)行表头列:表体与脚注里这一列的格子写 role="rowheader",读屏报某一格时连着念出它所在行的表头 (交叉表的行维度、分组标签、合计行的标题格)。列头不受影响,仍是 columnheader;合并格的起点跟着所在的列走。 给函数时按行决定:表体里这一列的格子问一次,入参是行定义与它在可见数据行里的序号(0 起算,与 cellSpan 的 rowIndex 相同),返回 true 才报 rowheader。分组报表只让组头行与小计行的分组格报行表头,明细行照旧是 gridcell。 表尾格子没有行定义,函数不调用,按行表头处理。
compare(a: TableRecordOf<TRow>, b: TableRecordOf<TRow>) => number数据驱动模式(给了 data)下这一列的升序比较,两条记录比大小:负数排前、正数排后。降序由表格倒过来用。 不给时按这一列的字段值比:数字与日期按大小,其余按字符串(数字串按数值序),空值无论升降都排最后。 只在列声明了 sortable 时用到。
filterOptionsTableFilterOption[]数据驱动模式下的筛选项:给了它,列头铺一颗筛选钮,点开是一组勾选项;勾了几项就只留下命中的行, 一项都不勾即不筛。勾选值存在 filterValue 里,按列 id 分开。
filter(record: TableRecordOf<TRow>, values: readonly string[]) => boolean数据驱动模式下一条记录是否命中这一列勾选的筛选值(values 至少一项)。 不给时把这一列的字段值转成字符串,在 values 里即命中;字段是数组时有一项在里面就算。
format(value: TValue, record: TableRecordOf<TRow>) => string数据驱动模式下格子里显示的文字,value 是这一列的字段值(不读字段的列恒为 undefined)。 不给时把字段值转成字符串,空值显示为空。要放图标、徽标这类内容时改用 cell 插槽。

TableRowDef ​

rows 的元素。

字段类型必填说明
idstring是全表唯一:DOM 身份(data-value)、选中 / 展开集合的元素、连接层查询行的键。
disabledboolean行禁用:不可选中也不可展开,但仍可聚焦、仍是方向键的起点,也不计入全选基数。
expandableboolean可展开:提供后才报告 aria-expanded,左右方向键与展开把手也才识别该行。 只要有一行提供,root 就从 role=grid 改为 role=treegrid。 有子行的行不提供也可展开,展开的是它的子行。
parentIdstring父行 id。提供后该行即为该父行的子行,收起父行时它随之隐藏。 有子行的行不再产出详情行:一行不可能同时既展开出子行、又展开出一块详情。 指向不存在的父行时按根行处理,不丢弃该行。

TableSortDescriptor ​

sort 的元素。

字段类型必填说明
idstring是
directionTableSortDirection是

事件 ​

自定义元素将载荷放在 detail;Vue 使用同名 emit。

事件载荷说明
sort-changeTableSortChangeDetails排序链变化;detail 为 { value: { id, direction }[] }
filter-value-changeTableFilterValueChangeDetails筛选值变化(数据驱动模式);detail 为 { value: Record&lt;列 id, string[]&gt; }
page-changeTablePageChangeDetails页码变化(数据驱动模式开了分页);detail 为 { value: number }
column-preference-changeTableFooterCellSpanDetails列偏好变化;detail 为 { value: TableColumnPreference }
selection-changeTableSelectionChangeDetails选中集合变化;detail 为 { value: string[] | 'all' }
expanded-value-changeTableExpandedValueChangeDetails展开集合变化;detail 为 { value: string[] }
actionTableActionDetails行主操作:点在数据行上或焦点在行上按 Enter;detail 为 { value, column },value 是行 id,column 是点中那一格的列 id、键盘触发时为 null
row-moveTableRowMoveDetails行换位;detail 为 { id, parent, index, ids },parent 为 null 即根层,index 是在该层的落位(已经过先移除后插入的修正),ids 是重排后的整份行序

插槽 ​

仅列出带载荷的插槽。

Vue 组件插槽载荷说明
XhTableRootdefaultTableRootSlotProps
XhTableRoottoolbarTableToolbarSlotProps工具条槽:搜索、筛选、密度与列设置等作用于整张表的控件写在这里。 它渲染为 root 的兄弟排在表前:root 是 grid 系角色,子节点只能是 row 与 rowgroup。
XhTableRootcellTableCellSlotData标准结构里一格的内容:参数里有记录、字段值与本来要显示的文字,别的列交出 text 即可。
XhTableRootempty—标准结构里空态占位的内容。

React 适配器 props ​

只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。

React 组件属性类型必填说明
XhTableCellvaluestring是列 id。
XhTableCellcolspannumber | string跨列数,从 value 所在列向后计算。
XhTableColumnHeadervaluestring是
XhTableColumnHeaderlevelnumber | string多级表头里这一格所在的表头行;省略时取所在表头行写明的层号。
XhTableColumnVisibilityTriggervaluestring列 id。写在列设置区中时必须提供;写在列标题中时可省略,跟随该列。
XhTableExpandedRowvaluestring是所属数据行的 id。
XhTableRootvirtualboolean标准结构的虚拟化开关:表体只铺滚到的那一屏行,滚的是表格根节点(吸顶表头、冻结列与行在同一个滚动面里)。 Virtualizer 那一块开了才加载;给了 children 时不用它,照旧自己套 Virtualizer。
XhTableRootestimateSizenumber开了 virtual 时数据行的预估高度(px),缺省 40;还没量到的行按已量到的平均高度估。
XhTableRootrenderCell(cell: TableCellSlotData<TRow>) => ReactNode标准结构里一格的内容:参数里有记录、字段值与本来要显示的文字,别的列交出 text 即可。
XhTableRootrenderEmpty() => ReactNode标准结构里空态占位的内容。
XhTableRoottoolbarSlotChildren<TableToolbarSlotProps>工具条槽:搜索、筛选、密度与列设置等作用于整张表的控件写在这里。 它渲染为 root 的兄弟排在表前:root 是 grid 系角色,子节点只能是 row 与 rowgroup。
XhTableRootchildrenSlotChildren<TableRootSlotProps>
XhTableRowvaluestring行 id:数据行必须提供,表头行与脚注行省略。
XhTableRowlevelnumber | string多级表头写明这是第几行表头、多行表尾写明这是第几行表尾(1 起算),行里的列头与表尾格子据此定位;只有一行时省略。

状态 ​

公开状态写入 data-state。

部件取值
row'open' | 'closed' | undefined
column-visibility-trigger'unchecked' | 'checked'
select-all-triggertableSelectionState([...cascaded.checked], tableCasca… | tableSelectionState(selection, selectableIds)
row-select-trigger'open' | 'closed' | undefined
expand-trigger'open' | 'closed' | undefined
expanded-row'open' | 'closed'

以下名称仅用于内部状态机。

状态:idle · resizing · columnDragging · rowDragging

事件:SORT.SET · SORT.TOGGLE · FILTER.SET · PAGE.SET · COLUMN_PREF.SET · COLUMN_RESIZE.START · COLUMN_RESIZE.MOVE · COLUMN_RESIZE.END · COLUMN_RESIZE.CANCEL · COLUMN_RESIZE.STEP · COLUMN_DRAG.START · COLUMN_DRAG.MOVE · COLUMN_DRAG.END · COLUMN_DRAG.CANCEL · COLUMN.MOVE_BY · ROW_DRAG.START · ROW_DRAG.MOVE · ROW_DRAG.END · ROW_DRAG.CANCEL · ROW.MOVE_BY · ROW.REORDER_BLOCKED · COLUMN_PREF.PATCH · SELECTION.SET · ROW.SELECT · SELECTION.ALL_TOGGLE · EXPANDED.SET · ROW.EXPAND · ROW.COLLAPSE · ROW.EXPAND_TOGGLE · ROW_MOTION.TRACKED · ROW.FOCUS · CELL.FOCUS · TABLE.BLUR · PRESS.START · PRESS.END

判据:canPress

connect API ​

getXxxProps() 返回对应部件的宿主属性。

成员类型说明
columnsreadonly TableColumn[]生效的列:前缀列在前、数据列在后,各自声明 kind。 列号、渲染顺序都以它为准;不需要前缀列时它与作者提供的一致。
draggableColumnsreadonly string[]可以拖动换位的列 id 段。声明了 reorderable、不是冻结列、且彼此相连。 冻结列与不可拖动的列是屏障,把可拖动范围切分为段;这里给出的是最长的一段。 用它决定是否渲染把手,与库内部判定能否落下的口径是同一份。
rowReorderDisabledReasonTableRowReorderReason | null行不可拖动的原因,可拖动时为 null。声明了 rowReorderable 才可能非空。 库不自行弹出提示:是否把原因显示给用户由使用者决定。
dropTargetTableDropTarget | null当前的落点;松手即落在此处。没有合法落点时为 null,指示线随之消失。
announcementstring读屏播报文本。渲染进 live-region,不进入视觉版面。
rowsreadonly TableRowDef[]作者提供的行定义。
visibleRowsreadonly TableVisibleRow[]展开展平后的可见行序列(详情行插在所属数据行之后)。
sortTableSortDescriptor[]
dataDrivenboolean给了 data,表格自己算排序、筛选与分页。
recordsreadonly TableRecord[]数据驱动模式下当前页的记录,排过序、筛过,与表体的数据行一一对应;手写结构时为空数组。
recordCountnumber数据驱动模式下筛过之后的总条数,分页按它算页数;手写结构时为可见数据行的条数。
recordOf(rowId: string) => TableRecord | undefined一行的记录(整份 data 里的,不止当前页);手写结构时恒为 undefined。
cellText(rowId: string, columnId: string) => string某一行某一列格子里显示的文字:列的 format,没有就取字段值转字符串;手写结构时为空串。
filterValueTableFilterValue
setFilter(columnId: string, values: string[]) => void改写一列的筛选值,空数组即这一列不筛;随之回到第 1 页。
paginatedboolean数据驱动模式开了 pagination:表格按页切片,表后铺分页。
pagenumber当前页码:开了分页时已夹到有效范围;否则为 page 的原值,缺省 1。
pageSizenumber每页条数:开了分页时缺省 10;否则为 pageSize 的原值,没给时为 0。
pageCountnumber总页数:开了分页时按筛过的总条数算,至少 1;否则为 1。
setPage(page: number) => void
translationsTableTranslations解析后的文案:排序钮、筛选钮的名字,空态与在途占位的字。
selectionTableSelection
selectionStateTableSelectionState全选把手的三态,只按可选行(未禁用)计算。
selectionModeTableSelectionMode
expandedValuestring[]
focusedRowstring | null焦点锚点;焦点不在表体中时为 null。
focusedColumnstring | nullnavigation 为 cell 时锚点所在的列,焦点在行上或不在表体中时为 null。
navigationTableNavigation表体的键盘导航粒度。
loadingboolean
emptyboolean表体为空(显式声明或 rows 为空)。
rowCountnumberaria-rowcount:表头行 + 可见行 + 表尾行。
footerRowCountnumber表尾占几行,即 footerRows 取整后的值。
columnCountnumberaria-colcount:列定义的条数。
headerRowsreadonly (readonly TableHeaderCell[])[]表头按层排好的格子,每层一行;没有分组时只有一行、每格是一列。 多级表头按它逐层渲染表头行与列头(带上 level),占位格也要渲。
headerRowCountnumber表头占几行,数据行的行号从它之后起算。
cellSpanOf(rowId: string, columnId: string) => { rowSpan: number, colSpan: number, covered: boolean }某一格此刻的合并情形:起点给出跨度,被合并掉的给 covered。没有 cellSpan 时恒是 1×1、不被合并。 作者据此省掉被合并格的内容,或在自绘时跳过它。
isSelected(value: string) => boolean
isExpanded(value: string) => boolean
sortDirection(value: string) => TableSortDirection | null该列当前的排序方向;不参与排序时为 null。
sortPriority(value: string) => number该列在排序链里的优先级,1 起算;不参与排序时为 0。
setSort(next: TableSortDescriptor[]) => void
toggleSort(value: string, options?: { append?: boolean }) => void
setSelection(next: TableSelection) => void
selectRow(value: string, options?: { extend?: boolean }) => void选中某一行。extend 为真时选中锚点到该行的范围(仅复选)。
toggleSelectAll() => void
setExpandedValue(next: string[]) => void
expandRow(value: string) => void
collapseRow(value: string) => void
toggleExpandRow(value: string) => void
getRootProps() => T['element']
getCaptionProps() => T['element']
getToolbarProps() => T['element']工具条:搜索、筛选、密度与列设置这些作用于整张表的控件放置在此。 它是 root 的兄弟不是子节点:root 是 grid 系角色,子节点只能是 row 与 rowgroup。 不提供 role:一条控件带是否需要 role=toolbar(连同该套方向键 roving)由作者决定, 需要时向其中放置一个 Toolbar 组件。
getColumnListProps() => T['element']列设置区:一列一行,行中放显隐把手、列名与作者自行编写的宽度 / 冻结 / 排序控件。 渲染内容按 columnSettings。
getColumnVisibilityTriggerProps(props: TableColumnProps) => T['element']一列的显隐把手(复选形态)。最后一列显示时它为 aria-disabled。
getHeaderProps() => T['element']
getBodyProps() => T['element']
getFooterProps() => T['element']
getHeaderRowProps(props?: TableHeaderRowProps) => T['element']表头行:占行号空间最前面的几行;多级表头下写明第几行。
getFooterRowProps(props?: TableFooterRowProps) => T['element']表尾行:占行号空间最后的几行;多行表尾下写明第几行。
rowNumber(rowId: string) => string该行显示的序号。平表是分页全局序号,树形是大纲编号。 不显示序号列时仍可调用:它是纯计算,不依赖是否有该列。
columnPreferenceTableColumnPreference当前的列偏好。原样交出即可存储。
columnSettingsreadonly TableColumnSetting[]列设置区按它渲染:作者定义的列,按偏好排序,隐藏的也在其中。 每条自带显隐、冻结、宽度与排序,足以渲染一整行设置项而不必回头比对两份数组。
setColumnHidden(columnId: string, hidden: boolean) => void隐藏 / 显示一列。
setColumnSticky(columnId: string, sticky: boolean | 'start' | 'end') => void修改一列的冻结档。false 为不冻结,true 等于 'start'。
moveColumn(columnId: string, toIndex: number) => void把一列移到某个位次(只在作者定义的列之间计算,0 起算)。
setColumnWidth(columnId: string, width: number | string) => void修改一列的宽度。
setColumnPreference(next?: TableColumnPreference) => void整份偏好替换;未提供时清空,回到作者定义的原样。
getRowProps(props: TableRowProps) => T['element']
getColumnHeaderProps(props: TableColumnProps) => T['element']
getColumnLabelProps() => T['element']列名。列头里唯一可收窄的一格:排序钮、列宽把手与列拖拽把手都是它的兄弟, 列名太长时由它出省略号,把手不会被挤出列头。只投部件属性,不带状态。
getCellProps(props: TableCellProps) => T['element']
getSelectAllTriggerProps() => T['element']
getRowSelectTriggerProps(props: TableRowProps) => T['element']
getSortTriggerProps(props: TableColumnProps) => T['element']排序钮。独立的定尺图标钮,排在列名之后;列名留在 column-header 上, 钮的可及名取 translations.sort(列名)。只有 sortable 的列才渲染它。
getColumnResizeTriggerProps(props: TableColumnProps) => T['element']列宽把手。只有 resizable 的列才渲染它。
getColumnDragTriggerProps(props: TableColumnProps) => T['element']列拖拽把手。只有 reorderable 的列才渲染它。
getRowDragTriggerProps(props: TableRowProps) => T['element']行拖动把手。触屏路径唯一的入口,不占 Tab 位。 常驻即可:rowReorderable 关闭或该表不可拖动时它声明 data-disabled、也不再让出滚动, 渲染不会出错。按是否可拖动决定是否渲染,会使 DOM 结构随状态变化。
getExpandTriggerProps(props: TableRowProps) => T['element']
getExpandedRowProps(props: TableRowProps) => T['element']
getEmptyProps() => T['element']
getLoadingProps() => T['element']
getLoadMoreTriggerProps() => T['element']取下一页的入口:是否还有下一页、点击后的行为都由作者决定, 连接层只保证取数在途期间不可点击。
getLiveRegionProps() => T['element']拖动过程的读屏播报区。视觉隐藏,文本取自 announcement。 它必须在拖动开始之前就在 DOM 上:读屏不播报后插入的节点。

无障碍 ​

键盘 ​

规格出处:W3C APG

按键生效条件行为
Enter / Spaceheld on 可按部件, 未加载且部件自身未禁用按住期间行、全选 / 行选 / 展开 / 列显隐把手、排序把手或取下一页按钮投影 data-pressed,与指针 :active 同一副按压面;抬起或失焦撤下,取数在途由机器撤下。选中、排序与展开语义照旧由这一次按键承担
Tab / Shift+Tabfocus outside the table body表体只占一个 Tab 位:焦点进入锚点行,无锚点时先落 body 再由它转投;再按一次 Tab 整体离开表体
ArrowDownfocus in table body焦点移到下一个可见数据行(禁用行跳过;详情行不是落点;loop 默认关,末行不回绕)
ArrowUpfocus in table body焦点移到上一个可见数据行(禁用行跳过;loop 默认关,首行不回绕)
Homefocus in table body焦点移到首个可见数据行
Endfocus in table body焦点移到末个可见数据行
Enterfocus on row, 该行未禁用触发行主操作(onAction,column 为 null),用来下钻或联动;焦点停在行内控件上时 Enter 归那个控件,选中仍归 Space
Spacefocus on row, selectionMode 非 none 且该行未禁用切换焦点行的选中(单选替换、复选增删);选不动时不吞这个键,页面照常滚动
ArrowRightfocus on 可展开且收起的行(dir=rtl 时改由 ArrowLeft 承担)就地展开当前行,焦点不动;不可展开、已展开或禁用的行上什么都不做且不吞键
ArrowLeftfocus on 可展开且已展开的行(dir=rtl 时改由 ArrowRight 承担)就地收起当前行,焦点不动;其余情形什么都不做且不吞键
Enter / Spacefocus on sort-trigger, 该列 sortable排序方向按 升序 → 降序 → 不排序 循环;按住 Shift 是追加到排序链而不是替换整条链
Enter / Spacefocus on select-all-trigger, selectionMode=multiple当前可选行全选中就整段清空,否则整段选上;三态由 aria-checked 报出(半选为 mixed)
Enter / Spacefocus on column-visibility-trigger藏起 / 放出这一列;设置区不是 roving 集合,一列一个 Tab 位,Tab 一路走下去即可逐列开关。只剩最后一列显示着时它转 aria-disabled,按了不动
Ctrl+A / Cmd+Afocus in table body, selectionMode=multiple与全选把手同义:当前可选行全选中就整段清空,否则整段选上(禁用行不算进基数)。单选与不可选的表格不吞这个键,交还浏览器的整页全选;按住不放的连发只算一次
ArrowLeft / ArrowRightfocus in column-resize-trigger,该列 resizable把这一列按 8px 收窄 / 加宽;往行尾侧推是加宽,rtl 下左右两键对调,语义恒是「加宽 / 收窄」
Shift+ArrowLeft / Shift+ArrowRightfocus in column-resize-trigger,该列 resizable按 40px 收窄 / 加宽,方向规则同上
ArrowLeft / ArrowRightfocus in column-drag-trigger,该列在可拖的那一段里把这一列往前 / 往后挪一位,按一下就是一次完整提交;往行首侧挪是往前,rtl 下左右两键对调,语义恒是「往前 / 往后」;已在段首 / 段末就不动,也不回绕
Home / Endfocus in column-drag-trigger,该列在可拖的那一段里把这一列挪到可拖那一段的段首 / 段末;rtl 下两键对调,语义恒是「段首 / 段末」;已经在那儿就不动
ArrowRightnavigation=cell,focus on 已展开或不可展开的行(dir=rtl 时改由 ArrowLeft 承担)焦点进到这一行的首格;收起的可展开行上仍是展开
ArrowLeftnavigation=cell,focus on 收起或不可展开、且有父行的行(dir=rtl 时改由 ArrowRight 承担)焦点回到父行;已展开的行上仍是收起,根层的行上什么都不做且不吞键
ArrowRight / ArrowLeftnavigation=cell,focus on 表体格子焦点在这一行的格子之间走一格;已在末格就停住,首格再往回走一格回到行;被合并掉的格子跳过。rtl 下两键对调,语义恒是「下一格 / 上一格」
ArrowDown / ArrowUpnavigation=cell,focus on 表体格子焦点移到下一个 / 上一个可见数据行的同一列(禁用行跳过,loop 默认关);那一行的这一列被合并掉时落到行上
Home / Endnavigation=cell,focus on 表体格子焦点移到本行的首格 / 末格
Ctrl+Home / Ctrl+Endnavigation=cell,focus in table body焦点在格子上时移到本列的首行 / 末行,在行上时移到首行 / 末行
Enter / F2navigation=cell,focus on 表体格子放开这一格的控件、焦点交给其中第一个(格内编辑),编辑时方向键等按键都归控件;格内没有控件时 Enter 触发行主操作(onAction,column 是这一格的列 id),F2 无事可做
Tab / Shift+Tabnavigation=cell,focus in 表体格子里的控件只在这一格的控件之间走,到头回绕,不离开这一格;格里只有一个控件时停在它上面;控件自己用掉了 Tab 时不管
Escape / F2navigation=cell,focus in 表体格子里的控件退出格内编辑,焦点回到这一格;控件自己用掉了 Escape(比如收起它的下拉)时不退出
Alt+ArrowUp / Alt+ArrowDownfocus in table body,rowReorderable 且行拖拽没有被阻断的原因把焦点行往前 / 往后挪一位,按一下就是一次完整提交,不进拖动态;纵轴与文字方向无关,rtl 下两键不对调;已在首行 / 末行就不动,也不回绕;焦点锚点跟着搬走的那一行,连按几下能一路挪到位。裸方向键仍是导航、Space 仍是选中、左右键仍是展开收起
Alt+ArrowLeft / Alt+ArrowRightfocus in table body,声明了 tree 或 rows 里有行声明了 parentId,rowReorderable 且行拖拽没有被阻断的原因把焦点行改一层缩进:往里是认上一个兄弟当爹,往外是变成父行的下一个兄弟;按一下就是一次完整提交,不进拖动态;横轴跟着文字方向翻,rtl 下两键对调,语义恒是「往里 / 往外」;没有上一个兄弟就缩不进去、已在根层就退不出来,两种情形都不动。没声明 tree、rows 里也一行都不带 parentId 时这两个键不归表格管,放行给页面

ARIA ​

以下属性由 connect 生成。

部件属性值
rootaria-busy'true' | undefined
rootaria-colcountcolumns.length || undefined
rootaria-labelledbycaption 部件的 id | undefined
rootaria-multiselectable'true' | 'false'
rootaria-rowcountheaderRowCount + visibleRows.length + footerRowCount
rootrole'treegrid' | 'grid'
headerrole'rowgroup'
bodyrole'rowgroup'
footerrole'rowgroup'
rowaria-controlsdetail 部件的 id | undefined
rowaria-disabled'true' | 'false'
rowaria-expanded'true' | 'false' | undefined
rowaria-levelmetaIndex.get(row.value)?.level | undefined
rowaria-posinsetmetaIndex.get(row.value)?.posInSet | undefined
rowaria-rowindexdataRowIndex.get(row.value)
rowaria-selected'true' | 'false' | undefined
rowaria-setsizemetaIndex.get(row.value)?.setSize | undefined
rowrole'row'
column-headeraria-colindexcell.colIndex
column-headeraria-colspancell.colSpan | undefined
column-headeraria-hidden'true'
column-headeraria-rowspancell.rowSpan | undefined
column-headeraria-sort'ascending' | 'descending' | 'none' | undefined
column-headerrole'columnheader'
cellaria-colindexcolumnIndex.get(cell.value)
cellaria-colspanMath.max(cell.colSpan ?? 1, origin?.colSpan ?? 1) | undefined
cellaria-hidden'true'
cellaria-rowspanorigin.rowSpan | undefined
cellrole'rowheader' | 'gridcell'
toolbararia-labellabel.toolbar
column-listaria-labellabel.columnList
column-listrole'group'
column-visibility-triggeraria-checked'false' | 'true'
column-visibility-triggeraria-disabled'false' | 'true'
column-visibility-triggeraria-labellabel.columnVisibility(def?.label ?? column.value)
column-visibility-triggerrole'checkbox'
select-all-triggeraria-checked'true' | 'mixed' | 'false'
select-all-triggeraria-disabled'false' | 'true'
select-all-triggeraria-labellabel.selectAll
select-all-triggerrole'checkbox'
row-select-triggeraria-hidden'true'
sort-triggeraria-disabled'false' | 'true'
sort-triggeraria-labellabel.sort(def?.label ?? column.value)
sort-triggerrole'button'
column-resize-triggeraria-disabled'false' | 'true'
column-resize-triggeraria-labellabel.columnResize(def?.label ?? column.value)
column-resize-triggeraria-orientation'vertical'
column-resize-triggeraria-valuemaxdef?.maxWidth | undefined
column-resize-triggeraria-valuemindef?.minWidth | undefined
column-resize-triggeraria-valuenowcolumnNumericWidth(context.get('columnPreference').wi…
column-resize-triggerrole'separator'
column-drag-triggeraria-disabled'false' | 'true'
column-drag-triggeraria-labellabel.columnDrag(def?.label ?? column.value)
column-drag-triggeraria-roledescriptionlabel.columnDragRoleDescription
column-drag-triggerrole'button'
row-drag-triggeraria-hidden'true'
expand-triggeraria-hidden'true'
expanded-rowaria-level(metaIndex.get(row.value)?.level ?? 1) + 1 | undefined
expanded-rowaria-posinset1 | undefined
expanded-rowaria-rowindexdetailRowIndex.get(row.value)
expanded-rowaria-setsize1 | undefined
expanded-rowrole'row'
live-regionaria-atomic'true'
live-regionaria-live'polite'
live-regionrole'status'
header-rowaria-rowindexMath.max(1, Math.min(Math.trunc(header?.level ?? 1) |…
footer-rowaria-rowindexheaderRowCount + visibleRows.length + level | undefined
header-rowrole'row'
footer-rowrole'row'
  • 表格的名字来自 caption 部件:放了 caption 时根(role=grid / treegrid)经 aria-labelledby 引用它。不放 caption 时在根上写 aria-label 或 aria-labelledby:Vue 写在 XhTableRoot 上,React 写在 <XhTableRoot> 上,Web Components 写在 data-xh-part="root" 的节点上;写在 <xh-table> 宿主上的也会转到根上,宿主上的命名改了跟着变。caption 后补进来时根改为引用它。什么都不写时根不写 aria-labelledby,不留指向未渲染节点的引用。
  • 排序钮是 role=button,自占一个 Tab 位;它不包列名,可及名取 translations.sort(列名)(默认 Sort by <列名>),当前方向由所在列头的 aria-sort 报出,钮上不重复。
  • column-label 不带角色与状态:列头 role=columnheader 的可及名由它里面的文字算出,视觉上被省略号截断的列名读屏仍读全文。
  • 列宽把手与列拖拽把手同样各占一个 Tab 位,名字分别取 translations.columnResize 与 translations.columnDrag。
  • 行内的勾选框与展开箭头对读屏隐藏:选中与展开都由行自身的属性与方向键 / 空格承担。
  • Enter 在焦点行上触发行主操作,与点击同一条路径;焦点停在行内控件上时 Enter 归那个控件。
  • 焦点缺省是行级的,合并格不改变键盘:上下键逐行走,每一行都是一个停靠点;纵向合并格只在起点行里报出(带 aria-rowspan),下面几行那一格是占位、读屏跳过。缺省不做单元格级导航(左右键是展开 / 收起),合并格因此没有「焦点落进合并格、再移出」的问题。navigation="cell" 时格子也是落点(tabindex 随锚点轮换),格子上画一圈内收的聚焦环;被合并掉的格子不是落点。
  • 行换位(拖放与 Alt + 方向键)之后表格自带的 live-region 播一句:平表说可见数据行里的第几位、共几位;树形表(声明了 tree,或有行带 parentId)说放进了哪一层,位次与总数按那一层的兄弟行算,父行的名字经 translations.item 取,放到根层时取 translations.rootLevel。
  • 级联的半选只画在行选把手上:row 角色没有 mixed 这一档,半选的父行对读屏报 aria-selected=false,勾了哪些子行由子行各自报出。

样式参考 ​

皮肤 ​

@xihan-ui/styles/table.css 按 [data-scope="table"][data-part="root"] 部件选择器书写,发布产物以挂载类 .xh-scope-table 代替其中的 data-scope(特异性相同),位于 xihan.components 层。覆盖样式使用 xihan.overrides,部件选择器照常可用。

forced-colors: active 下另有一套规则:颜色交给系统,边框与状态标记改用系统色关键字。

数据属性 ​

由 connect 生成;条件不成立时不输出无值属性。

部件属性值
rootdata-at-max-horizontal''(条件成立时才出现)
rootdata-at-min-horizontal''(条件成立时才出现)
rootdata-empty''(条件成立时才出现)
rootdata-fixed''(条件成立时才出现)
rootdata-loading''(条件成立时才出现)
rootdata-sizeprops.size
rootdata-split''(条件成立时才出现)
rootdata-striped''(条件成立时才出现)
rootdata-variantprops.variant
headerdata-fixed''(条件成立时才出现)
bodydata-empty''(条件成立时才出现)
bodydata-instant''(条件成立时才出现)
footerdata-fixed''(条件成立时才出现)
rowdata-disabled''(条件成立时才出现)
rowdata-draggable''(条件成立时才出现)
rowdata-dragging''(条件成立时才出现)
rowdata-drop'before' | 'after' | 'inside'
rowdata-highlighted''(条件成立时才出现)
rowdata-pressed''(条件成立时才出现)
rowdata-section'body'
rowdata-selected''(条件成立时才出现)
rowdata-state'open' | 'closed' | undefined
rowdata-xh-collection-context'page'
rowdata-xh-collection-item''
rowdata-xh-collection-sizeprops.size
column-headerdata-aligndef?.align
column-headerdata-covered''(条件成立时才出现)
column-headerdata-dragging''(条件成立时才出现)
column-headerdata-drop'before' | 'after'
column-headerdata-filtered''(条件成立时才出现)
column-headerdata-frozenundefined
column-headerdata-frozen-edge''(条件成立时才出现)
column-headerdata-group''(条件成立时才出现)
column-headerdata-row-span''(条件成立时才出现)
column-headerdata-sort'asc' | 'desc'
column-headerdata-sort-indextableSortIndexOf(sort, value) | undefined
column-headerdata-sortable''(条件成立时才出现)
celldata-aligndef?.align
celldata-covered''(条件成立时才出现)
celldata-disabled''(条件成立时才出现) | undefined
celldata-dragging''(条件成立时才出现)
celldata-drop'before' | 'after'
celldata-frozenundefined
celldata-frozen-edge''(条件成立时才出现)
celldata-row-span''(条件成立时才出现)
celldata-selected''(条件成立时才出现) | undefined
toolbardata-sizeprops.size
column-listdata-sizeprops.size
column-visibility-triggerdata-disabled''(条件成立时才出现)
column-visibility-triggerdata-pressed''(条件成立时才出现)
column-visibility-triggerdata-state'unchecked' | 'checked'
column-visibility-triggerdata-xh-action-control''
column-visibility-triggerdata-xh-action-display'always'
column-visibility-triggerdata-xh-action-profile'icon'
column-visibility-triggerdata-xh-action-sizeprops.size
column-visibility-triggerdata-xh-action-variant'outline'
column-visibility-triggerdata-xh-check-mark'unchecked' | 'checked'
column-visibility-triggerdata-xh-check-mark-profile'box'
select-all-triggerdata-disabled''(条件成立时才出现)
select-all-triggerdata-pressed''(条件成立时才出现)
select-all-triggerdata-statetableSelectionState([...cascaded.checked], tableCasca… | tableSelectionState(selection, selectableIds)
select-all-triggerdata-xh-action-control''
select-all-triggerdata-xh-action-display'always'
select-all-triggerdata-xh-action-profile'icon'
select-all-triggerdata-xh-action-sizeprops.size
select-all-triggerdata-xh-action-variant'outline'
select-all-triggerdata-xh-check-marktableSelectionState([...cascaded.checked], tableCasca… | tableSelectionState(selection, selectableIds)
select-all-triggerdata-xh-check-mark-profile'box'
row-select-triggerdata-disabled''(条件成立时才出现)
row-select-triggerdata-highlighted''(条件成立时才出现)
row-select-triggerdata-indeterminate''(条件成立时才出现)
row-select-triggerdata-pressed''(条件成立时才出现)
row-select-triggerdata-selected''(条件成立时才出现)
row-select-triggerdata-state'open' | 'closed' | undefined
row-select-triggerdata-xh-action-control''
row-select-triggerdata-xh-action-display'always'
row-select-triggerdata-xh-action-profile'icon'
row-select-triggerdata-xh-action-sizeprops.size
row-select-triggerdata-xh-action-variant'outline'
row-select-triggerdata-xh-check-mark'checked' | 'indeterminate' | 'unchecked'
row-select-triggerdata-xh-check-mark-profile'box'
sort-triggerdata-disabled''(条件成立时才出现)
sort-triggerdata-pressed''(条件成立时才出现)
sort-triggerdata-sort'asc' | 'desc'
sort-triggerdata-sort-indextableSortIndexOf(sort, value) | undefined
sort-triggerdata-xh-action-control''
sort-triggerdata-xh-action-display'always'
sort-triggerdata-xh-action-profile'icon'
sort-triggerdata-xh-action-sizeprops.size
sort-triggerdata-xh-action-variant'ghost'
column-resize-triggerdata-disabled''(条件成立时才出现)
column-resize-triggerdata-resizing''(条件成立时才出现)
column-drag-triggerdata-disabled''(条件成立时才出现)
column-drag-triggerdata-dragging''(条件成立时才出现)
row-drag-triggerdata-disabled''(条件成立时才出现)
row-drag-triggerdata-dragging''(条件成立时才出现)
expand-triggerdata-disabled''(条件成立时才出现)
expand-triggerdata-highlighted''(条件成立时才出现)
expand-triggerdata-pressed''(条件成立时才出现)
expand-triggerdata-selected''(条件成立时才出现)
expand-triggerdata-state'open' | 'closed' | undefined
expand-triggerdata-xh-action-control''
expand-triggerdata-xh-action-display'always'
expand-triggerdata-xh-action-profile'icon'
expand-triggerdata-xh-action-sizeprops.size
expand-triggerdata-xh-action-variant'ghost'
expanded-rowdata-dragging''(条件成立时才出现)
expanded-rowdata-state'open' | 'closed'
loadingdata-loading''(条件成立时才出现)
loadingdata-xh-loading-ring''
load-more-triggerdata-loading''(条件成立时才出现)
load-more-triggerdata-pressed''(条件成立时才出现)
load-more-triggerdata-xh-action-control''
load-more-triggerdata-xh-action-display'always'
load-more-triggerdata-xh-action-profile'row'
load-more-triggerdata-xh-action-sizeprops.size
load-more-triggerdata-xh-action-variant'ghost'
header-rowdata-section'header'
footer-rowdata-section'footer'

CSS 变量 ​

本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。

变量部件CSS 属性状态默认来源说明
--xh-table-bgrootbackgrounddefault
variant=outline
variant=subtle
--xh-bg-subtle
--xh-bg-surface
table 的 root 部件 background 覆盖槽。
--xh-table-borderrootbordervariant=outline--xh-border-defaulttable 的 root 部件 border 覆盖槽。
--xh-table-caption-fgcaptioncolordefault--xh-fg-mutedtable 的 caption 部件 color 覆盖槽。
--xh-table-caption-font-sizecaptionfont-sizedefault--xh-text-label-sizetable 的 caption 部件 font-size 覆盖槽。
--xh-table-caption-font-weightcaptionfont-weightdefault--xh-text-label-weighttable 的 caption 部件 font-weight 覆盖槽。
--xh-table-caption-pxcaptionpadding-inlinedefault--xh-space-3table 的 caption 部件 padding-inline 覆盖槽。
--xh-table-caption-pycaptionpadding-blockdefault--xh-space-2table 的 caption 部件 padding-block 覆盖槽。
--xh-table-cell-gapcell
column-header
gapdefault--xh-control-gap-mdtable 的 cell、column-header 部件 gap 覆盖槽。
--xh-table-cell-min-wcell
column-header
min-inline-sizedefault3remtable 的 cell、column-header 部件 min-inline-size 覆盖槽。
--xh-table-cell-pxcell
column-header
root
padding-inline
padding-inline-end
default
not(:last-child)
split
--xh-_table-cell-pxtable 的 cell、column-header、root 部件 padding-inline、padding-inline-end 覆盖槽。
--xh-table-cell-pycell
column-header
padding-blockdefault--xh-_table-cell-pytable 的 cell、column-header 部件 padding-block 覆盖槽。
--xh-table-column-fgcolumn-headercolordefault--xh-fg-defaulttable 的 column-header 部件 color 覆盖槽。
--xh-table-column-font-weightcolumn-headerfont-weightdefault--xh-font-weight-mediumtable 的 column-header 部件 font-weight 覆盖槽。
--xh-table-column-list-fgcolumn-listcolordefault--xh-fg-defaulttable 的 column-list 部件 color 覆盖槽。
--xh-table-column-list-font-sizecolumn-listfont-sizedefault--xh-text-secondary-sizetable 的 column-list 部件 font-size 覆盖槽。
--xh-table-column-list-gapcolumn-listgapdefault--xh-_table-column-list-gaptable 的 column-list 部件 gap 覆盖槽。
--xh-table-detail-bgexpanded-rowbackgrounddefault--xh-bg-subtletable 的 expanded-row 部件 background 覆盖槽。
--xh-table-detail-pxcell
expanded-row
padding-inlinedefault--xh-_table-cell-pxtable 的 cell、expanded-row 部件 padding-inline 覆盖槽。
--xh-table-detail-pycell
expanded-row
padding-blockdefault--xh-space-3table 的 cell、expanded-row 部件 padding-block 覆盖槽。
--xh-table-drag-fgcolumn-drag-triggercolordefault--xh-fg-subtletable 的 column-drag-trigger 部件 color 覆盖槽。
--xh-table-drag-fg-activecolumn-drag-triggercolordisabled
dragging
hover
not([data-disabled])
--xh-fg-defaulttable 的 column-drag-trigger 部件 color 覆盖槽。
--xh-table-drag-fg-disabledcolumn-drag-triggercolordisabled--xh-fg-disabledtable 的 column-drag-trigger 部件 color 覆盖槽。
--xh-table-drag-grip-hcolumn-drag-triggerblock-sizeempty--xh-space-2table 的 column-drag-trigger 部件 block-size 覆盖槽。
--xh-table-drag-grip-wcolumn-drag-triggerinline-sizeempty--xh-space-1table 的 column-drag-trigger 部件 inline-size 覆盖槽。
--xh-table-drag-sizecolumn-drag-triggerblock-size
inline-size
default--xh-control-indicator-sizetable 的 column-drag-trigger 部件 block-size、inline-size 覆盖槽。
--xh-table-dragging-opacitycell
column-header
expanded-row
row
opacitydragging--xh-state-dragging-opacitytable 的 cell、column-header、expanded-row、row 部件 opacity 覆盖槽。
--xh-table-drop-fgcell
column-header
row
background
box-shadow
drop
drop=after
drop=before
drop=inside
frozen
is([data-drop='before'], [data-drop='after'])
not([data-frozen])
--xh-bg-brandtable 的 cell、column-header、row 部件 background、box-shadow 覆盖槽。
--xh-table-drop-inside-bgbody
root
row
backgrounddisabled
drop=inside
not([data-disabled])
--xh-bg-subtle-hover-opaquetable 的 body、root、row 部件 background 覆盖槽。
--xh-table-drop-linecell
column-header
row
block-size
box-shadow
inline-size
drop
drop=after
drop=before
drop=inside
frozen
is([data-drop='before'], [data-drop='after'])
not([data-frozen])
--xh-stroke-thicktable 的 cell、column-header、row 部件 block-size、box-shadow、inline-size 覆盖槽。
--xh-table-expand-fgexpand-triggercolordefault
disabled
focus-visible
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-fg-subtletable 的 expand-trigger 部件 color 覆盖槽。
--xh-table-fgrootcolordefault--xh-fg-defaulttable 的 root 部件 color 覆盖槽。
--xh-table-filter-fgcolumn-header
trigger
colordefault--xh-fg-subtletable 的 column-header、trigger 部件 color 覆盖槽。
--xh-table-filter-fg-activecolumn-header
trigger
colorfiltered--xh-fg-brandtable 的 column-header、trigger 部件 color 覆盖槽。
--xh-table-filter-sizecolumn-header
trigger
--xh-icon-sizedefault--xh-_table-trigger-sizetable 的 column-header、trigger 部件 --xh-icon-size 覆盖槽。
--xh-table-font-sizeroot
row
font-sizedefault--xh-_table-font-sizetable 的 root、row 部件 font-size 覆盖槽。
--xh-table-footer-bgcell
footer
row
backgrounddefault
fixed
frozen
--xh-bg-subtle
--xh-bg-subtle-opaque
table 的 cell、footer、row 部件 background 覆盖槽。
--xh-table-footer-borderfooterborder-block-startdefault--xh-border-subtletable 的 footer 部件 border-block-start 覆盖槽。
--xh-table-footer-font-weightfooterfont-weightdefault--xh-font-weight-mediumtable 的 footer 部件 font-weight 覆盖槽。
--xh-table-frozen-edgecell
column-header
backgroundfrozen-edge
is([data-part='column-header'], [data-part='cell'])
--xh-border-defaulttable 的 cell、column-header 部件 background 覆盖槽。
--xh-table-header-bgcolumn-header
header
backgrounddefault
frozen
--xh-_table-header-bgtable 的 column-header、header 部件 background 覆盖槽。
--xh-table-header-borderheaderborder-block-enddefault--xh-border-subtletable 的 header 部件 border-block-end 覆盖槽。
--xh-table-icon-sizeroot--xh-icon-sizedefault
size=lg
size=sm
--xh-glyph-size-lg
--xh-glyph-size-md
--xh-glyph-size-sm
table 的 root 部件 --xh-icon-size 覆盖槽。
--xh-table-load-more-trigger-bg-hoverload-more-triggerbackground-colordisabled
hover
loading
not([data-disabled])
not([data-loading])
--xh-_action-variant-bg-hovertable 的 load-more-trigger 部件 background-color 覆盖槽。
--xh-table-load-more-trigger-fgload-more-triggercolordefault
disabled
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-fg-brand-strongtable 的 load-more-trigger 部件 color 覆盖槽。
--xh-table-load-more-trigger-font-sizeload-more-triggerfont-sizedefault--xh-_table-font-sizetable 的 load-more-trigger 部件 font-size 覆盖槽。
--xh-table-load-more-trigger-gapload-more-triggergapdefault--xh-space-2table 的 load-more-trigger 部件 gap 覆盖槽。
--xh-table-load-more-trigger-pxload-more-triggerpadding-inlinedefault--xh-space-4table 的 load-more-trigger 部件 padding-inline 覆盖槽。
--xh-table-load-more-trigger-pyload-more-triggerpadding-blockxh-action-profile=row--xh-space-3table 的 load-more-trigger 部件 padding-block 覆盖槽。
--xh-table-load-more-trigger-radiusload-more-triggerborder-radiusdefault--xh-shape-controltable 的 load-more-trigger 部件 border-radius 覆盖槽。
--xh-table-loading-durationloadinganimation-durationdefault--xh-motion-loop-spintable 的 loading 部件 animation-duration 覆盖槽。
--xh-table-max-hrootmax-block-sizedefault--xh-viewport-h-lgtable 的 root 部件 max-block-size 覆盖槽。
--xh-table-pagination-gaprootmargin-block-startdefault--xh-space-3table 的 root 部件 margin-block-start 覆盖槽。
--xh-table-radiusrootborder-radiusvariant=outline
variant=subtle
--xh-shape-surfacetable 的 root 部件 border-radius 覆盖槽。
--xh-table-resize-fgcolumn-resize-triggerbackgrounddefault--xh-border-defaulttable 的 column-resize-trigger 部件 background 覆盖槽。
--xh-table-resize-fg-activecolumn-resize-triggerbackgroundhover
resizing
--xh-bg-brandtable 的 column-resize-trigger 部件 background 覆盖槽。
--xh-table-resize-linecolumn-resize-triggerinline-sizedefault--xh-stroke-thintable 的 column-resize-trigger 部件 inline-size 覆盖槽。
--xh-table-resize-line-lengthcolumn-resize-triggerblock-sizedefault60%table 的 column-resize-trigger 部件 block-size 覆盖槽。
--xh-table-resize-radiuscolumn-resize-triggerborder-radiusdefault--xh-shape-pilltable 的 column-resize-trigger 部件 border-radius 覆盖槽。
--xh-table-resize-widthcolumn-resize-triggerinline-sizedefault--xh-space-2table 的 column-resize-trigger 部件 inline-size 覆盖槽。
--xh-table-row-bgrowbackground-colordefault--xh-bg-surfacetable 的 row 部件 background-color 覆盖槽。
--xh-table-row-bg-hoverrowbackground-colordisabled
error
highlighted
hover
is(:focus-visible, [data-highlighted])
not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])
--xh-bg-subtle-opaquetable 的 row 部件 background-color 覆盖槽。
--xh-table-row-bg-pressedrowbackground-colordisabled
error
is(:active, [data-pressed])
not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])
pressed
--xh-bg-subtle-hover-opaquetable 的 row 部件 background-color 覆盖槽。
--xh-table-row-bg-selectedrowbackground-colordisabled
error
is([aria-selected='true'], [data-selected])
not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])
selected
xh-collection-context=page
--xh-bg-brand-subtletable 的 row 部件 background-color 覆盖槽。
--xh-table-row-bg-stripedbody
root
row
background-colorstriped
where(:nth-of-type(even)
where([data-scope='table'][data-part='root'][data-striped] [data-scope='table'][data-part='body'])
--xh-bg-subtle-opaquetable 的 body、root、row 部件 background-color 覆盖槽。
--xh-table-row-borderbody
cell
column-header
expanded-row
footer
header
item
root
row
border-block-start
border-inline-end
index=0
is([data-part='header'], [data-part='body'], [data-part='footer'])
is([data-part='row'], [data-part='expanded-row'])
not(:last-child)
not([data-index='0'])
not([hidden])
split
--xh-border-subtletable 的 body、cell、column-header、expanded-row、footer、header、item、root、row 部件 border-block-start、border-inline-end 覆盖槽。
--xh-table-row-cursorrowcursordefault
disabled
error
highlighted
hover
is(: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])
pressed
selected
xh-collection-context=page
defaulttable 的 row 部件 cursor 覆盖槽。
--xh-table-row-drag-fgrow-drag-triggercolordefault--xh-fg-subtletable 的 row-drag-trigger 部件 color 覆盖槽。
--xh-table-row-drag-fg-activerow-drag-triggercolordisabled
dragging
hover
not([data-disabled])
--xh-fg-defaulttable 的 row-drag-trigger 部件 color 覆盖槽。
--xh-table-row-drag-fg-disabledrow-drag-triggercolordisabled--xh-fg-disabledtable 的 row-drag-trigger 部件 color 覆盖槽。
--xh-table-row-drag-grip-longrow-drag-triggerinline-sizeempty--xh-space-2table 的 row-drag-trigger 部件 inline-size 覆盖槽。
--xh-table-row-drag-grip-shortrow-drag-triggerblock-sizeempty--xh-space-1table 的 row-drag-trigger 部件 block-size 覆盖槽。
--xh-table-row-drag-sizerow-drag-triggerblock-size
inline-size
default--xh-control-indicator-sizetable 的 row-drag-trigger 部件 block-size、inline-size 覆盖槽。
--xh-table-row-fg-selectedrowcolordisabled
error
highlighted
hover
is(: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])
pressed
selected
xh-collection-context=page
--xh-fg-on-brand-subtletable 的 row 部件 color 覆盖槽。
--xh-table-sort-bg-hovercolumn-header
sort-trigger
trigger
background
background-color
disabled
hover
loading
not([data-disabled])
not([data-loading])
--xh-_action-variant-bg-hover
--xh-bg-subtle-hover
table 的 column-header、sort-trigger、trigger 部件 background、background-color 覆盖槽。
--xh-table-sort-bg-pressedsort-triggerbackground-colordisabled
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_action-variant-bg-pressedtable 的 sort-trigger 部件 background-color 覆盖槽。
--xh-table-sort-fgsort-triggercolordefault
disabled
focus-visible
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-fg-subtletable 的 sort-trigger 部件 color 覆盖槽。
--xh-table-sort-fg-activesort-triggercolordisabled
focus-visible
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
sort
sort-index
--xh-fg-defaulttable 的 sort-trigger 部件 color 覆盖槽。
--xh-table-sort-sizesort-trigger--xh-icon-sizedefault--xh-_table-trigger-sizetable 的 sort-trigger 部件 --xh-icon-size 覆盖槽。
--xh-table-span-layercell
column-header
z-indexis([data-part='cell'], [data-part='column-header'])
row-span
1table 的 cell、column-header 部件 z-index 覆盖槽。
--xh-table-state-fgempty
loading
colordefault--xh-fg-mutedtable 的 empty、loading 部件 color 覆盖槽。
--xh-table-state-gapempty
loading
gapdefault--xh-control-gap-mdtable 的 empty、loading 部件 gap 覆盖槽。
--xh-table-state-min-hempty
loading
min-block-sizedefault0table 的 empty、loading 部件 min-block-size 覆盖槽。
--xh-table-state-pxempty
loading
padding-inlinedefault--xh-control-px-smtable 的 empty、loading 部件 padding-inline 覆盖槽。
--xh-table-state-pyempty
loading
padding-blockdefault--xh-space-3table 的 empty、loading 部件 padding-block 覆盖槽。
--xh-table-sticky-column-layercell
column-header
row
z-indexdrop
drop=after
drop=before
drop=inside
focus-visible
frozen
is([data-drop='before'], [data-drop='after'])
not([data-drop])
1table 的 cell、column-header、row 部件 z-index 覆盖槽。
--xh-table-sticky-footer-layerfooterz-indexfixed--xh-layer-stickytable 的 footer 部件 z-index 覆盖槽。
--xh-table-sticky-header-layerheaderz-indexfixed--xh-layer-stickytable 的 header 部件 z-index 覆盖槽。
--xh-table-sticky-insetcell
column-header
inset-inline-end
inset-inline-start
frozen=end
frozen=start
0table 的 cell、column-header 部件 inset-inline-end、inset-inline-start 覆盖槽。
--xh-table-toolbar-fgtoolbarcolordefault--xh-fg-defaulttable 的 toolbar 部件 color 覆盖槽。
--xh-table-toolbar-gaptoolbargapdefault--xh-_table-toolbar-gaptable 的 toolbar 部件 gap 覆盖槽。
--xh-table-toolbar-pytoolbarpadding-blockdefault--xh-space-2table 的 toolbar 部件 padding-block 覆盖槽。
--xh-table-trigger-bg-checkedcolumn-visibility-trigger
expand-trigger
row-select-trigger
select-all-trigger
sort-trigger
--xh-ink-surface
background-color
border
border-color
disabled
focus-visible
hover
indeterminate
is(:active, [data-pressed])
is([data-selected], [data-indeterminate])
is([data-state='checked'], [data-state='indeterminate'])
loading
not([data-disabled])
not([data-loading])
pressed
selected
state=checked
state=indeterminate
xh-ink-surface
--xh-bg-brandtable 的 column-visibility-trigger、expand-trigger、row-select-trigger、select-all-trigger、sort-trigger 部件 --xh-ink-surface、background-color、border、border-color 覆盖槽。
--xh-table-trigger-bg-checked-pressedcolumn-visibility-trigger
expand-trigger
row-select-trigger
select-all-trigger
sort-trigger
background-colordisabled
indeterminate
is(:active, [data-pressed])
is([data-selected], [data-indeterminate])
is([data-state='checked'], [data-state='indeterminate'])
loading
not([data-disabled])
not([data-loading])
pressed
selected
state=checked
state=indeterminate
--xh-bg-brand-activetable 的 column-visibility-trigger、expand-trigger、row-select-trigger、select-all-trigger、sort-trigger 部件 background-color 覆盖槽。
--xh-table-trigger-bg-pressedcolumn-visibility-trigger
expand-trigger
row-select-trigger
select-all-trigger
sort-trigger
background-colordisabled
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-bg-subtle-hovertable 的 column-visibility-trigger、expand-trigger、row-select-trigger、select-all-trigger、sort-trigger 部件 background-color 覆盖槽。
--xh-table-trigger-bordercolumn-visibility-trigger
expand-trigger
row-select-trigger
select-all-trigger
sort-trigger
border
border-color
default
disabled
focus-visible
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-border-strongtable 的 column-visibility-trigger、expand-trigger、row-select-trigger、select-all-trigger、sort-trigger 部件 border、border-color 覆盖槽。
--xh-table-trigger-border-checkedcolumn-visibility-trigger
expand-trigger
row-select-trigger
select-all-trigger
sort-trigger
border
border-color
disabled
focus-visible
hover
indeterminate
is(:active, [data-pressed])
is([data-selected], [data-indeterminate])
is([data-state='checked'], [data-state='indeterminate'])
loading
not([data-disabled])
not([data-loading])
pressed
selected
state=checked
state=indeterminate
--xh-table-trigger-bg-checkedtable 的 column-visibility-trigger、expand-trigger、row-select-trigger、select-all-trigger、sort-trigger 部件 border、border-color 覆盖槽。
--xh-table-trigger-fgcolumn-visibility-trigger
row-select-trigger
select-all-trigger
colordefault
disabled
focus-visible
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-fg-on-brandtable 的 column-visibility-trigger、row-select-trigger、select-all-trigger 部件 color 覆盖槽。
--xh-table-trigger-radiuscolumn-drag-trigger
column-header
column-visibility-trigger
expand-trigger
row-drag-trigger
row-select-trigger
select-all-trigger
sort-trigger
trigger
border-radiusdefault--xh-shape-control
--xh-shape-inset
table 的 column-drag-trigger、column-header、column-visibility-trigger、expand-trigger、row-drag-trigger、row-select-trigger、select-all-trigger、sort-trigger、trigger 部件 border-radius 覆盖槽。
--xh-table-trigger-sizecolumn-header
column-visibility-trigger
expand-trigger
row-select-trigger
select-all-trigger
sort-trigger
trigger
--xh-icon-size
block-size
inline-size
min-block-size
min-inline-size
default
xh-action-profile=icon
xh-action-profile=row
--xh-control-indicator-sizetable 的 column-header、column-visibility-trigger、expand-trigger、row-select-trigger、select-all-trigger、sort-trigger、trigger 部件 --xh-icon-size、block-size、inline-size、min-block-size、min-inline-size 覆盖槽。

动效 ​

动效角色:按压 · 状态 · 切换 · 指示与换位 · 出现 · 列表 · 循环(见动效规范)。

可覆盖的动效槽:--xh-table-loading-duration。

共享关键帧 xh-fade-in · xh-fade-out · xh-item-in 由 family/motion.css 提供,皮肤 @import 它,单独引入仍成立;-webkit-mask-size · background-color · clip-path · color · mask-size · opacity · rotate · scale · translate 走 transition 过渡。时长与缓动读动效令牌,改令牌即改全局节奏。

系统开启减弱动效时由令牌层统一收敛,皮肤不另作判断。

响应式 ​

皮肤另按输入能力分档:pointer: coarse:同一份皮肤在触屏与带指针的设备上不一样,与视口宽度无关。

RTL ​

皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像;另有按 dir 分支的规则。

Released under The MIT License