dsh-plugin-core 0.12.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/design/tokens-css.generated.d.ts +1 -1
  2. package/dist/design/tokens-css.generated.d.ts.map +1 -1
  3. package/dist/design/tokens-css.generated.js +1 -1
  4. package/dist/design/tokens-css.generated.js.map +1 -1
  5. package/dist/ui/Pagination.d.ts +59 -0
  6. package/dist/ui/Pagination.d.ts.map +1 -0
  7. package/dist/ui/Pagination.js +110 -0
  8. package/dist/ui/Pagination.js.map +1 -0
  9. package/dist/ui/SectionHead.d.ts +24 -5
  10. package/dist/ui/SectionHead.d.ts.map +1 -1
  11. package/dist/ui/SectionHead.js +32 -5
  12. package/dist/ui/SectionHead.js.map +1 -1
  13. package/dist/ui/Table.d.ts +30 -0
  14. package/dist/ui/Table.d.ts.map +1 -0
  15. package/dist/ui/Table.js +92 -0
  16. package/dist/ui/Table.js.map +1 -0
  17. package/dist/ui/controls/pagination-css.generated.d.ts +2 -0
  18. package/dist/ui/controls/pagination-css.generated.d.ts.map +1 -0
  19. package/dist/ui/controls/pagination-css.generated.js +4 -0
  20. package/dist/ui/controls/pagination-css.generated.js.map +1 -0
  21. package/dist/ui/controls/section-head-css.generated.d.ts +2 -0
  22. package/dist/ui/controls/section-head-css.generated.d.ts.map +1 -0
  23. package/dist/ui/controls/section-head-css.generated.js +4 -0
  24. package/dist/ui/controls/section-head-css.generated.js.map +1 -0
  25. package/dist/ui/controls/table-css.generated.d.ts +2 -0
  26. package/dist/ui/controls/table-css.generated.d.ts.map +1 -0
  27. package/dist/ui/controls/table-css.generated.js +4 -0
  28. package/dist/ui/controls/table-css.generated.js.map +1 -0
  29. package/dist/ui/controls/table-plain-css.generated.d.ts +2 -0
  30. package/dist/ui/controls/table-plain-css.generated.d.ts.map +1 -0
  31. package/dist/ui/controls/table-plain-css.generated.js +4 -0
  32. package/dist/ui/controls/table-plain-css.generated.js.map +1 -0
  33. package/dist/ui/controls-css.d.ts +4 -0
  34. package/dist/ui/controls-css.d.ts.map +1 -1
  35. package/dist/ui/controls-css.js +7 -0
  36. package/dist/ui/controls-css.js.map +1 -1
  37. package/dist/ui/index.d.ts +5 -1
  38. package/dist/ui/index.d.ts.map +1 -1
  39. package/dist/ui/index.js +6 -0
  40. package/dist/ui/index.js.map +1 -1
  41. package/dist/ui/pagination-logic.d.ts +36 -0
  42. package/dist/ui/pagination-logic.d.ts.map +1 -0
  43. package/dist/ui/pagination-logic.js +73 -0
  44. package/dist/ui/pagination-logic.js.map +1 -0
  45. package/dist/ui/table-logic.d.ts +110 -0
  46. package/dist/ui/table-logic.d.ts.map +1 -0
  47. package/dist/ui/table-logic.js +81 -0
  48. package/dist/ui/table-logic.js.map +1 -0
  49. package/dist/ui/table-shared.d.ts +100 -0
  50. package/dist/ui/table-shared.d.ts.map +1 -0
  51. package/dist/ui/table-shared.js +222 -0
  52. package/dist/ui/table-shared.js.map +1 -0
  53. package/dist/utils/i18n.d.ts +1 -1
  54. package/dist/utils/i18n.d.ts.map +1 -1
  55. package/dist/utils/i18n.js +24 -0
  56. package/dist/utils/i18n.js.map +1 -1
  57. package/docs/design/structure.md +5 -3
  58. package/docs/help/ui.doc.ts +221 -2
  59. package/docs/help/ui.md +211 -2
  60. package/docs/requirements.md +47 -0
  61. package/package.json +1 -1
package/docs/help/ui.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  > EN: ui — base controls
6
6
 
7
- 导出:Button、IconButton、Segmented、SplitGroup、Input、PrefixedInput、NumberInput、Textarea、Checkbox、fieldMetricsOf、Picker、WorkspacePicker、ModelPicker、DateTimePicker、Card、StripCard、Chip、Modal、FieldHint、setTooltipRenderer、showToast、notify、showHostToast、Loader、LoaderBar、Loading、RunningBlocks、SectionHead、MarqueeText、LiveText、BackToTop、startResizeLayoutWidth、PanelDock、createPanelDock、DockPanel、ensureControls、setStyleKit
7
+ 导出:Button、IconButton、Segmented、SplitGroup、Input、PrefixedInput、NumberInput、Textarea、Checkbox、fieldMetricsOf、Picker、WorkspacePicker、ModelPicker、DateTimePicker、Card、StripCard、Chip、Table、Pagination、Modal、FieldHint、setTooltipRenderer、showToast、notify、showHostToast、Loader、LoaderBar、Loading、RunningBlocks、SectionHead、MarqueeText、LiveText、BackToTop、startResizeLayoutWidth、PanelDock、createPanelDock、DockPanel、ensureControls、setStyleKit
8
8
 
9
9
  ## 概览
10
10
 
