huibo-ui 0.5.0 → 0.6.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 (69) hide show
  1. package/dist/cjs/hb-form-item.cjs.entry.js +59 -9
  2. package/dist/cjs/hb-form-item.cjs.entry.js.map +1 -1
  3. package/dist/cjs/hb-form.cjs.entry.js +105 -2
  4. package/dist/cjs/hb-form.cjs.entry.js.map +1 -1
  5. package/dist/cjs/hb-select.cjs.entry.js +82 -10
  6. package/dist/cjs/hb-select.cjs.entry.js.map +1 -1
  7. package/dist/cjs/hb-steps.cjs.entry.js +1 -1
  8. package/dist/cjs/hb-steps.cjs.entry.js.map +1 -1
  9. package/dist/cjs/hb-table.cjs.entry.js +195 -27
  10. package/dist/cjs/hb-table.cjs.entry.js.map +1 -1
  11. package/dist/cjs/huibo-ui.cjs.js +1 -1
  12. package/dist/cjs/loader.cjs.js +1 -1
  13. package/dist/collection/components/Form/Form.js +205 -2
  14. package/dist/collection/components/Form/Form.js.map +1 -1
  15. package/dist/collection/components/Form/FormItem.js +117 -10
  16. package/dist/collection/components/Form/FormItem.js.map +1 -1
  17. package/dist/collection/components/Select/Select.js +105 -10
  18. package/dist/collection/components/Select/Select.js.map +1 -1
  19. package/dist/collection/components/Table/Table.js +273 -27
  20. package/dist/collection/components/Table/Table.js.map +1 -1
  21. package/dist/collection/utils/virtual-scroll.js +39 -0
  22. package/dist/collection/utils/virtual-scroll.js.map +1 -0
  23. package/dist/components/hb-form-item.js +62 -9
  24. package/dist/components/hb-form-item.js.map +1 -1
  25. package/dist/components/hb-form.js +110 -2
  26. package/dist/components/hb-form.js.map +1 -1
  27. package/dist/components/hb-select.js +87 -11
  28. package/dist/components/hb-select.js.map +1 -1
  29. package/dist/components/hb-steps.js +1 -1
  30. package/dist/components/hb-steps.js.map +1 -1
  31. package/dist/components/hb-table.js +203 -29
  32. package/dist/components/hb-table.js.map +1 -1
  33. package/dist/esm/hb-form-item.entry.js +59 -9
  34. package/dist/esm/hb-form-item.entry.js.map +1 -1
  35. package/dist/esm/hb-form.entry.js +105 -2
  36. package/dist/esm/hb-form.entry.js.map +1 -1
  37. package/dist/esm/hb-select.entry.js +82 -10
  38. package/dist/esm/hb-select.entry.js.map +1 -1
  39. package/dist/esm/hb-steps.entry.js +1 -1
  40. package/dist/esm/hb-steps.entry.js.map +1 -1
  41. package/dist/esm/hb-table.entry.js +195 -27
  42. package/dist/esm/hb-table.entry.js.map +1 -1
  43. package/dist/esm/huibo-ui.js +1 -1
  44. package/dist/esm/loader.js +1 -1
  45. package/dist/huibo-ui/huibo-ui.esm.js +1 -1
  46. package/dist/huibo-ui/huibo-ui.esm.js.map +1 -1
  47. package/dist/huibo-ui/{p-79b24b83.entry.js → p-2cf5bf20.entry.js} +2 -2
  48. package/dist/huibo-ui/{p-79b24b83.entry.js.map → p-2cf5bf20.entry.js.map} +1 -1
  49. package/dist/huibo-ui/p-4148d875.entry.js +2 -0
  50. package/dist/huibo-ui/p-4148d875.entry.js.map +1 -0
  51. package/dist/huibo-ui/{p-54a28052.entry.js → p-6bfe1954.entry.js} +2 -2
  52. package/dist/huibo-ui/p-6bfe1954.entry.js.map +1 -0
  53. package/dist/huibo-ui/{p-ac18c68b.entry.js → p-e8824b2c.entry.js} +2 -2
  54. package/dist/huibo-ui/p-e8824b2c.entry.js.map +1 -0
  55. package/dist/huibo-ui/p-f69599fa.entry.js +2 -0
  56. package/dist/huibo-ui/p-f69599fa.entry.js.map +1 -0
  57. package/dist/types/components/Form/Form.d.ts +57 -0
  58. package/dist/types/components/Form/FormItem.d.ts +23 -0
  59. package/dist/types/components/Select/Select.d.ts +19 -0
  60. package/dist/types/components/Table/Table.d.ts +103 -8
  61. package/dist/types/components.d.ts +148 -2
  62. package/dist/types/utils/virtual-scroll.d.ts +38 -0
  63. package/package.json +1 -1
  64. package/dist/huibo-ui/p-29092b85.entry.js +0 -2
  65. package/dist/huibo-ui/p-29092b85.entry.js.map +0 -1
  66. package/dist/huibo-ui/p-2bc30b1b.entry.js +0 -2
  67. package/dist/huibo-ui/p-2bc30b1b.entry.js.map +0 -1
  68. package/dist/huibo-ui/p-54a28052.entry.js.map +0 -1
  69. package/dist/huibo-ui/p-ac18c68b.entry.js.map +0 -1
