@manohub/ui 1.0.5 → 1.0.8

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 (84) hide show
  1. package/README.md +8 -2
  2. package/dist/components/badge/index.d.ts +24 -46
  3. package/dist/components/badge/index.js +7 -11
  4. package/dist/components/button/index.d.ts +11 -31
  5. package/dist/components/capsule/index.d.ts +13 -27
  6. package/dist/components/card/index.d.ts +21 -54
  7. package/dist/components/checkbox/index.d.ts +1 -9
  8. package/dist/components/code-block/index.d.ts +35 -62
  9. package/dist/components/collapse/index.d.ts +2 -0
  10. package/dist/components/dialog/index.d.ts +6 -15
  11. package/dist/components/divider/index.d.ts +1 -12
  12. package/dist/components/drawer/index.d.ts +7 -15
  13. package/dist/components/filter/index.d.ts +27 -69
  14. package/dist/components/filter/index.js +3 -8
  15. package/dist/components/form/index.d.ts +52 -132
  16. package/dist/components/form/index.js +5 -12
  17. package/dist/components/input/bordered.d.ts +4 -6
  18. package/dist/components/input/index.d.ts +47 -66
  19. package/dist/components/input/index.js +7 -8
  20. package/dist/components/input/number-input.d.ts +11 -23
  21. package/dist/components/layout/index.d.ts +21 -28
  22. package/dist/components/layout/index.js +5 -8
  23. package/dist/components/list-view/index.d.ts +10 -43
  24. package/dist/components/loading/index.d.ts +8 -19
  25. package/dist/components/nav/index.d.ts +14 -35
  26. package/dist/components/nav/index.js +1 -3
  27. package/dist/components/notice/index.d.ts +13 -33
  28. package/dist/components/notice/index.js +3 -5
  29. package/dist/components/notification/index.d.ts +0 -9
  30. package/dist/components/page/index.d.ts +8 -43
  31. package/dist/components/page/split-resize.d.ts +9 -18
  32. package/dist/components/pagination/index.d.ts +1 -5
  33. package/dist/components/panel/index.d.ts +27 -62
  34. package/dist/components/panel/index.js +1 -4
  35. package/dist/components/part-padding.d.ts +5 -13
  36. package/dist/components/query-state/index.d.ts +8 -32
  37. package/dist/components/radio/index.d.ts +2 -3
  38. package/dist/components/search/index.d.ts +12 -32
  39. package/dist/components/select/index.d.ts +7 -14
  40. package/dist/components/select/index.js +3 -3
  41. package/dist/components/select/select-panel.d.ts +23 -35
  42. package/dist/components/select/select-panel.js +5 -10
  43. package/dist/components/select/select-shared.d.ts +18 -44
  44. package/dist/components/select/select-tree.d.ts +11 -16
  45. package/dist/components/select/select-trigger.d.ts +6 -16
  46. package/dist/components/select/select-trigger.js +5 -11
  47. package/dist/components/skeleton/index.d.ts +1 -5
  48. package/dist/components/steps/index.d.ts +55 -111
  49. package/dist/components/steps/index.js +8 -16
  50. package/dist/components/switch/index.d.ts +17 -33
  51. package/dist/components/switch/index.js +2 -4
  52. package/dist/components/tabbar/index.d.ts +1 -6
  53. package/dist/components/table/index.d.ts +43 -105
  54. package/dist/components/table/index.js +166 -125
  55. package/dist/components/tabs/index.d.ts +22 -44
  56. package/dist/components/text/index.d.ts +10 -21
  57. package/dist/components/toast/index.d.ts +4 -13
  58. package/dist/components/tooltip/index.d.ts +1 -12
  59. package/dist/components/tree/index.d.ts +35 -77
  60. package/dist/components/tree/index.js +17 -33
  61. package/dist/components/upload/index.d.ts +1 -4
  62. package/dist/index.d.ts +3 -3
  63. package/dist/services/base.d.ts +32 -8
  64. package/dist/services/dialog.d.ts +10 -22
  65. package/dist/services/index.d.ts +2 -2
  66. package/dist/services/loading.d.ts +9 -12
  67. package/dist/services/loading.js +8 -1
  68. package/dist/services/toast.d.ts +4 -14
  69. package/dist/styles/components/button.css +2 -0
  70. package/dist/styles/components/capsule.css +2 -0
  71. package/dist/styles/components/checkbox.css +2 -0
  72. package/dist/styles/components/form.css +23 -28
  73. package/dist/styles/components/input.css +53 -71
  74. package/dist/styles/components/notice.css +2 -0
  75. package/dist/styles/components/page.css +25 -31
  76. package/dist/styles/components/pagination.css +4 -0
  77. package/dist/styles/components/radio.css +2 -0
  78. package/dist/styles/components/search.css +8 -10
  79. package/dist/styles/components/switch.css +2 -0
  80. package/dist/styles/components/tabbar.css +2 -0
  81. package/dist/styles/components/table.css +72 -58
  82. package/dist/styles/components/table.tokens.css +1 -1
  83. package/dist/styles/components/upload.css +2 -0
  84. package/package.json +2 -2
