diff --git a/.ua/index.json b/.ua/index.json index 96c3d2a..cf0f9e4 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-26T14:17:04.411Z", + "generatedAt": "2026-10-26T14:19:01.143Z", "repoRoot": ".", "packages": [ { @@ -2087,6 +2087,7 @@ "工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行", "表头粘性固定(滚动容器 max-height 420px),斑马纹 + 悬停高亮由 CSS 提供", "数据在途时传 loading(布尔或信号):表格体换成骨架行、筛选与翻页禁用,返回后置回 false 即恢复", + "分页条自带每页条数下拉(5 / 10 / 20 条,调用方传的 pageSize 若不在候选里会自动补进去):切换即改页大小并回第 1 页,首末页时上/下一页置灰不再派发无效页码", "需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用", "需要批量操作时传 selected 信号:内部勾选实时写回,外部置空或赋值即同步整表(跨页保留,按 rowKey 存)", "可排序表头的 aria-sort 跟随排序态同步(ascending / descending / none),读屏能直接播报当前列序,箭头只是视觉补充", diff --git a/llms-full.txt b/llms-full.txt index fc26854..c0897a6 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-26T14:17:04.411Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-26T14:19:01.143Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -1037,11 +1037,12 @@ dataTable({ data: users, selectable: true, columns: [{ key: 'name', header: '姓 8. 工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行 9. 表头粘性固定(滚动容器 max-height 420px),斑马纹 + 悬停高亮由 CSS 提供 10. 数据在途时传 loading(布尔或信号):表格体换成骨架行、筛选与翻页禁用,返回后置回 false 即恢复 -11. 需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用 -12. 需要批量操作时传 selected 信号:内部勾选实时写回,外部置空或赋值即同步整表(跨页保留,按 rowKey 存) -13. 可排序表头的 aria-sort 跟随排序态同步(ascending / descending / none),读屏能直接播报当前列序,箭头只是视觉补充 -14. 需要导出时开 exportable:按钮导的是筛选+排序后的全量行,与当前页无关,单元格按 format 输出 -15. 整行可点(进详情、选中一行做后续操作)传 onRowClick:行会变成可聚焦项,Enter / 空格与点击等价;不传时行不进 Tab 序列,纯展示表不会被读屏当成按钮 +11. 分页条自带每页条数下拉(5 / 10 / 20 条,调用方传的 pageSize 若不在候选里会自动补进去):切换即改页大小并回第 1 页,首末页时上/下一页置灰不再派发无效页码 +12. 需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用 +13. 需要批量操作时传 selected 信号:内部勾选实时写回,外部置空或赋值即同步整表(跨页保留,按 rowKey 存) +14. 可排序表头的 aria-sort 跟随排序态同步(ascending / descending / none),读屏能直接播报当前列序,箭头只是视觉补充 +15. 需要导出时开 exportable:按钮导的是筛选+排序后的全量行,与当前页无关,单元格按 format 输出 +16. 整行可点(进详情、选中一行做后续操作)传 onRowClick:行会变成可聚焦项,Enter / 空格与点击等价;不传时行不进 Tab 序列,纯展示表不会被读屏当成按钮 现场演示:apps/docs/demo.html#sec-table diff --git a/packages/skin/ui/src/dataTable.ts b/packages/skin/ui/src/dataTable.ts index f10a7d5..13a78e8 100644 --- a/packages/skin/ui/src/dataTable.ts +++ b/packages/skin/ui/src/dataTable.ts @@ -9,10 +9,10 @@ import { import { iconSearch } from './icons.js'; import { computed, effect, h, renderList, signal } from '@ui-arch/frame'; import { attachResize } from './columnResize.js'; import { exportButton } from './csv.js'; -import { button } from './button.js'; import { skeleton } from './skeleton.js'; import { textField } from './textField.js'; import { emptyRow } from './tableEmpty.js'; +import { tablePager } from './tablePager.js'; import { attachDataSource, readRows } from './tableDataSource.js'; import type { DataTableProps, TableApi } from './types.js'; @@ -188,12 +188,12 @@ export function dataTable(props: DataTableProps): H props.exportable ? exportButton(table, props.columns, props.exportName ?? 'table.csv') : null, // 导出筛选+排序后的全量行 ), h('div', { class: 'ua-table-scroll' }, h('table', null, colgroup, h('thead', null, h('tr', null, selectable ? headCheck : null, ...props.columns.map(th))), tbody)), - h( - 'div', - { class: 'ua-pager', role: 'navigation', 'aria-label': '分页' }, - button({ label: '上一页', variant: 'ghost', disabled: props.loading, onClick: () => table.dispatch({ type: 'setPage', page: info().page - 1 }) }), - h('span', { class: 'ua-page-info', role: 'status', 'aria-live': 'polite' }, () => `第 ${info().page} / ${info().totalPages} 页 · 共 ${visible().total} 行`), // 翻页/筛选后面向读屏播报 - button({ label: '下一页', variant: 'ghost', disabled: props.loading, onClick: () => table.dispatch({ type: 'setPage', page: info().page + 1 }) }), - ), + tablePager({ + info, + total: () => visible().total, + loading: busy, + onPage: (page) => table.dispatch({ type: 'setPage', page }), + onPageSize: (pageSize) => table.dispatch({ type: 'setPageSize', pageSize }), + }), ); } diff --git a/packages/skin/ui/src/tablePager.ts b/packages/skin/ui/src/tablePager.ts new file mode 100644 index 0000000..a61c2f6 --- /dev/null +++ b/packages/skin/ui/src/tablePager.ts @@ -0,0 +1,36 @@ +import { effect, h, signal } from '@ui-arch/frame'; +import { button } from './button.js'; +import { select } from './select.js'; +import type { TablePagerProps } from './types.js'; + +/*** 表格分页条:上一页 / 页码播报 / 下一页 + 每页条数切换;页码与总页数都是内核派生快照,这里只读不持有 ***/ +export function tablePager(props: TablePagerProps): HTMLElement { + const sizes = props.pageSizes ?? [5, 10, 20]; // 每页条数候选:默认三档,够覆盖表格常见密度 + const initial = props.info().pageSize; // 调用方在建表时可能传了候选外的页大小,补进候选避免下拉显示空白 + const list = sizes.includes(initial) ? sizes : [...sizes, initial].sort((a, b) => a - b); + const sizeSig = signal(String(initial)); // 下拉受控值:外部(内核)改页大小时由下方 effect 同步 + const atFirst = () => props.info().page <= 1; // 首/末页边界:按钮置灰,避免派发无效页码 + const atLast = () => props.info().page >= props.info().totalPages; + + effect(() => { + const cur = String(props.info().pageSize); + if (cur !== sizeSig()) sizeSig(cur); // 内核页大小变化(含列表恢复默认)→ 回写下拉 + }); + + const sizeSelect = select({ + size: 'sm', + options: list.map((n) => ({ value: String(n), label: `${n} 条/页` })), + value: sizeSig, + disabled: () => props.loading(), // 加载中不改页大小:避免与在途结果竞争 + onChange: (v) => props.onPageSize(Number(v)), + }); + + return h( + 'div', + { class: 'ua-pager', role: 'navigation', 'aria-label': '分页' }, + button({ label: '上一页', variant: 'ghost', disabled: () => props.loading() || atFirst(), onClick: () => props.onPage(Math.max(1, props.info().page - 1)) }), + h('span', { class: 'ua-page-info', role: 'status', 'aria-live': 'polite' }, () => `第 ${props.info().page} / ${props.info().totalPages} 页 · 共 ${props.total()} 行`), // 翻页 / 筛选后面向读屏播报 + button({ label: '下一页', variant: 'ghost', disabled: () => props.loading() || atLast(), onClick: () => props.onPage(Math.min(props.info().totalPages, props.info().page + 1)) }), + sizeSelect, + ); +} diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index f8119bb..6bcd534 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -813,6 +813,16 @@ export interface SpaceProps { /*** 表格扩展接口:table-core 实例 + 皮肤层用到的两个取数方法 ***/ export type TableApi = TableInstance & { getPageInfo(): PageInfo; selectAllVisible(): void }; +/*** 分页条入参:取值一律走读取器,保持响应式依赖收集 ***/ +export interface TablePagerProps { + info: () => PageInfo; // 分页快照读取器(page / pageSize / totalPages) + total: () => number; // 筛选后总行数,只用于播报文案 + loading: () => boolean; // 加载态:翻页与改页大小一并禁用 + pageSizes?: number[]; // 每页条数候选;缺省 [5, 10, 20] + onPage: (page: number) => void; // 翻页回调(入参已按边界钳制) + onPageSize: (pageSize: number) => void; // 改页大小回调 +} + /*** 锚点导航项 ***/ export interface AnchorItem { href: string; // 目标区块地址(#id);对应元素须存在于页面上 diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index 454fb82..47046ac 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -1958,6 +1958,7 @@ "工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行", "表头粘性固定(滚动容器 max-height 420px),斑马纹 + 悬停高亮由 CSS 提供", "数据在途时传 loading(布尔或信号):表格体换成骨架行、筛选与翻页禁用,返回后置回 false 即恢复", + "分页条自带每页条数下拉(5 / 10 / 20 条,调用方传的 pageSize 若不在候选里会自动补进去):切换即改页大小并回第 1 页,首末页时上/下一页置灰不再派发无效页码", "需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用", "需要批量操作时传 selected 信号:内部勾选实时写回,外部置空或赋值即同步整表(跨页保留,按 rowKey 存)", "可排序表头的 aria-sort 跟随排序态同步(ascending / descending / none),读屏能直接播报当前列序,箭头只是视觉补充",