@@ -42,6 +42,13 @@ export class Table {
42
42
  treeProps;
43
43
  /** 是否开启行内编辑模式(默认关闭)。需列配置 column.editable=true 才对该列生效 */
44
44
  editable = false;
45
+ /**
46
+ * 内置分页配置(T1)。传入后 Table 自动对 data 切片并在底部渲染 <hb-pagination>。
47
+ * 不传则不分页(向后兼容)。
48
+ */
49
+ pagination;
50
+ /** 行 className(函数,按行返回附加 class,T8) */
51
+ rowClassName;
45
52
  sortProp = '';
46
53
  sortDirection = null;
47
54
  currentRow = -1;
@@ -52,6 +59,12 @@ export class Table {
52
59
  expandedRowKeys = [];
53
60
  /** 行内编辑:当前正在编辑的单元格 { key, prop },null 表示无 */
54
61
  editingCell = null;
62
+ /** T1 分页:内部当前页(非受控时用) */
63
+ internalPage = 1;
64
+ /** T1 分页:内部每页条数(非受控时用) */
65
+ internalPageSize = 10;
66
+ /** T3 筛选:内部筛选状态 { prop: 选中的 value 列表 }(非受控时用) */
67
+ internalFilters = {};
55
68
  /** 排序变化事件 */
56
69
  hbSortChange;
57
70
  /** 行点击事件 */
@@ -64,10 +77,21 @@ export class Table {
64
77
  hbExpandChange;
65
78
  /** 行内编辑提交事件 */
66
79
  hbCellChange;
80
+ /** T1 分页变化事件 { current, pageSize } */
81
+ hbPageChange;
82
+ /** T3 筛选变化事件 { prop, values } */
83
+ hbFilterChange;
67
84
  handleDataChange() {
68
85
  this.selectedRows = new Set();
69
86
  this.emitSelection();
70
87
  }
88
+ handlePaginationChange() {
89
+ // 受控分页:从 pagination.current/pageSize 初始化内部状态
90
+ if (this.pagination) {
91
+ this.internalPage = this.pagination.current ?? this.pagination.defaultCurrent ?? 1;
92
+ this.internalPageSize = this.pagination.pageSize ?? this.pagination.defaultPageSize ?? 10;
93
+ }
94
+ }
71
95
  /** 树形模式是否启用(显式传入 treeProps 即启用) */
72
96
  get isTreeMode() {
73
97
  return !!this.treeProps;
@@ -78,9 +102,6 @@ export class Table {
78
102
  }
79
103
  /**
80
104
  * 把树拍平成可见行列表:仅保留「祖先链路全部已展开」的后代。
81
- * @param list 当前层级的数组
82
- * @param level 当前层级(根 = 0)
83
- * @param parentKeyPath 父级 key 路径,用于构造深层稳定 key(避免不同分支同 index 撞 key)
84
105
  */
85
106
  flattenTree(list, level = 0, parentKeyPath = '') {
86
107
  const field = this.childrenField;
@@ -91,7 +112,6 @@ export class Table {
91
112
  const kids = row[field];
92
113
  const hasKids = Array.isArray(kids) && kids.length > 0;
93
114
  result.push({ row, level, key, hasChildren: hasKids, index: i });
94
- // 仅当该行已展开时,递归拍入其 children
95
115
  if (hasKids && this.expandedRowKeys.indexOf(key) !== -1) {
96
116
  result.push(...this.flattenTree(kids, level + 1, keyPath));
97
117
  }
@@ -116,7 +136,6 @@ export class Table {
116
136
  commitEdit = (e, row, prop) => {
117
137
  const input = e.target;
118
138
  const value = input.value;
119
- // 就地更新(简单可用版):修改 row[prop] 后强制重渲染
120
139
  row[prop] = value;
121
140
  this.editingCell = null;
122
141
  this.hbCellChange.emit({ row, prop, value });
@@ -134,7 +153,6 @@ export class Table {
134
153
  };
135
154
  allCheckboxRef;
136
155
  componentDidRender() {
137
- // indeterminate 不是可反射的 HTML 属性,必须通过 DOM 属性设置
138
156
  if (this.allCheckboxRef) {
139
157
  this.allCheckboxRef.indeterminate = this.isIndeterminate;
140
158
  }
@@ -165,7 +183,24 @@ export class Table {
165
183
  get virtualTotalHeight() {
166
184
  return this.data.length * (this.itemHeight > 0 ? this.itemHeight : 1);
167
185
  }
168
- handleSort = (prop) => {
186
+ /**
187
+ * T2:解析某列的有效排序方向。
188
+ * 受控(col.sortOrder 设置)→ 用该值;否则用内部 state(sortProp/sortDirection)。
189
+ */
190
+ effectiveSortOrder(col) {
191
+ if (col.sortOrder !== undefined)
192
+ return col.sortOrder;
193
+ if (this.sortProp === col.prop)
194
+ return this.sortDirection;
195
+ return null;
196
+ }
197
+ handleSort = (col) => {
198
+ const prop = col.prop;
199
+ // T2:受控排序(col.sortOrder 已定义)→ 不内部切换,仅 emit 交外部决策
200
+ if (col.sortOrder !== undefined) {
201
+ this.hbSortChange.emit({ prop, order: col.sortOrder });
202
+ return;
203
+ }
169
204
  if (this.sortProp === prop) {
170
205
  this.sortDirection = this.sortDirection === 'ascending' ? 'descending' : this.sortDirection === 'descending' ? null : 'ascending';
171
206
  }
@@ -175,13 +210,38 @@ export class Table {
175
210
  }
176
211
  this.hbSortChange.emit({ prop, order: this.sortDirection });
177
212
  };
213
+ /**
214
+ * T2 增强的排序数据:支持 col.sorter 自定义排序函数 + 受控 sortOrder。
215
+ * 多列排序不支持(与 antd 行为一致,取首个有效排序列)。
216
+ */
178
217
  get sortedData() {
179
- if (!this.sortProp || !this.sortDirection)
218
+ // 找到当前生效的排序列
219
+ let activeCol;
220
+ let activeOrder = null;
221
+ for (const col of this.columns) {
222
+ if (col.sortable || col.sorter) {
223
+ const order = this.effectiveSortOrder(col);
224
+ if (order) {
225
+ activeCol = col;
226
+ activeOrder = order;
227
+ break;
228
+ }
229
+ }
230
+ }
231
+ // 兼容旧逻辑:若没有列级 sortOrder,回退到内部 sortProp/sortDirection
232
+ if (!activeCol && this.sortProp && this.sortDirection) {
233
+ activeOrder = this.sortDirection;
234
+ }
235
+ if (!activeOrder)
180
236
  return this.data;
181
- const direction = this.sortDirection === 'ascending' ? 1 : -1;
237
+ const direction = activeOrder === 'ascending' ? 1 : -1;
238
+ const sortProp = activeCol ? activeCol.prop : this.sortProp;
239
+ const sorterFn = activeCol && activeCol.sorter;
182
240
  return [...this.data].sort((a, b) => {
183
- const va = a[this.sortProp];
184
- const vb = b[this.sortProp];
241
+ if (sorterFn)
242
+ return sorterFn(a, b) * direction;
243
+ const va = a[sortProp];
244
+ const vb = b[sortProp];
185
245
  if (va < vb)
186
246
  return -1 * direction;
187
247
  if (va > vb)
@@ -189,6 +249,72 @@ export class Table {
189
249
  return 0;
190
250
  });
191
251
  }
252
+ /**
253
+ * T3 筛选:应用所有列的 onFilter。
254
+ * 受控(col.filteredValue 已定义)→ 用该值;否则用内部 internalFilters[prop]。
255
+ */
256
+ get filteredData() {
257
+ let result = this.sortedData;
258
+ for (const col of this.columns) {
259
+ if (!col.onFilter || !col.filters)
260
+ continue;
261
+ const values = col.filteredValue !== undefined ? col.filteredValue : this.internalFilters[col.prop];
262
+ if (!values || values.length === 0)
263
+ continue;
264
+ result = result.filter(row => values.some(v => col.onFilter(row, v)));
265
+ }
266
+ return result;
267
+ }
268
+ /**
269
+ * T1 分页:取当前页数据切片(仅当 pagination 配置时)。
270
+ * 在 sorted+filtered 之后切片。
271
+ */
272
+ get pagedData() {
273
+ const filtered = this.filteredData;
274
+ if (!this.pagination)
275
+ return filtered;
276
+ const page = this.currentPage;
277
+ const size = this.currentPageSize;
278
+ const start = (page - 1) * size;
279
+ return filtered.slice(start, start + size);
280
+ }
281
+ /** T1:当前页(受控优先) */
282
+ get currentPage() {
283
+ return this.pagination?.current ?? this.internalPage;
284
+ }
285
+ /** T1:当前每页条数(受控优先) */
286
+ get currentPageSize() {
287
+ return this.pagination?.pageSize ?? this.internalPageSize;
288
+ }
289
+ /** T1:分页切换 */
290
+ handlePageChange = (page) => {
291
+ this.internalPage = page;
292
+ this.hbPageChange.emit({ current: page, pageSize: this.currentPageSize });
293
+ };
294
+ /** T1:每页条数切换 */
295
+ handlePageSizeChange = (size) => {
296
+ this.internalPageSize = size;
297
+ // 切条数后回到第一页避免越界
298
+ this.internalPage = 1;
299
+ this.hbPageChange.emit({ current: 1, pageSize: size });
300
+ };
301
+ /** T3:筛选下拉切换某项 */
302
+ handleFilterToggle = (col, value) => {
303
+ const current = col.filteredValue !== undefined ? col.filteredValue : this.internalFilters[col.prop] || [];
304
+ const next = current.includes(value) ? current.filter(v => v !== value) : [...current, value];
305
+ // 仅内部状态时更新;受控(filteredValue 已定义)则只 emit 交外部
306
+ if (col.filteredValue === undefined) {
307
+ this.internalFilters = { ...this.internalFilters, [col.prop]: next };
308
+ }
309
+ this.hbFilterChange.emit({ prop: col.prop, values: next });
310
+ };
311
+ /** T3:清空某列筛选 */
312
+ handleFilterReset = (col) => {
313
+ if (col.filteredValue === undefined) {
314
+ this.internalFilters = { ...this.internalFilters, [col.prop]: [] };
315
+ }
316
+ this.hbFilterChange.emit({ prop: col.prop, values: [] });
317
+ };
192
318
  get isAllSelected() {
193
319
  return this.data.length > 0 && this.selectedRows.size === this.data.length;
194
320
  }
@@ -233,10 +359,14 @@ export class Table {
233
359
  get hasActions() {
234
360
  return !!this.actions && this.actions.length > 0;
235
361
  }
362
+ /** T3:某列是否有激活筛选 */
363
+ isColumnFiltered(col) {
364
+ const values = col.filteredValue !== undefined ? col.filteredValue : this.internalFilters[col.prop];
365
+ return !!values && values.length > 0;
366
+ }
236
367
  /**
237
368
  * 计算固定列的 sticky 偏移(左侧/右侧累计宽度)。
238
369
  * P1 优化:缓存结果,仅当 columns 引用变化时重算。
239
- * 修复前是 getter,被每个 <td>/<th> 反复调用,复杂度 O(rows×cols²)。
240
370
  */
241
371
  _fixedOffsetsCache = null;
242
372
  _fixedOffsetsColumnsRef = null;
@@ -280,11 +410,18 @@ export class Table {
280
410
  }
281
411
  return style;
282
412
  }
413
+ /**
414
+ * 渲染单元格内容(T7:优先 col.render,其次 formatter,最后原值)。
415
+ */
416
+ renderCell(col, row, rowIndex) {
417
+ if (col.render)
418
+ return col.render(row, rowIndex);
419
+ if (col.formatter)
420
+ return col.formatter(row, rowIndex);
421
+ return row[col.prop] ?? '';
422
+ }
283
423
  /**
284
424
  * 渲染单行(普通 + 虚拟滚动 + 树形 共用)。
285
- * - 普通模式:rowIndex 为在 sortedData 中的全局索引。
286
- * - 树形模式:fr.level 决定缩进,第一列渲染展开箭头,key 取 fr.key。
287
- * fr 为可选;未传时按普通模式渲染。
288
425
  */
289
426
  renderRow(row, rowIndex, fr) {
290
427
  const tree = !!fr;
@@ -292,15 +429,17 @@ export class Table {
292
429
  const rowKeyVal = fr ? fr.key : this.rowKey ? row[this.rowKey] : rowIndex;
293
430
  const indent = level * 16;
294
431
  const isEditing = (prop) => !!this.editingCell && this.editingCell.rowKey === rowKeyVal && this.editingCell.prop === prop;
432
+ // T8:行 className
433
+ const extraRowClass = this.rowClassName ? this.rowClassName(row, rowIndex) : '';
295
434
  return (h("tr", { key: rowKeyVal, class: {
296
435
  'hb-table__row': true,
297
436
  'hb-table__row--striped': this.stripe && rowIndex % 2 === 1,
298
437
  'hb-table__row--current': this.highlightCurrentRow && rowIndex === this.currentRow,
299
438
  'hb-table__row--selected': this.selectedRows.has(rowIndex),
300
439
  'hb-table__row--tree': tree,
440
+ [extraRowClass]: !!extraRowClass,
301
441
  }, onClick: () => this.handleRowClick(row, rowIndex) }, this.selectable && (h("td", { class: "hb-table__selection", onClick: e => e.stopPropagation() }, h("input", { type: "checkbox", class: "hb-table__checkbox", checked: this.selectedRows.has(rowIndex), onChange: e => this.toggleRow(rowIndex, e) }))), this.columns.map((col, colIdx) => {
302
442
  const off = this.getFixedOffsets()[col.prop];
303
- // 树形模式:第一列加缩进 + 展开箭头
304
443
  const isFirstCol = colIdx === 0;
305
444
  const showExpand = tree && isFirstCol && fr.hasChildren;
306
445
  const expanded = showExpand && this.expandedRowKeys.indexOf(rowKeyVal) !== -1;
@@ -311,49 +450,75 @@ export class Table {
311
450
  'hb-table__cell--fixed-left': off?.side === 'left',
312
451
  'hb-table__cell--fixed-right': off?.side === 'right',
313
452
  'hb-table__cell--editable': editableActive,
453
+ 'hb-table__cell--ellipsis': !!col.ellipsis,
314
454
  }, onClick: editableActive ? e => this.startEdit(e, rowKeyVal, col.prop) : undefined }, tree && isFirstCol && h("span", { class: "hb-table__indent", style: { display: 'inline-block', width: `${indent}px` }, "aria-hidden": "true" }), showExpand && (h("button", { type: "button", class: { 'hb-table__expand': true, 'hb-table__expand--expanded': expanded }, "aria-label": expanded ? '收起' : '展开', onClick: e => {
315
455
  e.stopPropagation();
316
456
  this.toggleExpand(row, rowKeyVal);
317
- } }, expanded ? '▼' : '▶')), showExpand === false && tree && isFirstCol && h("span", { class: "hb-table__expand-placeholder", style: { display: 'inline-block', width: '16px' }, "aria-hidden": "true" }), editing ? (h("input", { class: "hb-table__edit-input", type: "text", value: row[col.prop] ?? '', onBlur: e => this.commitEdit(e, row, col.prop), onKeyDown: e => this.onEditKeydown(e, row, col.prop) })) : (h("slot", { name: `cell-${col.prop}` }, col.formatter ? col.formatter(row, rowIndex) : (row[col.prop] ?? '')))));
457
+ } }, expanded ? '▼' : '▶')), showExpand === false && tree && isFirstCol && h("span", { class: "hb-table__expand-placeholder", style: { display: 'inline-block', width: '16px' }, "aria-hidden": "true" }), editing ? (h("input", { class: "hb-table__edit-input", type: "text", value: row[col.prop] ?? '', onBlur: e => this.commitEdit(e, row, col.prop), onKeyDown: e => this.onEditKeydown(e, row, col.prop) })) : (
458
+ // T7:col.render 优先(支持返回 JSX);否则 formatter;否则原值
459
+ h("slot", { name: `cell-${col.prop}` }, this.renderCell(col, row, rowIndex)))));
318
460
  }), this.hasActions && (h("td", { class: "hb-table__td--actions", style: { textAlign: 'right', whiteSpace: 'nowrap' }, onClick: e => e.stopPropagation() }, this.actions.map(act => (h("button", { type: "button", class: {
319
461
  'hb-table__action': true,
320
462
  'hb-table__action--primary': act.type === 'primary',
321
463
  'hb-table__action--danger': act.type === 'danger',
322
464
  }, disabled: act.disabled ? act.disabled(row, rowIndex) : false, onClick: () => this.handleAction(row, rowIndex, act.key) }, act.label)))))));
323
465
  }
466
+ /**
467
+ * T3:渲染表头某列的筛选下拉。
468
+ */
469
+ renderColumnFilter = (col) => {
470
+ if (!col.filters || col.filters.length === 0 || !col.onFilter)
471
+ return null;
472
+ const activeValues = col.filteredValue !== undefined ? col.filteredValue : this.internalFilters[col.prop] || [];
473
+ const filtered = this.isColumnFiltered(col);
474
+ return (h("span", { class: "hb-table__filter", onClick: e => e.stopPropagation() }, h("details", { class: "hb-table__filter-dropdown" }, h("summary", { class: { 'hb-table__filter-trigger': true, 'hb-table__filter-trigger--active': filtered }, "aria-label": "\u7B5B\u9009" }, h("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("path", { d: "M22 3H2l8 9.46V19l4 2v-8.54L22 3z" }))), h("div", { class: "hb-table__filter-menu", role: "menu" }, h("ul", { class: "hb-table__filter-list" }, col.filters.map(f => {
475
+ const checked = activeValues.includes(f.value);
476
+ return (h("li", { class: "hb-table__filter-item", role: "menuitemcheckbox", "aria-checked": checked ? 'true' : 'false' }, h("label", { class: "hb-table__filter-label" }, h("input", { type: "checkbox", class: "hb-table__filter-checkbox", checked: checked, onChange: () => this.handleFilterToggle(col, f.value) }), h("span", null, f.label))));
477
+ })), h("div", { class: "hb-table__filter-actions" }, h("button", { type: "button", class: "hb-table__filter-reset", onClick: () => this.handleFilterReset(col) }, "\u91CD\u7F6E"))))));
478
+ };
324
479
  /** 渲染表头(普通 + 虚拟滚动共用,表头始终固定不随滚) */
325
480
  renderHeader() {
326
481
  return (h("thead", null, h("tr", null, this.selectable && (h("th", { class: "hb-table__selection" }, h("input", { type: "checkbox", class: "hb-table__checkbox", checked: this.isAllSelected, ref: el => {
327
482
  this.allCheckboxRef = el;
328
483
  }, onChange: this.toggleAll }))), this.columns.map(col => {
329
484
  const off = this.getFixedOffsets()[col.prop];
485
+ // T2:列可排序的条件(sortable 或 sorter)
486
+ const canSort = !!col.sortable || !!col.sorter;
487
+ const order = this.effectiveSortOrder(col);
330
488
  return (h("th", { key: col.prop, style: this.cellStyle(col.prop, {
331
489
  width: col.width,
332
490
  minWidth: col.minWidth,
333
491
  textAlign: col.align || 'left',
334
492
  }), class: {
335
- 'hb-table__th--sortable': col.sortable,
493
+ 'hb-table__th--sortable': canSort,
336
494
  'hb-table__cell--fixed-left': off?.side === 'left',
337
495
  'hb-table__cell--fixed-right': off?.side === 'right',
338
- }, onClick: col.sortable ? () => this.handleSort(col.prop) : undefined }, h("div", { class: "hb-table__th-content" }, h("span", null, col.label), col.sortable && (h("span", { class: "hb-table__sort" }, h("span", { class: { 'hb-table__sort-icon': true, 'hb-table__sort-icon--active': this.sortProp === col.prop && this.sortDirection === 'ascending' } }, "\u25B2"), h("span", { class: { 'hb-table__sort-icon': true, 'hb-table__sort-icon--active': this.sortProp === col.prop && this.sortDirection === 'descending' } }, "\u25BC"))))));
496
+ }, onClick: canSort ? () => this.handleSort(col) : undefined }, h("div", { class: "hb-table__th-content" }, h("span", null, col.label), canSort && (h("span", { class: "hb-table__sort" }, h("span", { class: { 'hb-table__sort-icon': true, 'hb-table__sort-icon--active': order === 'ascending' } }, "\u25B2"), h("span", { class: { 'hb-table__sort-icon': true, 'hb-table__sort-icon--active': order === 'descending' } }, "\u25BC"))), this.renderColumnFilter(col))));
339
497
  }), this.hasActions && (h("th", { class: "hb-table__th--actions", style: { textAlign: 'right', whiteSpace: 'nowrap' } }, "\u64CD\u4F5C")))));
