From 45555428112bf2d6a628440db5caeff2b6a01aa0 Mon Sep 17 00:00:00 2001 From: ui-arch-bot Date: Fri, 23 Oct 2026 17:01:46 +0800 Subject: [PATCH] =?UTF-8?q?iter:=20d19=20dataTable=20=E9=80=89=E4=B8=AD?= =?UTF-8?q?=E6=80=81=E5=8F=97=E6=8E=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ua/backlog.json | 8 ++++++++ .ua/index.json | 10 ++++++++-- .ua/iterations.log | 1 + apps/demo/main.ts | 14 +++++++++++++- llms-full.txt | 4 +++- packages/skin/ui/src/dataTable.ts | 14 ++++++++++++++ packages/skin/ui/src/types.ts | 1 + packages/skin/ui/ua.manifest.json | 8 +++++++- 8 files changed, 55 insertions(+), 5 deletions(-) diff --git a/.ua/backlog.json b/.ua/backlog.json index 3c64d07..975a2c9 100644 --- a/.ua/backlog.json +++ b/.ua/backlog.json @@ -200,6 +200,14 @@ "acceptance": "checkSkinCss():skin-dom 类型包里所有 .css 出现 #hex 字面量即报错并指出文件行号;5 处 #fff 改为 var(--ua-color-bg)(信号色底上的反色,暗色自动翻转);负向测试(临时加 .ua-tmp{color:#fff})能拦下,移除后回归全绿;AGENTS.md 门禁说明同步;npm run check 全绿", "status": "done", "notes": "rgba(...) 未禁:仅用于阴影与遮罩透明度叠加,不承载品牌色,禁掉会逼出更难读的写法(已在函数注释写明边界);实现上先随 python 写文件把正则转义搞坏两次,改用编辑工具直写并做负向验证确认门禁真实生效" + }, + { + "id": "d19", + "title": "dataTable 选中态受控(selected 信号)", + "motivation": "选中集只在内核里,外部拿不到也无法预设,批量操作/清空选择只能靠内部工具栏;与 page 受控桥接形成对称能力", + "acceptance": "DataTableProps 新增 selected?: Signal,与内核选择双向同步(内容比较避免来回派发,外部数组拷贝保护内核);demo 首表传 picked 信号,展示已选计数与「清空选择」按钮;manifest/文档/llms-full.txt 同步;npm run check 全绿", + "status": "done", + "notes": "复用 page 桥接的同一套思路:两个 effect 各管一向,sameKeys 内容比较收敛;内部写回拷贝 [...internal],防外部改动内核数组;demo 需 import type { RowKey } 才能对齐 Signal 的逆变" } ] } diff --git a/.ua/index.json b/.ua/index.json index 4cf45bf..5ccb2de 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-23T09:00:48.440Z", + "generatedAt": "2026-10-23T09:01:45.831Z", "repoRoot": ".", "packages": [ { @@ -1253,6 +1253,11 @@ "n": "loading", "t": "boolean | Signal", "d": "加载态:为真时数据行换成骨架行(行数随 pageSize),筛选输入与翻页按钮临时禁用,容器带 aria-busy" + }, + { + "n": "selected", + "t": "Signal", + "d": "受控选中行信号:与内核选择双向同步(内容比较,不来回派发),供批量操作读取/改写" } ], "example": "dataTable({ data: users, selectable: true, columns: [{ key: 'name', header: '姓名', sortable: true }], pageSize: 5 })", @@ -1266,7 +1271,8 @@ "工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行", "表头粘性固定(滚动容器 max-height 420px),斑马纹 + 悬停高亮由 CSS 提供", "数据在途时传 loading(布尔或信号):表格体换成骨架行、筛选与翻页禁用,返回后置回 false 即恢复", - "需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用" + "需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用", + "需要批量操作时传 selected 信号:内部勾选实时写回,外部置空或赋值即同步整表(跨页保留,按 rowKey 存)" ] } ] diff --git a/.ua/iterations.log b/.ua/iterations.log index ea33f86..1940b87 100644 --- a/.ua/iterations.log +++ b/.ua/iterations.log @@ -23,3 +23,4 @@ {"time": "2026-10-23T16:58:51+08:00", "id": "d16", "result": "done", "summary": "dataTable 消费内核列宽 width:colgroup 固定像素 / flex 自适应,选择列 44px;demo 三列演示宽档;manifest/llms 同步,check 全绿"} {"time": "2026-10-23T16:59:46+08:00", "id": "d17", "result": "done", "summary": "表单错误态打通包裹式控件:focusout/change 取代 blur + aria-invalid + .ua-invalid 后代选择器覆盖 select/radio/slider;demo 加必填 select;check 全绿"} {"time": "2026-10-23T17:00:55+08:00", "id": "d18", "result": "done", "summary": "ua verify 新增皮肤硬编码颜色门禁(#hex 禁止,rgba 仅限阴影/遮罩),清掉 ui.css 5 处 #fff 改令牌;负向测试确认能拦;check 全绿"} +{"time": "2026-10-23T17:01:46+08:00", "id": "d19", "result": "done", "summary": "dataTable 新增 selected 受控信号:与内核选择双向同步(内容比较 + 拷贝保护),demo 展示已选计数与清空;check 全绿"} diff --git a/apps/demo/main.ts b/apps/demo/main.ts index b8a1007..9a89152 100644 --- a/apps/demo/main.ts +++ b/apps/demo/main.ts @@ -30,6 +30,7 @@ import { toast, tooltip, } from '@ui-arch/ui'; +import type { RowKey } from '@ui-arch/ui'; /*** 侧边目录:挂载后从 .sec 反推锚点,IntersectionObserver 高亮视口内区块(清单改动自动跟随) ***/ function buildToc(toc: HTMLElement, content: HTMLElement): void { @@ -109,6 +110,9 @@ const tags = signal(['设计', '前端', '组件库']); // —— 滑块:音量与禁用态演示 —— const volume = signal(40); +// —— 受控选中:dataTable 内部勾选实时写回,外部可清空 —— +const picked = signal([]); + // —— 表格加载态:切换后数据行变骨架行,筛选与翻页临时禁用 —— const tableLoading = signal(false); @@ -392,7 +396,15 @@ const content = h( { key: 'city', header: '城市', align: 'center', width: 'flex' }, ], pageSize: 5, - }), h('div', { class: 'row' }, switchButton({ label: '加载态', checked: tableLoading }))), + selected: picked, + }), h( + 'div', + { class: 'row' }, + switchButton({ label: '加载态', checked: tableLoading }), + badge({ label: '受控选中', variant: 'default' }), + h('span', { class: 'out' }, () => `已选 ${picked().length} 行`), + button({ label: '清空选择', variant: 'ghost', onClick: () => picked([]) }), + )), card( { title: '独立分页器联动(pageSize 3,共用页码信号)' }, dataTable({ diff --git a/llms-full.txt b/llms-full.txt index 81ed03d..c209794 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-23T09:00:48.440Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-23T09:01:45.831Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -695,6 +695,7 @@ const name = signal(''); const f = form({ onSubmit: submit }, [formItem({ label: - selectable(默认 false):boolean —— 启用行选择列:全选三态(含半选)、行 checkbox、已选计数;缺省 false - page:Signal —— 受控页码信号:与内核分页双向同步,传同一个信号给独立 pagination 组件即可联动 - loading:boolean | Signal —— 加载态:为真时数据行换成骨架行(行数随 pageSize),筛选输入与翻页按钮临时禁用,容器带 aria-busy +- selected:Signal —— 受控选中行信号:与内核选择双向同步(内容比较,不来回派发),供批量操作读取/改写 示例: @@ -714,6 +715,7 @@ dataTable({ data: users, selectable: true, columns: [{ key: 'name', header: '姓 8. 表头粘性固定(滚动容器 max-height 420px),斑马纹 + 悬停高亮由 CSS 提供 9. 数据在途时传 loading(布尔或信号):表格体换成骨架行、筛选与翻页禁用,返回后置回 false 即恢复 10. 需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用 +11. 需要批量操作时传 selected 信号:内部勾选实时写回,外部置空或赋值即同步整表(跨页保留,按 rowKey 存) 现场演示:apps/docs/demo.html#sec-table diff --git a/packages/skin/ui/src/dataTable.ts b/packages/skin/ui/src/dataTable.ts index fe1cd60..8cc427a 100644 --- a/packages/skin/ui/src/dataTable.ts +++ b/packages/skin/ui/src/dataTable.ts @@ -60,6 +60,20 @@ export function dataTable(props: DataTableProps): H }); } + // —— 受控选中桥接:外部信号 ↔ 内核选择集;比较内容而非引用,避免来回派发 —— + if (props.selected) { + const selSig = props.selected; + const sameKeys = (a: RowKey[], b: RowKey[]): boolean => a.length === b.length && a.every((k, i) => k === b[i]); + effect(() => { + const external = selSig(); + if (!sameKeys(external, state().selection)) table.dispatch({ type: 'setSelection', keys: external }); // 外部改写 → 内核 + }); + effect(() => { + const internal = state().selection; + if (!sameKeys(internal, selSig())) selSig([...internal]); // 内部勾选 → 写回外部(拷贝避免外部改到内核数组) + }); + } + const filterKey = props.filterKey ?? props.columns[0].key; const colspan = props.columns.length + (selectable ? 1 : 0); diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index da31f69..c728bdd 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -54,6 +54,7 @@ export interface DataTableProps { selectable?: boolean; // 是否启用行选择列(含全选三态与已选计数);缺省 false page?: Signal; // 受控页码信号:与内核分页双向同步,可接独立 pagination 组件联动 loading?: boolean | Signal; // 加载态:为真时数据行换成骨架行,筛选与翻页禁用 + selected?: Signal; // 受控选中行信号:与内核选择双向同步,供批量操作读取 } /*** 选项条目:select / radioGroup / tabs 共用的选项形状 ***/ diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index f48e1e1..c47162d 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -1124,6 +1124,11 @@ "n": "loading", "t": "boolean | Signal", "d": "加载态:为真时数据行换成骨架行(行数随 pageSize),筛选输入与翻页按钮临时禁用,容器带 aria-busy" + }, + { + "n": "selected", + "t": "Signal", + "d": "受控选中行信号:与内核选择双向同步(内容比较,不来回派发),供批量操作读取/改写" } ], "example": "dataTable({ data: users, selectable: true, columns: [{ key: 'name', header: '姓名', sortable: true }], pageSize: 5 })", @@ -1137,7 +1142,8 @@ "工具栏输入框即筛选,输入即时过滤并回第 1 页;无匹配时显示空状态行", "表头粘性固定(滚动容器 max-height 420px),斑马纹 + 悬停高亮由 CSS 提供", "数据在途时传 loading(布尔或信号):表格体换成骨架行、筛选与翻页禁用,返回后置回 false 即恢复", - "需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用" + "需要独立分页器联动时传 page 信号:const page = signal(1),dataTable({ page }) 与 pagination({ page }) 共用", + "需要批量操作时传 selected 信号:内部勾选实时写回,外部置空或赋值即同步整表(跨页保留,按 rowKey 存)" ] } ]