Table 表格
多行同构记录按列排开,支持排序、选择、展开与吸顶。
用法
columns 是列号与列宽的唯一事实源,rows 是行序与行号的唯一事实源,标记只负责外观;列名装在 column-label 里,它是列头里唯一可收窄的一格,排序与列宽把手写在它旁边
组件结构
加粗的是必需部件。
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 点击是追加到排序链,直接点击是整条链换为该列
多选
selectionMode 默认 none,声明 multiple 才有选择机制;选择列也要在 columns 中占一条,否则右侧列号错位
行展开
行上标注 expandable 才识别展开按钮与左右方向键;详情行占一个真实行号,收起只加 hidden 不卸载内部节点
密度
size 只写为 root 的 data-size,改变的是单元格纵向内边距与字号;三档并排,差别在行高上
空态与加载态
两个占位节点常驻只依靠 hidden 显隐:表体为空且在取数时显示加载环,取完没有行才显示空态;已有行时重新取数不换占位,表体保留上一帧淡下
多行表尾
footerRows 把行号空间的最后几行留给表尾,每一行表尾写明 level;表尾单元格不属于任何数据行,也就没有选中与禁用
表头吸顶、表尾吸底与列吸附
root 自身就是滚动容器:stickyHeader 固定表头,stickyFooter 固定表尾,列上标注 sticky 的固定该列
单选
selectionMode 设为 single:选中集合最多一个元素,再次点击已选中的行即清空,焦点行按空格同理
跨列单元格
colspan 从它自己的列向后计算,报告为 aria-colspan;1 与省略同义,因此只在真正跨列时写
单元格就地编辑
表体的方向键与 Home/End 是挂在 body 上的冒泡监听,可编辑控件上阻止冒泡后这些键就回归输入框自身
多级表头与表头分组
列给出 children 即为分组:表头按 headerRows 逐层渲染,分组格的跨列数、行号与较浅列头的纵向跨行都由表格算出
列过滤
过滤按钮是列标题中的一段内容,过滤结果就是宿主计算后传入的 rows;表头是表体的兄弟,按钮上的按键不会被表体接管
树形表格
rows 按契约就是一条已摊平的可见行序列:层级三项逐行声明,缩进落在首格的内边距上
拖拽调整列宽
列上标注 resizable 才识别改宽把手;拖出表头仍跟随,方向键一次 8px、按住 Shift 一次 40px
只渲染窗口内的行
一万行交给 Virtualizer:表格经 virtualizer 接上它的 collectionVirtualizer,行号与方向键仍按完整行序走,DOM 里只有窗口那十几行
放进滚动区
表格交给滚动区的视口滚动,两条自绘滚动条与吸顶表头、吸附列一起工作;表格自身不再定高
前缀列与分页序号
prefix-columns 让库把序号/多选列插在最前面并占用列号;序号是分页全局序号,翻到第二页不会重新从 1 开始
范围选择
按住 Shift 点击勾选框选中一段;焦点落在表体中按 Ctrl/Cmd + A 全选。禁用行占据顺序位置但不被选入
点第一行的勾选框,再按住 Shift 点第五行 —— 中间整段一起选上(禁用那行跳过)。 再按住 Shift 点第三行,选区会往回收,起点不变。 焦点落在表体里按 Ctrl/Cmd + A 全选。
拖拽调整列位
列上标注 reorderable 才识别拖拽把手;也可以 Tab 到它用方向键移动,Home / End 到两端
拖拽调整行位
整行都是拖动源,按住拖到目标位置松手;也可以 Tab 进表体后按 Alt + 上下键移动。库只报告新行序,写回归使用者
触屏拖动把手
整行拖动只识别鼠标与笔;触屏要按住行首的把手才能拖动,代价是该小块区域不再随表格滚动。键盘路径照常:Tab 进表体后 Alt + 上下键
树形表拖拽
行声明了 parentId 即为树:拖到一行中段是放进该行(更换父节点),拖到上下两端仍是插在它前后;键盘使用 Alt + 上下键同层移动、Alt + 左右键改变缩进。库报告的是移动到哪个父节点下的第几位外加重排后的整份行序,写回归宿主:按 ids 重排、再把该行的 parentId 设为 parent,两件都做才一致。是否允许移动由 allowRowDrop 决定
列设置与工具条
工具条渲染为表的兄弟排在表前(root 是 grid,工具条不能进入其中);列设置区按 columnSettings 渲染,隐藏的列也在其中,只剩最后一列显示时该按钮转为禁用
树形表级联勾选
cascade 与树的级联同一套算法:勾父行整枝带上,子行勾满父行跟着勾中、勾了一部分显示半选,禁用行的子树不动;对外值缺省只收叶行
单元格合并
cellSpan 逐格询问合并区的大小:部门列按连续相同的值纵向合并,汇总行的两个季度横向合并;作者照常逐格渲染,被合并掉的格子由表格收起或留成占位
导出 CSV
工具条里放一个下载按钮:点击时按当前的排序与列头现拼 CSV,Excel 打开不乱码要带 BOM,字段里的逗号、引号与换行按规则转义
行主操作
点一行报告行与点中的那一列,焦点停在行上按 Enter 报告行:据此下钻或联动
单元格导航
navigation="cell" 照 treegrid 在格子之间走:Tab 进表体落在行上,右键进到格子,方向键在格子之间走、上下键走同一列;Enter 或 F2 进格内的输入框编辑,编辑时方向键归输入框,Esc 退回格子
数据驱动
给 data 与 columns 就是一张会排序、会筛选、会分页的表:列 id 即字段名,排序、筛选与切页都由表格自己算,表后自动铺一组分页
格子插槽
数据驱动下要在格子里放标签、链接这类内容时接管那一格:参数里有记录、字段值与本来要显示的文字,别的列照常交出文字;不读字段的列写 field: null,文字由 format 从整条记录拼
数据驱动的虚拟滚动
一万行只加一个 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
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
captioned | boolean | 作者渲染了 caption 部件时置真,由适配器统计而不是判断标题文字是否有值。 为假时根不输出 aria-labelledby:指向未渲染的 id 是无效引用,作者写在根上的 aria-label / aria-labelledby 也会被它压住。 | |
columns | TableColumnDef<TRow>[] | 列定义,列号与列总数的唯一事实源。默认为空表。 | |
rows | TableRowDef[] | 行定义,行序与行号的唯一事实源。默认为空表。给了 data 时不再读它,行定义由 data 推出。 | |
data | readonly TRow[] | 数据驱动模式:给了它,表格自己按排序链、筛选值与页码算出当前页的记录,行定义随之推出(rows 不再读)。 列 id 即记录里的字段名(或列的 field);不写结构时按标准结构铺出表头、表体与空态,格子的文字取 format 或字段值。 远程分页、远程排序这类只交一页数据进来的表格仍用 rows 手写结构。 | |
rowKey | TableField<TRow> | ((record: TableRecordOf<TRow>) => string) | 数据驱动模式下记录的行 id:字段名或取值函数,缺省取 id 字段;记录里没有时退回它在 data 里的下标。 | |
filterValue | TableFilterValue | 筛选值(列 id → 勾选的值)。提供即受控:写入只发 onFilterValueChange。只在数据驱动模式下参与计算。 | |
defaultFilterValue | TableFilterValue | ||
pagination | boolean | 数据驱动模式下分页:按 page 与 pageSize 切出当前页,表后铺一组分页按筛过的总条数算页数。 筛选值一变回到第 1 页;数据变短时页码夹到最后一页。 | |
defaultPage | number | 非受控初始页码,默认 1。 | |
sort | TableSortDescriptor[] | 排序链。提供即受控:cell 直读 prop,写入只发 onSortChange 不落内部值。 | |
defaultSort | TableSortDescriptor[] | ||
selection | TableSelection | 选中集合。提供即受控,语义同上。 | |
defaultSelection | TableSelection | ||
expandedValue | string[] | 展开集合。提供即受控,语义同上。 | |
defaultExpandedValue | string[] | ||
selectionMode | TableSelectionMode | 默认 none:未声明则没有选择机制,行也不报告 aria-selected。 | |
virtualizer | CollectionVirtualizer | 与 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)。 | |
cascade | boolean | 树形表(行声明了 parentId)在 multiple 下父子级联勾选,与 Tree 的 cascade 同一套算法: 勾父整枝传导、子全勾父勾、部分勾选的父行把手显示半选,禁用行的子树整棵冻结。 级联下不接 Shift 范围选。默认 false;平表与 single 下无效。 | |
tree | boolean | 声明这是一棵树,不必等某一行带上 parentId:根节点报 treegrid、行写 aria-level, Alt + 左右键改层级归表格,任何一行都能接受「放进去」(仍经 allowRowDrop 把关), 叶子行不写 aria-expanded。缺省 false:按行有没有 parentId 推断。 | |
checkedStrategy | CascadeStrategy | 级联下对外选中值的收敛策略,默认 child(只收叶行);parent = 最高整枝,all = 全部勾中的行。与 Tree 同义。 | |
prefixColumns | TableColumnKind[] | 需要的前缀列,按给定顺序插在最前面,默认不插入任何列。 它们由库插入并占用列号:不占用时右侧所有列的 aria-colindex 会整体错位, 这正是使用者手工向 columns 中添加假列的原因。作者按 api.columns 渲染即可, 每一项都声明 kind。 | |
columnPreference | TableColumnPreference | 列偏好。提供即受控:内部不自行修改,写入只发 onColumnPreferenceChange。 持久化归使用者:库只负责把它计算进生效列。 | |
defaultColumnPreference | TableColumnPreference | ||
page | number | 当前页码与每页条数。手写结构时只用于计算序号,不参与切片:切片归调用方(或分页组件的 api.slice), 都未提供时序号回退为可见序。数据驱动模式开了 pagination 时表格按它们切片,pageSize 缺省 10; page 提供即受控,写入只发 onPageChange。 | |
pageSize | number | ||
loading | boolean | 数据加载中:root 报告 aria-busy;表体为空时加载态节点显示,已有行时表体原样保留、照常可操作。 | |
empty | boolean | 显式声明表体为空;未提供时按 rows 是否为空推导。 | |
stickyHeader | boolean | 表头吸顶:只写 data-fixed(布尔),固定的实现归皮肤。列冻结使用 data-frozen,两者不同名。 | |
striped | boolean | 斑马纹:表体偶数行换一层浅底。 | |
variant | ControlVariant | 形态:outline 画外框与圆角(默认),ghost 去掉外框只留行间横线,subtle 淡底。默认 outline。 | |
ruled | boolean | 列与列之间加竖分隔线,写为 root 上的 data-split。 | |
footerRows | number | 表尾行数,缺省 0。行号空间的最后几行留给它们,aria-rowcount 也把它们计入; 每一行表尾经 getFooterRowProps 写明第几行(level)。 | |
stickyFooter | boolean | 表尾吸底:只写 data-fixed(布尔),钉在滚动容器下沿的实现归皮肤,与 stickyHeader 对称。 | |
loop | boolean | 上下键到达首尾是否回绕,默认 false。 | |
navigation | TableNavigation | 表体的键盘导航粒度,缺省 row(只落在行上);cell 时格子也是焦点落点,见 TableNavigation。 | |
dir | Direction | 文字方向,默认 ltr;只对调左右方向键的展开 / 收起语义。 | |
size | Size | 密度:sm / md / lg。只影响单元格的纵向内边距与字号,列宽算法不受影响。 | |
translations | Partial<TableTranslations> | ||
rowReorderable | boolean | 行可以拖动换位。整行都是拖动源;另有一个不占 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 的元素。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 是 | 全表唯一:既是 DOM 身份(data-value),也是排序链与列号索引的键。 数据驱动模式下没写 field 时它也是记录里的字段名:给了行类型时只能写它的字段,写错编译即报错。 |
field | TableField<TRow> | null | 数据驱动模式下这一列读记录里的哪个字段,缺省即列 id。列 id 想另起名字(同一个字段铺两列)时写在这里; 写 null 是不读字段的列(操作列、由几个字段拼出来的列),格子的文字靠 format 或 cell 插槽,排序与筛选靠 compare / filter。 | |
label | string | 展示名。只供调用方渲染,不作为可及名。 | |
sortable | boolean | 可排序:提供后才产出 aria-sort,排序把手也才响应按键与点击。 | |
sticky | boolean | 'start' | 'end' | 横向冻结(左右滚动时该列固定),写为条目上的 data-frozen。true 等于 'start'(固定在行首侧),'end' 固定在行尾侧。 与表头吸顶的 data-fixed 是两件事:那是布尔,这个带方向,同名会使 [data-fixed] 一条选择器命中两种语义。 同侧有多列吸附时,连接层按前面各列的宽度累加出偏移,写入 --xh-table-sticky-inset: 数字列宽直接累加,不是数字(没写、百分比、fr 这类)的列取挂载后实测的列头宽度,量到之前该侧从该列起暂时贴边。 | |
width | string | number | 列宽。数字按 px 处理,字符串原样写入内联 inline-size。它是伸缩的基准:表格比各列之和宽时 与其余列一起分剩余空间,窄时一起收;要定宽就把 minWidth / maxWidth 写成与它同一个数。 | |
minWidth | number | 列宽下限(px):布局里伸缩时不窄过它,拖动改宽也不窄过它。 未提供时布局下限是皮肤的 --xh-table-cell-min-w,拖动下限是 TABLE_COLUMN_MIN_WIDTH。 | |
maxWidth | number | 列宽上限(px):布局里伸缩时不宽过它,拖动改宽也不宽过它。未提供时不封顶。 | |
resizable | boolean | 该列的宽度可以拖动修改。提供后才产出改宽把手。 | |
children | TableColumnDef<TRow>[] | 表头分组:给了 children 即为分组列,只在表头占一格、横跨它全部叶子列,不进列号空间, 也不承载数据、排序、改宽与冻结。叶子列才是生效列;嵌套几层表头就有几行, 较浅的叶子列(含前缀列)的列头纵向跨到最后一行。分组内的叶子列都要给出宽度, 分组那一格的宽度才能按叶子列之和算准。 | |
reorderable | boolean | 该列可以拖动换位。提供后才产出拖拽把手:每个把手都是一个 Tab 位, 未声明的表格不承担该代价。 不可拖动的列与冻结列一样是屏障:跨过它落下会把它挤走,而作者已声明该列不动。 | |
align | 'start' | 'center' | 'end' | 列内容的对齐方向,写为列头与数据格上的 data-align。 不提供时由皮肤决定(默认 start)。 | |
rowHeader | boolean | ((row: TableRowDef, rowIndex: number) => boolean) | 行表头列:表体与脚注里这一列的格子写 role="rowheader",读屏报某一格时连着念出它所在行的表头 (交叉表的行维度、分组标签、合计行的标题格)。列头不受影响,仍是 columnheader;合并格的起点跟着所在的列走。 给函数时按行决定:表体里这一列的格子问一次,入参是行定义与它在可见数据行里的序号(0 起算,与 cellSpan 的 rowIndex 相同),返回 true 才报 rowheader。分组报表只让组头行与小计行的分组格报行表头,明细行照旧是 gridcell。 表尾格子没有行定义,函数不调用,按行表头处理。 | |
compare | (a: TableRecordOf<TRow>, b: TableRecordOf<TRow>) => number | 数据驱动模式(给了 data)下这一列的升序比较,两条记录比大小:负数排前、正数排后。降序由表格倒过来用。 不给时按这一列的字段值比:数字与日期按大小,其余按字符串(数字串按数值序),空值无论升降都排最后。 只在列声明了 sortable 时用到。 | |
filterOptions | TableFilterOption[] | 数据驱动模式下的筛选项:给了它,列头铺一颗筛选钮,点开是一组勾选项;勾了几项就只留下命中的行, 一项都不勾即不筛。勾选值存在 filterValue 里,按列 id 分开。 | |
filter | (record: TableRecordOf<TRow>, values: readonly string[]) => boolean | 数据驱动模式下一条记录是否命中这一列勾选的筛选值(values 至少一项)。 不给时把这一列的字段值转成字符串,在 values 里即命中;字段是数组时有一项在里面就算。 | |
format | (value: TValue, record: TableRecordOf<TRow>) => string | 数据驱动模式下格子里显示的文字,value 是这一列的字段值(不读字段的列恒为 undefined)。 不给时把字段值转成字符串,空值显示为空。要放图标、徽标这类内容时改用 cell 插槽。 |
TableRowDef
rows 的元素。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 是 | 全表唯一:DOM 身份(data-value)、选中 / 展开集合的元素、连接层查询行的键。 |
disabled | boolean | 行禁用:不可选中也不可展开,但仍可聚焦、仍是方向键的起点,也不计入全选基数。 | |
expandable | boolean | 可展开:提供后才报告 aria-expanded,左右方向键与展开把手也才识别该行。 只要有一行提供,root 就从 role=grid 改为 role=treegrid。 有子行的行不提供也可展开,展开的是它的子行。 | |
parentId | string | 父行 id。提供后该行即为该父行的子行,收起父行时它随之隐藏。 有子行的行不再产出详情行:一行不可能同时既展开出子行、又展开出一块详情。 指向不存在的父行时按根行处理,不丢弃该行。 |
TableSortDescriptor
sort 的元素。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 是 | |
direction | TableSortDirection | 是 |
事件
自定义元素将载荷放在 detail;Vue 使用同名 emit。
| 事件 | 载荷 | 说明 |
|---|---|---|
sort-change | TableSortChangeDetails | 排序链变化;detail 为 { value: { id, direction }[] } |
filter-value-change | TableFilterValueChangeDetails | 筛选值变化(数据驱动模式);detail 为 { value: Record<列 id, string[]> } |
page-change | TablePageChangeDetails | 页码变化(数据驱动模式开了分页);detail 为 { value: number } |
column-preference-change | TableFooterCellSpanDetails | 列偏好变化;detail 为 { value: TableColumnPreference } |
selection-change | TableSelectionChangeDetails | 选中集合变化;detail 为 { value: string[] | 'all' } |
expanded-value-change | TableExpandedValueChangeDetails | 展开集合变化;detail 为 { value: string[] } |
action | TableActionDetails | 行主操作:点在数据行上或焦点在行上按 Enter;detail 为 { value, column },value 是行 id,column 是点中那一格的列 id、键盘触发时为 null |
row-move | TableRowMoveDetails | 行换位;detail 为 { id, parent, index, ids },parent 为 null 即根层,index 是在该层的落位(已经过先移除后插入的修正),ids 是重排后的整份行序 |
插槽
仅列出带载荷的插槽。
| Vue 组件 | 插槽 | 载荷 | 说明 |
|---|---|---|---|
XhTableRoot | default | TableRootSlotProps | |
XhTableRoot | toolbar | TableToolbarSlotProps | 工具条槽:搜索、筛选、密度与列设置等作用于整张表的控件写在这里。 它渲染为 root 的兄弟排在表前:root 是 grid 系角色,子节点只能是 row 与 rowgroup。 |
XhTableRoot | cell | TableCellSlotData | 标准结构里一格的内容:参数里有记录、字段值与本来要显示的文字,别的列交出 text 即可。 |
XhTableRoot | empty | — | 标准结构里空态占位的内容。 |
React 适配器 props
只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。
| React 组件 | 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
XhTableCell | value | string | 是 | 列 id。 |
XhTableCell | colspan | number | string | 跨列数,从 value 所在列向后计算。 | |
XhTableColumnHeader | value | string | 是 | |
XhTableColumnHeader | level | number | string | 多级表头里这一格所在的表头行;省略时取所在表头行写明的层号。 | |
XhTableColumnVisibilityTrigger | value | string | 列 id。写在列设置区中时必须提供;写在列标题中时可省略,跟随该列。 | |
XhTableExpandedRow | value | string | 是 | 所属数据行的 id。 |
XhTableRoot | virtual | boolean | 标准结构的虚拟化开关:表体只铺滚到的那一屏行,滚的是表格根节点(吸顶表头、冻结列与行在同一个滚动面里)。 Virtualizer 那一块开了才加载;给了 children 时不用它,照旧自己套 Virtualizer。 | |
XhTableRoot | estimateSize | number | 开了 virtual 时数据行的预估高度(px),缺省 40;还没量到的行按已量到的平均高度估。 | |
XhTableRoot | renderCell | (cell: TableCellSlotData<TRow>) => ReactNode | 标准结构里一格的内容:参数里有记录、字段值与本来要显示的文字,别的列交出 text 即可。 | |
XhTableRoot | renderEmpty | () => ReactNode | 标准结构里空态占位的内容。 | |
XhTableRoot | toolbar | SlotChildren<TableToolbarSlotProps> | 工具条槽:搜索、筛选、密度与列设置等作用于整张表的控件写在这里。 它渲染为 root 的兄弟排在表前:root 是 grid 系角色,子节点只能是 row 与 rowgroup。 | |
XhTableRoot | children | SlotChildren<TableRootSlotProps> | ||
XhTableRow | value | string | 行 id:数据行必须提供,表头行与脚注行省略。 | |
XhTableRow | level | number | string | 多级表头写明这是第几行表头、多行表尾写明这是第几行表尾(1 起算),行里的列头与表尾格子据此定位;只有一行时省略。 |
状态
公开状态写入 data-state。
| 部件 | 取值 |
|---|---|
row | 'open' | 'closed' | undefined |
column-visibility-trigger | 'unchecked' | 'checked' |
select-all-trigger | tableSelectionState([...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() 返回对应部件的宿主属性。
| 成员 | 类型 | 说明 |
|---|---|---|
columns | readonly TableColumn[] | 生效的列:前缀列在前、数据列在后,各自声明 kind。 列号、渲染顺序都以它为准;不需要前缀列时它与作者提供的一致。 |
draggableColumns | readonly string[] | 可以拖动换位的列 id 段。声明了 reorderable、不是冻结列、且彼此相连。 冻结列与不可拖动的列是屏障,把可拖动范围切分为段;这里给出的是最长的一段。 用它决定是否渲染把手,与库内部判定能否落下的口径是同一份。 |
rowReorderDisabledReason | TableRowReorderReason | null | 行不可拖动的原因,可拖动时为 null。声明了 rowReorderable 才可能非空。 库不自行弹出提示:是否把原因显示给用户由使用者决定。 |
dropTarget | TableDropTarget | null | 当前的落点;松手即落在此处。没有合法落点时为 null,指示线随之消失。 |
announcement | string | 读屏播报文本。渲染进 live-region,不进入视觉版面。 |
rows | readonly TableRowDef[] | 作者提供的行定义。 |
visibleRows | readonly TableVisibleRow[] | 展开展平后的可见行序列(详情行插在所属数据行之后)。 |
sort | TableSortDescriptor[] | |
dataDriven | boolean | 给了 data,表格自己算排序、筛选与分页。 |
records | readonly TableRecord[] | 数据驱动模式下当前页的记录,排过序、筛过,与表体的数据行一一对应;手写结构时为空数组。 |
recordCount | number | 数据驱动模式下筛过之后的总条数,分页按它算页数;手写结构时为可见数据行的条数。 |
recordOf | (rowId: string) => TableRecord | undefined | 一行的记录(整份 data 里的,不止当前页);手写结构时恒为 undefined。 |
cellText | (rowId: string, columnId: string) => string | 某一行某一列格子里显示的文字:列的 format,没有就取字段值转字符串;手写结构时为空串。 |
filterValue | TableFilterValue | |
setFilter | (columnId: string, values: string[]) => void | 改写一列的筛选值,空数组即这一列不筛;随之回到第 1 页。 |
paginated | boolean | 数据驱动模式开了 pagination:表格按页切片,表后铺分页。 |
page | number | 当前页码:开了分页时已夹到有效范围;否则为 page 的原值,缺省 1。 |
pageSize | number | 每页条数:开了分页时缺省 10;否则为 pageSize 的原值,没给时为 0。 |
pageCount | number | 总页数:开了分页时按筛过的总条数算,至少 1;否则为 1。 |
setPage | (page: number) => void | |
translations | TableTranslations | 解析后的文案:排序钮、筛选钮的名字,空态与在途占位的字。 |
selection | TableSelection | |
selectionState | TableSelectionState | 全选把手的三态,只按可选行(未禁用)计算。 |
selectionMode | TableSelectionMode | |
expandedValue | string[] | |
focusedRow | string | null | 焦点锚点;焦点不在表体中时为 null。 |
focusedColumn | string | null | navigation 为 cell 时锚点所在的列,焦点在行上或不在表体中时为 null。 |
navigation | TableNavigation | 表体的键盘导航粒度。 |
loading | boolean | |
empty | boolean | 表体为空(显式声明或 rows 为空)。 |
rowCount | number | aria-rowcount:表头行 + 可见行 + 表尾行。 |
footerRowCount | number | 表尾占几行,即 footerRows 取整后的值。 |
columnCount | number | aria-colcount:列定义的条数。 |
headerRows | readonly (readonly TableHeaderCell[])[] | 表头按层排好的格子,每层一行;没有分组时只有一行、每格是一列。 多级表头按它逐层渲染表头行与列头(带上 level),占位格也要渲。 |
headerRowCount | number | 表头占几行,数据行的行号从它之后起算。 |
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 | 该行显示的序号。平表是分页全局序号,树形是大纲编号。 不显示序号列时仍可调用:它是纯计算,不依赖是否有该列。 |
columnPreference | TableColumnPreference | 当前的列偏好。原样交出即可存储。 |
columnSettings | readonly 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 / Space | held on 可按部件, 未加载且部件自身未禁用 | 按住期间行、全选 / 行选 / 展开 / 列显隐把手、排序把手或取下一页按钮投影 data-pressed,与指针 :active 同一副按压面;抬起或失焦撤下,取数在途由机器撤下。选中、排序与展开语义照旧由这一次按键承担 |
Tab / Shift+Tab | focus outside the table body | 表体只占一个 Tab 位:焦点进入锚点行,无锚点时先落 body 再由它转投;再按一次 Tab 整体离开表体 |
ArrowDown | focus in table body | 焦点移到下一个可见数据行(禁用行跳过;详情行不是落点;loop 默认关,末行不回绕) |
ArrowUp | focus in table body | 焦点移到上一个可见数据行(禁用行跳过;loop 默认关,首行不回绕) |
Home | focus in table body | 焦点移到首个可见数据行 |
End | focus in table body | 焦点移到末个可见数据行 |
Enter | focus on row, 该行未禁用 | 触发行主操作(onAction,column 为 null),用来下钻或联动;焦点停在行内控件上时 Enter 归那个控件,选中仍归 Space |
Space | focus on row, selectionMode 非 none 且该行未禁用 | 切换焦点行的选中(单选替换、复选增删);选不动时不吞这个键,页面照常滚动 |
ArrowRight | focus on 可展开且收起的行(dir=rtl 时改由 ArrowLeft 承担) | 就地展开当前行,焦点不动;不可展开、已展开或禁用的行上什么都不做且不吞键 |
ArrowLeft | focus on 可展开且已展开的行(dir=rtl 时改由 ArrowRight 承担) | 就地收起当前行,焦点不动;其余情形什么都不做且不吞键 |
Enter / Space | focus on sort-trigger, 该列 sortable | 排序方向按 升序 → 降序 → 不排序 循环;按住 Shift 是追加到排序链而不是替换整条链 |
Enter / Space | focus on select-all-trigger, selectionMode=multiple | 当前可选行全选中就整段清空,否则整段选上;三态由 aria-checked 报出(半选为 mixed) |
Enter / Space | focus on column-visibility-trigger | 藏起 / 放出这一列;设置区不是 roving 集合,一列一个 Tab 位,Tab 一路走下去即可逐列开关。只剩最后一列显示着时它转 aria-disabled,按了不动 |
Ctrl+A / Cmd+A | focus in table body, selectionMode=multiple | 与全选把手同义:当前可选行全选中就整段清空,否则整段选上(禁用行不算进基数)。单选与不可选的表格不吞这个键,交还浏览器的整页全选;按住不放的连发只算一次 |
ArrowLeft / ArrowRight | focus in column-resize-trigger,该列 resizable | 把这一列按 8px 收窄 / 加宽;往行尾侧推是加宽,rtl 下左右两键对调,语义恒是「加宽 / 收窄」 |
Shift+ArrowLeft / Shift+ArrowRight | focus in column-resize-trigger,该列 resizable | 按 40px 收窄 / 加宽,方向规则同上 |
ArrowLeft / ArrowRight | focus in column-drag-trigger,该列在可拖的那一段里 | 把这一列往前 / 往后挪一位,按一下就是一次完整提交;往行首侧挪是往前,rtl 下左右两键对调,语义恒是「往前 / 往后」;已在段首 / 段末就不动,也不回绕 |
Home / End | focus in column-drag-trigger,该列在可拖的那一段里 | 把这一列挪到可拖那一段的段首 / 段末;rtl 下两键对调,语义恒是「段首 / 段末」;已经在那儿就不动 |
ArrowRight | navigation=cell,focus on 已展开或不可展开的行(dir=rtl 时改由 ArrowLeft 承担) | 焦点进到这一行的首格;收起的可展开行上仍是展开 |
ArrowLeft | navigation=cell,focus on 收起或不可展开、且有父行的行(dir=rtl 时改由 ArrowRight 承担) | 焦点回到父行;已展开的行上仍是收起,根层的行上什么都不做且不吞键 |
ArrowRight / ArrowLeft | navigation=cell,focus on 表体格子 | 焦点在这一行的格子之间走一格;已在末格就停住,首格再往回走一格回到行;被合并掉的格子跳过。rtl 下两键对调,语义恒是「下一格 / 上一格」 |
ArrowDown / ArrowUp | navigation=cell,focus on 表体格子 | 焦点移到下一个 / 上一个可见数据行的同一列(禁用行跳过,loop 默认关);那一行的这一列被合并掉时落到行上 |
Home / End | navigation=cell,focus on 表体格子 | 焦点移到本行的首格 / 末格 |
Ctrl+Home / Ctrl+End | navigation=cell,focus in table body | 焦点在格子上时移到本列的首行 / 末行,在行上时移到首行 / 末行 |
Enter / F2 | navigation=cell,focus on 表体格子 | 放开这一格的控件、焦点交给其中第一个(格内编辑),编辑时方向键等按键都归控件;格内没有控件时 Enter 触发行主操作(onAction,column 是这一格的列 id),F2 无事可做 |
Tab / Shift+Tab | navigation=cell,focus in 表体格子里的控件 | 只在这一格的控件之间走,到头回绕,不离开这一格;格里只有一个控件时停在它上面;控件自己用掉了 Tab 时不管 |
Escape / F2 | navigation=cell,focus in 表体格子里的控件 | 退出格内编辑,焦点回到这一格;控件自己用掉了 Escape(比如收起它的下拉)时不退出 |
Alt+ArrowUp / Alt+ArrowDown | focus in table body,rowReorderable 且行拖拽没有被阻断的原因 | 把焦点行往前 / 往后挪一位,按一下就是一次完整提交,不进拖动态;纵轴与文字方向无关,rtl 下两键不对调;已在首行 / 末行就不动,也不回绕;焦点锚点跟着搬走的那一行,连按几下能一路挪到位。裸方向键仍是导航、Space 仍是选中、左右键仍是展开收起 |
Alt+ArrowLeft / Alt+ArrowRight | focus in table body,声明了 tree 或 rows 里有行声明了 parentId,rowReorderable 且行拖拽没有被阻断的原因 | 把焦点行改一层缩进:往里是认上一个兄弟当爹,往外是变成父行的下一个兄弟;按一下就是一次完整提交,不进拖动态;横轴跟着文字方向翻,rtl 下两键对调,语义恒是「往里 / 往外」;没有上一个兄弟就缩不进去、已在根层就退不出来,两种情形都不动。没声明 tree、rows 里也一行都不带 parentId 时这两个键不归表格管,放行给页面 |
ARIA
以下属性由 connect 生成。
| 部件 | 属性 | 值 |
|---|---|---|
root | aria-busy | 'true' | undefined |
root | aria-colcount | columns.length || undefined |
root | aria-labelledby | caption 部件的 id | undefined |
root | aria-multiselectable | 'true' | 'false' |
root | aria-rowcount | headerRowCount + visibleRows.length + footerRowCount |
root | role | 'treegrid' | 'grid' |
header | role | 'rowgroup' |
body | role | 'rowgroup' |
footer | role | 'rowgroup' |
row | aria-controls | detail 部件的 id | undefined |
row | aria-disabled | 'true' | 'false' |
row | aria-expanded | 'true' | 'false' | undefined |
row | aria-level | metaIndex.get(row.value)?.level | undefined |
row | aria-posinset | metaIndex.get(row.value)?.posInSet | undefined |
row | aria-rowindex | dataRowIndex.get(row.value) |
row | aria-selected | 'true' | 'false' | undefined |
row | aria-setsize | metaIndex.get(row.value)?.setSize | undefined |
row | role | 'row' |
column-header | aria-colindex | cell.colIndex |
column-header | aria-colspan | cell.colSpan | undefined |
column-header | aria-hidden | 'true' |
column-header | aria-rowspan | cell.rowSpan | undefined |
column-header | aria-sort | 'ascending' | 'descending' | 'none' | undefined |
column-header | role | 'columnheader' |
cell | aria-colindex | columnIndex.get(cell.value) |
cell | aria-colspan | Math.max(cell.colSpan ?? 1, origin?.colSpan ?? 1) | undefined |
cell | aria-hidden | 'true' |
cell | aria-rowspan | origin.rowSpan | undefined |
cell | role | 'rowheader' | 'gridcell' |
toolbar | aria-label | label.toolbar |
column-list | aria-label | label.columnList |
column-list | role | 'group' |
column-visibility-trigger | aria-checked | 'false' | 'true' |
column-visibility-trigger | aria-disabled | 'false' | 'true' |
column-visibility-trigger | aria-label | label.columnVisibility(def?.label ?? column.value) |
column-visibility-trigger | role | 'checkbox' |
select-all-trigger | aria-checked | 'true' | 'mixed' | 'false' |
select-all-trigger | aria-disabled | 'false' | 'true' |
select-all-trigger | aria-label | label.selectAll |
select-all-trigger | role | 'checkbox' |
row-select-trigger | aria-hidden | 'true' |
sort-trigger | aria-disabled | 'false' | 'true' |
sort-trigger | aria-label | label.sort(def?.label ?? column.value) |
sort-trigger | role | 'button' |
column-resize-trigger | aria-disabled | 'false' | 'true' |
column-resize-trigger | aria-label | label.columnResize(def?.label ?? column.value) |
column-resize-trigger | aria-orientation | 'vertical' |
column-resize-trigger | aria-valuemax | def?.maxWidth | undefined |
column-resize-trigger | aria-valuemin | def?.minWidth | undefined |
column-resize-trigger | aria-valuenow | columnNumericWidth(context.get('columnPreference').wi… |
column-resize-trigger | role | 'separator' |
column-drag-trigger | aria-disabled | 'false' | 'true' |
column-drag-trigger | aria-label | label.columnDrag(def?.label ?? column.value) |
column-drag-trigger | aria-roledescription | label.columnDragRoleDescription |
column-drag-trigger | role | 'button' |
row-drag-trigger | aria-hidden | 'true' |
expand-trigger | aria-hidden | 'true' |
expanded-row | aria-level | (metaIndex.get(row.value)?.level ?? 1) + 1 | undefined |
expanded-row | aria-posinset | 1 | undefined |
expanded-row | aria-rowindex | detailRowIndex.get(row.value) |
expanded-row | aria-setsize | 1 | undefined |
expanded-row | role | 'row' |
live-region | aria-atomic | 'true' |
live-region | aria-live | 'polite' |
live-region | role | 'status' |
header-row | aria-rowindex | Math.max(1, Math.min(Math.trunc(header?.level ?? 1) |… |
footer-row | aria-rowindex | headerRowCount + visibleRows.length + level | undefined |
header-row | role | 'row' |
footer-row | role | '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 生成;条件不成立时不输出无值属性。
| 部件 | 属性 | 值 |
|---|---|---|
root | data-at-max-horizontal | ''(条件成立时才出现) |
root | data-at-min-horizontal | ''(条件成立时才出现) |
root | data-empty | ''(条件成立时才出现) |
root | data-fixed | ''(条件成立时才出现) |
root | data-loading | ''(条件成立时才出现) |
root | data-size | props.size |
root | data-split | ''(条件成立时才出现) |
root | data-striped | ''(条件成立时才出现) |
root | data-variant | props.variant |
header | data-fixed | ''(条件成立时才出现) |
body | data-empty | ''(条件成立时才出现) |
body | data-instant | ''(条件成立时才出现) |
footer | data-fixed | ''(条件成立时才出现) |
row | data-disabled | ''(条件成立时才出现) |
row | data-draggable | ''(条件成立时才出现) |
row | data-dragging | ''(条件成立时才出现) |
row | data-drop | 'before' | 'after' | 'inside' |
row | data-highlighted | ''(条件成立时才出现) |
row | data-pressed | ''(条件成立时才出现) |
row | data-section | 'body' |
row | data-selected | ''(条件成立时才出现) |
row | data-state | 'open' | 'closed' | undefined |
row | data-xh-collection-context | 'page' |
row | data-xh-collection-item | '' |
row | data-xh-collection-size | props.size |
column-header | data-align | def?.align |
column-header | data-covered | ''(条件成立时才出现) |
column-header | data-dragging | ''(条件成立时才出现) |
column-header | data-drop | 'before' | 'after' |
column-header | data-filtered | ''(条件成立时才出现) |
column-header | data-frozen | undefined |
column-header | data-frozen-edge | ''(条件成立时才出现) |
column-header | data-group | ''(条件成立时才出现) |
column-header | data-row-span | ''(条件成立时才出现) |
column-header | data-sort | 'asc' | 'desc' |
column-header | data-sort-index | tableSortIndexOf(sort, value) | undefined |
column-header | data-sortable | ''(条件成立时才出现) |
cell | data-align | def?.align |
cell | data-covered | ''(条件成立时才出现) |
cell | data-disabled | ''(条件成立时才出现) | undefined |
cell | data-dragging | ''(条件成立时才出现) |
cell | data-drop | 'before' | 'after' |
cell | data-frozen | undefined |
cell | data-frozen-edge | ''(条件成立时才出现) |
cell | data-row-span | ''(条件成立时才出现) |
cell | data-selected | ''(条件成立时才出现) | undefined |
toolbar | data-size | props.size |
column-list | data-size | props.size |
column-visibility-trigger | data-disabled | ''(条件成立时才出现) |
column-visibility-trigger | data-pressed | ''(条件成立时才出现) |
column-visibility-trigger | data-state | 'unchecked' | 'checked' |
column-visibility-trigger | data-xh-action-control | '' |
column-visibility-trigger | data-xh-action-display | 'always' |
column-visibility-trigger | data-xh-action-profile | 'icon' |
column-visibility-trigger | data-xh-action-size | props.size |
column-visibility-trigger | data-xh-action-variant | 'outline' |
column-visibility-trigger | data-xh-check-mark | 'unchecked' | 'checked' |
column-visibility-trigger | data-xh-check-mark-profile | 'box' |
select-all-trigger | data-disabled | ''(条件成立时才出现) |
select-all-trigger | data-pressed | ''(条件成立时才出现) |
select-all-trigger | data-state | tableSelectionState([...cascaded.checked], tableCasca… | tableSelectionState(selection, selectableIds) |
select-all-trigger | data-xh-action-control | '' |
select-all-trigger | data-xh-action-display | 'always' |
select-all-trigger | data-xh-action-profile | 'icon' |
select-all-trigger | data-xh-action-size | props.size |
select-all-trigger | data-xh-action-variant | 'outline' |
select-all-trigger | data-xh-check-mark | tableSelectionState([...cascaded.checked], tableCasca… | tableSelectionState(selection, selectableIds) |
select-all-trigger | data-xh-check-mark-profile | 'box' |
row-select-trigger | data-disabled | ''(条件成立时才出现) |
row-select-trigger | data-highlighted | ''(条件成立时才出现) |
row-select-trigger | data-indeterminate | ''(条件成立时才出现) |
row-select-trigger | data-pressed | ''(条件成立时才出现) |
row-select-trigger | data-selected | ''(条件成立时才出现) |
row-select-trigger | data-state | 'open' | 'closed' | undefined |
row-select-trigger | data-xh-action-control | '' |
row-select-trigger | data-xh-action-display | 'always' |
row-select-trigger | data-xh-action-profile | 'icon' |
row-select-trigger | data-xh-action-size | props.size |
row-select-trigger | data-xh-action-variant | 'outline' |
row-select-trigger | data-xh-check-mark | 'checked' | 'indeterminate' | 'unchecked' |
row-select-trigger | data-xh-check-mark-profile | 'box' |
sort-trigger | data-disabled | ''(条件成立时才出现) |
sort-trigger | data-pressed | ''(条件成立时才出现) |
sort-trigger | data-sort | 'asc' | 'desc' |
sort-trigger | data-sort-index | tableSortIndexOf(sort, value) | undefined |
sort-trigger | data-xh-action-control | '' |
sort-trigger | data-xh-action-display | 'always' |
sort-trigger | data-xh-action-profile | 'icon' |
sort-trigger | data-xh-action-size | props.size |
sort-trigger | data-xh-action-variant | 'ghost' |
column-resize-trigger | data-disabled | ''(条件成立时才出现) |
column-resize-trigger | data-resizing | ''(条件成立时才出现) |
column-drag-trigger | data-disabled | ''(条件成立时才出现) |
column-drag-trigger | data-dragging | ''(条件成立时才出现) |
row-drag-trigger | data-disabled | ''(条件成立时才出现) |
row-drag-trigger | data-dragging | ''(条件成立时才出现) |
expand-trigger | data-disabled | ''(条件成立时才出现) |
expand-trigger | data-highlighted | ''(条件成立时才出现) |
expand-trigger | data-pressed | ''(条件成立时才出现) |
expand-trigger | data-selected | ''(条件成立时才出现) |
expand-trigger | data-state | 'open' | 'closed' | undefined |
expand-trigger | data-xh-action-control | '' |
expand-trigger | data-xh-action-display | 'always' |
expand-trigger | data-xh-action-profile | 'icon' |
expand-trigger | data-xh-action-size | props.size |
expand-trigger | data-xh-action-variant | 'ghost' |
expanded-row | data-dragging | ''(条件成立时才出现) |
expanded-row | data-state | 'open' | 'closed' |
loading | data-loading | ''(条件成立时才出现) |
loading | data-xh-loading-ring | '' |
load-more-trigger | data-loading | ''(条件成立时才出现) |
load-more-trigger | data-pressed | ''(条件成立时才出现) |
load-more-trigger | data-xh-action-control | '' |
load-more-trigger | data-xh-action-display | 'always' |
load-more-trigger | data-xh-action-profile | 'row' |
load-more-trigger | data-xh-action-size | props.size |
load-more-trigger | data-xh-action-variant | 'ghost' |
header-row | data-section | 'header' |
footer-row | data-section | 'footer' |
CSS 变量
本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。
| 变量 | 部件 | CSS 属性 | 状态 | 默认来源 | 说明 |
|---|---|---|---|---|---|
--xh-table-bg | root | background | defaultvariant=outlinevariant=subtle | --xh-bg-subtle--xh-bg-surface | table 的 root 部件 background 覆盖槽。 |
--xh-table-border | root | border | variant=outline | --xh-border-default | table 的 root 部件 border 覆盖槽。 |
--xh-table-caption-fg | caption | color | default | --xh-fg-muted | table 的 caption 部件 color 覆盖槽。 |
--xh-table-caption-font-size | caption | font-size | default | --xh-text-label-size | table 的 caption 部件 font-size 覆盖槽。 |
--xh-table-caption-font-weight | caption | font-weight | default | --xh-text-label-weight | table 的 caption 部件 font-weight 覆盖槽。 |
--xh-table-caption-px | caption | padding-inline | default | --xh-space-3 | table 的 caption 部件 padding-inline 覆盖槽。 |
--xh-table-caption-py | caption | padding-block | default | --xh-space-2 | table 的 caption 部件 padding-block 覆盖槽。 |
--xh-table-cell-gap | cellcolumn-header | gap | default | --xh-control-gap-md | table 的 cell、column-header 部件 gap 覆盖槽。 |
--xh-table-cell-min-w | cellcolumn-header | min-inline-size | default | 3rem | table 的 cell、column-header 部件 min-inline-size 覆盖槽。 |
--xh-table-cell-px | cellcolumn-headerroot | padding-inlinepadding-inline-end | defaultnot(:last-child)split | --xh-_table-cell-px | table 的 cell、column-header、root 部件 padding-inline、padding-inline-end 覆盖槽。 |
--xh-table-cell-py | cellcolumn-header | padding-block | default | --xh-_table-cell-py | table 的 cell、column-header 部件 padding-block 覆盖槽。 |
--xh-table-column-fg | column-header | color | default | --xh-fg-default | table 的 column-header 部件 color 覆盖槽。 |
--xh-table-column-font-weight | column-header | font-weight | default | --xh-font-weight-medium | table 的 column-header 部件 font-weight 覆盖槽。 |
--xh-table-column-list-fg | column-list | color | default | --xh-fg-default | table 的 column-list 部件 color 覆盖槽。 |
--xh-table-column-list-font-size | column-list | font-size | default | --xh-text-secondary-size | table 的 column-list 部件 font-size 覆盖槽。 |
--xh-table-column-list-gap | column-list | gap | default | --xh-_table-column-list-gap | table 的 column-list 部件 gap 覆盖槽。 |
--xh-table-detail-bg | expanded-row | background | default | --xh-bg-subtle | table 的 expanded-row 部件 background 覆盖槽。 |
--xh-table-detail-px | cellexpanded-row | padding-inline | default | --xh-_table-cell-px | table 的 cell、expanded-row 部件 padding-inline 覆盖槽。 |
--xh-table-detail-py | cellexpanded-row | padding-block | default | --xh-space-3 | table 的 cell、expanded-row 部件 padding-block 覆盖槽。 |
--xh-table-drag-fg | column-drag-trigger | color | default | --xh-fg-subtle | table 的 column-drag-trigger 部件 color 覆盖槽。 |
--xh-table-drag-fg-active | column-drag-trigger | color | disableddragginghovernot([data-disabled]) | --xh-fg-default | table 的 column-drag-trigger 部件 color 覆盖槽。 |
--xh-table-drag-fg-disabled | column-drag-trigger | color | disabled | --xh-fg-disabled | table 的 column-drag-trigger 部件 color 覆盖槽。 |
--xh-table-drag-grip-h | column-drag-trigger | block-size | empty | --xh-space-2 | table 的 column-drag-trigger 部件 block-size 覆盖槽。 |
--xh-table-drag-grip-w | column-drag-trigger | inline-size | empty | --xh-space-1 | table 的 column-drag-trigger 部件 inline-size 覆盖槽。 |
--xh-table-drag-size | column-drag-trigger | block-sizeinline-size | default | --xh-control-indicator-size | table 的 column-drag-trigger 部件 block-size、inline-size 覆盖槽。 |
--xh-table-dragging-opacity | cellcolumn-headerexpanded-rowrow | opacity | dragging | --xh-state-dragging-opacity | table 的 cell、column-header、expanded-row、row 部件 opacity 覆盖槽。 |
--xh-table-drop-fg | cellcolumn-headerrow | backgroundbox-shadow | dropdrop=afterdrop=beforedrop=insidefrozenis([data-drop='before'], [data-drop='after'])not([data-frozen]) | --xh-bg-brand | table 的 cell、column-header、row 部件 background、box-shadow 覆盖槽。 |
--xh-table-drop-inside-bg | bodyrootrow | background | disableddrop=insidenot([data-disabled]) | --xh-bg-subtle-hover-opaque | table 的 body、root、row 部件 background 覆盖槽。 |
--xh-table-drop-line | cellcolumn-headerrow | block-sizebox-shadowinline-size | dropdrop=afterdrop=beforedrop=insidefrozenis([data-drop='before'], [data-drop='after'])not([data-frozen]) | --xh-stroke-thick | table 的 cell、column-header、row 部件 block-size、box-shadow、inline-size 覆盖槽。 |
--xh-table-expand-fg | expand-trigger | color | defaultdisabledfocus-visiblehoveris(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-fg-subtle | table 的 expand-trigger 部件 color 覆盖槽。 |
--xh-table-fg | root | color | default | --xh-fg-default | table 的 root 部件 color 覆盖槽。 |
--xh-table-filter-fg | column-headertrigger | color | default | --xh-fg-subtle | table 的 column-header、trigger 部件 color 覆盖槽。 |
--xh-table-filter-fg-active | column-headertrigger | color | filtered | --xh-fg-brand | table 的 column-header、trigger 部件 color 覆盖槽。 |
--xh-table-filter-size | column-headertrigger | --xh-icon-size | default | --xh-_table-trigger-size | table 的 column-header、trigger 部件 --xh-icon-size 覆盖槽。 |
--xh-table-font-size | rootrow | font-size | default | --xh-_table-font-size | table 的 root、row 部件 font-size 覆盖槽。 |
--xh-table-footer-bg | cellfooterrow | background | defaultfixedfrozen | --xh-bg-subtle--xh-bg-subtle-opaque | table 的 cell、footer、row 部件 background 覆盖槽。 |
--xh-table-footer-border | footer | border-block-start | default | --xh-border-subtle | table 的 footer 部件 border-block-start 覆盖槽。 |
--xh-table-footer-font-weight | footer | font-weight | default | --xh-font-weight-medium | table 的 footer 部件 font-weight 覆盖槽。 |
--xh-table-frozen-edge | cellcolumn-header | background | frozen-edgeis([data-part='column-header'], [data-part='cell']) | --xh-border-default | table 的 cell、column-header 部件 background 覆盖槽。 |
--xh-table-header-bg | column-headerheader | background | defaultfrozen | --xh-_table-header-bg | table 的 column-header、header 部件 background 覆盖槽。 |
--xh-table-header-border | header | border-block-end | default | --xh-border-subtle | table 的 header 部件 border-block-end 覆盖槽。 |
--xh-table-icon-size | root | --xh-icon-size | defaultsize=lgsize=sm | --xh-glyph-size-lg--xh-glyph-size-md--xh-glyph-size-sm | table 的 root 部件 --xh-icon-size 覆盖槽。 |
--xh-table-load-more-trigger-bg-hover | load-more-trigger | background-color | disabledhoverloadingnot([data-disabled])not([data-loading]) | --xh-_action-variant-bg-hover | table 的 load-more-trigger 部件 background-color 覆盖槽。 |
--xh-table-load-more-trigger-fg | load-more-trigger | color | defaultdisabledhoveris(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-fg-brand-strong | table 的 load-more-trigger 部件 color 覆盖槽。 |
--xh-table-load-more-trigger-font-size | load-more-trigger | font-size | default | --xh-_table-font-size | table 的 load-more-trigger 部件 font-size 覆盖槽。 |
--xh-table-load-more-trigger-gap | load-more-trigger | gap | default | --xh-space-2 | table 的 load-more-trigger 部件 gap 覆盖槽。 |
--xh-table-load-more-trigger-px | load-more-trigger | padding-inline | default | --xh-space-4 | table 的 load-more-trigger 部件 padding-inline 覆盖槽。 |
--xh-table-load-more-trigger-py | load-more-trigger | padding-block | xh-action-profile=row | --xh-space-3 | table 的 load-more-trigger 部件 padding-block 覆盖槽。 |
--xh-table-load-more-trigger-radius | load-more-trigger | border-radius | default | --xh-shape-control | table 的 load-more-trigger 部件 border-radius 覆盖槽。 |
--xh-table-loading-duration | loading | animation-duration | default | --xh-motion-loop-spin | table 的 loading 部件 animation-duration 覆盖槽。 |
--xh-table-max-h | root | max-block-size | default | --xh-viewport-h-lg | table 的 root 部件 max-block-size 覆盖槽。 |
--xh-table-pagination-gap | root | margin-block-start | default | --xh-space-3 | table 的 root 部件 margin-block-start 覆盖槽。 |
--xh-table-radius | root | border-radius | variant=outlinevariant=subtle | --xh-shape-surface | table 的 root 部件 border-radius 覆盖槽。 |
--xh-table-resize-fg | column-resize-trigger | background | default | --xh-border-default | table 的 column-resize-trigger 部件 background 覆盖槽。 |
--xh-table-resize-fg-active | column-resize-trigger | background | hoverresizing | --xh-bg-brand | table 的 column-resize-trigger 部件 background 覆盖槽。 |
--xh-table-resize-line | column-resize-trigger | inline-size | default | --xh-stroke-thin | table 的 column-resize-trigger 部件 inline-size 覆盖槽。 |
--xh-table-resize-line-length | column-resize-trigger | block-size | default | 60% | table 的 column-resize-trigger 部件 block-size 覆盖槽。 |
--xh-table-resize-radius | column-resize-trigger | border-radius | default | --xh-shape-pill | table 的 column-resize-trigger 部件 border-radius 覆盖槽。 |
--xh-table-resize-width | column-resize-trigger | inline-size | default | --xh-space-2 | table 的 column-resize-trigger 部件 inline-size 覆盖槽。 |
--xh-table-row-bg | row | background-color | default | --xh-bg-surface | table 的 row 部件 background-color 覆盖槽。 |
--xh-table-row-bg-hover | row | background-color | disablederrorhighlightedhoveris(:focus-visible, [data-highlighted])not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error]) | --xh-bg-subtle-opaque | table 的 row 部件 background-color 覆盖槽。 |
--xh-table-row-bg-pressed | row | background-color | disablederroris(:active, [data-pressed])not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])pressed | --xh-bg-subtle-hover-opaque | table 的 row 部件 background-color 覆盖槽。 |
--xh-table-row-bg-selected | row | background-color | disablederroris([aria-selected='true'], [data-selected])not([aria-disabled='true'], [data-disabled], [aria-busy='true'], [data-error])selectedxh-collection-context=page | --xh-bg-brand-subtle | table 的 row 部件 background-color 覆盖槽。 |
--xh-table-row-bg-striped | bodyrootrow | background-color | stripedwhere(:nth-of-type(even)where([data-scope='table'][data-part='root'][data-striped] [data-scope='table'][data-part='body']) | --xh-bg-subtle-opaque | table 的 body、root、row 部件 background-color 覆盖槽。 |
--xh-table-row-border | bodycellcolumn-headerexpanded-rowfooterheaderitemrootrow | border-block-startborder-inline-end | index=0is([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-subtle | table 的 body、cell、column-header、expanded-row、footer、header、item、root、row 部件 border-block-start、border-inline-end 覆盖槽。 |
--xh-table-row-cursor | row | cursor | 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=page | default | table 的 row 部件 cursor 覆盖槽。 |
--xh-table-row-drag-fg | row-drag-trigger | color | default | --xh-fg-subtle | table 的 row-drag-trigger 部件 color 覆盖槽。 |
--xh-table-row-drag-fg-active | row-drag-trigger | color | disableddragginghovernot([data-disabled]) | --xh-fg-default | table 的 row-drag-trigger 部件 color 覆盖槽。 |
--xh-table-row-drag-fg-disabled | row-drag-trigger | color | disabled | --xh-fg-disabled | table 的 row-drag-trigger 部件 color 覆盖槽。 |
--xh-table-row-drag-grip-long | row-drag-trigger | inline-size | empty | --xh-space-2 | table 的 row-drag-trigger 部件 inline-size 覆盖槽。 |
--xh-table-row-drag-grip-short | row-drag-trigger | block-size | empty | --xh-space-1 | table 的 row-drag-trigger 部件 block-size 覆盖槽。 |
--xh-table-row-drag-size | row-drag-trigger | block-sizeinline-size | default | --xh-control-indicator-size | table 的 row-drag-trigger 部件 block-size、inline-size 覆盖槽。 |
--xh-table-row-fg-selected | row | 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=page | --xh-fg-on-brand-subtle | table 的 row 部件 color 覆盖槽。 |
--xh-table-sort-bg-hover | column-headersort-triggertrigger | backgroundbackground-color | disabledhoverloadingnot([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-pressed | sort-trigger | background-color | disabledis(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-_action-variant-bg-pressed | table 的 sort-trigger 部件 background-color 覆盖槽。 |
--xh-table-sort-fg | sort-trigger | color | defaultdisabledfocus-visiblehoveris(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-fg-subtle | table 的 sort-trigger 部件 color 覆盖槽。 |
--xh-table-sort-fg-active | sort-trigger | color | disabledfocus-visiblehoveris(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressedsortsort-index | --xh-fg-default | table 的 sort-trigger 部件 color 覆盖槽。 |
--xh-table-sort-size | sort-trigger | --xh-icon-size | default | --xh-_table-trigger-size | table 的 sort-trigger 部件 --xh-icon-size 覆盖槽。 |
--xh-table-span-layer | cellcolumn-header | z-index | is([data-part='cell'], [data-part='column-header'])row-span | 1 | table 的 cell、column-header 部件 z-index 覆盖槽。 |
--xh-table-state-fg | emptyloading | color | default | --xh-fg-muted | table 的 empty、loading 部件 color 覆盖槽。 |
--xh-table-state-gap | emptyloading | gap | default | --xh-control-gap-md | table 的 empty、loading 部件 gap 覆盖槽。 |
--xh-table-state-min-h | emptyloading | min-block-size | default | 0 | table 的 empty、loading 部件 min-block-size 覆盖槽。 |
--xh-table-state-px | emptyloading | padding-inline | default | --xh-control-px-sm | table 的 empty、loading 部件 padding-inline 覆盖槽。 |
--xh-table-state-py | emptyloading | padding-block | default | --xh-space-3 | table 的 empty、loading 部件 padding-block 覆盖槽。 |
--xh-table-sticky-column-layer | cellcolumn-headerrow | z-index | dropdrop=afterdrop=beforedrop=insidefocus-visiblefrozenis([data-drop='before'], [data-drop='after'])not([data-drop]) | 1 | table 的 cell、column-header、row 部件 z-index 覆盖槽。 |
--xh-table-sticky-footer-layer | footer | z-index | fixed | --xh-layer-sticky | table 的 footer 部件 z-index 覆盖槽。 |
--xh-table-sticky-header-layer | header | z-index | fixed | --xh-layer-sticky | table 的 header 部件 z-index 覆盖槽。 |
--xh-table-sticky-inset | cellcolumn-header | inset-inline-endinset-inline-start | frozen=endfrozen=start | 0 | table 的 cell、column-header 部件 inset-inline-end、inset-inline-start 覆盖槽。 |
--xh-table-toolbar-fg | toolbar | color | default | --xh-fg-default | table 的 toolbar 部件 color 覆盖槽。 |
--xh-table-toolbar-gap | toolbar | gap | default | --xh-_table-toolbar-gap | table 的 toolbar 部件 gap 覆盖槽。 |
--xh-table-toolbar-py | toolbar | padding-block | default | --xh-space-2 | table 的 toolbar 部件 padding-block 覆盖槽。 |
--xh-table-trigger-bg-checked | column-visibility-triggerexpand-triggerrow-select-triggerselect-all-triggersort-trigger | --xh-ink-surfacebackground-colorborderborder-color | disabledfocus-visiblehoverindeterminateis(:active, [data-pressed])is([data-selected], [data-indeterminate])is([data-state='checked'], [data-state='indeterminate'])loadingnot([data-disabled])not([data-loading])pressedselectedstate=checkedstate=indeterminatexh-ink-surface | --xh-bg-brand | table 的 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-pressed | column-visibility-triggerexpand-triggerrow-select-triggerselect-all-triggersort-trigger | background-color | disabledindeterminateis(:active, [data-pressed])is([data-selected], [data-indeterminate])is([data-state='checked'], [data-state='indeterminate'])loadingnot([data-disabled])not([data-loading])pressedselectedstate=checkedstate=indeterminate | --xh-bg-brand-active | table 的 column-visibility-trigger、expand-trigger、row-select-trigger、select-all-trigger、sort-trigger 部件 background-color 覆盖槽。 |
--xh-table-trigger-bg-pressed | column-visibility-triggerexpand-triggerrow-select-triggerselect-all-triggersort-trigger | background-color | disabledis(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-bg-subtle-hover | table 的 column-visibility-trigger、expand-trigger、row-select-trigger、select-all-trigger、sort-trigger 部件 background-color 覆盖槽。 |
--xh-table-trigger-border | column-visibility-triggerexpand-triggerrow-select-triggerselect-all-triggersort-trigger | borderborder-color | defaultdisabledfocus-visiblehoveris(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-border-strong | table 的 column-visibility-trigger、expand-trigger、row-select-trigger、select-all-trigger、sort-trigger 部件 border、border-color 覆盖槽。 |
--xh-table-trigger-border-checked | column-visibility-triggerexpand-triggerrow-select-triggerselect-all-triggersort-trigger | borderborder-color | disabledfocus-visiblehoverindeterminateis(:active, [data-pressed])is([data-selected], [data-indeterminate])is([data-state='checked'], [data-state='indeterminate'])loadingnot([data-disabled])not([data-loading])pressedselectedstate=checkedstate=indeterminate | --xh-table-trigger-bg-checked | table 的 column-visibility-trigger、expand-trigger、row-select-trigger、select-all-trigger、sort-trigger 部件 border、border-color 覆盖槽。 |
--xh-table-trigger-fg | column-visibility-triggerrow-select-triggerselect-all-trigger | color | defaultdisabledfocus-visiblehoveris(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressed | --xh-fg-on-brand | table 的 column-visibility-trigger、row-select-trigger、select-all-trigger 部件 color 覆盖槽。 |
--xh-table-trigger-radius | column-drag-triggercolumn-headercolumn-visibility-triggerexpand-triggerrow-drag-triggerrow-select-triggerselect-all-triggersort-triggertrigger | border-radius | default | --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-size | column-headercolumn-visibility-triggerexpand-triggerrow-select-triggerselect-all-triggersort-triggertrigger | --xh-icon-sizeblock-sizeinline-sizemin-block-sizemin-inline-size | defaultxh-action-profile=iconxh-action-profile=row | --xh-control-indicator-size | table 的 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 分支的规则。
