定价

DataTable 使用手册

v1.15.0 · @snowui-design-system/components

许可阅读:首次阅读组件文档、源码或 npm 使用流程,先读完整 LICENSE;同任务同版本去重。公开 API 配置与内部实现修改分别按许可处理;全部组件使用统一运行包,源码权益以许可为准。

组件介绍

统一管理数据的查询、排序与分页状态,可自由组合工具栏、表格和分页器,构建完整的数据浏览界面。

使用选择

文件或记录列表需要搜索、筛选、排序、分页、勾选或操作列时使用。DataTable 协调数据状态,内部组合 TableToolbar、Table 系列与 Pagination;纯展示用 Table。选择需显式启用 selection,并使用稳定 rowKey 与行 rowId;操作列通过 actions 配置。业务请求函数与数据格式化由页面提供。

  • client 模式在本地处理数据,server 模式由页面请求与提供结果;children 接收当前渲染状态。

  • 显式启用 selection / actions;rowKey 默认是索引,排序或更新数据时应改用稳定业务 ID,并让 TableRow.rowId 与其一致。

  • 页码从 1 开始;首次 loading、后台 refreshing 和追加 loadMore 是不同状态。

快速开始

以下示例展示推荐的基本组合。更多场景与组合限制见后文。

<script lang="ts">	import type { Snippet } from 'svelte';	import { Avatar, Button, Chip, formatDate, Frame, Pagination, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, type TableSortState } from '@snowui-design-system/components';	import { DataTable, TableToolbar, type DataTableLoadMoreRequest, type DataTableRenderState } from '@snowui-design-system/components';	import { Apple, AvatarFemale01, AvatarFemale02, AvatarFemale03, AvatarMale01, AvatarMale02, Facebook, FigmaA, Google, Gradient01, MagicWand, Medium, Perplexity, Question, Slack, SnowLogo, SortAscending, Calendar, ThreeDots } from '@snowui-design-system/resource-svelte';	import { OverflowActions, OverflowAction } from '@snowui-design-system/components';	const DEMO_NOW = new Date("2026-06-19T04:00:00Z");	const minute = 60 * 1000;	const hour = 60 * minute;	const day = 24 * hour;	    const baseRows = [	        { createdAt: new Date(DEMO_NOW.getTime() - 10 * 1000), store: "SnowUI Pro", logo: SnowLogo, subscriber: "Natali Craig", avatar: AvatarFemale01, plan: "Monthly $19.9", term: "First", price: 19.9, status: "Active" },	        { createdAt: new Date(DEMO_NOW.getTime() - minute), store: "Apple", logo: Apple, subscriber: "Kate Morrison", avatar: AvatarFemale02, plan: "Affiliate Monthly $19.9", term: "Renewal", price: 19.9, status: "Active" },	        { createdAt: new Date(DEMO_NOW.getTime() - hour), store: "Magic 3D Store", logo: MagicWand, subscriber: "Drew Cano", avatar: AvatarMale01, plan: "Monthly $19.9", term: "First", price: 9.95, status: "Active" },	        { createdAt: new Date(DEMO_NOW.getTime() - day), store: "Facebook", logo: Facebook, subscriber: "Orlando Diggs", avatar: AvatarMale02, plan: "Affiliate Monthly $19.9", term: "Renewal", price: 9.95, status: "Active" },	        { createdAt: new Date("2025-02-02T12:00:00Z"), store: "My illustration world", logo: Gradient01, subscriber: "Andi Lane", avatar: AvatarFemale03, plan: "Affiliate Yearly $189", term: "Renewal", price: 189, status: "Active" },	        { createdAt: new Date(DEMO_NOW.getTime() - 20 * 1000), store: "Medium", logo: Medium, subscriber: "Natali Craig", avatar: AvatarFemale01, plan: "Yearly $189", term: "First", price: 189, status: "Active" },	        { createdAt: new Date(DEMO_NOW.getTime() - 2 * minute), store: "Perplexity", logo: Perplexity, subscriber: "Kate Morrison", avatar: AvatarFemale02, plan: "Yearly $189", term: "First", price: 189, status: "Active" },	        { createdAt: new Date(DEMO_NOW.getTime() - 2 * hour), store: "Figma", logo: FigmaA, subscriber: "Drew Cano", avatar: AvatarMale01, plan: "Yearly $189", term: "First", price: 189, status: "Active" },	        { createdAt: new Date(DEMO_NOW.getTime() - day - hour), store: "Google", logo: Google, subscriber: "Orlando Diggs", avatar: AvatarMale02, plan: "Yearly $189", term: "First", price: 189, status: "Expired" },	        { createdAt: new Date("2025-02-02T10:00:00Z"), store: "Slack", logo: Slack, subscriber: "Andi Lane", avatar: AvatarFemale03, plan: "Yearly $189", term: "First", price: 189, status: "Expired" },	    ];	    const initialRows = Array.from({ length: 200 }, (_, index) => {	        const source = baseRows[index % baseRows.length];	        return {	            ...source,	            id: index + 1,	            note: source.term === "Renewal" ? "Subscription renewal" : "New subscription",	            createdAt: new Date(	                source.createdAt.getTime() -	                    Math.floor(index / baseRows.length) * day,	            ),	        };	    });	    type Row = (typeof initialRows)[number];	let rows = $state(initialRows);	let page = $state(1);	let pageSize = $state(10);	let tableState = $state<DataTableRenderState<Row>>();	let selectedRowIds = $state<Array<string | number>>([]);	let query = $state('');	let sort = $state<TableSortState>({ columnId: 'createdAt', direction: 'descending' });	let feedback = $state('');	const ACTIVE_ROW_ID = 2;	const sorters = { createdAt: (row: Row) => row.createdAt, store: (row: Row) => row.store };	const cellLinkStyle = 'color: inherit; text-decoration: none;';	async function copySelected() {	  try {	    await navigator.clipboard.writeText(rows.filter(row => selectedRowIds.includes(row.id)).map(row => `${row.store} — ${row.subscriber}`).join('\n'));	    feedback = 'Copied.';	  } catch (error) { feedback = 'Copy unavailable.'; throw error; }	}	function deleteSelected() {	  rows = rows.filter(row => !selectedRowIds.includes(row.id));	  selectedRowIds = [];	  feedback = 'Deleted.';	}</script>     {#snippet subscriptionsHeader(showNote: boolean)}        <TableHeader            ><TableRow>                <TableHead as="selection" />                <TableHead columnId="createdAt" label="Date" resizable>Date</TableHead>                <TableHead columnId="store" label="Stores" resizable>Stores</TableHead>                <TableHead columnId="subscriber" label="Subscriber" resizable>Subscriber</TableHead>                <TableHead columnId="price" label="Price" resizable>Price</TableHead>                <TableHead columnId="status" label="Status" resizable>Status</TableHead>                {#if showNote}<TableHead columnId="note" label="Note" resizable>Note</TableHead>{/if}                <TableHead as="actions" />            </TableRow></TableHeader        >    {/snippet}    {#snippet subscriptionsBody(stateRows: Row[], showNote: boolean, activeRowId: number, onlyLastAction: boolean, footer: Snippet | undefined = undefined)}        <TableBody>            {#each stateRows as row (row.id)}                <TableRow rowId={row.id} active={row.id === activeRowId}>                    <TableCell as="selection" />                    <TableCell columnId="createdAt" singleLine gap={1}>                        <Text tag="a" lines={1} onclick={(event) => { event.preventDefault(); event.stopPropagation(); }} onauxclick={(event) => { event.preventDefault(); event.stopPropagation(); }} style={cellLinkStyle} href={`#subscription-${row.id}-created-at`}>                            {formatDate(                                    row.createdAt,                                    { relative: true, now: DEMO_NOW, locale: 'en-US' },                                )}                        </Text>                    </TableCell>                    <TableCell columnId="store" singleLine>                        <Avatar as="leading" src={row.logo} size={24} />                        <Text tag="a" lines={1} onclick={(event) => { event.preventDefault(); event.stopPropagation(); }} onauxclick={(event) => { event.preventDefault(); event.stopPropagation(); }} style={cellLinkStyle} href={`#subscription-${row.id}-store`}>{row.store}</Text>                    </TableCell>                    <TableCell columnId="subscriber" singleLine>                        <Avatar as="leading" src={row.avatar} size={24} />                        <Text tag="a" lines={1} onclick={(event) => { event.preventDefault(); event.stopPropagation(); }} onauxclick={(event) => { event.preventDefault(); event.stopPropagation(); }} style={cellLinkStyle} href={`#subscription-${row.id}-subscriber`}>{row.subscriber}</Text>                    </TableCell>                    <TableCell columnId="price" singleLine gap={1}>                        <Text tag="a" lines={1} onclick={(event) => { event.preventDefault(); event.stopPropagation(); }} onauxclick={(event) => { event.preventDefault(); event.stopPropagation(); }} style={cellLinkStyle} href={`#subscription-${row.id}-price`}>${row.price}</Text>                        {#if row.term === "Renewal"}<Icon as="trailing" icon={Question} size={16} aria-label="Renewal price" />{/if}                    </TableCell>                    <TableCell columnId="status">                        <a onclick={(event) => { event.preventDefault(); event.stopPropagation(); }} onauxclick={(event) => { event.preventDefault(); event.stopPropagation(); }} style={cellLinkStyle} href={`#subscription-${row.id}-status`}>                            <Chip                                variant="background"                                style={`color:${row.status === "Active" ? "var(--secondary-green)" : "var(--black-40)"};`}                                >{row.status}</Chip                            >                        </a>                    </TableCell>                    {#if showNote}<TableCell columnId="note" nowrap={false} lines={false}>                            {row.note}                    </TableCell>{/if}                    <TableCell as="actions">                        <OverflowActions moreLabel={`More actions for ${row.subscriber}`}>                            {#if !onlyLastAction}                                <OverflowAction as="item" id="edit" label="Edit" onclick={() => feedback = `Editing ${row.subscriber}.`} />                                <OverflowAction as="item" id="info" label="Renewal info" icon="question" onclick={() => feedback = `${row.term} · ${row.plan}`} />                                <OverflowAction as="item" id="sort" label="Sort by store" icon="sort-ascending" onclick={() => sort = { columnId: 'store', direction: 'ascending' }} />                            {/if}                            <OverflowAction as="item" id="details" label="Subscription details" onclick={() => feedback = `${row.store} · ${row.status}`} />                        </OverflowActions>                    </TableCell>                </TableRow>            {/each}            {#if stateRows.length === 0}                <TableRow data-empty-state="true">                    <TableCell                        colspan={showNote ? 8 : 7}                        align="center"                        lines={false}                        tooltip={false}                        style="height: 25rem;"                    >                        No subscriptions                    </TableCell>                </TableRow>            {/if}            {#if footer}{@render footer()}{/if}        </TableBody>    {/snippet}<DataTable defaultSort={{ columnId: "createdAt", direction: "descending" }} variant="striped" {rows} {sorters} rowKey={(row) => row.id}  selection actions horizontalScroll={false} roleLayout="overlay" bind:query bind:sort bind:pageSize bind:page  bind:state={tableState} bind:selectedRowIds>  <TableToolbar searchPlaceholder="Search subscriptions…" filterKey="status"    filterOptions={[{ label: 'Active', value: 'Active' }, { label: 'Expired', value: 'Expired' }]}    onCopy={copySelected} onDelete={deleteSelected} />  <Table ariaLabel="Subscriptions">    {@render subscriptionsHeader(true)}    {@render subscriptionsBody(tableState?.rows ?? [], true, ACTIVE_ROW_ID, false)}  </Table>  <Pagination page={tableState?.page ?? 1} total={tableState?.totalPages ?? 1}    bind:pageSize pageSizeOptions={[10, 20, 50]} onPageChange={(value) => page = value} /></DataTable>

API

关键字 类型 默认值 描述
rows T[] [] 当前数据
mode DataTableMode "client" 客户端切片或服务端受控模式 默认行为:client。
sorters Record<string, Sorter<T>> {} 仅复杂派生列需要显式提供
searcher Searcher<T> — 覆盖 client 模式搜索逻辑 默认行为:浅层字符串/数字匹配。
filterer Filterer<T> — 覆盖 client 模式筛选逻辑 默认行为:字段等值匹配。
rowKey (row: T, index: number) => string | number (_row, index) => index 稳定行身份读取器 默认行为:index。
sort TableSortState defaultSort 当前排序列与方向;client 模式执行排序,server 模式由调用方处理。
defaultSort TableSortState — 兼容用的初始单列排序;新代码优先在 TableHead 上声明 defaultSort
query string "" 当前搜索文本;client 模式执行搜索,server 模式由调用方处理。
filters Record<string, unknown> {} client 模式按字段执行等值或数组包含过滤
page number 1 当前页码,从 1 开始;渲染状态会夹取到有效页范围。
pageSize number 10 每页行数;变化时自动回到第 1 页
totalRows number — server 模式总行数
loading boolean false 首次加载状态,传递给表格加载与渲染状态。
refreshing boolean false 后台刷新状态,保留现有数据并通过渲染状态提供。
loadMore DataTableLoadMore<T> — 增量数据源;request 包含 offset、limit 和 AbortSignal
loadMoreSize number 5 每次增量加载的最大行数
loadMoreMode DataTableLoadMoreMode "manual" 手动调用 state.loadMore(),或由内部表尾哨兵自动触发 默认行为:manual。
loadMoreThreshold number 80 auto 模式越过表格底部后继续滚动多少 px 才触发
loadMoreThrottleMs number 500 auto 模式首次进入触发区后的确认等待时间,同时作为连续请求的最小间隔;首次等待期间离开触发区则取消
hasMore boolean — 显式覆盖是否仍可加载;否则优先按 totalRows 推导,再按返回数量推断 默认行为:自动推导。
error unknown — 外部错误,优先于增量加载错误进入 state.error;组件不自动生成业务错误文案。
resetPageOnQuery boolean true 查询或筛选变化时将页码重置为 1。
onSortChange (sort: TableSortState | undefined) => void — 排序变化回调,取消排序时传入 undefined。
onQueryChange (query: string) => void — 查询文本变化时回调。
onPageChange (page: number) => void — 页码变化时回调,参数从 1 开始。
onPageSizeChange (pageSize: number) => void — 规范化后的每页行数变化时触发
onStateChange (state: DataTableRenderState<T>) => void — 当前渲染状态更新时回调,包含可见行、分页、选择及加载状态。
state DataTableRenderState<T> — 可绑定的当前渲染状态
variant DataTableVariant "divided" 行视觉;斑马纹或分割线 默认行为:divided。
selectedRowIds Array<string | number> [] 已选行 id;选择状态由 DataTable 持有
selection boolean false 是否显示 checkbox 选择列;为 false 时 as="selection" 的 Head/Cell 不渲染
actions boolean | DataTableActionsConfig<T> false 是否显示操作列;配置对象会在空 actions Cell 中默认生成 OverflowActions
horizontalScroll boolean true 向 Table 下发横向滚动策略;false 时从右向左收起允许隐藏的普通列,保护列仍超宽时滚动兜底。
roleLayout TableRoleLayout "auto" 宽时角色列常驻,空间不足先转 hover 浮层;column / overlay 固定指定布局。
roleOverlayLayers number 4 overlay 渐进模糊层数
roleOverlayBlurMode TableRoleOverlayBlurMode "progressive" overlay 模糊算法 默认行为:progressive。
roleOverlayUniformFeather number 0.3 uniform 模式内侧羽化比例
onRowPreview (row: T, state: DataTableRenderState<T>, event: MouseEvent) => void — 配置后,TableRow 行内空白点击打开行预览;未配置时空白点击不响应
onSelectedRowIdsChange (rowIds: Array<string | number>) => void — 选择变化后的完整快照
children Snippet<[DataTableRenderState<T>]> — 表格、工具栏和分页组合内容,接收当前渲染状态。

未重新声明的继承能力请查所属基础组件文档;本表只列本组件源码具名公开属性。

使用边界与详细说明

列标题通过 TableHead 的 children 插槽传入,支持文字、图标及非交互组件组合。纯图标标题需提供可访问名称;可排序列不要在标题内嵌套交互控件。详见 TableHead 图标与组件标题。

布局模型

主布局模型:复合管理布局。

结构:

  • Root Frame(column 状态与组合外壳)

    • 使用方组合的 TableToolbar(可选固定操作区)

    • Table(语义表格区)

    • Pagination(可选分页区)

    • 使用方声明的 loading / error / empty 等其他状态区

局部布局来源:Root Frame 使用 column + contentFill 管理直系区域;TableToolbar 保持自身的 row 槽位宿主布局,Table 保持原生表格布局,Pagination 保持内联外壳布局。DataTable 只协调区域排列和数据状态,不改写子组件的主布局模型。

padding 归属:DataTable 根层不制造 padding;工具栏、表格 Cell、分页和自定义状态区分别由各自组件负责。内部 Table 的表头与数据 Cell 左右 padding 按 Table 容器宽度在 4px 到 12px 之间响应,上下保持 8px,表格外缘最小 8px、相邻列内容间最小 8px;DataTable 不复制单元格 padding 实现。

gap 归属:Root Frame 统一控制直系区域间距,默认为 var(--gap-3)(12px);子组件内部 gap 由子组件自身控制。

滚动归属:DataTable 不另建滚动区,水平溢出直接继承内部 Table 的局部横向 viewport 与视口贴底轨道;纵向虚拟化或固定表头仍由上层业务区域明确提供。loadMoreMode="auto" 只在 DataTable 内部观察表尾哨兵:优先使用最近的纵向滚动祖先作为 root,找不到时使用页面视口,不改变纵向滚动容器归属。

columnId 与行对象字段同名时,DataTable 自动读取原始字段并使该 TableHead 可排序。因此日期列保存 Date、数字时间戳或带时区的 ISO 8601 日期时间即可,不需要声明 sortType: "date"。展示值来自计算结果时传 sorters。无同名字段且无 sorter 的 TableHead 保持静态。

未在任何 TableHead 或 DataTable 上声明 defaultSort,且当前 sort === undefined 时,DataTable 不推导第一列升序,也不施加任何其他隐式排序。客户端模式直接保留 rows prop 的输入顺序;页面传入 filteredRows 时,就恢复 / 保持 filteredRows 的原始相对顺序。

TableHead defaultSort 同时定义默认列与默认方向,优先于 DataTable 自身的兼容 prop。默认时该列按“默认 → 反向 → 默认”循环,仅反向显示激活视觉。TableHead 同时声明 defaultSortActive 时,默认方向也显示激活视觉,循环改为“默认 → 反向 → 无排序 → 默认”。其他列保持“无排序 → ascending → descending → 无排序”。

<script lang="ts">  import { Pagination, Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@snowui-design-system/components';  import { DataTable, TableToolbar, type DataTableRenderState } from '@snowui-design-system/components';  const rows = [{ id: 'one', name: 'Project files' }];  let page = $state(1);  let state = $state<DataTableRenderState<(typeof rows)[number]>>();</script> <DataTable {rows} rowKey={(row) => row.id} bind:page bind:state variant="striped">  <TableToolbar sort={false} filter={false} />  <Table>    <TableHeader><TableRow><TableHead columnId="name">名称</TableHead></TableRow></TableHeader>    <TableBody>      {#each state?.rows ?? [] as row (row.id)}        <TableRow rowId={row.id}><TableCell columnId="name">{row.name}</TableCell></TableRow>      {/each}    </TableBody>  </Table>  <Pagination {page} total={state?.totalPages ?? 1} onPageChange={(next) => page = next} /></DataTable>

相对日期的正确写法是 formatDate(row.createdAt, { relative: true, now })。排序继续比较 row.createdAt,不会比较 Just now。

DataTable 的工具栏、Table 和 Pagination 默认区域间距为 12px。striped 从第一行开始使用 Surface/3 作为奇数行背景,不显示分割线,所有数据行始终保持 12px 外缘圆角;divided 的表头与静态数据行两端保持直角,表头下边线为 Black/20% 0.5px,数据行分割线为 Black/10% 0.5px,末行无分割线。分割线模式下,hover 行首尾形成 12px 圆角,且上下相邻分割线同时隐藏;鼠标移出时背景立即退场,不显示圆角恢复直角的背景残影。

响应式

与 Table 共用完整响应式规则:先角色列转浮层,再压缩间距,最后滚动或从右向左隐藏普通列。horizontalScroll 默认 true;设为 false 时允许隐藏,TableHead responsiveHide={false} 保护指定列,第一普通列始终保留。最终单列的纯文本可收缩,不换行内容省略并默认提供 Tooltip 全文;硬尺寸或不可压缩控件仍滚动兜底。Table 的显式配置优先;DataTable 不另建测量或滚动层。

选择列与操作列

默认 只读表:selection={false}、actions={false},不显示 checkbox 列与操作列。markup 中仍可声明 <TableHead as="selection" /> / <TableCell as="actions"> 等;DataTable 关闭时整列不渲染、不参与列注册。

只读默认态同时跳过行选择 Set 查询与全选集合扫描;TableRow 不读取选中状态、不注册 rowId。开启 selection 后再恢复完整选择路径,DOM 与交互协议不变。

 <DataTable {rows}>…</DataTable>  <DataTable selection actions {rows}>…</DataTable>  <DataTable selection {rows}>…</DataTable>  <DataTable actions {rows}>…</DataTable>

actions={true} 保留原有显式组合:业务仍可在每个 <TableCell as="actions"> 中自行书写按钮。传入配置对象时,未声明 children 的 actions Cell 默认使用 OverflowActions;显式 children 始终替换默认内容。

<DataTable    {rows}    rowKey={(row) => row.id}    actions={{        items: (row) => [            { id: "edit", label: "编辑", icon: "pencil-simple" },            { type: "separator", id: "danger" },            { id: "delete", label: "删除", icon: "trash", disabled: !row.canDelete },        ],        overflow: "progressive",        overflowFrom: "end",        width: 120,        minWidth: 40,        maxWidth: 240,        resizable: true,        onAction: ({ row, actionId }) => runRowAction(row, actionId),    }}>    ...</DataTable>

配置模式的默认列宽为 160px;compact="auto"、overflow="all"、overflowFrom="end"。resizable 默认 false;仅 roleLayout="column" 支持 actions 列拖动,overlay 模式始终不提供列宽 Indicator。拖动结果由 Table 的 columnWidths.__actions 接管并实时下发到各行;每个 OverflowActions 接收数值 availableWidth,不会为每个 Cell 新建尺寸观察器。overlay 模式使用同一 actions 宽度作为浮层宽度。

当 More 菜单展开时,当前响应式分配被冻结,行 hover 显隐规则也把打开的 SelectButton 视为保持可见,避免列宽或指针离开行后触发器消失。Separator 只在两侧仍有操作时保留,不会独立留在可见区域或菜单首尾。

行预览

配置 onRowPreview 后,DataTable 通过 Table Context 向 TableRow 提供行预览处理器。TableRow 只在行内空白区域触发该处理器;点击链接、按钮、输入控件、选择列或 actions 列控件不会触发行预览。未配置 onRowPreview 时,行内空白点击不响应;selection={true} 时选择仍由 selection 角色列负责。

首次加载 Skeleton rows

loading && rows.length === 0 时,DataTable 通过 Table Context 向后代 TableBody 提供首次加载语义;TableBody 保留真实 TableHeader,并按已注册的 TableHead 列结构自动生成与当前 pageSize 相同数量的 40px Skeleton 数据行。Skeleton 行使用真实 tr / td 语义结构但整体 aria-hidden,DataTable 根继续输出 aria-busy="true"。

  • 骨架行数直接跟随规范化后的 pageSize;默认 pageSize=10,因此未设置分页行数时生成 10 行。

  • 不提供独立 Skeleton 行数属性,避免加载态与真实分页数据高度不一致,尽可能降低首次数据出现时的页面跳动。

  • Skeleton 只替代首次空数据加载;loading 或 refreshing 时若已有 rows,继续保留现有数据,并在 TableBody 末尾追加跨列居中的 Spinner 行,避免刷新时布局跳动且明确表达仍在加载。

  • TableHead 通过双方文档明确的 Table Context 注册列顺序、角色与列宽标识;DataTable / TableBody 不扫描子组件类名或内部 DOM。

  • TableBody loading={false} 可显式关闭从 DataTable Context 继承的 Skeleton rows;独立 Table 可用 <TableBody loading> 主动启用,此时无法读取分页行数,回退为 10 行。

操作组配置与视觉预算

actions配置新增maxVisible(缺省3)、buttonProps、parts和moreLabel,类型复用OverflowActions;单项支持target/rel/buttonProps/parts。缺省progressive从末端收纳,显式all、compact与方向保持。组级样式只作用于默认生成组,单项buttonProps优先于单项parts.Button。显式children替换默认内容;其中OverflowActions共享本表布局预算,不继承默认生成组的视觉。

操作区先按本表实际可见viewport的20%、14rem及显式/拖动列宽上限取小,并预留选择和受保护数据宽度;总预算包含padding、More及渐隐覆盖。More最小命中尺寸优先,极窄冲突时局部滚动,不缩小字体/控件或为保操作先隐藏数据。

DataTable 样式覆盖

先阅读 完整 LICENSE;同任务、同版本、同范围去重。

优先用组件公开 Props 匹配合适样式,尽可能保留原有视觉。确需改写时按 样式选择与改写顺序 使用自己的 class(含项目已有 Tailwind)或实例 style。以下名称来自源码实际消费的公开 override;先核对目标作用域,不修改私有 DOM。采用 SnowUI Token 时选择正确类别,外部应用不被要求安装一方 utilities。

本组件源文件没有直接消费已登记的专属 override。组合子组件的样式通过公开 parts 或显式 child 配置;不要凭名称猜测变量。

设计指南 · Token · 组合边界

DataTable 类型与状态

必填属性:无具名必填属性。

可双向绑定:bind:sort、bind:query、bind:filters、bind:page、bind:pageSize、bind:state、bind:selectedRowIds。

继承声明:Omit<FrameProps, "children" | "variant">。继承能力以基类公开文档为准;Omit 中的字段已排除,不能按基类原样使用。

共享 Frame API · 返回 API

以下为公开属性实际引用的类型声明,来自源码;Svelte Snippet 表示内容片段,ComponentProps 表示对应组件的 Props。

DataTableMode

来源:components/src/components/DataTable.svelte

export type DataTableMode = "client" | "server";

Sorter

来源:components/src/components/DataTable.svelte

type Sorter<T> = (row: T) => unknown;

Searcher

来源:components/src/components/DataTable.svelte

type Searcher<T> = (row: T, query: string) => boolean;

Filterer

来源:components/src/components/DataTable.svelte

type Filterer<T> = (row: T, filters: Record<string, unknown>) => boolean;

TableSortState

来源:components/src/lib/table-context.svelte.ts

export interface TableSortState {    columnId: string;    direction: TableSortDirection;}

TableSortDirection

来源:components/src/lib/table-context.svelte.ts

export type TableSortDirection = "ascending" | "descending";

DataTableLoadMore

来源:components/src/components/DataTable.svelte

export type DataTableLoadMore<T> = (        request: DataTableLoadMoreRequest,    ) => DataTableLoadMoreSource<T> | Promise<DataTableLoadMoreSource<T>>;

DataTableLoadMoreRequest

来源:components/src/components/DataTable.svelte

export interface DataTableLoadMoreRequest {        offset: number;        limit: number;        signal: AbortSignal;    }

DataTableLoadMoreSource

来源:components/src/components/DataTable.svelte

export type DataTableLoadMoreSource<T> = Iterable<T> | AsyncIterable<T>;

DataTableLoadMoreMode

来源:components/src/components/DataTable.svelte

export type DataTableLoadMoreMode = "manual" | "auto";

DataTableRenderState

来源:components/src/components/DataTable.svelte

export interface DataTableRenderState<T = unknown> {        rows: T[];        sourceRows: T[];        query: string;        sort?: TableSortState;        page: number;        pageSize: number;        totalRows: number;        totalPages: number;        loading: boolean;        refreshing: boolean;        loadingMore: boolean;        hasMore: boolean;        loadMoreSize: number;        filters: Record<string, unknown>;        error?: unknown;        getRowKey: (row: T, index: number) => string | number;        loadMore: () => Promise<void>;    }

DataTableVariant

来源:components/src/components/DataTable.svelte

export type DataTableVariant = "striped" | "divided";

DataTableActionsConfig

来源:components/src/components/DataTable.svelte

export interface DataTableActionsConfig<T> {        items: (row: T, state: DataTableRenderState<T>) => DataTableActionsItem[];        onAction?: (event: DataTableActionEvent<T>) => void;        compact?: "auto" | "none";        overflow?: "all" | "progressive";        overflowFrom?: "start" | "end";        width?: number;        minWidth?: number;        maxWidth?: number;        resizable?: boolean;        maxVisible?: number;        buttonProps?: ComponentProps<typeof OverflowActions>["buttonProps"];        parts?: ComponentProps<typeof OverflowActions>["parts"];        moreLabel?: string;    }

DataTableActionsItem

来源:components/src/components/DataTable.svelte

export type DataTableActionsItem =        | DataTableActionItem        | DataTableActionSeparatorItem;

DataTableActionItem

来源:components/src/components/DataTable.svelte

export interface DataTableActionItem extends OverflowActionsActionItem {        type?: "action";        id: string;        label: string;        icon?: string | Snippet;        disabled?: boolean;        href?: string;        onAction?: () => void;    }

OverflowActionsActionItem

来源:components/src/components/OverflowActions.svelte

export interface OverflowActionsActionItem {        type?: "action";        id: string;        label: string;        icon?: OverflowActionIcon;        disabled?: boolean;        href?: string;        target?: string;        rel?: string;        onAction?: () => void;        buttonProps?: GlobalButtonProps;        parts?: ComponentProps<typeof OverflowAction>["parts"];    }

OverflowActionIcon

来源:components/src/logic/overflow-actions.svelte.ts

export type OverflowActionIcon = string | Snippet;

GlobalButtonProps

来源:components/src/components/OverflowActions.svelte

export type GlobalButtonProps = Omit<ButtonProps, ControlledGlobalButtonKeys>;

ButtonProps

来源:components/src/components/OverflowActions.svelte

type ButtonProps = ComponentProps<typeof Button>;

ControlledGlobalButtonKeys

来源:components/src/components/OverflowActions.svelte

type ControlledGlobalButtonKeys =        | "children"        | "as"        | "iconOnly"        | "disabled"        | "href"        | "onclick"        | "aria-label"        | "title";

DataTableActionSeparatorItem

来源:components/src/components/DataTable.svelte

export interface DataTableActionSeparatorItem {        type: "separator";        id: string;    }

TableRoleLayout

来源:components/src/lib/table-context.svelte.ts

export type TableRoleLayout = "auto" | "column" | "overlay";

TableRoleOverlayBlurMode

来源:components/src/lib/table-context.svelte.ts

export type TableRoleOverlayBlurMode = "progressive" | "uniform";