340
498
  }
499
+ /** T1:渲染内置分页底部栏 */
500
+ renderPagination() {
501
+ if (!this.pagination)
502
+ return null;
503
+ const total = this.filteredData.length;
504
+ const page = this.currentPage;
505
+ const size = this.currentPageSize;
506
+ const totalPages = Math.max(1, Math.ceil(total / size));
507
+ const showTotal = this.pagination.showTotal;
508
+ return (h("div", { class: "hb-table__pagination" }, showTotal && h("span", { class: "hb-table__pagination-total" }, "\u5171 ", total, " \u6761"), h("span", { class: "hb-table__pagination-pages" }, h("button", { type: "button", class: "hb-table__pagination-btn", disabled: page <= 1, onClick: () => this.handlePageChange(page - 1), "aria-label": "\u4E0A\u4E00\u9875" }, "\u2039"), h("span", { class: "hb-table__pagination-current" }, page, " / ", totalPages), h("button", { type: "button", class: "hb-table__pagination-btn", disabled: page >= totalPages, onClick: () => this.handlePageChange(page + 1), "aria-label": "\u4E0B\u4E00\u9875" }, "\u203A")), this.pagination.pageSizes && (h("select", { class: "hb-table__pagination-size", onChange: e => this.handlePageSizeChange(parseInt(e.target.value, 10)) }, this.pagination.pageSizes.map(s => (h("option", { value: String(s), selected: s === size }, s, " \u6761/\u9875")))))));
509
+ }
341
510
  render() {
342
- const displayData = this.sortedData;
511
+ // T1+T2+T3:排序 筛选 → 分页 依次应用
512
+ const displayData = this.pagedData;
343
513
  const isEmpty = displayData.length === 0;
344
- // 树形模式:把可见树拍平成带 level 的行列表(仅含已展开链路上的后代)
345
514
  const flatRows = this.isTreeMode ? this.flattenTree(displayData) : [];
346
- // 虚拟滚动:固定高度滚动视口 + 撑高容器 + 仅渲染可见窗口行(绝对定位 offset)
347
- // 注意:树形 + 虚拟滚动组合较复杂,此处虚拟滚动沿用普通行渲染(不展开树),
348
- // 保留树形仅在普通模式下生效以降低风险。
349
515
  const renderVirtualBody = () => {
350
516
  const { startIndex, endIndex } = this.visibleRange;
351
517
  const offsetTop = startIndex * (this.itemHeight > 0 ? this.itemHeight : 1);
352
518
  return (h("div", { class: "hb-table__virtual-viewport", style: { height: `${this.virtualScrollHeight}px`, overflowY: 'auto' }, onScroll: this.handleVirtualScroll }, h("div", { class: "hb-table__virtual-spacer", style: { height: `${this.virtualTotalHeight}px`, position: 'relative' } }, h("table", { class: "hb-table__table hb-table__table--virtual" }, h("tbody", null, isEmpty ? (h("tr", null, h("td", { colSpan: this.columns.length + (this.selectable ? 1 : 0) + (this.hasActions ? 1 : 0), class: "hb-table__empty" }, this.emptyText))) : (h("tr", { style: { height: `${offsetTop}px` }, "aria-hidden": "true" })), displayData.slice(startIndex, endIndex).map((row, i) => this.renderRow(row, startIndex + i)))))));
353
519
  };
354
- // 普通模式:树形启用时按拍平列表渲染(带 level 缩进 + 展开箭头),否则原样按行渲染
355
520
  const renderNormalBody = () => (h("tbody", null, isEmpty ? (h("tr", null, h("td", { colSpan: this.columns.length + (this.selectable ? 1 : 0) + (this.hasActions ? 1 : 0), class: "hb-table__empty" }, this.emptyText))) : this.isTreeMode ? (flatRows.map((fr, i) => this.renderRow(fr.row, i, fr))) : (displayData.map((row, rowIndex) => this.renderRow(row, rowIndex)))));
356
- return (h("div", { class: { 'hb-table': true, [`hb-table--${this.size}`]: true, 'hb-table--border': this.border, 'hb-table--virtual': this.virtualScroll } }, this.loading && (h("div", { class: "hb-table__loading" }, h("div", { class: "hb-table__loading-spinner" }))), this.virtualScroll ? (h("div", { class: "hb-table__virtual-wrapper" }, h("table", { class: "hb-table__table" }, this.renderHeader()), renderVirtualBody())) : (h("div", { class: "hb-table__wrapper", style: this.maxHeight ? { maxHeight: this.maxHeight, overflow: 'auto' } : undefined }, h("table", { class: "hb-table__table" }, this.renderHeader(), renderNormalBody())))));
521
+ return (h("div", { class: { 'hb-table': true, [`hb-table--${this.size}`]: true, 'hb-table--border': this.border, 'hb-table--virtual': this.virtualScroll, 'hb-table--has-pagination': !!this.pagination } }, this.loading && (h("div", { class: "hb-table__loading" }, h("div", { class: "hb-table__loading-spinner" }))), this.virtualScroll ? (h("div", { class: "hb-table__virtual-wrapper" }, h("table", { class: "hb-table__table" }, this.renderHeader()), renderVirtualBody())) : (h("div", { class: "hb-table__wrapper", style: this.maxHeight ? { maxHeight: this.maxHeight, overflow: 'auto' } : undefined }, h("table", { class: "hb-table__table" }, this.renderHeader(), renderNormalBody()))), this.renderPagination()));
357
522
  }