@@ -296,6 +296,215 @@ h(StripCard, {
296
296
  | `className` | `string` | — | 追加 class(只允许补布局,不许覆盖皮肤) | Extra class (layout only, never overrides the skin) |
297
297
  | `children` | `ReactNode` | — | 标签内容(maxWidth + 纯文字时自动接跑马灯) | Tag content (auto-marqueed when maxWidth + plain text) |
298
298
 
299
+ ## Table(表格)
300
+
301
+ > EN: Table
302
+
303
+ 第一档外观 Plain:轻量表格 —— 表头默认是中性灰底(`--dshpc-surface-sunken`,与通栏同一层灰)+ 下边线,双行分组表头也清楚;通栏标题反而用更实的表面色(亮 #f6f8fa / 暗 #151b23);数据行只保留极淡底线,**默认没有一条竖线**;可点行悬浮是极淡的中性灰(亮色 ≈1.5% 淡黑、暗色 ≈3% 淡白),选中行淡蓝底 + 左侧 3px 蓝条。
304
+
305
+ > EN: First skin, Plain: a lightweight table — the header defaults to a neutral grey (--dshpc-surface-sunken, the same wash the banner uses) with a bottom rule that keeps two-row grouped headers legible, while the banner title uses a firmer surface tone (light #f6f8fa / dark #151b23); data rows keep only a very faint bottom line and, by default, **no vertical lines at all**; clickable rows hover to an extremely faint neutral grey (a ~1.5% light black wash in light mode, a ~3% light white wash in dark), and the selected row gets a blue tint plus a 3px blue bar on the left.
306
+
307
+ 三个表级开关:density 三档(compact / default / comfortable,改行高与水平内距 —— default 行高 44 / 48、水平内距 12px,compact 36 / 36 与 8px,comfortable 52 / 60 与 16px)、striped(偶数行淡底斑马纹)、borders(**默认 y**:只留上下两条、没有左右边;all 四边 = 带全框;x 只留左右两条;none 全不要)。
308
+
309
+ > EN: Three table-level switches: `density` (compact / default / comfortable — row height and horizontal padding; default is 44 / 48 rows with 12px padding, compact 36 / 36 with 8px, comfortable 52 / 60 with 16px), `striped` (a faint tint on even rows), and `borders` (**y by default**: top and bottom only, no left/right edges; all for a full frame; x for left+right only; none for nothing).
310
+
311
+ 列级配置:align 居中 / 右对齐,divider=true 才在该列左缘画竖线(**默认不画**,首列恒不画 —— 「列间不要线」是默认值,要线才开;分组列自己不写时跟随它第一个子列,组头与子列的竖线才连成一条),padding 单独调该列水平内距(px),headerTone 给该列表头换语义色(business / success / warning / danger / 扩展八色,全部映射 token,暗色自动跟随)。
312
+
313
+ > EN: Per-column config: `align` for centring / right alignment; `divider=true` draws the divider on that column’s left edge (**off by default**, and the first column never draws one — “no lines between columns” is the default, opt in for lines; a group column that omits it follows its first child, so the header and child dividers line up); `padding` adjusts that column’s horizontal padding in px; `headerTone` recolours that column’s header from the semantic palette (business / success / warning / danger and the eight extended tones — all mapped to tokens, so dark mode follows automatically).
314
+
315
+ 数据驱动、组件不取数:columns 可嵌套 children 形成多行分组表头(colSpan / rowSpan 由组件算);rows 是 { key, cells },cells 按叶子列 key 取内容。单元格有两种写法 —— 直接给任意 ReactNode(头像 + 姓名 + 编号这种多内容排版、状态胶囊、行尾按钮都由调用方塞),或给 { content, colSpan, rowSpan, align } 对象做跨列 / 跨行。跨行时下一行不要给该列 key(缺 key = 该格不渲染、让位给上一行;要空单元格请显式传 null)。传 onRowClick 才可点。
316
+
317
+ > EN: Data-driven, never self-fetching: columns may nest children for a multi-row grouped header (colSpan / rowSpan are computed); rows are { key, cells } looked up by leaf-column key. A cell is either any ReactNode (multi-line layouts such as avatar + name + ID, status pills, row-end buttons are all the caller’s) or a { content, colSpan, rowSpan, align } object for column/row spanning. When spanning rows, the next row must omit that column’s key (a missing key renders nothing and yields the slot to the previous row; pass null explicitly for an empty cell). Rows are clickable only with onRowClick.
318
+
319
+ 通栏分组:传 groups(与 rows 二选一)就能在一个表格里放下多个板块 —— 每组渲染成「一行横跨所有列的通栏标题 → 这一组自己的表头 → 这一组的任务行」,组与组之间用一条实线隔开。典型场景是多个工作区的任务:仍是一张表,不会拆成两张、也不会出现两个互不相干的表头挤在一起;分组模式下没有 <thead>,表头跟着每组走。组内任务行与普通表一样,给 `selectedKeys` / `onRowClick` 就有选中与悬浮(不给就是静态表)。通栏标题由 `SectionHead` 装进通栏并带上它的固定标签(4px 圆头短竖线):颜色用 `groupAccent` 换(内置语义色名映射 token、也可直接给 CSS 色值),传 `false` 整条不画。
320
+
321
+ > EN: Banner groups: pass groups (instead of rows) to hold several sections in one table — each group renders as a full-width banner row spanning every column → that group’s own header → that group’s rows, with a solid rule between groups. The canonical case is tasks across several workspaces: it stays one table rather than two tables with unrelated headers. Group mode has no thead; the header travels with each group. Rows inside a group behave like ordinary rows: pass `selectedKeys` / `onRowClick` for selection and hover (omit them for a static table). The banner title is rendered through `SectionHead`, carrying its fixed 4px rounded bar: recolour it with `groupAccent` (a built-in semantic name mapped to tokens, or a raw CSS colour), or pass `false` to drop it entirely.
322
+
323
+ 外观分档 = 分组件 + 分文件,没有 variant:Table 只 import 自己那份皮肤 CSS;后续 TableGrid(网格线)/ TableZebra(斑马纹)是同族独立组件,各自 import 各自皮肤。消费方引哪个就只打包哪个 —— 引一个表格不会把另一档的 CSS 也带进包里。
324
+
325
+ > EN: Skins are separate components and separate files — no variant: Table imports only its own skin CSS; future TableGrid (grid lines) / TableZebra (zebra stripes) are sibling components importing their own skins. Importing one packs only that one — choosing a table never drags the other skin into the bundle.
326
+
327
+ 边界:不负责取数 / 排序 / 虚拟滚动;横向滚动容器与圆角裁剪归调用方;表头不做吸顶(分组表头是多行,统一 top:0 会在滚动时互相盖住)。分页可选:给 `pagination` 就让表格自己切片并在表下摆页码(详见 Pagination 一节),不给就完全不分页。没有数据时显示内置空态(emptyText 可覆盖)。加载态两种,都只放图标、不显示文案:首屏一条数据都没有时用 `loading`(居中三块加载动画、最大号,较高占位,不遮挡已有数据),已有数据翻下一页时用 `loadingMore`(底部一行、中号三块动画;整行通栏,与上方数据行之间补一条实线,左右两条在 borders=all / x 时由表级外框兜住、y / none 本来就没有,且 y / none 时状态行下方那条表底线也一起去掉,只留上方一条实线);滚动加载由 `onLoadMore` 本身当开关 —— 传了就监听底部、滚到附近自动调一次,到底(`hasMore: false`)显示「没有更多数据」(`noMoreText` 换文案 / `false` 不渲染)。滚动加载与 `pagination` / `groups` 互斥。
328
+
329
+ > EN: Boundaries: no fetching / sorting / virtual scrolling; the horizontal scroll container and rounded clipping are the caller’s; headers do not stick (the grouped header spans multiple rows, and a single top:0 would overlap itself while scrolling). Paging is optional: pass `pagination` and the table slices rows itself and renders the pager underneath (see the Pagination section); omit it and nothing pages. With no rows, a built-in empty state shows (override with emptyText). Two loading states, icon only with no visible copy: `loading` for a first load with no rows at all (centred three-block loader at the largest size, a taller placeholder that never covers existing rows), and `loadingMore` when appending the next page (one full-width bottom row with the middle-size three-block loader; a solid rule is added above it, while the left/right edges come from the table frame when borders=all / x and are absent for y / none — and for y / none the bottom table rule under the status row is dropped too, so only the rule above it remains). Scroll loading uses `onLoadMore` itself as the switch: pass it and the table watches the bottom and calls it once when you get near; at the end (`hasMore: false`) it shows “No more data” (`noMoreText` to change the copy / `false` to render nothing). Scroll loading is mutually exclusive with `pagination` / `groups`.
330
+
331
+ | 参数 | 类型 | 必填 | 默认 | 说明(zh) | 说明(en) |
332
+ |---|---|---|---|---|---|
333
+ | `columns` | `TableColumn[]` | 必填 | — | 列定义;children 非空即分组表头(自动算 colSpan / rowSpan),叶子 key 用来取单元格;width 数字按 px;align 对齐;divider=true 才画该列左竖线(默认不画);padding 该列水平内距(px);headerTone 该列表头语义色 | Column definitions; non-empty children makes a group header (colSpan / rowSpan computed); leaf keys select cells; numeric width means px; align sets alignment; divider=true draws that column’s left divider (off by default); padding sets its horizontal padding (px); headerTone recolours its header |
334
+ | `rows` | `TableRow[]` | — | — | 行数据 { key, cells }(与 groups 二选一):key 兼作选中标识与 React key;cells 按叶子列 key 给内容(ReactNode 或 { content, colSpan, rowSpan, align });缺 key = 不渲染、给上一行 rowSpan 让位 | Row data { key, cells } (either this or groups): key doubles as selection id and React key; cells map leaf-column key to content (ReactNode or { content, colSpan, rowSpan, align }); a missing key renders nothing and yields to the previous row’s rowSpan |
335
+ | `groups` | `TableGroup[]` | — | — | 分组数据 { key, title, rows }(与 rows 二选一,传了就忽略 rows):每组 = 一行横跨全部列的通栏标题 + 自己的表头 + 自己的数据行,多组连续排布、组间一条实线;用于「一个表格展示多个工作区 / 板块」 | Group data { key, title, rows } (instead of rows; when given, rows is ignored): each group is a banner row spanning every column + its own header + its own rows, with groups stacked and separated by a solid rule — for showing several workspaces / sections in one table |
336
+ | `groupAccent` | `SectionHeadAccent \| false` | — | 'default' | 通栏标题前固定标签(4px 圆头短竖线,由 SectionHead 绘制)的颜色(只对 groups 生效):内置语义色名映射 token,也可直接给 CSS 色值;不传 / default = 主题默认(浅色深灰、深色浅灰),false = 整条不画 | Colour of the fixed bar before each banner title (4px rounded, drawn by SectionHead; groups only): a built-in semantic name maps to tokens, or pass any CSS colour; omitted / default = theme default (dark grey in light, light grey in dark), false = no bar |
337
+ | `density` | `'compact' \| 'default' \| 'comfortable'` | — | 'default' | 行密度:行高与水平内距 —— default 44 / 48 与 12px,compact 36 / 36 与 8px,comfortable 52 / 60 与 16px | Row density: height and horizontal padding — default 44 / 48 with 12px, compact 36 / 36 with 8px, comfortable 52 / 60 with 16px |
338
+ | `striped` | `boolean` | — | false | 偶数行淡底斑马纹(hover / 选中仍优先) | Faint tint on even rows (hover / selection still win) |
339
+ | `borders` | `'all' \| 'x' \| 'y' \| 'none'` | — | 'y' | 外框范围:y 只上下(默认,左右没有边)/ all 四边全框 / x 只左右 / none 全不要 | Outer border: y for top+bottom only (default, no left/right) / all for a full frame / x for left+right only / none for nothing |
340
+ | `selectedKeys` | `(string \| number)[]` | — | — | 选中行 key 列表(高亮样式由皮肤决定);有 rowSpan 跨行时,块内任一行的 key 都让整块高亮(不用调用方自己展开) | Keys of selected rows (the highlight is the skin’s call); with a rowSpan block, a key of any row in it highlights the whole block (no expansion needed on the caller side) |
341
+ | `onRowClick` | `(row, index) => void` | — | — | 传了行才可点:获得指针光标与 Enter / 空格激活 | Only when given are rows interactive: pointer cursor plus Enter / Space activation |
342
+ | `emptyText` | `ReactNode` | — | 内置字典 | 空态文案;不传走核心字典(随 setLocale 中英切换) | Empty-state copy; defaults to the core dictionary (follows setLocale) |
343
+ | `pagination` | `TablePaginationConfig` | — | — | 内建分页(与 Pagination 同参数,total 可省):不传 total = 本地切片,给了 total = 服务端分页(不切片);groups 模式忽略本项 | Built-in paging (same params as Pagination, total optional): omit total to slice locally; pass total for server-side paging (no slicing); ignored in groups mode |
344
+ | `loading` | `boolean` | — | false | 整表加载中(首屏一条数据都没有时用):主体换成较高占位(居中三块加载动画,最大号,只有图标没有文案);已有数据时不遮挡数据 | Whole-table loading (first load with no rows): swaps the body for a taller placeholder (centred three-block loader at the largest size, icon only); never covers existing rows |
345
+ | `loadingHeight` | `number` | — | 160 | 整表加载占位区高度(px) | Height (px) of the whole-table loading placeholder |
346
+ | `loadingMore` | `boolean` | — | false | 底部「加载更多」中(已有数据、正在取下一页):数据行下方插一行中号三块加载动画(只有图标没有文案),整行通栏并在上方补一条实线;只在给了 onLoadMore 时生效 | Bottom “loading more” (rows exist, fetching the next page): inserts one full-width row with the middle-size three-block loader (icon only) and a solid rule above it; only effective when onLoadMore is given |
347
+ | `hasMore` | `boolean` | — | true | 是否还有下一页;false 时底部显示「没有更多数据」(noMoreText 可换,false 整行不渲染)。只在给了 onLoadMore 时生效 | Whether another page exists; false shows “No more data” (change with noMoreText, false renders nothing). Only effective when onLoadMore is given |
348
+ | `noMoreText` | `ReactNode` | — | 内置字典 | 到底文案;不传走核心字典,传 false 则到底时整行不渲染 | End-of-list copy; defaults to the core dictionary, false renders nothing at the end |
349
+ | `onLoadMore` | `() => void` | — | — | 滚动到底自动加载下一页:这个 props 本身就是开关 —— 传了就装底部哨兵并监听,不传完全不监听;加载中 / 到底不重复触发。与 pagination / groups 互斥 | Auto-load the next page when scrolled near the bottom: this prop is the switch — pass it to install the sentinel and watch, omit it and nothing listens; guarded while loading or at the end. Mutually exclusive with pagination / groups |
350
+ | `ariaLabel` | `string` | — | — | 表格无障碍名(没有可见标题时请务必给) | Accessible name for the table (give it when there is no visible caption) |
351
+ | `translate` | `Translate` | — | — | 覆盖内置文案翻译(插件自带 i18n 时用) | Override built-in copy translation (for plugins with their own i18n) |
352
+ | `className / style` | `string / CSSProperties` | — | — | 只允许补布局,不许覆盖皮肤 | Layout only; never override the skin |
353
+
354
+ ```tsx id="ui-table-example"
355
+ import { Table, Chip } from 'dsh-plugin-core/ui'
356
+
357
+ const columns = [
358
+ { key: 'id', title: 'Patient ID', width: 140 },
359
+ {
360
+ key: 'patient', title: 'Patient', headerTone: 'business',
361
+ children: [
362
+ // divider:true ⇒ 这一列左缘画竖线(默认不画 —— 表格默认只有横线)
363
+ // 分组列自己不写时跟随第一个子列 ⇒ 组头与子列的竖线连成一条
364
+ { key: 'name', title: 'Name', width: 200, padding: 12, divider: true },
365
+ { key: 'age', title: 'Age / Sex', width: 100, align: 'center', divider: true },
366
+ ],
367
+ },
368
+ { key: 'status', title: 'Status', width: 130, headerTone: 'success', divider: true },
369
+ ]
370
+
371
+ const rows = [{
372
+ key: 'S-2418',
373
+ cells: {
374
+ id: 'S-2418',
375
+ // 单元格可以装任意 ReactNode:头像 + 名字 + 编号的多行排版由调用方决定
376
+ name: h('div', null, h('strong', null, 'Chen, Wei'), h('div', null, 'MRN-88214')),
377
+ age: '62 / M',
378
+ status: h(Chip, { tone: 'success', size: 'xs' }, 'Reviewed'),
379
+ },
380
+ }, {
381
+ // 跨列:对象写法占 id + name 两格
382
+ key: 'S-2420',
383
+ cells: { id: { content: 'S-2420 · escalated', colSpan: 2 }, age: null, status: 'Pending' },
384
+ }]
385
+
386
+ h(Table, {
387
+ columns, rows,
388
+ density: 'compact', // compact / default / comfortable
389
+ striped: true, // 偶数行淡底
390
+ borders: 'all', // y 只上下(默认,无左右边)/ all 四边 / x 只左右 / none
391
+ selectedKeys: [picked],
392
+ onRowClick: (row) => setPicked(row.key),
393
+ })
394
+
395
+ // 通栏分组:一个表格里放多个工作区(groups 与 rows 二选一)
396
+ // 每组 = 横跨全部列的通栏标题 → 这一组自己的表头 → 这一组的任务行,组间一条实线
397
+ h(Table, {
398
+ columns: [
399
+ { key: 'task', title: 'Task', width: 260 },
400
+ { key: 'state', title: 'Status', width: 120, align: 'center' },
401
+ ],
402
+ groups: [
403
+ { key: 'ws-a', title: '工作区 A', rows: [
404
+ { key: 'a1', cells: { task: '修表格斑马纹', state: '进行中' } },
405
+ ] },
406
+ { key: 'ws-b', title: '工作区 B', rows: [
407
+ { key: 'b1', cells: { task: '补通栏文档', state: '已完成' } },
408
+ ] },
409
+ ],
410
+ // 通栏标题前的固定标签(短竖线):由 SectionHead 绘制,4px 圆头、高 ≈ 字号;
411
+ // 默认随主题(浅色深灰 / 深色浅灰);groupAccent 换色(内置色名或任意色值,如 '#7c3aed'),
412
+ // false 整条关掉。Table 只负责把标题装进通栏,样式走 SectionHead 那一份。
413
+ groupAccent: 'business',
414
+ // 组内任务行与普通表一样:给 selectedKeys / onRowClick 就有选中与悬浮
415
+ selectedKeys: [picked],
416
+ onRowClick: (row) => setPicked(row.key),
417
+ ariaLabel: 'Tasks by workspace',
418
+ })
419
+
420
+ // 第二档外观是另一个组件、另一份 CSS(预留):引 TableGrid / TableZebra 时
421
+ // 只打包它自己那份皮肤,不会把 Table 的皮肤一起带上。
422
+ ```
423
+
424
+ ## Pagination(分页)
425
+
426
+ > EN: Pagination
427
+
428
+ 通行口径(Ant Design / MUI / Element Plus 一致):调用方只给 `page`(当前页,1 起)、`pageSize`(每页条数,默认 10)、`total`(总条数,不是总页数),总页数 `ceil(total / pageSize)` 与页码序列都由控件算 —— 消费方不自己算页数、不自己写渲染循环。受控组件:页码由调用方 state 持有,控件只发 `onChange(page, pageSize)`;越界的 page 会被夹回有效范围,不会渲染出空页。
429
+
430
+ > EN: Shared convention (Ant Design / MUI / Element Plus agree): you pass `page` (1-based current page), `pageSize` (default 10) and `total` (row count, not page count), and the control derives `ceil(total / pageSize)` plus the whole page sequence — you never compute pages or write a render loop. Fully controlled: your state owns the page; the control emits `onChange(page, pageSize)` and clamps out-of-range pages so an empty page is never rendered.
431
+
432
+ 省略号窗口:首尾各固定 `boundaryCount` 页、当前页两侧各留 `siblingCount` 页(默认 1 / 1,最多 7 个位置,与 Element Plus 的 pager-count:7 等价)。间隔正好 1 页时直接显示那个页号、不画省略号(各家共同的细节,否则会出现「1 2 … 4」)。上一页 / 下一页恒在,到边界置灰而非隐藏。
433
+
434
+ > EN: Ellipsis window: `boundaryCount` pages pinned at each end and `siblingCount` pages around the current one (default 1 / 1 — at most 7 slots, equivalent to Element Plus pager-count:7). When the gap is exactly one page the number itself is shown instead of an ellipsis (a detail every library shares, otherwise you get “1 2 … 4”). Prev / Next are always present and grey out at the ends rather than disappearing.
435
+
436
+ 四个可选能力都是开关(不传就不出现):`pageSizeOptions` 出「每页条数」下拉(切换时页码回到第 1 页,与 antd / MUI 同口径)、`showTotal` 出总数文案(`true` 走内置字典,也可给函数自定义)、`hideOnSinglePage` 单页时整条不渲染、`simple` 只留「‹ 2 / 36 ›」。位置由 `align` 决定:left / center / right,**默认 right**。
437
+
438
+ > EN: Four optional bits are switches (omit them and they vanish): `pageSizeOptions` adds a rows-per-page select (changing it resets to page 1, same as antd / MUI), `showTotal` adds the total copy (`true` uses the built-in dictionary, or pass a renderer), `hideOnSinglePage` drops the whole bar when there is a single page, and `simple` keeps only “‹ 2 / 36 ›”. Placement comes from `align`: left / center / right, **right by default**.
439
+
440
+ 无障碍:根节点是 `nav`(`aria-label` 默认走内置字典「分页」,可覆盖),当前页带 `aria-current="page"`,每个页码按钮有「第 n 页」的可读名,上一页 / 下一页的文案与禁用态都由控件负责 —— 调用方不用补 aria。
441
+
442
+ > EN: Accessibility: the root is a `nav` (its `aria-label` defaults to the built-in “Pagination”, overridable), the current page carries `aria-current="page"`, every page button has a readable “Page n” name, and prev/next labels plus disabled states are the control’s job — callers add no aria.
443
+
444
+ 与 Table 两种接法:① 独立控件,自己摆在表格下方 / 工具栏(表格不传 `pagination`);② 内建 —— 给 `Table` 传 `pagination`,它先按当前页切片 `rows` 再渲染,并把分页条排在表格下方(`total` 缺省 = `rows.length` 本地分页;给了 `total` 则不切片,按服务端分页只摆页码)。`groups` 通栏模式与分页互斥,传了 groups 就忽略 pagination。
445
+
446
+ > EN: Two ways to wire it to Table: ① standalone — place it yourself under the table or in a toolbar (Table gets no `pagination`); ② built in — pass `pagination` to Table and it slices `rows` by the current page before rendering, with the pager placed underneath (omit `total` for local paging where total = rows.length; pass `total` for server-side paging where rows are not sliced and only the pager is rendered). The `groups` banner mode is mutually exclusive: when groups are given, pagination is ignored.
447
+
448
+ | 参数 | 类型 | 必填 | 默认 | 说明(zh) | 说明(en) |
449
+ |---|---|---|---|---|---|
450
+ | `page / total` | `number / number` | 必填 | — | 当前页(1 起,越界自动夹回)/ 总条数(不是总页数) | Current page (1-based, clamped) / row count (not page count) |
451
+ | `pageSize` | `number` | — | 10 | 每页条数;总页数由它算出来 | Rows per page; the page count derives from it |
452
+ | `onChange` | `(page, pageSize) => void` | 必填 | — | 切页与切每页条数都走它;切条数时页码回到 1 | Fires for page changes and rows-per-page changes; the latter resets to page 1 |
453
+ | `align` | `'left' \| 'center' \| 'right'` | — | 'right' | 分页条位置(默认放右边) | Pager placement (right by default) |
454
+ | `siblingCount / boundaryCount` | `number / number` | — | 1 / 1 | 当前页两侧页数 / 首尾固定页数;1/1 合计最多 7 个位置 | Pages beside the current one / pages pinned at each end; 1/1 gives at most 7 slots |
455
+ | `pageSizeOptions` | `number[]` | — | — | 传了才出现「每页条数」下拉 | Only then does the rows-per-page select appear |
456
+ | `showTotal` | `boolean \| (total, from, to) => ReactNode` | — | — | 总数文案:不传 = 不显示(默认关),传 true 走内置字典,传函数可自定义 | Total copy: omitted = hidden (off by default), true uses the dictionary, a renderer customises it |
457
+ | `hideOnSinglePage` | `boolean` | — | false | 只有一页时整条不渲染 | Render nothing when there is a single page |
458
+ | `simple` | `boolean` | — | false | 简洁模式:只留「‹ 2 / 36 ›」 | Simple mode: only “‹ 2 / 36 ›” |
459
+ | `disabled / ariaLabel / translate` | `boolean / string / Translate` | — | false / 内置「分页」/ — | 整条禁用 / 无障碍名 / 覆盖内置文案 | Disable the bar / accessible name / override built-in copy |
460
+ | `Table · pagination` | `TablePaginationConfig` | — | — | 同上参数,total 可省:不传 = 本地切片,给了 = 服务端分页(不切片) | Same params, total optional: omit to slice locally, pass it for server-side paging (no slicing) |
461
+
462
+ ```tsx id="ui-pagination-example"
463
+ import { Pagination, Table } from 'dsh-plugin-core/ui'
464
+
465
+ // 独立控件:只给当前页 / 每页条数 / 总条数 —— 总页数与省略窗口由控件算,
466
+ // 消费方永远不自己算页数、也不自己渲染页码
467
+ h(Pagination, {
468
+ page, pageSize, total: 86,
469
+ onChange: (nextPage, nextSize) => { setPage(nextPage); setPageSize(nextSize) },
470
+ align: 'right', // left / center / right(默认 right)
471
+ pageSizeOptions: [10, 20, 50], // 传了才出现「每页条数」下拉
472
+ showTotal: true, // 传了才出现「第 1-10 条,共 86 条」
473
+ siblingCount: 1, // 当前页两侧留几页(默认 1)
474
+ boundaryCount: 1, // 首尾各固定几页(默认 1;1/1 合计最多 7 个位置)
475
+ hideOnSinglePage: true, // 只有一页时整条不渲染
476
+ })
477
+
478
+ // 简洁模式:只留上一页 / 下一页与「2 / 36」
479
+ h(Pagination, { page, pageSize, total: 86, simple: true, onChange })
480
+
481
+ // Table 内建:不传 total = 本地分页(rows 是全量,表格自己按当前页切片,页码排在表下)
482
+ h(Table, {
483
+ columns, rows,
484
+ pagination: { page, pageSize, showTotal: true, onChange },
485
+ })
486
+
487
+ // 服务端分页:给了 total 就不切片(rows 只放当前页那几条),表格只负责摆页码
488
+ h(Table, {
489
+ columns, rows: currentPageRows,
490
+ pagination: { page, pageSize, total: 86, onChange },
491
+ })
492
+
493
+ // 加载态(滚动加载,与分页二选一):onLoadMore 本身就是开关 —— 传了就监听底部
494
+ h(Table, {
495
+ columns, rows: [],
496
+ loading: true, // 首屏一条数据都没有:整表高占位(默认 160px)
497
+ loadingHeight: 160,
498
+ })
499
+
500
+ h(Table, {
501
+ columns, rows,
502
+ loadingMore: true, // 正在取下一页:底部一行小转圈
503
+ hasMore: true, // false ⇒ 底部「没有更多数据」;noMoreText: false 可关
504
+ onLoadMore: () => fetchNextPage(), // 滚到接近底部自动触发一次
505
+ })
506
+ ```
507
+
299
508
  ## 反馈件
300
509
 
301
510
  > EN: Feedback
@@ -344,7 +553,7 @@ Modal 是自绘 overlay,不走 react-dom portal。
344
553
  | `LoadingBlock({ text? })` | `` | | 就地渲染的方块加载卡(转圈在上、文字在下) | In-place square loading card (spinner above text) |
345
554
  | `Loading({ anchorId? })` | `` | | 贴到主内容容器右下角的加载指示;anchorId 默认 dshpc-main | Loading indicator pinned to the main container’s bottom-right; anchorId defaults to dshpc-main |
346
555
  | `RunningBlocks()` | `` | | 运行中的占位块 | Placeholder blocks while running |
347
- | `SectionHead({ icon, title, right? })` | `` | | 分区标题 | Section heading |
556
+ | `SectionHead({ icon?, title, right?, accent?, flush? })` | `` | | 分区标题:标签(可选,画出来的短竖线)/ icon + 标题 + 右侧控件。accent = 标签颜色(内置色名 / 任意色值 / default / false 不画);flush = 去底边距 | Section heading: optional drawn bar / icon + title + right controls. accent = bar colour (built-in name / raw value / default / false for none); flush = no bottom margin |
348
557
  | `MarqueeText({ text, style?, title?, className? })` | `` | | 超长省略、hover 滚动 | Ellipsis on overflow, scroll on hover |
349
558
  | `LiveText({ render, style? })` | `` | | 定时重渲染(相对时间这类) | Periodic re-render (for relative time etc.) |
350
559
  | `BackToTop({ anchorId? })` | `` | | 回到顶部,贴主容器右缘外侧 | Back-to-top, outside the main container’s right edge |
@@ -52,6 +52,49 @@ dsh-plugin-core/
52
52
  - 日期时间:`DateTimePicker`(2026-10-24 新增:单值 / 时间段 × 精度 day/hour/minute/second;
53
53
  数据恒完整本地 ISO 到毫秒、**显示只到精度**;起始端 floor、结束端 ceil)
54
54
  - 「统一某类元素」的控件:`SectionHead` 等
55
+ - 表格:`Table`(第一档外观 Plain,2026-10-30 新增并扩展:外框(默认只留上下两条)、中性灰双行分组表头 + 下边线、
56
+ 竖分隔线(列级 opt-in,默认不画)、数据行只留极淡底线、可点行悬浮极浅中性灰、选中行淡色底 + 左侧蓝条)。
57
+ 表头 / 通栏 / 行 hover 走专有 token(2026-10-30 用户二次定案:**表头与通栏互换底色**):
58
+ `--dshpc-table-head-bg` = `--dshpc-surface-sunken`(原来的通栏灰)、
59
+ `--dshpc-table-banner-bg` 亮 `#f6f8fa` / 暗 `#151b23`(原来的表头色)、
60
+ `--dshpc-table-row-hover` 亮 `rgba(15,15,15,.015)` / 暗 `rgba(255,255,255,.03)`
61
+ (用户两次都嫌亮:第一次要「特别浅的灰、不要淡蓝」,第二次要求「再浅一半,特别淡」,
62
+ 暗底上用半透明白、亮底上用半透明黑)。
63
+ 数据驱动 `columns` + `rows` / `groups`,组件不取数;
64
+ **配置面三层**(2026-10-30 用户要求「既要有知识又要方便配置」):
65
+ - 表级 `density`(compact 36/36 + 8px / default 44/48 + 12px / comfortable 52/60 + 16px,
66
+ 前为行高、后者水平内距)、`striped`(斑马纹)、
67
+ `borders`(all 四边 / x 只左右 / y 只上下(**默认**)/ none);
68
+ - 列级 `align`、`divider`(**默认 false = 不画**,给 `true` 才画该列左竖线;首列恒不画;
69
+ 分组列自己不写时跟随它第一个叶子列,保证组头与子列的竖线连成一条 —— 2026-10-30 用户真机
70
+ 指出「蓝底组头没有边、绿底叶子列有边」,正是漏了这条继承)、
71
+ `padding`(该列水平内距 px)、`headerTone`(表头语义色,全映射 token);
72
+ - 单元格级:值可为任意 ReactNode,或 `{ content, colSpan, rowSpan, align }` 做跨列 / 跨行;
73
+ 缺 key = 该格不渲染、给上一行 rowSpan 让位;
74
+ - 结构级 `groups`(与 `rows` 二选一,2026-10-30 用户要的通栏样式):
75
+ 每组 = 一行横跨全部列的**通栏标题** → 这一组自己的表头 → 这一组的数据行,
76
+ 多组垂直连续排布、组间一条实线;通栏与组表头之间也有一条线(2026-10-30 用户要求「那之间也要有 border」)。
77
+ 用于「一个表格展示多个工作区 / 多个板块」。
78
+ 分组模式不渲染 `<thead>`(一个 table 只有一个),表头在每组自己的 `<tbody>` 里;
79
+ - 行态优先级(2026-10-30 真机事故钉死):斑马纹选择器只能到 3 个类级别,
80
+ 皮肤的 hover / 选中必须 4 级 —— 否则基础层后注入时偶数行永远被斑马纹盖住。
81
+ hover 走 `--dshpc-table-row-hover`(中性灰),选中走 `business-soft` + 左侧 3px `business` 条,
82
+ 不要再回到 `--dshpc-open-bg`(2026-10-30 用户:要浅灰不要淡蓝)。
83
+ - **三种外观(2026-10-30 用户定)**:①全框 = `borders:'all'` + 各列 `divider:true`;
84
+ ②左右无边 = `borders:'y'` + 各列 `divider:true`;③**默认** = 不传 `borders`(落 y)且列都不给 `divider`
85
+ —— 所有竖着的边都没有,只剩横线。
86
+ 竖分隔线按列声明(格子上的 `data-divider`),**不用相邻兄弟选择器** —— 后者会让每行首格
87
+ 永远没有左线(即 2026-10-30 真机看到的「Name 左边没 border」),且跨列后会画错位置。
88
+ 外观分档 = **分组件 + 分文件**(`Table` / 预留 `TableGrid` 网格线 / `TableZebra` 斑马纹),
89
+ 消费方引哪个就只打包哪份皮肤 CSS,**不用 `variant` 汇总**。
90
+ **可选内建分页**(2026-10-30 用户拍板「两者都做」):给 `pagination` 就让表格自己按当前页切片并在表下摆页码;
91
+ 不传 = 完全不变(不包外层 div、DOM 与旧行为一致),`groups` 模式忽略分页
92
+ - 分页:`Pagination`(2026-10-30 新增,照通行范式:**传 `page` / `pageSize` / `total`,总页数与省略窗口由控件算**
93
+ ——消费方不自己算页数、不写渲染方法;受控 `onChange(page, pageSize)`)。
94
+ `align` 左 / 中 / 右(**默认 right**);`siblingCount` / `boundaryCount` 控省略窗口(默认 1/1,
95
+ 间隔恰好 1 页时直接显示页号不画省略号);可选能力(用户拍板全要):`hideOnSinglePage`、
96
+ `showTotal`(`boolean | (from,to,total)=>ReactNode`)、`pageSizeOptions`(传了才出现每页条数下拉)、
97
+ `simple`;`disabled` / `ariaLabel` / `translate`。a11y = `nav[aria-label]` + `aria-current="page"`。
55
98
  - 反馈件:`Modal` / `Loader` 家族 / `Loading` / `toast` / 空态
56
99
  - Loader 家族按「一个前缀 + 具象名」命名(2026-10-30 用户定):`Loader`(通用图标)/
57
100
  `LoaderBar`(胶囊不定进度)。两者的边界完全一致 —— **只画图形,不带背景、内距、定位**,
@@ -178,6 +221,8 @@ dsh-plugin-core/
178
221
  | ui / ModelPicker | `variant: 'grouped' \| 'cascade'`(分组:一个下拉、完全独立 / 联动:供应商与模型两个下拉联动;2026-10-20 用户拍板两形态都要);**两种形态 onChange 都发最终模型 value**;「默认模型」选项默认提供(`allowDefault:false` 关),选中发 `''`。默认读 connectHost 喂入的真实目录,`groups` 仅作覆盖。**禁止用数字或无意义名** |
179
222
  | ui / Picker | `mode: 'single' \| 'multiple'`、`max`(到上限已选项仍可反选)、`searchable`(搜索框开关)、`icon`(触发器前置 icon)、选项级 `icon`、`hint`(问号提示内置)、分组、空态、降级 |
180
223
  | ui / DateTimePicker | `mode: 'single' \| 'range'`、`precision: 'day' \| 'hour' \| 'minute' \| 'second'`(同时管弹层步进、显示截断与默认宽度);single 传 `string \| null`、range 传 `[start, end] \| null`(本地 ISO 到毫秒);草稿态「确定」才 onChange,没选日期不传数据;起端补零 / 止端补满(day ⇒ 止 `23:59:59.999`) |
224
+ | ui / Table | `columns`(`children` 非空 = 分组表头,组件算 colSpan / rowSpan;`width` 数字按 px、`align` 控对齐)、`rows`(`{ key, cells }`,cells 按叶子 key 给 ReactNode)、`selectedKeys`、`onRowClick`(传了才可点,Enter / 空格等价)、`emptyText`(默认核心字典)、`ariaLabel`、`pagination`(可选内建分页,见下行);**外观分组件分文件**:`Table` 只 import 自皮肤 CSS,预留 `TableGrid` / `TableZebra` 各自独立,引哪个打包哪个;列宽合计超过容器时由调用方的滚动容器兜住 |
225
+ | ui / Pagination | `Pagination({ page, total, pageSize?, onChange, align?, siblingCount?, boundaryCount?, pageSizeOptions?, showTotal?, hideOnSinglePage?, simple?, disabled?, ariaLabel?, translate? })` —— 只给当前页 / 每页条数 / 总条数,**总页数 `ceil(total/pageSize)`、越界夹取、省略窗口全由控件算**;`align` 默认 `right`;`pageSizeOptions` 传了才出现下拉(改 size 时 `onChange(1, next)`);`hideOnSinglePage` 为 `true` 且只有一页时整条不渲染。<br>`Table` 内建:`pagination?: Omit<PaginationProps,'total'> & { total?: number }` —— **不传 `total` = 本地分页**(`rows` 是全量,表格按当前页切片),**传了 `total` = 服务端分页**(`rows` 只放当前页那几条,不切片) |
181
226
  | explorer | `WorkspaceFilesFace`(`list` / `stat` / `read` 最小面)由 `connectHost(ctx)` 接线后核心自己持有,组件不抓宿主全局、调用方也不逐次传(2026-10-28 改);对外 = 两命令式能力 `openFilePreview({ mount, path })`(侧边栏预览)+ `pickWorkspaceFile({ anchor })`(Promise 选文件,回传工作区相对路径),共用同一 `FileBrowser` 面板(2026-10-20 拍板) |
182
227
  | explorer / WorkspacePicker | `WorkspacePicker({ mode, max, onChange, allowDefault, defaultLabel, … })` —— **无数据参数**:connectHost 接线后自动订官方 `workspaces` 服务快照,选中值为 `workspaceId`;「所有工作区」默认项默认提供、选中发 `''`(2026-10-20) |
183
228
  | session | `SessionPreview({ sessionId, source, variant })` —— `source` = 取数能力接口(会话预览内部有交互:传输文件等,光有静态消息流不够) |
@@ -194,6 +239,8 @@ dsh-plugin-core/
194
239
  | v2+ | `explorer` 重做(2026-10-20) | ✅ 已完成(FileBrowser 面板 + `openFilePreview` / `pickWorkspaceFile` 两能力 + 全量预览:md / 代码高亮 / HTML / PDF / Office;官方件经 `./ui/official` 子路径) |
195
240
  | v3 | `session` | ⚠️ **半完成**:契约与三种承载已好,**官方会话 UI 镜像未搬**(只做最简渲染) |
196
241
  | v3+ | 控件样式按需注入(2026-10-30) | ✅ 已完成(`css.ts` 停全量聚合;27 个组件各自注入自己那份;只 `import { Button }` 时 CSS 文本 71.7 KB → 9.4 KB。**不采用 `@layer`**:未分层样式压过任何层且层级裁决优先于特异性,包进层会被宿主元素级重置反压) |
242
+ | v3+ | 表格样式(2026-10-30) | ✅ 第一档 Plain 落地(`Table`:基础层 + 皮肤分文件注入,中性灰表头 / 极淡行线 / 极淡中性灰 hover;表头与通栏互换底色;**默认无竖线、外框只上下**,竖线按列 opt-in;三种外观 = all+divider / y+divider / 默认)+ 通栏分组 `groups`;预留 `TableGrid` / `TableZebra` 两档同族独立组件 |
243
+ | v3+ | 分页(2026-10-30) | ✅ 已完成(独立 `Pagination` + `Table` 内建 `pagination`;纯逻辑 `pagination-logic.ts` 可单测;默认 right、1/1 窗口、`hideOnSinglePage` / `showTotal` / `pageSizeOptions` / `simple` 全铺;本地 / 服务端分页以「传不传 `total`」分叉) |
197
244
  | 文档 | README 接入指南 | ✅ 已完成 |
198
245
 
199
246
  **未做**(按重要性):消费者插件接入 / 会话 UI 镜像 / 类名隔离 /
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-core",
3
- "version": "0.12.1",
3
+ "version": "0.13.0",
4
4
  "packageManager": "pnpm@12.3.4",
5
5
  "description": "Shared base for DSH plugins: design tokens, UI controls, utils, workspace explorer, session preview",
6
6
  "keywords": [