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 显隐:表体为空且在取数时显示加载态,取数完成后没有行才显示空态
脚注合计
footer 把行号空间的最后一行留给脚注;脚注单元格不属于任何数据行,也就没有选中与禁用
表头吸顶与列吸附
root 自身就是滚动容器:stickyHeader 固定表头,列上标注 sticky 的固定该列
单选
selectionMode 设为 single:选中集合最多一个元素,再次点击已选中的行即清空,焦点行按空格同理
跨列单元格
colspan 从它自己的列向后计算,报告为 aria-colspan;1 与省略同义,因此只在真正跨列时写
单元格就地编辑
表体的方向键与 Home/End 是挂在 body 上的冒泡监听,可编辑控件上阻止冒泡后这些键就回归输入框自身
多行表头与表头分组
表头写几行就是几行;分组格的跨列数与两行表头的行号由标记声明,columns 仍只登记叶子列
列过滤
过滤按钮是列标题中的一段内容,过滤结果就是宿主计算后传入的 rows;表头是表体的兄弟,按钮上的按键不会被表体接管
树形表格
rows 按契约就是一条已摊平的可见行序列:层级三项逐行声明,缩进落在首格的内边距上
拖拽调整列宽
列上标注 resizable 才识别改宽把手;拖出表头仍跟随,方向键一次 8px、按住 Shift 一次 40px
只渲染窗口内的行
全量 rows 照常交给 root(那只是行序与行号的元信息,不产生 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 渲染,隐藏的列也在其中,只剩最后一列显示时该按钮转为禁用
设计指引
何时使用
- 每条记录有多个字段需要按列对照。
- 需要排序、筛选、批量选择。
何时不用
- 每条只有标题和一句描述时,使用列表,表格的列头是额外负担。
- 移动端窄屏上横向滚动的表格难以使用,考虑换成卡片列表。
特性
- 排序、选择、展开三套状态各自可受控。
- 列名放在
column-label里,它是列头里唯一可收窄的一格:列名太长时由它出省略号。排序钮、列宽把手与列拖拽把手都是它的兄弟,不装进它里面。列头是 flex 行,裸写的文本是匿名 flex item、缩不下去,窄列上会把定尺的把手连同外边距一起挤出列头盒,所以不可排序、不可改宽的列也要用它。 - 排序钮(
sort-trigger)是列头里独立的定尺图标钮,不包列名:列名留在column-label上,钮写在列名之后、被推到行尾侧与列宽把手并排;点列头文字不排序,点钮才排序。 - 选中行铺品牌淡底行面并由行首的勾选方框标记;悬停与按下只换面,行的几何与吸附列不动。
- 表头吸顶与列吸附、条纹、密度、边框都是开关。
- 支持多行表头与表头分组、跨列单元格、树形表格、单元格就地编辑、列过滤、拖拽调列宽。
- 行数很大时只渲染窗口内的行。
- 工具条(
toolbar)与列设置区(column-list+column-visibility-trigger)把排序、列宽与显隐接出:设置区按columnSettings渲染,隐藏的列也包含在内。两块都放在root之外:root是 grid 系角色,子节点只能是行与行组。 - 三种非条目相位各有部件:空(
empty)、在途(loading)、还有更多(load-more-trigger)。取下一页按钮的行为由作者决定,取数在途时自动停用。
组合
最佳实践
- 列宽尽量固定,不让内容长度决定列宽,否则翻页时整张表会重排。
- 批量选择要显示已选条数,并在跨页时说明选中范围。
反模式
- 列多到必须横向滚动却不吸附首列,滚动后无法分辨行。
- 用表格做页面布局。
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
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
columns | TableColumnDef[] | 列定义,列号与列总数的唯一事实源。默认为空表。 | |
rows | TableRowDef[] | 行定义,行序与行号的唯一事实源。默认为空表。 | |
sort | TableSortDescriptor[] | 排序链。提供即受控:cell 直读 prop,写入只发 onSortChange 不落内部值。 | |
defaultSort | TableSortDescriptor[] | ||
selection | TableSelection | 选中集合。提供即受控,语义同上。 | |
defaultSelection | TableSelection | ||
expandedValue | string[] | 展开集合。提供即受控,语义同上。 | |
defaultExpandedValue | string[] | ||
selectionMode | TableSelectionMode | 默认 none:未声明则没有选择机制,行也不报告 aria-selected。 | |
prefixColumns | TableColumnKind[] | 需要的前缀列,按给定顺序插在最前面,默认不插入任何列。 它们由库插入并占用列号:不占用时右侧所有列的 aria-colindex 会整体错位, 这正是使用者手工向 columns 中添加假列的原因。作者按 api.columns 渲染即可, 每一项都声明 kind。 | |
columnPreference | TableColumnPreference | 列偏好。提供即受控:内部不自行修改,写入只发 onColumnPreferenceChange。 持久化归使用者:库只负责把它计算进生效列。 | |
defaultColumnPreference | TableColumnPreference | ||
page | number | 当前页码与每页条数,只用于计算序号,不参与切片:切片归调用方 (或分页组件的 api.slice)。都未提供时序号回退为可见序。 | |
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。 | |
footer | boolean | 表格带脚注行。行号空间的最后一行留给它,aria-rowcount 也把它计入。 | |
loop | boolean | 上下键到达首尾是否回绕,默认 false。 | |
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 | ||
onSelectionChange | (details: TableSelectionChangeDetails) => void | ||
onExpandedValueChange | (details: TableExpandedValueChangeDetails) => void |
TableColumnDef
columns 的元素。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 是 | 全表唯一:既是 DOM 身份(data-value),也是排序链与列号索引的键。 |
label | string | 展示名。只供调用方渲染,不作为可及名。 | |
sortable | boolean | 可排序:提供后才产出 aria-sort,排序把手也才响应按键与点击。 | |
sticky | boolean | 'start' | 'end' | 横向冻结(左右滚动时该列固定),写为条目上的 data-frozen。true 等于 'start'(固定在行首侧),'end' 固定在行尾侧。 与表头吸顶的 data-fixed 是两件事:那是布尔,这个带方向,同名会使 [data-fixed] 一条选择器命中两种语义。 同侧有多列吸附时,连接层按前面各列的数字列宽累加出偏移,写入 --xh-table-sticky-inset; 有一列宽度不是数字时无法计算,该侧从该列起都回退为贴边。 | |
width | string | number | 列宽。数字按 px 处理,字符串原样写入内联 inline-size。 | |
minWidth | number | 拖动改列宽时的下限(px)。未提供时使用 TABLE_COLUMN_MIN_WIDTH。 | |
maxWidth | number | 拖动改列宽时的上限(px)。未提供时不封顶。 | |
resizable | boolean | 该列的宽度可以拖动修改。提供后才产出改宽把手。 | |
reorderable | boolean | 该列可以拖动换位。提供后才产出拖拽把手:每个把手都是一个 Tab 位, 未声明的表格不承担该代价。 不可拖动的列与冻结列一样是屏障:跨过它落下会把它挤走,而作者已声明该列不动。 |
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 }[] } |
column-preference-change | `` | 列偏好变化;detail 为 { value: TableColumnPreference } |
selection-change | TableSelectionChangeDetails | 选中集合变化;detail 为 { value: string[] | 'all' } |
expanded-value-change | TableExpandedValueChangeDetails | 展开集合变化;detail 为 { value: string[] } |
row-move | TableRowMoveDetails | 行换位;detail 为 { id, parent, index, ids },parent 为 null 即根层,index 是在该层的落位(已经过先移除后插入的修正),ids 是重排后的整份行序 |
插槽
仅列出带载荷的插槽。
| Vue 组件 | 插槽 | 载荷 | 说明 |
|---|---|---|---|
XhTableRoot | default | TableRootSlotProps | |
XhTableRoot | toolbar | TableToolbarSlotProps | 工具条槽:搜索、筛选、密度与列设置等作用于整张表的控件写在这里。 它渲染为 root 的兄弟排在表前:root 是 grid 系角色,子节点只能是 row 与 rowgroup。 |
React 适配器 props
只列各组件自己声明的那些:继承自 ComponentPropsWithRef 的 DOM 属性不在其中,根组件上与上面 Props 表同名的也不重复列。Vue 的对应物是上面的插槽表。
| React 组件 | 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
XhTableCell | value | string | 是 | 列 id。 |
XhTableCell | colspan | number | string | 跨列数,从 value 所在列向后计算。 | |
XhTableColumnHeader | value | string | 是 | |
XhTableColumnVisibilityTrigger | value | string | 列 id。写在列设置区中时必须提供;写在列标题中时可省略,跟随该列。 | |
XhTableExpandedRow | value | string | 是 | 所属数据行的 id。 |
XhTableRoot | toolbar | SlotChildren<TableToolbarSlotProps> | 工具条槽:搜索、筛选、密度与列设置等作用于整张表的控件写在这里。 它渲染为 root 的兄弟排在表前:root 是 grid 系角色,子节点只能是 row 与 rowgroup。 | |
XhTableRoot | children | SlotChildren<TableRootSlotProps> | ||
XhTableRow | value | string | 行 id:数据行必须提供,表头行与脚注行省略。 |
状态
公开状态写入 data-state。
| 部件 | 取值 |
|---|---|
row | 'open' | 'closed' | undefined |
column-visibility-trigger | 'unchecked' | 'checked' |
select-all-trigger | 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 · 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.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[] | |
selection | TableSelection | |
selectionState | TableSelectionState | 全选把手的三态,只按可选行(未禁用)计算。 |
selectionMode | TableSelectionMode | |
expandedValue | string[] | |
focusedRow | string | null | 焦点锚点;焦点不在表体中时为 null。 |
loading | boolean | |
empty | boolean | 表体为空(显式声明或 rows 为空)。 |
rowCount | number | aria-rowcount:表头行 + 可见行 + 脚注行。 |
columnCount | number | aria-colcount:列定义的条数。 |
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 | () => T['element'] | 表头行:恒占行号空间的第 1 行。 |
getFooterRowProps | () => 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 | 焦点移到末个可见数据行 |
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 下两键对调,语义恒是「段首 / 段末」;已经在那儿就不动 |
Alt+ArrowUp / Alt+ArrowDown | focus in table body,rowReorderable 且行拖拽没有被阻断的原因 | 把焦点行往前 / 往后挪一位,按一下就是一次完整提交,不进拖动态;纵轴与文字方向无关,rtl 下两键不对调;已在首行 / 末行就不动,也不回绕;焦点锚点跟着搬走的那一行,连按几下能一路挪到位。裸方向键仍是导航、Space 仍是选中、左右键仍是展开收起 |
Alt+ArrowLeft / Alt+ArrowRight | focus in table body,rows 里有行声明了 parentId,rowReorderable 且行拖拽没有被阻断的原因 | 把焦点行改一层缩进:往里是认上一个兄弟当爹,往外是变成父行的下一个兄弟;按一下就是一次完整提交,不进拖动态;横轴跟着文字方向翻,rtl 下两键对调,语义恒是「往里 / 往外」;没有上一个兄弟就缩不进去、已在根层就退不出来,两种情形都不动。rows 里一行都不带 parentId 时这两个键不归表格管,放行给页面 |
ARIA
以下属性由 connect 生成。
| 部件 | 属性 | 值 |
|---|---|---|
root | aria-busy | 'true' | undefined |
root | aria-colcount | columns.length || undefined |
root | aria-labelledby | caption 部件的 id |
root | aria-multiselectable | 'true' | 'false' |
root | aria-rowcount | HEADER_ROW_COUNT + visibleRows.length + (hasFooter ? … |
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 | columnIndex.get(column.value) |
column-header | aria-sort | 'ascending' | 'descending' | 'none' | undefined |
column-header | role | 'columnheader' |
cell | aria-colindex | columnIndex.get(cell.value) |
cell | aria-colspan | cell.colSpan | undefined |
cell | role | '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 | 'draggable column' |
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 | 1 |
footer-row | aria-rowindex | HEADER_ROW_COUNT + visibleRows.length + (hasFooter ? … | undefined |
header-row | role | 'row' |
footer-row | role | 'row' |
- 排序钮是
role=button,自占一个 Tab 位;它不包列名,可及名取translations.sort(列名)(默认Sort by <列名>),当前方向由所在列头的aria-sort报出,钮上不重复。 column-label不带角色与状态:列头role=columnheader的可及名由它里面的文字算出,视觉上被省略号截断的列名读屏仍读全文。- 列宽把手与列拖拽把手同样各占一个 Tab 位,名字分别取
translations.columnResize与translations.columnDrag。 - 行内的勾选框与展开箭头对读屏隐藏:选中与展开都由行自身的属性与方向键 / 空格承担。
样式参考
皮肤
@xihan-ui/styles/table.css 使用 [data-scope="table"][data-part="root"] 部件选择器,位于 xihan.components 与 xihan.motion 层。覆盖样式使用 xihan.overrides。
数据属性
由 connect 生成;条件不成立时不输出无值属性。
| 部件 | 属性 | 值 |
|---|---|---|
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 | ''(条件成立时才出现) |
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-dragging | ''(条件成立时才出现) |
column-header | data-drop | 'before' | 'after' |
column-header | data-frozen | undefined |
column-header | data-sort | 'asc' | 'desc' |
column-header | data-sort-index | tableSortIndexOf(sort, value) | undefined |
column-header | data-sortable | ''(条件成立时才出现) |
cell | data-disabled | ''(条件成立时才出现) | undefined |
cell | data-dragging | ''(条件成立时才出现) |
cell | data-drop | 'before' | 'after' |
cell | data-frozen | undefined |
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' |
select-all-trigger | data-disabled | ''(条件成立时才出现) |
select-all-trigger | data-pressed | ''(条件成立时才出现) |
select-all-trigger | data-state | 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' |
row-select-trigger | data-disabled | ''(条件成立时才出现) |
row-select-trigger | data-highlighted | ''(条件成立时才出现) |
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' |
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' |
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 | footerheaderroot | borderborder-block-endborder-block-start | defaultvariant=outline | --xh-border-default | table 的 footer、header、root 部件 border、border-block-end、border-block-start 覆盖槽。 |
--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-header | padding-inline | default | --xh-control-px-sm | table 的 cell、column-header 部件 padding-inline 覆盖槽。 |
--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-muted | table 的 column-header 部件 color 覆盖槽。 |
--xh-table-column-font-weight | column-header | font-weight | default | --xh-text-label-weight | 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-space-4 | 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-font-size | rootrow | font-size | default | --xh-_table-font-size | table 的 root、row 部件 font-size 覆盖槽。 |
--xh-table-footer-bg | footer | background | default | --xh-bg-subtle | table 的 footer 部件 background 覆盖槽。 |
--xh-table-footer-font-weight | footer | font-weight | default | --xh-font-weight-medium | table 的 footer 部件 font-weight 覆盖槽。 |
--xh-table-header-bg | column-headerheader | background | defaultfrozen | --xh-bg-subtle-opaque | table 的 column-header、header 部件 background 覆盖槽。 |
--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 | default | --xh-motion-loop-shimmer | table 的 loading 部件 animation 覆盖槽。 |
--xh-table-max-h | root | max-block-size | default | --xh-viewport-h-lg | table 的 root 部件 max-block-size 覆盖槽。 |
--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-rowfooterheaderrootrow | border-block-startborder-inline-end | is([data-part='header'], [data-part='body'], [data-part='footer'])is([data-part='row'], [data-part='expanded-row'])not(:last-child)not([hidden])split | --xh-border-subtle | table 的 body、cell、column-header、expanded-row、footer、header、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 | sort-trigger | background-color | disabledhoverloadingnot([data-disabled])not([data-loading]) | --xh-_action-variant-bg-hover | table 的 sort-trigger 部件 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-state-fg | emptyloading | color | default | --xh-fg-muted | table 的 empty、loading 部件 color 覆盖槽。 |
--xh-table-state-gap | emptyloading | gap | default | --xh-space-2 | table 的 empty、loading 部件 gap 覆盖槽。 |
--xh-table-state-min-h | emptyloading | min-block-size | default | 8rem | table 的 empty、loading 部件 min-block-size 覆盖槽。 |
--xh-table-state-px | emptyloading | padding-inline | default | --xh-space-4 | table 的 empty、loading 部件 padding-inline 覆盖槽。 |
--xh-table-state-py | emptyloading | padding-block | default | --xh-space-6 | table 的 empty、loading 部件 padding-block 覆盖槽。 |
--xh-table-sticky-column-layer | cellcolumn-headerrow | z-index | drop=afterdrop=beforedrop=insidefrozenis([data-drop='before'], [data-drop='after']) | 1 | table 的 cell、column-header、row 部件 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-visiblehoveris(:active, [data-pressed])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 | disabledis(:active, [data-pressed])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-control | 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-visiblehoveris(:active, [data-pressed])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 | --xh-_ring-colorbackground-colorcolor | defaultdisabledfocus-visiblehoveris(:active, [data-pressed])loadingnot([data-disabled])not([data-loading])pressedstate=indeterminate | --xh-fg-on-brand | table 的 column-visibility-trigger、row-select-trigger、select-all-trigger 部件 --xh-_ring-color、background-color、color 覆盖槽。 |
--xh-table-trigger-radius | column-drag-triggercolumn-visibility-triggerexpand-triggerrow-drag-triggerrow-select-triggerselect-all-triggersort-trigger | border-radius | default | --xh-shape-control--xh-shape-inset | table 的 column-drag-trigger、column-visibility-trigger、expand-trigger、row-drag-trigger、row-select-trigger、select-all-trigger、sort-trigger 部件 border-radius 覆盖槽。 |
--xh-table-trigger-size | column-visibility-triggerexpand-triggerrow-select-triggerselect-all-triggersort-trigger | --xh-icon-sizeblock-sizeinline-sizemin-block-sizemin-inline-size | defaultxh-action-profile=iconxh-action-profile=row | --xh-control-indicator-size | table 的 column-visibility-trigger、expand-trigger、row-select-trigger、select-all-trigger、sort-trigger 部件 --xh-icon-size、block-size、inline-size、min-block-size、min-inline-size 覆盖槽。 |
动效
动效角色:按压 · 状态 · 切换 · 循环(见动效规范)。
可覆盖的动效槽:--xh-table-loading-duration。
关键帧 xh-table-loading-pulse 随皮肤自带,不引用别处文件里的名字;background-color · rotate · scale 走 transition 过渡。时长与缓动读动效令牌,改令牌即改全局节奏。
prefers-reduced-motion: reduce 下本组件另有降级规则。
响应式
皮肤另按输入能力分档:pointer: coarse:同一份皮肤在触屏与带指针的设备上不一样,与视口宽度无关。
RTL
皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像;另有按 dir 分支的规则。