358
523
  static get is() { return "hb-table"; }
359
524
  static get encapsulation() { return "shadow"; }
@@ -721,6 +886,51 @@ export class Table {
721
886
  "attribute": "editable",
722
887
  "reflect": false,
723
888
  "defaultValue": "false"
889
+ },
890
+ "pagination": {
891
+ "type": "unknown",
892
+ "mutable": false,
893
+ "complexType": {
894
+ "original": "TablePagination",
895
+ "resolved": "TablePagination",
896
+ "references": {
897
+ "TablePagination": {
898
+ "location": "local",
899
+ "path": "/Users/vscoderwhy/Desktop/product/web-common/huibo-ui/src/components/Table/Table.tsx",
900
+ "id": "src/components/Table/Table.tsx::TablePagination"
901
+ }
902
+ }
903
+ },
904
+ "required": false,
905
+ "optional": true,
906
+ "docs": {
907
+ "tags": [],
908
+ "text": "\u5185\u7F6E\u5206\u9875\u914D\u7F6E\uFF08T1\uFF09\u3002\u4F20\u5165\u540E Table \u81EA\u52A8\u5BF9 data \u5207\u7247\u5E76\u5728\u5E95\u90E8\u6E32\u67D3 <hb-pagination>\u3002\n\u4E0D\u4F20\u5219\u4E0D\u5206\u9875\uFF08\u5411\u540E\u517C\u5BB9\uFF09\u3002"
909
+ },
910
+ "getter": false,
911
+ "setter": false
912
+ },
913
+ "rowClassName": {
914
+ "type": "unknown",
915
+ "mutable": false,
916
+ "complexType": {
917
+ "original": "(row: Record<string, any>, index: number) => string",
918
+ "resolved": "(row: Record<string, any>, index: number) => string",
919
+ "references": {
920
+ "Record": {
921
+ "location": "global",
922
+ "id": "global::Record"
923
+ }
924
+ }
925
+ },
926
+ "required": false,
927
+ "optional": true,
928
+ "docs": {
929
+ "tags": [],
930
+ "text": "\u884C className\uFF08\u51FD\u6570\uFF0C\u6309\u884C\u8FD4\u56DE\u9644\u52A0 class\uFF0CT8\uFF09"
931
+ },
932
+ "getter": false,
933
+ "setter": false
724
934
  }