@@ -13,6 +13,8 @@ export interface TableColumn {
13
13
  * - `'120px'` —— 固定宽(`fixed` 下是硬宽,**容器变窄也不收缩**,可能横向滚动)
14
14
  * - `'20%'` —— 百分比(**吃满 + 严格按比例 + 任意容器宽不溢出**,响应式首选)
15
15
  * - 不传 —— 单用时吃剩余/等分;**与 `px` 混用会被压到 0**,不建议
16
+ *
17
+ * 用法见 `docs/demos/table/`(每个维度一份可运行样例;文档页 `docs/components/table.md`)。
16
18
  */
17
19
  width?: string | number;
18
20
  align?: 'left' | 'center' | 'right';
@@ -30,11 +32,9 @@ export interface TableColumn {
30
32
  */
31
33
  wrap?: boolean;
32
34
  /**
33
- * 列下限(px):防止窄容器把该列压没。**给了才下发 `min-width`** ——
34
- * ⚠️ 它**不是硬下限**:实测(Chrome)`<col>` 上的 `min-width` 会被当成**分配权重**参与按比例
35
- * 分摊 —— 800 宽容器下 `width:50% + min-width:500px` 的列实测只有 **444**,不是 500。
36
- * 想要真下限得换机制(该列直接给 px 硬宽,或把内容包一层带 `min-width` 的盒)。
37
- * 只在调用方显式要时才下发(`<col>` 上不写时不会出现)。
35
+ * 列下限(px):防止窄容器把该列压没。**给了才下发 `min-width`**(`<col>` 上不写就不会出现)。⚠️ **不是硬下限**:
36
+ * 实测 `<col>` 上的 `min-width` 会被当成**分配权重**参与按比例分摊 —— 800 宽容器下 `width:50% + min-width:500px`
37
+ * 的列只有 **444**。想要真下限得换机制(该列直接给 px 硬宽,或把内容包一层带 `min-width` 的盒)。
38
38
  */
39
39
  minWidth?: number;
40
40
  /** 必填列:表头加星号 */
@@ -43,6 +43,8 @@ export interface TableColumn {
43
43
  sortable?: boolean;
44
44
  }
45
45
  export type TableSortOrder = 'asc' | 'desc' | null;
46
+ /** 满高档取值:不传 / `false` = 高度由内容撑起;`true` / `'parent'` = 占满父级;`number` = 表体滚动上限(px) */
47
+ export type TableFill = boolean | 'parent' | number;
46
48
  /** 行数据:至少能用 `rowKey` 取到稳定标识 */
47
49
  export type TableRow = Record<string, unknown>;
48
50
  /** 行级事件(`onRow` 的返回):给整行挂点击与类,省掉「每列都写一次 onClick」 */
@@ -61,15 +63,6 @@ export interface TableRowProps {
61
63
  * **回写是受控的**:`setValue(next)` 只把「这一格变成了什么」上报出去
62
64
  * (= 发一次 `onCellChange(row, column.key, next, index)`)。本件**从不改 `data`** ——
63
65
  * 新数组与「这次改动该不该被接受」都由调用方决定,与行选中同一套受控口径。
64
- *
65
- * @example 编辑格:套 `Form.Item` 拿错误位,控件自己绑值、回写走 `setValue`
66
- * <Table :columns="columns" :data="rows" row-key="id" layout="fixed" :on-cell-change="write">
67
- * <template #cell="{ row, column, value, index, setValue }">
68
- * <!-- 格子里的框由控件自己带:默认有框 / `:bordered="false"` 无框 / `bordered="focus"` 聚焦才显框 -->
69
- * <Input v-if="column.key === 'name'" :model-value="String(value ?? '')" @change="setValue" />
70
- * <template v-else>{{ value }}</template>
71
- * </template>
72
- * </Table>
73
66
  */
74
67
  export interface TableCellSlotScope {
75
68
  row: TableRow;
@@ -80,28 +73,17 @@ export interface TableCellSlotScope {
80
73
  /** 上报该格的新值(= 发一次 `onCellChange`);调用方没接 `onCellChange` 时是空操作 */
81
74
  setValue: (next: unknown) => void;
82
75
  }
83
- /**
84
- * 行级可勾选判定(`selectable` 的函数形态):返回 `false` 的行渲染**禁用态**勾选框,
85
- * 且不进表头全选的增删范围。签名与 `onRow` / `rowClass` 一致(都给 `index`)。
86
- */
76
+ /** 行级可勾选判定(`selectable` 的函数形态):返回 `false` 的行渲染**禁用态**勾选框,且不进表头全选的增删范围;签名与 `onRow` / `rowClass` 一致(都给 `index`)。 */
87
77
  export type TableRowSelectable = (row: TableRow, index: number) => boolean;
88
- /** 分页档:`false` 不渲染;给对象就渲染分页条(**本件不切片,`data` 是当前页数据**) */
89
- export interface TablePagination {
90
- total: number;
91
- current?: number;
92
- pageSize?: number;
93
- pageSizeOptions?: number[];
94
- onChange?: (page: number, pageSize: number) => void;
95
- }
96
78
  /**
97
- * 表格(**自绘**;列 × 数据的表格骨架 + 选择列 + 展开行 + 排序事件 + 分页 + 三态)。
79
+ * 表格(**自绘**;列 × 数据的表格骨架 + 选择列 + 展开行 + 排序事件 + 三态)。
98
80
  *
99
81
  * ## 这个组件负责什么、不负责什么(都是刻意的)
100
82
  *
101
83
  * **负责**:把 `columns` × `data` 渲成表格;列对齐 / 宽度 / 省略 / 必填;行选中(勾选列复用本包的
102
84
  * `Checkbox` 组件 —— 它内部才是真实 `<input type="checkbox">`,故面上的
103
85
  * `tr:has(.mh-table__col-check input:checked)` 零 JS 路径仍成立;选择器**限定在勾选列内**,否则功能列的
104
- * `Switch` 也会把整行点亮);展开行;排序按钮的**状态与事件**;加载态与空态;分页条的排布与事件透传;
86
+ * `Switch` 也会把整行点亮);展开行;排序按钮的**状态与事件**;加载态与空态;
105
87
  * `rowClass` / `onRow` 让调用方标分组行、禁用行与挂行级点击。
106
88
  *
107
89
  * **不负责**:
@@ -109,38 +91,8 @@ export interface TablePagination {
109
91
  * - **排序算法归调用方**(列宽拖拽同理):规范只给了 `↕` 字形与拖拽线两处视觉,而 `↕` 是文字符号(本包禁
110
92
  * 拿文字符号冒充图标),故朝向改用现成字形 `chevron-up` / `chevron-down` 表达 —— 本件发 `sort` 事件、
111
93
  * 画态,真正排序 / 改列宽由调用方决定(它有数据、也知道业务口径);
112
- * - **分页切片**:`pagination` 只渲染分页条并透传页码事件,**`data` 始终是当前页数据** —— 组件自己切片会
113
- * 让「服务端分页」与「客户端分页」两套口径藏在同一个 prop 里;
94
+ * - **分页条**:本件不内建(`data` 始终是给它的那页数据),页码状态、切片与分页条的摆放都归调用方;
114
95
  * - **错误态**:归 `QueryState`(表格只管列数据)。
115
- *
116
- * @example 基本列表(表默认满宽;列宽给权重 + `fixed` 才严格按比例)
117
- * <Table
118
- * layout="fixed" striped row-key="id" :data="rows"
119
- * :columns="[
120
- * { key: 'name', title: '名称', width: 3 },
121
- * { key: 'amount', title: '金额', width: 1, align: 'right' },
122
- * { key: 'description', title: '描述', width: 4, wrap: true },
123
- * ]"
124
- * :pagination="{ total: 120, current: page, onChange: (p) => (page = p) }"
125
- * />
126
- *
127
- * @example 操作列(用 `actions` 插槽 + 一列 `align:'right'`;行内操作同族,`Button.Group` 收口)
128
- * <Table :columns="columns" :data="rows" row-key="id">
129
- * {{
130
- * actions: ({ row }) => (
131
- * <Button.Group>
132
- * <Button variant="text" onClick={() => open(row)}>编辑</Button>
133
- * <Button variant="text" danger onClick={() => remove(row)}>删除</Button>
134
- * </Button.Group>
135
- * ),
136
- * }}
137
- * </Table>
138
- *
139
- * @example 单元格自定义 + 按行判定可勾选
140
- * <Table :columns="columns" :data="rows" row-key="id" v-model="selected"
141
- * :selectable="(row) => row.status === 'draft'">
142
- * {{ cell: ({ value, column }) => column.key === 'status' ? <Text tone="success">{value}</Text> : value }}
143
- * </Table>
144
96
  */
145
97
  export declare const Table: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
146
98
  columns: {
@@ -181,11 +133,8 @@ export declare const Table: import("vue").DefineComponent<import("vue").ExtractP
181
133
  default: boolean;
182
134
  };
183
135
  /**
184
- * 单元格值变更 —— 由 `#cell` 插槽里的 `setValue` 触发,是**唯一的回写通道**。
185
- *
186
- * 本件**不改 `data`**(与本仓表格一贯的克制口径一致:全受控、只发事件、不排序、不切片);
187
- * 要不要接受、以及新数组怎么产生,都由调用方决定。
188
- * 没接这个回调时 `setValue` 是空操作(插槽里的控件照旧可绑,不会报错)。
136
+ * 单元格值变更 —— 由 `#cell` 插槽里的 `setValue` 触发,是**唯一的回写通道**。本件**不改 `data`**(全受控、只发
137
+ * 事件、不排序、不切片);要不要接受、新数组怎么产生都由调用方决定;没接这个回调时 `setValue` 是空操作。
189
138
  */
190
139
  onCellChange: {
191
140
  type: PropType<(row: TableRow, key: string, value: unknown, index: number) => void>;
@@ -216,9 +165,19 @@ export declare const Table: import("vue").DefineComponent<import("vue").ExtractP
216
165
  type: BooleanConstructor;
217
166
  default: boolean;
218
167
  };
219
- /** 外框交给包裹层(满高表格用) */
168
+ /**
169
+ * 满高档(高度与滚动归属;详见 `docs/components/table.md` 的「满高与滚动」):
170
+ *
171
+ * - 不传 / `false` —— 高度由内容撑起(单表结构,与不带本 prop 时完全一致);
172
+ * - `true` / `'parent'` —— 占满父级高度(`height: 100%`),**表体滚、表头固定在表体上方**,
173
+ * 外框与圆角归包裹层;父级需可确定高度(`auto` 父级下 `100%` 解析不出来);
174
+ * - `number` —— 表体滚动上限 = 该像素值,**父级高度不确定时的显式兜底档**
175
+ * (CSS 无法探测父级是否定高,故兜底只能由调用方声明)。
176
+ *
177
+ * 满高档一律按 `table-layout: fixed` 渲染(表头与表体是两个表格盒,`auto` 各自算宽必然错列)。
178
+ */
220
179
  fill: {
221
- type: BooleanConstructor;
180
+ type: PropType<TableFill>;
222
181
  default: boolean;
223
182
  };
224
183
  /**
@@ -234,10 +193,6 @@ export declare const Table: import("vue").DefineComponent<import("vue").ExtractP
234
193
  type: PropType<"auto" | "fixed">;
235
194
  default: string;
236
195
  };
237
- stickyHead: {
238
- type: BooleanConstructor;
239
- default: boolean;
240
- };
241
196
  stickyCol: {
242
197
  type: BooleanConstructor;
243
198
  default: boolean;
@@ -296,11 +251,6 @@ export declare const Table: import("vue").DefineComponent<import("vue").ExtractP
296
251
  type: PropType<(keys: Array<string | number>) => void>;
297
252
  default: undefined;
298
253
  };
299
- /** 分页档:`false` 不渲染底部分页条 */
300
- pagination: {
301
- type: PropType<false | TablePagination>;
302
- default: boolean;
303
- };
304
254
  }>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
305
255
  /** 选中行 key 列表(`v-model`) */
306
256
  'update:modelValue': (keys: Array<string | number>) => boolean;
@@ -350,11 +300,8 @@ export declare const Table: import("vue").DefineComponent<import("vue").ExtractP
350
300
  default: boolean;
351
301
  };
352
302
  /**
353
- * 单元格值变更 —— 由 `#cell` 插槽里的 `setValue` 触发,是**唯一的回写通道**。
354
- *
355
- * 本件**不改 `data`**(与本仓表格一贯的克制口径一致:全受控、只发事件、不排序、不切片);
356
- * 要不要接受、以及新数组怎么产生,都由调用方决定。
357
- * 没接这个回调时 `setValue` 是空操作(插槽里的控件照旧可绑,不会报错)。
303
+ * 单元格值变更 —— 由 `#cell` 插槽里的 `setValue` 触发,是**唯一的回写通道**。本件**不改 `data`**(全受控、只发
304
+ * 事件、不排序、不切片);要不要接受、新数组怎么产生都由调用方决定;没接这个回调时 `setValue` 是空操作。
358
305
  */
359
306
  onCellChange: {
360
307
  type: PropType<(row: TableRow, key: string, value: unknown, index: number) => void>;
@@ -385,9 +332,19 @@ export declare const Table: import("vue").DefineComponent<import("vue").ExtractP
385
332
  type: BooleanConstructor;
386
333
  default: boolean;
387
334
  };
388
- /** 外框交给包裹层(满高表格用) */
335
+ /**
336
+ * 满高档(高度与滚动归属;详见 `docs/components/table.md` 的「满高与滚动」):
337
+ *
338
+ * - 不传 / `false` —— 高度由内容撑起(单表结构,与不带本 prop 时完全一致);
339
+ * - `true` / `'parent'` —— 占满父级高度(`height: 100%`),**表体滚、表头固定在表体上方**,
340
+ * 外框与圆角归包裹层;父级需可确定高度(`auto` 父级下 `100%` 解析不出来);
341
+ * - `number` —— 表体滚动上限 = 该像素值,**父级高度不确定时的显式兜底档**
342
+ * (CSS 无法探测父级是否定高,故兜底只能由调用方声明)。
343
+ *
344
+ * 满高档一律按 `table-layout: fixed` 渲染(表头与表体是两个表格盒,`auto` 各自算宽必然错列)。
345
+ */
389
346
  fill: {
390
- type: BooleanConstructor;
347
+ type: PropType<TableFill>;
391
348
  default: boolean;
392
349
  };
393
350
  /**
@@ -403,10 +360,6 @@ export declare const Table: import("vue").DefineComponent<import("vue").ExtractP
403
360
  type: PropType<"auto" | "fixed">;
404
361
  default: string;
405
362
  };
406
- stickyHead: {
407
- type: BooleanConstructor;
408
- default: boolean;
409
- };
410
363
  stickyCol: {
411
364
  type: BooleanConstructor;
412
365
  default: boolean;
@@ -465,11 +418,6 @@ export declare const Table: import("vue").DefineComponent<import("vue").ExtractP
465
418
  type: PropType<(keys: Array<string | number>) => void>;
466
419
  default: undefined;
467
420
  };
468
- /** 分页档:`false` 不渲染底部分页条 */
469
- pagination: {
470
- type: PropType<false | TablePagination>;
471
- default: boolean;
472
- };
473
421
  }>> & Readonly<{
474
422
  onSort?: ((payload: {
475
423
  key: string;
@@ -478,7 +426,7 @@ export declare const Table: import("vue").DefineComponent<import("vue").ExtractP
478
426
  onChange?: ((keys: (string | number)[]) => any) | undefined;
479
427
  "onUpdate:modelValue"?: ((keys: (string | number)[]) => any) | undefined;
480
428
  }>, {
481
- fill: boolean;
429
+ fill: TableFill;
482
430
  layout: "fixed" | "auto";
483
431
  size: ControlSize;
484
432
  compact: boolean;
@@ -494,7 +442,6 @@ export declare const Table: import("vue").DefineComponent<import("vue").ExtractP
494
442
  selectable: boolean | TableRowSelectable;
495
443
  striped: boolean;
496
444
  onCellChange: (row: TableRow, key: string, value: unknown, index: number) => void;
497
- stickyHead: boolean;
498
445
  stickyCol: boolean;
499
446
  showHeader: boolean;
500
447
  sortKey: string;
@@ -502,24 +449,15 @@ export declare const Table: import("vue").DefineComponent<import("vue").ExtractP
502
449
  rowClass: (row: TableRow, index: number) => string;
503
450
  onRow: (row: TableRow, index: number) => TableRowProps;
504
451
  expandable: (row: TableRow, index: number) => VNodeChild;
505
- pagination: false | TablePagination;
506
452
  }, SlotsType<{
507
- /**
508
- * 单元格:`{ row, column, value, index, setValue }`(不给则直接输出原值)。
509
- *
510
- * **编辑格也走这里**(见 `TableCellSlotScope` 的说明):本件不替调用方渲染控件,
511
- * 只给值、给位置、给一个受控的回写口 `setValue`。
512
- */
453
+ /** 单元格插槽:`{ row, column, value, index, setValue }`(不给则直接输出原值)。**编辑格也走这里** —— 本件不替调用方渲染控件,只给值、给位置、给一个受控的回写口。 */
513
454
  cell?: (context: TableCellSlotScope) => VNodeChild;
514
- /** 行尾操作区:`{ row, index }`(面里用 `align: 'right'` 的列承载) */
515
- actions?: (context: {
516
- row: TableRow;
517
- index: number;
518
- }) => VNodeChild;
519
455
  /** 批量栏:选中非空时出现在表格上方(`{ selected }`) */
520
456
  batch?: (context: {
521
457
  selected: Array<string | number>;
522
458
  }) => VNodeChild;
523
459
  /** 空态自定义(插画 + 操作按钮);不给则渲染 `emptyText` 一行 */
524
460
  empty?: () => VNodeChild;
461
+ /** 合计行:渲染在滚动区之外(表格下方),容器与标签 / 数值子样式见 `.mh-table__summary*` */
462
+ summary?: () => VNodeChild;
525
463
  }>, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -1,7 +1,6 @@
1
- import { defineComponent, computed, ref, createVNode } from "vue";
1
+ import { defineComponent, computed, ref, onMounted, onUpdated, createVNode } from "vue";
2
2
  import { Icon } from "@manohub/icon";
3
3
  import { Checkbox } from "../checkbox/index.js";
4
- import { Pagination } from "../pagination/index.js";
5
4
  const Table = /* @__PURE__ */ defineComponent({
6
5
  name: "Table",
7
6
  inheritAttrs: false,
@@ -44,11 +43,8 @@ const Table = /* @__PURE__ */ defineComponent({
44
43
  default: false
45
44
  },
46
45
  /**
47
- * 单元格值变更 —— 由 `#cell` 插槽里的 `setValue` 触发,是**唯一的回写通道**。
48
- *
49
- * 本件**不改 `data`**(与本仓表格一贯的克制口径一致:全受控、只发事件、不排序、不切片);
50
- * 要不要接受、以及新数组怎么产生,都由调用方决定。
51
- * 没接这个回调时 `setValue` 是空操作(插槽里的控件照旧可绑,不会报错)。
46
+ * 单元格值变更 —— 由 `#cell` 插槽里的 `setValue` 触发,是**唯一的回写通道**。本件**不改 `data`**(全受控、只发
47
+ * 事件、不排序、不切片);要不要接受、新数组怎么产生都由调用方决定;没接这个回调时 `setValue` 是空操作。
52
48
  */
53
49
  onCellChange: {
54
50
  type: Function,
@@ -79,9 +75,19 @@ const Table = /* @__PURE__ */ defineComponent({
79
75
  type: Boolean,
80
76
  default: true
81
77
  },
82
- /** 外框交给包裹层(满高表格用) */
78
+ /**
79
+ * 满高档(高度与滚动归属;详见 `docs/components/table.md` 的「满高与滚动」):
80
+ *
81
+ * - 不传 / `false` —— 高度由内容撑起(单表结构,与不带本 prop 时完全一致);
82
+ * - `true` / `'parent'` —— 占满父级高度(`height: 100%`),**表体滚、表头固定在表体上方**,
83
+ * 外框与圆角归包裹层;父级需可确定高度(`auto` 父级下 `100%` 解析不出来);
84
+ * - `number` —— 表体滚动上限 = 该像素值,**父级高度不确定时的显式兜底档**
85
+ * (CSS 无法探测父级是否定高,故兜底只能由调用方声明)。
86
+ *
87
+ * 满高档一律按 `table-layout: fixed` 渲染(表头与表体是两个表格盒,`auto` 各自算宽必然错列)。
88
+ */
83
89
  fill: {
84
- type: Boolean,
90
+ type: [Boolean, String, Number],
85
91
  default: false
86
92
  },
87
93
  /**
@@ -97,10 +103,6 @@ const Table = /* @__PURE__ */ defineComponent({
97
103
  type: String,
98
104
  default: "auto"
99
105
  },
100
- stickyHead: {
101
- type: Boolean,
102
- default: false
103
- },
104
106
  stickyCol: {
105
107
  type: Boolean,
106
108
  default: false
@@ -158,11 +160,6 @@ const Table = /* @__PURE__ */ defineComponent({
158
160
  onExpandChange: {
159
161
  type: Function,
160
162
  default: void 0
161
- },
162
- /** 分页档:`false` 不渲染底部分页条 */
163
- pagination: {
164
- type: [Boolean, Object],
165
- default: false
166
163
  }
167
164
  },
168
165
  emits: {
@@ -245,121 +242,165 @@ const Table = /* @__PURE__ */ defineComponent({
245
242
  return "asc";
246
243
  };
247
244
  const sortGlyph = (key) => props.sortKey === key && props.sortOrder === "asc" ? "chevron-up" : "chevron-down";
248
- return () => {
249
- const tableClass = ["mh-table", resolvedSize.value === "sm" ? "mh-table--xs" : "", resolvedSize.value === "lg" ? "mh-table--lg" : "", props.bordered ? "" : "mh-table--borderless", props.fill ? "mh-table--fill" : "", props.layout === "fixed" ? "mh-table--fixed" : "", props.stickyHead ? "mh-table--sticky-head" : "", props.stickyCol ? "mh-table--sticky-col" : "", attrs.class].filter(Boolean).join(" ");
250
- const pager = typeof props.pagination === "object" ? props.pagination : null;
251
- return createVNode("div", {
252
- "class": "mh-table-wrapper",
253
- "style": attrs.style
254
- }, [slots.batch && selected.value.length > 0 ? createVNode("div", {
255
- "class": "mh-table__batch"
256
- }, [slots.batch({
257
- selected: selected.value
258
- })]) : null, createVNode("table", {
259
- "class": tableClass
260
- }, [colElements(), props.showHeader ? createVNode("thead", null, [createVNode("tr", null, [props.expandable ? createVNode("th", {
261
- "class": "mh-table__col-expand",
262
- "aria-label": "展开"
263
- }, null) : null, hasSelectColumn.value ? createVNode("th", {
264
- "class": "mh-table__col-check"
265
- }, [createVNode(Checkbox, {
266
- "aria-label": "全选",
267
- "modelValue": allSelected.value,
268
- "indeterminate": someSelected.value && !allSelected.value,
269
- "disabled": headerCheckboxDisabled.value,
270
- "onChange": toggleAll
271
- }, null)]) : null, props.columns.map((column) => createVNode("th", {
272
- "key": column.key,
273
- "class": [column.required ? "mh-table__cell-required" : "", column.align === "center" ? "mh-table__cell-center" : "", column.align === "right" ? "mh-table__col-actions" : ""].filter(Boolean).join(" ")
274
- }, [column.title, column.sortable ? createVNode("button", {
245
+ const renderHead = () => createVNode("thead", null, [createVNode("tr", null, [props.expandable ? createVNode("th", {
246
+ "class": "mh-table__col-expand",
247
+ "aria-label": "展开"
248
+ }, null) : null, hasSelectColumn.value ? createVNode("th", {
249
+ "class": "mh-table__col-check"
250
+ }, [createVNode(Checkbox, {
251
+ "aria-label": "全选",
252
+ "modelValue": allSelected.value,
253
+ "indeterminate": someSelected.value && !allSelected.value,
254
+ "disabled": headerCheckboxDisabled.value,
255
+ "onChange": toggleAll
256
+ }, null)]) : null, props.columns.map((column) => createVNode("th", {
257
+ "key": column.key,
258
+ "class": [column.required ? "mh-table__cell-required" : "", column.align === "center" ? "mh-table__cell-center" : "", column.align === "right" ? "mh-table__col-right" : ""].filter(Boolean).join(" ")
259
+ }, [column.title, column.sortable ? createVNode("button", {
260
+ "type": "button",
261
+ "class": ["mh-table__sort", props.sortKey === column.key && props.sortOrder ? "mh-table__sort--active" : ""].filter(Boolean).join(" "),
262
+ "aria-label": `按${column.title}排序`,
263
+ "onClick": () => emit("sort", {
264
+ key: column.key,
265
+ order: nextOrder(column.key)
266
+ })
267
+ }, [createVNode(Icon, {
268
+ "name": sortGlyph(column.key),
269
+ "class": ["mh-table__sort-icon", props.sortKey === column.key && props.sortOrder === "asc" ? "mh-table__sort-icon--asc" : "", props.sortKey === column.key && props.sortOrder === "desc" ? "mh-table__sort-icon--desc" : ""].filter(Boolean).join(" ")
270
+ }, null)]) : null]))])]);
271
+ const renderBody = () => createVNode("tbody", null, [isEmpty.value ? createVNode("tr", {
272
+ "class": "mh-table__empty-row"
273
+ }, [createVNode("td", {
274
+ "class": "mh-table__empty",
275
+ "colspan": columnCount.value
276
+ }, [slots.empty ? slots.empty() : props.emptyText])]) : null, props.data.map((row, index) => {
277
+ const key = row[props.rowKey];
278
+ const rowProps = props.onRow?.(row, index);
279
+ const isExpanded = expanded.value.includes(key);
280
+ const rowState = [props.striped && index % 2 === 1 ? "is-striped" : "", selected.value.includes(key) ? "is-selected" : "", rowProps?.onClick ? "is-clickable" : "", rowProps?.class ?? "", props.rowClass?.(row, index) ?? ""].filter(Boolean).join(" ");
281
+ return [createVNode("tr", {
282
+ "key": key,
283
+ "class": rowState,
284
+ "data-state": selected.value.includes(key) ? "selected" : "idle",
285
+ "onClick": rowProps?.onClick ? (event) => rowProps.onClick?.(row, index, event) : void 0
286
+ }, [props.expandable ? createVNode("td", {
287
+ "class": "mh-table__col-expand"
288
+ }, [createVNode("button", {
275
289
  "type": "button",
276
- "class": ["mh-table__sort", props.sortKey === column.key && props.sortOrder ? "mh-table__sort--active" : ""].filter(Boolean).join(" "),
277
- "aria-label": `按${column.title}排序`,
278
- "onClick": () => emit("sort", {
279
- key: column.key,
280
- order: nextOrder(column.key)
281
- })
290
+ "class": "mh-table__expand",
291
+ "aria-label": isExpanded ? "收起" : "展开",
292
+ "aria-expanded": isExpanded ? "true" : "false",
293
+ "onClick": (event) => {
294
+ event.stopPropagation();
295
+ toggleExpand(key);
296
+ }
282
297
  }, [createVNode(Icon, {
283
- "name": sortGlyph(column.key),
284
- "class": ["mh-table__sort-icon", props.sortKey === column.key && props.sortOrder === "asc" ? "mh-table__sort-icon--asc" : "", props.sortKey === column.key && props.sortOrder === "desc" ? "mh-table__sort-icon--desc" : ""].filter(Boolean).join(" ")
285
- }, null)]) : null]))])]) : null, createVNode("tbody", null, [isEmpty.value ? createVNode("tr", {
286
- "class": "mh-table__empty-row"
298
+ "name": "chevron-down",
299
+ "class": "mh-table__expand-icon"
300
+ }, null)])]) : null, hasSelectColumn.value ? (
301
+ /* 勾选列复用 `Checkbox`(不手写原生 input)。行本身可能带 `onRow` 的 onClick:勾选不该顺带触发行
302
+ 点击 —— 阻断挂在**单元格**上(`Checkbox` 的根是 `label`,点它会冒泡两次,只挡 input 挡不住)。
303
+ `selectable` 为函数时逐行判定:返回 `false` 的行给禁用态(置灰 + not-allowed)。 */
304
+ createVNode("td", {
305
+ "class": "mh-table__col-check",
306
+ "onClick": (event) => event.stopPropagation()
307
+ }, [createVNode(Checkbox, {
308
+ "aria-label": "选择本行",
309
+ "modelValue": selected.value.includes(key),
310
+ "disabled": !isRowSelectable(row, index),
311
+ "onChange": () => toggleRow(key)
312
+ }, null)])
313
+ ) : null, props.columns.map((column) => createVNode("td", {
314
+ "key": column.key,
315
+ "class": cellClass(column).join(" ")
316
+ }, [slots.cell ? (
317
+ /* 展示格与编辑格都走这条(含行尾操作列):值、位置、回写口一并交给调用方 */
318
+ slots.cell({
319
+ row,
320
+ column,
321
+ value: row[column.key],
322
+ index,
323
+ setValue: cellSetValue(row, column, index)
324
+ })
325
+ ) : String(row[column.key] ?? "")]))]), isExpanded ? createVNode("tr", {
326
+ "key": `${key}-expand`,
327
+ "class": "mh-table__expand-row"
287
328
  }, [createVNode("td", {
288
- "class": "mh-table__empty",
289
329
  "colspan": columnCount.value
290
- }, [slots.empty ? slots.empty() : props.emptyText])]) : null, props.data.map((row, index) => {
291
- const key = row[props.rowKey];
292
- const rowProps = props.onRow?.(row, index);
293
- const isExpanded = expanded.value.includes(key);
294
- const rowState = [props.striped && index % 2 === 1 ? "is-striped" : "", selected.value.includes(key) ? "is-selected" : "", rowProps?.onClick ? "is-clickable" : "", rowProps?.class ?? "", props.rowClass?.(row, index) ?? ""].filter(Boolean).join(" ");
295
- return [createVNode("tr", {
296
- "key": key,
297
- "class": rowState,
298
- "data-state": selected.value.includes(key) ? "selected" : "idle",
299
- "onClick": rowProps?.onClick ? (event) => rowProps.onClick?.(row, index, event) : void 0
300
- }, [props.expandable ? createVNode("td", {
301
- "class": "mh-table__col-expand"
302
- }, [createVNode("button", {
303
- "type": "button",
304
- "class": "mh-table__expand",
305
- "aria-label": isExpanded ? "收起" : "展开",
306
- "aria-expanded": isExpanded ? "true" : "false",
307
- "onClick": (event) => {
308
- event.stopPropagation();
309
- toggleExpand(key);
310
- }
311
- }, [createVNode(Icon, {
312
- "name": "chevron-down",
313
- "class": "mh-table__expand-icon"
314
- }, null)])]) : null, hasSelectColumn.value ? (
315
- /* 勾选列复用 `Checkbox`(不在表格里手写原生 input)。行本身可能带 onClick(onRow):
316
- 勾选不该顺带触发行点击 —— 阻断挂在**单元格**上,因为 `Checkbox` 的根是 `label`,
317
- 点它会冒泡两次(label 自身一次 + 转发给 input 一次),只挡 input 挡不住行点击。
318
- `selectable` 为函数时逐行判定:返回 `false` 的行给禁用态(置灰 + not-allowed)。 */
319
- createVNode("td", {
320
- "class": "mh-table__col-check",
321
- "onClick": (event) => event.stopPropagation()
322
- }, [createVNode(Checkbox, {
323
- "aria-label": "选择本行",
324
- "modelValue": selected.value.includes(key),
325
- "disabled": !isRowSelectable(row, index),
326
- "onChange": () => toggleRow(key)
327
- }, null)])
328
- ) : null, props.columns.map((column) => createVNode("td", {
329
- "key": column.key,
330
- "class": cellClass(column).join(" ")
331
- }, [slots.cell ? (
332
- /* 编辑格与展示格都走这条:值、位置、回写口一并交给调用方 */
333
- slots.cell({
334
- row,
335
- column,
336
- value: row[column.key],
337
- index,
338
- setValue: cellSetValue(row, column, index)
339
- })
340
- ) : slots.actions && column.align === "right" ? slots.actions({
341
- row,
342
- index
343
- }) : String(row[column.key] ?? "")]))]), isExpanded ? createVNode("tr", {
344
- "key": `${key}-expand`,
345
- "class": "mh-table__expand-row"
346
- }, [createVNode("td", {
347
- "colspan": columnCount.value
348
- }, [props.expandable?.(row, index)])]) : null];
349
- })])]), props.loading ? createVNode("div", {
330
+ }, [props.expandable?.(row, index)])]) : null];
331
+ })]);
332
+ const headBoxRef = ref(null);
333
+ const headTableRef = ref(null);
334
+ const scrollBoxRef = ref(null);
335
+ const syncHead = () => {
336
+ const box = scrollBoxRef.value;
337
+ const head = headBoxRef.value;
338
+ if (!box) return;
339
+ if (headTableRef.value) headTableRef.value.style.transform = `translateX(${-box.scrollLeft}px)`;
340
+ if (head) {
341
+ const gutter = `${box.offsetWidth - box.clientWidth}px`;
342
+ if (head.style.paddingRight !== gutter) head.style.paddingRight = gutter;
343
+ }
344
+ };
345
+ onMounted(syncHead);
346
+ onUpdated(syncHead);
347
+ return () => {
348
+ const fill = props.fill !== false;
349
+ const tableClass = ["mh-table", resolvedSize.value === "sm" ? "mh-table--xs" : "", resolvedSize.value === "lg" ? "mh-table--lg" : "", props.bordered ? "" : "mh-table--borderless", fill ? "mh-table--fill" : "", fill || props.layout === "fixed" ? "mh-table--fixed" : "", attrs.class].filter(Boolean).join(" ");
350
+ const bodyTableClass = props.stickyCol ? `${tableClass} mh-table--sticky-col` : tableClass;
351
+ const cellTokens = fill || props.layout === "fixed" ? {
352
+ "--ui-control-min-width": "0"
353
+ } : void 0;
354
+ const batchBar = slots.batch && selected.value.length > 0 ? createVNode("div", {
355
+ "class": "mh-table__batch"
356
+ }, [slots.batch({
357
+ selected: selected.value
358
+ })]) : null;
359
+ const summaryBar = slots.summary ? createVNode("div", {
360
+ "class": "mh-table__summary"
361
+ }, [slots.summary()]) : null;
362
+ const loadingOverlay = props.loading ? createVNode("div", {
350
363
  "class": "mh-table__loading",
351
364
  "aria-busy": "true"
352
365
  }, [createVNode("span", {
353
366
  "class": "mh-table__spinner"
354
- }, null)]) : null, pager ? createVNode("div", {
355
- "class": "mh-table__pagination"
356
- }, [createVNode(Pagination, {
357
- "total": pager.total,
358
- "modelValue": pager.current ?? 1,
359
- "pageSize": pager.pageSize ?? 10,
360
- "pageSizeOptions": pager.pageSizeOptions,
361
- "onChange": (payload) => pager.onChange?.(payload.page, payload.pageSize)
362
- }, null)]) : null]);
367
+ }, null)]) : null;
368
+ if (!fill) {
369
+ return createVNode("div", {
370
+ "class": "mh-table-wrapper",
371
+ "style": attrs.style
372
+ }, [batchBar, createVNode("table", {
373
+ "class": bodyTableClass,
374
+ "style": cellTokens
375
+ }, [colElements(), props.showHeader ? renderHead() : null, renderBody()]), summaryBar, loadingOverlay]);
376
+ }
377
+ const wrapperStyle = props.fill === true || props.fill === "parent" ? [{
378
+ height: "100%"
379
+ }, attrs.style] : attrs.style;
380
+ const scrollStyle = typeof props.fill === "number" ? {
381
+ maxHeight: `${props.fill}px`
382
+ } : void 0;
383
+ return createVNode("div", {
384
+ "class": "mh-table-wrapper mh-table-wrapper--fill",
385
+ "style": wrapperStyle
386
+ }, [batchBar, props.showHeader ? createVNode("div", {
387
+ "class": "mh-table__head",
388
+ "ref": headBoxRef
389
+ }, [createVNode("table", {
390
+ "class": tableClass,
391
+ "ref": headTableRef,
392
+ "style": cellTokens
393
+ }, [colElements(), renderHead()])]) : null, createVNode("div", {
394
+ "class": "mh-table__body"
395
+ }, [createVNode("div", {
396
+ "class": "mh-table-scroll",
397
+ "ref": scrollBoxRef,
398
+ "onScroll": syncHead,
399
+ "style": scrollStyle
400
+ }, [createVNode("table", {
401
+ "class": bodyTableClass,
402
+ "style": cellTokens
403
+ }, [colElements(), renderBody()])]), loadingOverlay]), summaryBar]);
363
404
  };
364
405
  }
365
406
  });