725
935
  };
726
936
  }
@@ -732,7 +942,10 @@ export class Table {
732
942
  "selectedRows": {},
733
943
  "scrollTop": {},
734
944
  "expandedRowKeys": {},
735
- "editingCell": {}
945
+ "editingCell": {},
946
+ "internalPage": {},
947
+ "internalPageSize": {},
948
+ "internalFilters": {}
736
949
  };
737
950
  }
738
951
  static get events() {
@@ -857,12 +1070,45 @@ export class Table {
857
1070
  }
858
1071
  }
859
1072
  }
1073
+ }, {
1074
+ "method": "hbPageChange",
1075
+ "name": "hbPageChange",
1076
+ "bubbles": true,
1077
+ "cancelable": true,
1078
+ "composed": true,
1079
+ "docs": {
1080
+ "tags": [],
1081
+ "text": "T1 \u5206\u9875\u53D8\u5316\u4E8B\u4EF6 { current, pageSize }"
1082
+ },
1083
+ "complexType": {
1084
+ "original": "{ current: number; pageSize: number }",
1085
+ "resolved": "{ current: number; pageSize: number; }",
1086
+ "references": {}
1087
+ }
1088
+ }, {
1089
+ "method": "hbFilterChange",
1090
+ "name": "hbFilterChange",
1091
+ "bubbles": true,
1092
+ "cancelable": true,
1093
+ "composed": true,
1094
+ "docs": {
1095
+ "tags": [],
1096
+ "text": "T3 \u7B5B\u9009\u53D8\u5316\u4E8B\u4EF6 { prop, values }"
1097
+ },
1098
+ "complexType": {
1099
+ "original": "{ prop: string; values: (string | number)[] }",
1100
+ "resolved": "{ prop: string; values: (string | number)[]; }",
1101
+ "references": {}
1102
+ }
860
1103
  }];
861
1104
  }
862
1105
  static get watchers() {
863
1106
  return [{
864
1107
  "propName": "data",
865
1108
  "methodName": "handleDataChange"
1109
+ }, {
1110
+ "propName": "pagination",
1111
+ "methodName": "handlePaginationChange"
866
1112
  }];
867
1113
  }
868
1114
  }