@veltra/desktop 1.0.8 → 1.0.9

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 (183) hide show
  1. package/dist/components/action/action-group.vue.d.ts +2 -2
  2. package/dist/components/action/action.vue.d.ts +3 -3
  3. package/dist/components/auto-complete/auto-complete.vue.d.ts +1 -1
  4. package/dist/components/batch-edit/style.js +2 -2
  5. package/dist/components/button/button.vue.d.ts +1 -1
  6. package/dist/components/cascade/cascade.js +15 -13
  7. package/dist/components/cascade/cascade.js.map +1 -1
  8. package/dist/components/cascade/cascade.vue.d.ts +4 -4
  9. package/dist/components/cascade/node.js.map +1 -1
  10. package/dist/components/cascade/use-check.js +3 -2
  11. package/dist/components/cascade/use-check.js.map +1 -1
  12. package/dist/components/cascade/use-select.js +3 -3
  13. package/dist/components/cascade/use-select.js.map +1 -1
  14. package/dist/components/date-panel/date-panel.vue.d.ts +1 -1
  15. package/dist/components/date-picker/date-picker.js +9 -9
  16. package/dist/components/date-picker/date-picker.js.map +1 -1
  17. package/dist/components/date-picker/date-picker.vue.d.ts +1 -1
  18. package/dist/components/date-range-picker/date-range-picker.js +9 -9
  19. package/dist/components/date-range-picker/date-range-picker.js.map +1 -1
  20. package/dist/components/date-range-picker/date-range-picker.vue.d.ts +1 -1
  21. package/dist/components/empty/empty.vue.d.ts +1 -1
  22. package/dist/components/file-picker/file-picker.vue.d.ts +1 -1
  23. package/dist/components/file-picker/helper.js +19 -7
  24. package/dist/components/file-picker/helper.js.map +1 -1
  25. package/dist/components/file-viewer/file-viewer.js +294 -0
  26. package/dist/components/file-viewer/file-viewer.js.map +1 -0
  27. package/dist/components/file-viewer/file-viewer.vue.d.ts +42 -0
  28. package/dist/components/file-viewer/helper.js +185 -0
  29. package/dist/components/file-viewer/helper.js.map +1 -0
  30. package/dist/components/file-viewer/index.d.ts +1 -0
  31. package/dist/components/file-viewer/previewers/docx-previewer.js +76 -0
  32. package/dist/components/file-viewer/previewers/docx-previewer.js.map +1 -0
  33. package/dist/components/file-viewer/previewers/image-previewer.js +64 -0
  34. package/dist/components/file-viewer/previewers/image-previewer.js.map +1 -0
  35. package/dist/components/file-viewer/previewers/pdf-previewer.js +90 -0
  36. package/dist/components/file-viewer/previewers/pdf-previewer.js.map +1 -0
  37. package/dist/components/file-viewer/previewers/sheet-previewer.js +175 -0
  38. package/dist/components/file-viewer/previewers/sheet-previewer.js.map +1 -0
  39. package/dist/components/file-viewer/previewers/text-previewer.js +74 -0
  40. package/dist/components/file-viewer/previewers/text-previewer.js.map +1 -0
  41. package/dist/components/file-viewer/previewers/video-previewer.js +62 -0
  42. package/dist/components/file-viewer/previewers/video-previewer.js.map +1 -0
  43. package/dist/components/file-viewer/style.d.ts +1 -0
  44. package/dist/components/file-viewer/style.js +4 -0
  45. package/dist/components/file-viewer/style2.css +445 -0
  46. package/dist/components/grid-input/grid-input.vue.d.ts +1 -1
  47. package/dist/components/index.d.ts +12 -9
  48. package/dist/components/input/input.vue.d.ts +1 -1
  49. package/dist/components/multi-select/multi-select-option.js +14 -10
  50. package/dist/components/multi-select/multi-select-option.js.map +1 -1
  51. package/dist/components/multi-select/multi-select.js +16 -19
  52. package/dist/components/multi-select/multi-select.js.map +1 -1
  53. package/dist/components/multi-select/multi-select.vue.d.ts +3 -3
  54. package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +4 -4
  55. package/dist/components/notification/notification.vue.d.ts +1 -1
  56. package/dist/components/number-input/number-input.vue.d.ts +1 -1
  57. package/dist/components/number-range-input/number-range-input.vue.d.ts +2 -2
  58. package/dist/components/palette/di.js.map +1 -1
  59. package/dist/components/palette/palette-alpha.js +7 -10
  60. package/dist/components/palette/palette-alpha.js.map +1 -1
  61. package/dist/components/palette/palette-hue.js +5 -7
  62. package/dist/components/palette/palette-hue.js.map +1 -1
  63. package/dist/components/palette/palette-sv.js +8 -10
  64. package/dist/components/palette/palette-sv.js.map +1 -1
  65. package/dist/components/palette/palette.js +22 -24
  66. package/dist/components/palette/palette.js.map +1 -1
  67. package/dist/components/password-input/password-input.vue.d.ts +1 -1
  68. package/dist/components/pop-confirm/pop-confirm.vue.d.ts +1 -1
  69. package/dist/components/progress-nodes/progress-nodes.vue.d.ts +3 -3
  70. package/dist/components/rich-text-editor/rich-text-editor.vue.d.ts +2 -2
  71. package/dist/components/select/select.js +14 -12
  72. package/dist/components/select/select.js.map +1 -1
  73. package/dist/components/select/select.vue.d.ts +2 -2
  74. package/dist/components/slider/slider-thumb.js +10 -13
  75. package/dist/components/slider/slider-thumb.js.map +1 -1
  76. package/dist/components/slider/slider.js +12 -15
  77. package/dist/components/slider/slider.js.map +1 -1
  78. package/dist/components/table/di.js.map +1 -1
  79. package/dist/components/table/node/row.d.ts +10 -1
  80. package/dist/components/table/node/row.js +9 -0
  81. package/dist/components/table/node/row.js.map +1 -1
  82. package/dist/components/table/style.js +1 -1
  83. package/dist/components/table/table-body.js +25 -26
  84. package/dist/components/table/table-body.js.map +1 -1
  85. package/dist/components/table/table-cell.js +16 -9
  86. package/dist/components/table/table-cell.js.map +1 -1
  87. package/dist/components/table/table-row.js +78 -19
  88. package/dist/components/table/table-row.js.map +1 -1
  89. package/dist/components/table/table.js +76 -34
  90. package/dist/components/table/table.js.map +1 -1
  91. package/dist/components/table/use-fixed-columns.js +27 -8
  92. package/dist/components/table/use-fixed-columns.js.map +1 -1
  93. package/dist/components/table/use-rows.js +2 -2
  94. package/dist/components/table/use-rows.js.map +1 -1
  95. package/dist/components/table/use-table.js +25 -3
  96. package/dist/components/table/use-table.js.map +1 -1
  97. package/dist/components/tabs/index.d.ts +3 -1
  98. package/dist/components/tabs/style2.css +169 -107
  99. package/dist/components/tabs/tabs-horizontal.js +133 -0
  100. package/dist/components/tabs/tabs-horizontal.js.map +1 -0
  101. package/dist/components/tabs/tabs-horizontal.vue.d.ts +29 -0
  102. package/dist/components/tabs/tabs-vertical.js +89 -0
  103. package/dist/components/tabs/tabs-vertical.js.map +1 -0
  104. package/dist/components/tabs/tabs-vertical.vue.d.ts +28 -0
  105. package/dist/components/tabs/tabs.js +75 -105
  106. package/dist/components/tabs/tabs.js.map +1 -1
  107. package/dist/components/tabs/tabs.vue.d.ts +9 -23
  108. package/dist/components/tabs/use-tabs-bar.js +105 -0
  109. package/dist/components/tabs/use-tabs-bar.js.map +1 -0
  110. package/dist/components/textarea/textarea.vue.d.ts +1 -1
  111. package/dist/components/tip/tip.vue.d.ts +2 -2
  112. package/dist/components/tree/style2.css +45 -23
  113. package/dist/components/tree/tree-node.js +50 -35
  114. package/dist/components/tree/tree-node.js.map +1 -1
  115. package/dist/components/tree/tree.js +27 -20
  116. package/dist/components/tree/tree.js.map +1 -1
  117. package/dist/components/tree/tree.vue.d.ts +1 -1
  118. package/dist/components/tree/use-check.js +52 -14
  119. package/dist/components/tree/use-check.js.map +1 -1
  120. package/dist/components/tree/use-tree-nodes.js +51 -24
  121. package/dist/components/tree/use-tree-nodes.js.map +1 -1
  122. package/dist/components/tree-select/tree-select.vue.d.ts +3 -3
  123. package/dist/components/watermark/watermark.vue.d.ts +1 -1
  124. package/dist/index.d.ts +57 -53
  125. package/dist/index.js +51 -48
  126. package/dist/types/file-viewer.d.ts +71 -0
  127. package/dist/types/index.d.ts +3 -2
  128. package/dist/types/tabs.d.ts +106 -14
  129. package/dist/types/tree.d.ts +6 -1
  130. package/package.json +32 -31
  131. package/src/components/cascade/cascade.vue +18 -18
  132. package/src/components/cascade/node.ts +4 -1
  133. package/src/components/cascade/use-check.ts +5 -4
  134. package/src/components/cascade/use-select.ts +4 -5
  135. package/src/components/date-picker/date-picker.vue +10 -9
  136. package/src/components/date-range-picker/date-range-picker.vue +12 -12
  137. package/src/components/file-picker/helper.ts +21 -10
  138. package/src/components/file-viewer/file-viewer.vue +311 -0
  139. package/src/components/file-viewer/helper.ts +193 -0
  140. package/src/components/file-viewer/index.ts +1 -0
  141. package/src/components/file-viewer/previewers/docx-previewer.vue +78 -0
  142. package/src/components/file-viewer/previewers/image-previewer.vue +64 -0
  143. package/src/components/file-viewer/previewers/pdf-previewer.vue +90 -0
  144. package/src/components/file-viewer/previewers/sheet-previewer.vue +210 -0
  145. package/src/components/file-viewer/previewers/text-previewer.vue +76 -0
  146. package/src/components/file-viewer/previewers/video-previewer.vue +72 -0
  147. package/src/components/file-viewer/style.scss +504 -0
  148. package/src/components/file-viewer/style.ts +4 -0
  149. package/src/components/index.ts +2 -0
  150. package/src/components/multi-select/multi-select-option.vue +16 -3
  151. package/src/components/multi-select/multi-select.vue +17 -9
  152. package/src/components/palette/di.ts +5 -3
  153. package/src/components/palette/palette-alpha.vue +7 -10
  154. package/src/components/palette/palette-hue.vue +5 -7
  155. package/src/components/palette/palette-sv.vue +12 -14
  156. package/src/components/palette/palette.vue +19 -21
  157. package/src/components/select/select.vue +21 -19
  158. package/src/components/slider/slider-thumb.vue +11 -14
  159. package/src/components/slider/slider.vue +15 -15
  160. package/src/components/table/di.ts +20 -3
  161. package/src/components/table/node/row.ts +10 -1
  162. package/src/components/table/table-body.vue +28 -33
  163. package/src/components/table/table-cell.vue +17 -9
  164. package/src/components/table/table-row.tsx +83 -17
  165. package/src/components/table/table.vue +73 -45
  166. package/src/components/table/use-fixed-columns.ts +46 -12
  167. package/src/components/table/use-rows.ts +14 -3
  168. package/src/components/table/use-table.ts +27 -3
  169. package/src/components/tabs/index.ts +2 -0
  170. package/src/components/tabs/style.scss +217 -134
  171. package/src/components/tabs/tabs-horizontal.vue +125 -0
  172. package/src/components/tabs/tabs-vertical.vue +75 -0
  173. package/src/components/tabs/tabs.vue +56 -127
  174. package/src/components/tabs/use-tabs-bar.ts +119 -0
  175. package/src/components/tree/style.scss +74 -29
  176. package/src/components/tree/tree-node.vue +10 -4
  177. package/src/components/tree/tree.vue +39 -12
  178. package/src/components/tree/use-check.ts +79 -23
  179. package/src/components/tree/use-tree-nodes.ts +85 -34
  180. package/src/types/file-viewer.ts +69 -0
  181. package/src/types/index.ts +1 -0
  182. package/src/types/tabs.ts +113 -19
  183. package/src/types/tree.ts +6 -1
@@ -2,21 +2,68 @@ import _sfc_main from "../icon/icon.js";
2
2
  import _sfc_main$1 from "../button/button.js";
3
3
  import { TableDIKey } from "./di.js";
4
4
  import _sfc_main$2 from "./table-cell.js";
5
- import { createVNode, defineComponent, inject, isVNode, mergeProps } from "vue";
6
- import { bem } from "@veltra/utils";
5
+ import { createVNode, defineComponent, inject, isVNode, mergeProps, onUnmounted } from "vue";
7
6
  import { ArrowRight } from "@veltra/icons/normal";
8
7
  //#region src/components/table/table-row.tsx
8
+ /**
9
+ * A3:行修饰类组合静态查找表。
10
+ *
11
+ * 位模式:bit2 = isCurrent, bit1 = checked, bit0 = expanded。
12
+ * 与 `bem.is('...')` 对于 `'current' | 'checked' | 'expanded'` 的输出完全等价,
13
+ * 直接从 index 读字符串,消除每行渲染期间的字符串拼接和 3 次 bem.is 调用。
14
+ *
15
+ * 放在模块顶层使所有 TableRow 实例共享一份引用(不会随 setup 重建)。
16
+ */
9
17
  function _isSlot(s) {
10
18
  return typeof s === "function" || Object.prototype.toString.call(s) === "[object Object]" && !isVNode(s);
11
19
  }
20
+ const ROW_MOD_CLASS = [
21
+ "",
22
+ "is-expanded",
23
+ "is-checked",
24
+ "is-checked is-expanded",
25
+ "is-current",
26
+ "is-current is-expanded",
27
+ "is-current is-checked",
28
+ "is-current is-checked is-expanded"
29
+ ];
30
+ function rowModCls(row) {
31
+ return ROW_MOD_CLASS[(row.isCurrent ? 4 : 0) | (row.checked ? 2 : 0) | (row.expanded ? 1 : 0)];
32
+ }
12
33
  const UTableRow = defineComponent({
13
34
  name: "TableRow",
14
- props: ["row"],
35
+ props: {
36
+ row: {
37
+ type: Object,
38
+ required: true
39
+ },
40
+ index: {
41
+ type: Number,
42
+ required: true
43
+ }
44
+ },
15
45
  setup(props) {
16
- const { row } = props;
17
- const { cls, columnConfig, getColumnSlotsNode, toggleTreeRowExpand, getCellCtx, handleRowClick, handleCellClick, tableProps, measureElement } = inject(TableDIKey);
46
+ const { cls, columnConfig, getColumnSlotsNode, toggleTreeRowExpand, getCellCtx, handleRowClick, handleCellClick, tableProps, measureElement, getCellClass } = inject(TableDIKey);
47
+ const rowBaseCls = cls.e("row");
48
+ const expandToggleCls = cls.e("expand-toggle");
49
+ const expandSpaceCls = cls.e("expand-space");
50
+ const measureRefCache = /* @__PURE__ */ new Map();
51
+ const getMeasureRef = (index) => {
52
+ let ref = measureRefCache.get(index);
53
+ if (ref === void 0) {
54
+ ref = (el) => {
55
+ measureElement(index, el);
56
+ };
57
+ measureRefCache.set(index, ref);
58
+ }
59
+ return ref;
60
+ };
61
+ onUnmounted(() => {
62
+ measureRefCache.clear();
63
+ });
18
64
  const { columns, expandColumn } = columnConfig;
19
65
  return () => {
66
+ const { row } = props;
20
67
  const _expandColumn = expandColumn.value;
21
68
  let expandCell = null;
22
69
  if (_expandColumn) {
@@ -25,13 +72,13 @@ const UTableRow = defineComponent({
25
72
  if (!cellSpan || cellSpan.colspan && cellSpan.rowspan) {
26
73
  const marginLeft = tableProps.tree ? (row.depth - 1) * 14 : 0;
27
74
  expandCell = createVNode(_sfc_main$2, mergeProps({
28
- "column": _expandColumn,
75
+ "cellClass": getCellClass(_expandColumn),
29
76
  "left": _expandColumn.style.left,
30
77
  "right": _expandColumn.style.right,
31
78
  "key": row.uid + _expandColumn.key
32
79
  }, tableProps.mergeCell?.(expandCtx)), { default: () => [!row.isLeaf || tableProps.expandable && !row.isExpandRow ? createVNode(_sfc_main$1, {
33
80
  "text": true,
34
- "class": cls.e("expand-toggle"),
81
+ "class": expandToggleCls,
35
82
  "type": "primary",
36
83
  "size": "small",
37
84
  "circle": true,
@@ -41,19 +88,17 @@ const UTableRow = defineComponent({
41
88
  },
42
89
  "style": `margin-left: ${marginLeft}px`
43
90
  }, { default: () => [createVNode(_sfc_main, null, { default: () => [createVNode(ArrowRight, null, null)] })] }) : createVNode("i", {
44
- "class": cls.e("expand-space"),
91
+ "class": expandSpaceCls,
45
92
  "style": `margin-left: ${marginLeft}px`
46
93
  }, null), getColumnSlotsNode(expandCtx)] });
47
94
  }
48
95
  }
96
+ const mod = rowModCls(row);
97
+ const rowCls = mod ? rowBaseCls + " " + mod : rowBaseCls;
49
98
  return createVNode("tr", {
50
- "class": [
51
- cls.e("row"),
52
- bem.is("expanded", row.expanded),
53
- bem.is("checked", row.checked)
54
- ],
99
+ "class": rowCls,
55
100
  "onClick": (e) => handleRowClick(row, e),
56
- "ref": measureElement,
101
+ "ref": getMeasureRef(props.index),
57
102
  "key": row.uid
58
103
  }, [expandCell, columns.value.map((column) => {
59
104
  const cellCtx = getCellCtx(row, column);
@@ -61,7 +106,7 @@ const UTableRow = defineComponent({
61
106
  if (cellSpan && (!cellSpan.colspan || !cellSpan.rowspan)) return null;
62
107
  const cellNode = getColumnSlotsNode(cellCtx);
63
108
  return createVNode(_sfc_main$2, mergeProps({
64
- "column": column,
109
+ "cellClass": getCellClass(column),
65
110
  "left": column.style.left,
66
111
  "right": column.style.right,
67
112
  "key": row.uid + column.key,
@@ -73,10 +118,24 @@ const UTableRow = defineComponent({
73
118
  });
74
119
  const UExpandTableRow = defineComponent({
75
120
  name: "ExpandTableRow",
76
- props: ["row"],
121
+ props: {
122
+ row: {
123
+ type: Object,
124
+ required: true
125
+ },
126
+ index: {
127
+ type: Number,
128
+ required: true
129
+ }
130
+ },
77
131
  setup(props) {
78
132
  const { getExpandRowSlotsNode, cls, measureElement, columnConfig } = inject(TableDIKey);
79
133
  const { leafColumns } = columnConfig;
134
+ const rowBaseCls = cls.e("row");
135
+ const cellCls = cls.e("cell");
136
+ const measureRef = (el) => {
137
+ measureElement(props.index, el);
138
+ };
80
139
  return () => {
81
140
  const { row } = props;
82
141
  const node = getExpandRowSlotsNode({
@@ -86,11 +145,11 @@ const UExpandTableRow = defineComponent({
86
145
  rowData: row.data
87
146
  });
88
147
  return createVNode("tr", {
89
- "class": cls.e("row"),
90
- "ref": measureElement
148
+ "class": rowBaseCls,
149
+ "ref": measureRef
91
150
  }, [createVNode("td", {
92
151
  "colspan": leafColumns.value.length,
93
- "class": cls.e("cell")
152
+ "class": cellCls
94
153
  }, [node])]);
95
154
  };
96
155
  }
@@ -1 +1 @@
1
- {"version":3,"file":"table-row.js","names":["ArrowRight","bem","defineComponent","DefineComponent","VNodeRef","inject","JSX","TableRow","ITableRow","UButton","UIcon","TableDIKey","UTabelCell","_isSlot","s","Object","prototype","toString","call","_isVNode","UTableRow","row","name","props","setup","cls","columnConfig","getColumnSlotsNode","toggleTreeRowExpand","getCellCtx","handleRowClick","handleCellClick","tableProps","measureElement","columns","expandColumn","_expandColumn","value","expandCell","Element","expandCtx","cellSpan","mergeCell","colspan","rowspan","marginLeft","tree","depth","_createVNode","_mergeProps","style","left","right","uid","key","default","isLeaf","expandable","isExpandRow","e","stopPropagation","is","expanded","checked","map","column","cellCtx","cellNode","UExpandTableRow","getExpandRowSlotsNode","leafColumns","node","index","rowData","data","length"],"sources":["../../../src/components/table/table-row.tsx"],"sourcesContent":["import { ArrowRight } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { defineComponent, type DefineComponent, type VNodeRef } from 'vue'\nimport { inject } from 'vue'\nimport type { JSX } from 'vue/jsx-runtime'\n\nimport type { TableRow as ITableRow } from '../../types'\nimport { UButton } from '../button'\nimport { UIcon } from '../icon'\nimport { TableDIKey } from './di'\nimport UTabelCell from './table-cell.vue'\n\nexport const UTableRow: DefineComponent<{ row: ITableRow }> = defineComponent({\n name: 'TableRow',\n\n props: ['row'],\n\n setup(props) {\n const { row } = props\n\n const {\n cls,\n columnConfig,\n getColumnSlotsNode,\n toggleTreeRowExpand,\n getCellCtx,\n handleRowClick,\n handleCellClick,\n tableProps,\n measureElement\n } = inject(TableDIKey)!\n\n const { columns, expandColumn } = columnConfig\n\n return () => {\n const _expandColumn = expandColumn.value\n\n let expandCell: JSX.Element | null = null\n if (_expandColumn) {\n const expandCtx = getCellCtx(row, _expandColumn)\n const cellSpan = tableProps.mergeCell?.(expandCtx)\n\n if (!cellSpan || (cellSpan.colspan && cellSpan.rowspan)) {\n const marginLeft = tableProps.tree ? (row.depth - 1) * 14 : 0\n expandCell = (\n <UTabelCell\n column={_expandColumn}\n left={_expandColumn.style.left}\n right={_expandColumn.style.right}\n key={row.uid + _expandColumn.key}\n {...tableProps.mergeCell?.(expandCtx)}\n >\n {!row.isLeaf || (tableProps.expandable && !row.isExpandRow) ? (\n <UButton\n text\n class={cls.e('expand-toggle')}\n type='primary'\n size='small'\n circle\n onClick={(e) => {\n e.stopPropagation()\n toggleTreeRowExpand(row)\n }}\n style={`margin-left: ${marginLeft}px`}\n >\n <UIcon>\n <ArrowRight />\n </UIcon>\n </UButton>\n ) : (\n <i class={cls.e('expand-space')} style={`margin-left: ${marginLeft}px`}></i>\n )}\n\n {getColumnSlotsNode(expandCtx)}\n </UTabelCell>\n )\n }\n }\n\n return (\n <tr\n class={[cls.e('row'), bem.is('expanded', row.expanded), bem.is('checked', row.checked)]}\n onClick={(e) => handleRowClick(row, e)}\n ref={measureElement as VNodeRef}\n key={row.uid}\n >\n {expandCell}\n\n {columns.value.map((column) => {\n const cellCtx = getCellCtx(row, column)\n const cellSpan = tableProps.mergeCell?.(cellCtx)\n if (cellSpan && (!cellSpan.colspan || !cellSpan.rowspan)) {\n return null\n }\n\n const cellNode = getColumnSlotsNode(cellCtx)\n\n return (\n <UTabelCell\n column={column}\n left={column.style.left}\n right={column.style.right}\n key={row.uid + column.key}\n // @ts-ignore\n onClick={(e) => handleCellClick(row, column, e)}\n {...cellSpan}\n >\n {cellNode}\n </UTabelCell>\n )\n })}\n </tr>\n )\n }\n }\n})\n\nexport const UExpandTableRow = defineComponent({\n name: 'ExpandTableRow',\n props: ['row'],\n setup(props) {\n const { getExpandRowSlotsNode, cls, measureElement, columnConfig } = inject(TableDIKey)!\n const { leafColumns } = columnConfig\n\n return () => {\n const { row } = props\n const node = getExpandRowSlotsNode({\n row,\n index: row.index,\n columns: leafColumns.value,\n rowData: row.data\n })\n return (\n <tr class={cls.e('row')} ref={measureElement as VNodeRef}>\n <td colspan={leafColumns.value.length} class={cls.e('cell')}>\n {node}\n </td>\n </tr>\n )\n }\n }\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"table-row.js","names":["ArrowRight","defineComponent","DefineComponent","inject","onUnmounted","JSX","TableRow","ITableRow","UButton","UIcon","TableDIKey","UTabelCell","_isSlot","s","Object","prototype","toString","call","_isVNode","ROW_MOD_CLASS","const","rowModCls","row","i","isCurrent","checked","expanded","UTableRow","index","name","props","type","required","Number","setup","cls","columnConfig","getColumnSlotsNode","toggleTreeRowExpand","getCellCtx","handleRowClick","handleCellClick","tableProps","measureElement","getCellClass","rowBaseCls","e","expandToggleCls","expandSpaceCls","measureRefCache","Map","el","getMeasureRef","ref","get","undefined","Element","set","clear","columns","expandColumn","_expandColumn","value","expandCell","expandCtx","cellSpan","mergeCell","colspan","rowspan","marginLeft","tree","depth","_createVNode","_mergeProps","style","left","right","uid","key","default","isLeaf","expandable","isExpandRow","stopPropagation","mod","rowCls","map","column","cellCtx","cellNode","UExpandTableRow","getExpandRowSlotsNode","leafColumns","cellCls","measureRef","node","rowData","data","length"],"sources":["../../../src/components/table/table-row.tsx"],"sourcesContent":["import { ArrowRight } from '@veltra/icons/normal'\nimport { defineComponent, type DefineComponent } from 'vue'\nimport { inject, onUnmounted } from 'vue'\nimport type { JSX } from 'vue/jsx-runtime'\n\nimport type { TableRow as ITableRow } from '../../types'\nimport { UButton } from '../button'\nimport { UIcon } from '../icon'\nimport { TableDIKey } from './di'\nimport UTabelCell from './table-cell.vue'\n\n/**\n * A3:行修饰类组合静态查找表。\n *\n * 位模式:bit2 = isCurrent, bit1 = checked, bit0 = expanded。\n * 与 `bem.is('...')` 对于 `'current' | 'checked' | 'expanded'` 的输出完全等价,\n * 直接从 index 读字符串,消除每行渲染期间的字符串拼接和 3 次 bem.is 调用。\n *\n * 放在模块顶层使所有 TableRow 实例共享一份引用(不会随 setup 重建)。\n */\nconst ROW_MOD_CLASS: readonly string[] = [\n '',\n 'is-expanded',\n 'is-checked',\n 'is-checked is-expanded',\n 'is-current',\n 'is-current is-expanded',\n 'is-current is-checked',\n 'is-current is-checked is-expanded'\n] as const\n\nfunction rowModCls(row: ITableRow): string {\n const i = (row.isCurrent ? 4 : 0) | (row.checked ? 2 : 0) | (row.expanded ? 1 : 0)\n return ROW_MOD_CLASS[i]!\n}\n\nexport const UTableRow: DefineComponent<{ row: ITableRow; index: number }> = defineComponent({\n name: 'TableRow',\n\n props: {\n row: { type: Object as () => ITableRow, required: true },\n index: { type: Number, required: true }\n },\n\n setup(props) {\n const {\n cls,\n columnConfig,\n getColumnSlotsNode,\n toggleTreeRowExpand,\n getCellCtx,\n handleRowClick,\n handleCellClick,\n tableProps,\n measureElement,\n getCellClass\n } = inject(TableDIKey)!\n\n // A1:把 cls.e('row') / cls.e('expand-toggle') 等静态字符串提升到 setup 一次性计算,\n // 避免每次 render 重新走 `cls.e` 拼接。\n const rowBaseCls = cls.e('row')\n const expandToggleCls = cls.e('expand-toggle')\n const expandSpaceCls = cls.e('expand-space')\n\n // A2:measureRef 按 index 缓存闭包。\n // 行虚拟化下,同一 TableRow 实例的 props.index 大多稳定,但在虚拟化重排或展开行切换时\n // 仍会发生变化;把 (index) → refFn 缓存起来,既保证引用稳定(利于 Vue 的 ref diff),\n // 又避免在未来可能的\"同实例跨 index 复用\"情形下重复分配闭包。\n const measureRefCache = new Map<number, (el: unknown) => void>()\n const getMeasureRef = (index: number): ((el: unknown) => void) => {\n let ref = measureRefCache.get(index)\n if (ref === undefined) {\n ref = (el: unknown) => {\n measureElement(index, el as Element | null)\n }\n measureRefCache.set(index, ref)\n }\n return ref\n }\n onUnmounted(() => {\n measureRefCache.clear()\n })\n\n const { columns, expandColumn } = columnConfig\n\n return () => {\n const { row } = props\n const _expandColumn = expandColumn.value\n\n let expandCell: JSX.Element | null = null\n if (_expandColumn) {\n const expandCtx = getCellCtx(row, _expandColumn)\n const cellSpan = tableProps.mergeCell?.(expandCtx)\n\n if (!cellSpan || (cellSpan.colspan && cellSpan.rowspan)) {\n const marginLeft = tableProps.tree ? (row.depth - 1) * 14 : 0\n expandCell = (\n <UTabelCell\n cellClass={getCellClass(_expandColumn)}\n left={_expandColumn.style.left}\n right={_expandColumn.style.right}\n key={row.uid + _expandColumn.key}\n {...tableProps.mergeCell?.(expandCtx)}\n >\n {!row.isLeaf || (tableProps.expandable && !row.isExpandRow) ? (\n <UButton\n text\n class={expandToggleCls}\n type='primary'\n size='small'\n circle\n onClick={(e) => {\n e.stopPropagation()\n toggleTreeRowExpand(row)\n }}\n style={`margin-left: ${marginLeft}px`}\n >\n <UIcon>\n <ArrowRight />\n </UIcon>\n </UButton>\n ) : (\n <i class={expandSpaceCls} style={`margin-left: ${marginLeft}px`}></i>\n )}\n\n {getColumnSlotsNode(expandCtx)}\n </UTabelCell>\n )\n }\n }\n\n // A3:行 class 由 base + 3-bit 组合查找产出,避免 Vue 的 class 数组归一化开销。\n const mod = rowModCls(row)\n const rowCls = mod ? rowBaseCls + ' ' + mod : rowBaseCls\n\n return (\n <tr\n class={rowCls}\n onClick={(e) => handleRowClick(row, e)}\n ref={getMeasureRef(props.index)}\n key={row.uid}\n >\n {expandCell}\n\n {columns.value.map((column) => {\n const cellCtx = getCellCtx(row, column)\n const cellSpan = tableProps.mergeCell?.(cellCtx)\n if (cellSpan && (!cellSpan.colspan || !cellSpan.rowspan)) {\n return null\n }\n\n const cellNode = getColumnSlotsNode(cellCtx)\n\n return (\n <UTabelCell\n cellClass={getCellClass(column)}\n left={column.style.left}\n right={column.style.right}\n key={row.uid + column.key}\n // @ts-ignore\n onClick={(e) => handleCellClick(row, column, e)}\n {...cellSpan}\n >\n {cellNode}\n </UTabelCell>\n )\n })}\n </tr>\n )\n }\n }\n})\n\nexport const UExpandTableRow = defineComponent({\n name: 'ExpandTableRow',\n props: {\n row: { type: Object as () => ITableRow, required: true },\n index: { type: Number, required: true }\n },\n setup(props) {\n const { getExpandRowSlotsNode, cls, measureElement, columnConfig } = inject(TableDIKey)!\n const { leafColumns } = columnConfig\n\n const rowBaseCls = cls.e('row')\n const cellCls = cls.e('cell')\n\n const measureRef = (el: unknown) => {\n measureElement(props.index, el as Element | null)\n }\n\n return () => {\n const { row } = props\n const node = getExpandRowSlotsNode({\n row,\n index: row.index,\n columns: leafColumns.value,\n rowData: row.data\n })\n return (\n <tr class={rowBaseCls} ref={measureRef}>\n <td colspan={leafColumns.value.length} class={cellCls}>\n {node}\n </td>\n </tr>\n )\n }\n }\n})\n"],"mappings":"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"}
@@ -9,10 +9,21 @@ import { useColumns } from "./use-columns.js";
9
9
  import { useFixedColumns } from "./use-fixed-columns.js";
10
10
  import { useRows } from "./use-rows.js";
11
11
  import { useTable } from "./use-table.js";
12
- import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createVNode, defineComponent, nextTick, normalizeClass, normalizeStyle, openBlock, provide, renderList, renderSlot, shallowRef, toRef, unref, useSlots, useTemplateRef, watch, withCtx } from "vue";
13
- import { bem, setStyles, withUnit } from "@veltra/utils";
14
- import { useFallbackProps, useVirtual } from "@veltra/compositions";
12
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createTextVNode, createVNode, defineComponent, normalizeClass, normalizeStyle, openBlock, provide, renderList, renderSlot, shallowRef, toDisplayString, toRef, unref, useSlots, useTemplateRef, watch, withCtx } from "vue";
13
+ import { bem, withUnit } from "@veltra/utils";
14
+ import { useFallbackProps, useVirtualizer } from "@veltra/compositions";
15
15
  //#region src/components/table/table.vue
16
+ const _hoisted_1 = {
17
+ key: 0,
18
+ "aria-hidden": "true"
19
+ };
20
+ const _hoisted_2 = ["colspan"];
21
+ const _hoisted_3 = {
22
+ key: 1,
23
+ "aria-hidden": "true"
24
+ };
25
+ const _hoisted_4 = ["colspan"];
26
+ const LENGTH_DELTA_RATIO_TO_RESET_SCROLL = .5;
16
27
  const _sfc_main = /* @__PURE__ */ defineComponent({
17
28
  name: "Table",
18
29
  __name: "table",
@@ -60,9 +71,14 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
60
71
  const slots = useSlots();
61
72
  const cls = bem("table");
62
73
  const { size } = useFallbackProps([props], { size: "default" });
74
+ const scrollRef = shallowRef();
75
+ const beforeSpacerRef = useTemplateRef("beforeSpacerRef");
76
+ const afterSpacerRef = useTemplateRef("afterSpacerRef");
77
+ const isScrollingRelay = shallowRef(false);
63
78
  const { rows, toggleTreeRowExpand, allExpanded, toggleAllTreeRowExpand, rowForest, handleRowClick, handleCellClick, getRowByData } = useRows({
64
79
  props,
65
- emit
80
+ emit,
81
+ isScrolling: isScrollingRelay
66
82
  });
67
83
  const { createCheckColumn, createSelectColumn, clearChecked, clearSelected, checkedRows } = useCheck({
68
84
  size,
@@ -80,39 +96,47 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
80
96
  cls
81
97
  });
82
98
  const { leafColumns } = columnConfig;
83
- const { handleScroll, leftFixed, rightFixed } = useFixedColumns();
99
+ const { handleScroll, leftFixed, rightFixed } = useFixedColumns({ isScrolling: isScrollingRelay });
84
100
  const { getColumnSlotsNode, getExpandRowSlotsNode, getHeaderSlotsNode, getCellClass, getCellCtx, getHeaderCellClass } = useTable({
85
101
  props,
86
102
  cls,
87
103
  leftFixed,
88
104
  rightFixed
89
105
  });
90
- const scrollRef = shallowRef();
91
106
  const { showResizeLine, resizeLineRef, colgroupRef } = useColResize({
92
107
  scrollRef,
93
108
  leafColumns
94
109
  });
95
- const virtualCtx = useVirtual({
110
+ const { virtualizer, items, isScrolling } = useVirtualizer({
96
111
  count: computed(() => rows.value.length),
97
- scrollEl: computed(() => scrollRef.value?.containerRef ?? null),
98
- estimateSize: () => 52,
99
- virtualThreshold: toRef(props, "virtualThreshold")
112
+ scrollEl: () => scrollRef.value?.containerRef ?? null,
113
+ beforeEl: beforeSpacerRef,
114
+ afterEl: afterSpacerRef,
115
+ estimateSize: () => 41,
116
+ getItemKey: (i) => rows.value[i]?.uid ?? i
117
+ });
118
+ const virtualEnabled = computed(() => {
119
+ const t = props.virtualThreshold;
120
+ return t ? rows.value.length > t : true;
100
121
  });
101
- const { virtualList, totalHeight, virtualEnabled } = virtualCtx;
102
- const spaceHeight = computed(() => {
103
- if (virtualList.value.length) return totalHeight.value - virtualList.value[virtualList.value.length - 1].end + virtualList.value[0].start;
104
- return totalHeight.value;
122
+ watch(isScrolling, (v) => {
123
+ isScrollingRelay.value = v;
105
124
  });
106
- const spaceRef = shallowRef();
107
- watch(spaceHeight, (spaceHeight) => {
108
- nextTick(() => {
109
- spaceRef.value && setStyles(spaceRef.value, { height: spaceHeight ? `${spaceHeight}px` : void 0 });
110
- });
111
- }, { immediate: true });
112
- const tableBodyRef = useTemplateRef("tableBody");
113
- watch(() => props.data, () => {
114
- tableBodyRef.value?.setBodyTransform(0);
115
- scrollRef.value?.scrollTo({ y: 0 });
125
+ /**
126
+ * E1:数据替换时的条件置顶。
127
+ *
128
+ * 旧实现无差别地 `scrollTo({ y: 0 })`,在「行内增删 / 排序 / 单行更新」等
129
+ * 场景会把用户当前阅读位置跳走;仅在「翻页 / 显著数据集替换」时才需要复位。
130
+ *
131
+ * 判定阈值:|Δlen| / max(newLen, oldLen) ≥ 0.5,即行数变化量超过更大侧的一半时视为显著变化。
132
+ * 选择 0.5 是因为典型的分页/搜索场景会带来 50% 以上的行数差(通常完全替换数据集),
133
+ * 而行内追加 / 删除个位数通常只带来 <5% 的变化;两者区分度足够宽。
134
+ */
135
+ watch(() => props.data, (next, prev) => {
136
+ const nextLen = next?.length ?? 0;
137
+ const prevLen = prev?.length ?? 0;
138
+ const denom = Math.max(nextLen, prevLen, 1);
139
+ if (Math.abs(nextLen - prevLen) / denom >= LENGTH_DELTA_RATIO_TO_RESET_SCROLL) scrollRef.value?.scrollTo({ y: 0 });
116
140
  });
117
141
  provide(TableDIKey, {
118
142
  tableProps: props,
@@ -130,7 +154,9 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
130
154
  getCellCtx,
131
155
  getHeaderCellClass,
132
156
  toggleTreeRowExpand,
133
- ...virtualCtx
157
+ virtualList: items,
158
+ virtualEnabled,
159
+ measureElement: (index, el) => virtualizer.measureElement(index, el)
134
160
  });
135
161
  const tableFootRef = useTemplateRef("tableFoot");
136
162
  __expose({
@@ -155,6 +181,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
155
181
  onScroll: unref(handleScroll)
156
182
  }, {
157
183
  default: withCtx(() => [
184
+ createTextVNode(toDisplayString(console.log(1)) + " ", 1),
158
185
  createElementVNode("table", { class: normalizeClass(unref(cls).e("wrap")) }, [
159
186
  createElementVNode("colgroup", {
160
187
  ref_key: "colgroupRef",
@@ -170,7 +197,18 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
170
197
  }, null, 6);
171
198
  }), 128))], 512),
172
199
  createVNode(unref(TableHead)),
173
- createVNode(_sfc_main$2, { ref: "tableBody" }, createSlots({
200
+ createCommentVNode("\n 虚拟滚动上占位:替代 tbody transform,避免 table-layout: fixed 下列宽抖动。\n 高度由 useVirtualizer 命令式写入 `style.height`,不经过模板响应式绑定,\n 因此滚动窗口变化不会引起表格模板重渲染。\n "),
201
+ virtualEnabled.value ? (openBlock(), createElementBlock("tbody", _hoisted_1, [createElementVNode("tr", {
202
+ ref_key: "beforeSpacerRef",
203
+ ref: beforeSpacerRef
204
+ }, [createElementVNode("td", {
205
+ colspan: unref(leafColumns).length,
206
+ style: {
207
+ "padding": "0",
208
+ "border": "none"
209
+ }
210
+ }, null, 8, _hoisted_2)], 512)])) : createCommentVNode("v-if", true),
211
+ createVNode(_sfc_main$2, null, createSlots({
174
212
  default: withCtx(() => [renderSlot(_ctx.$slots, "body", {
175
213
  columns: unref(leafColumns),
176
214
  rows: unref(rows)
@@ -180,14 +218,18 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
180
218
  name: "empty",
181
219
  fn: withCtx(() => [renderSlot(_ctx.$slots, "empty")]),
182
220
  key: "0"
183
- } : void 0]), 1536),
184
- createCommentVNode(" 占用空间,用来撑开表格高度 "),
185
- unref(virtualEnabled) ? (openBlock(), createElementBlock("tbody", {
186
- key: 0,
187
- ref_key: "spaceRef",
188
- ref: spaceRef,
189
- style: { width: "1px" }
190
- }, null, 512)) : createCommentVNode("v-if", true),
221
+ } : void 0]), 1024),
222
+ createCommentVNode(" 虚拟滚动下占位(高度由 useVirtualizer 命令式写入) "),
223
+ virtualEnabled.value ? (openBlock(), createElementBlock("tbody", _hoisted_3, [createElementVNode("tr", {
224
+ ref_key: "afterSpacerRef",
225
+ ref: afterSpacerRef
226
+ }, [createElementVNode("td", {
227
+ colspan: unref(leafColumns).length,
228
+ style: {
229
+ "padding": "0",
230
+ "border": "none"
231
+ }
232
+ }, null, 8, _hoisted_4)], 512)])) : createCommentVNode("v-if", true),
191
233
  createVNode(_sfc_main$3, { ref: "tableFoot" }, {
192
234
  default: withCtx(() => [renderSlot(_ctx.$slots, "foot", {
193
235
  columns: unref(leafColumns),
@@ -1 +1 @@
1
- {"version":3,"file":"table.js","names":[],"sources":["../../../src/components/table/table.vue"],"sourcesContent":["<template>\n <u-scroll\n :class=\"[\n cls.b,\n cls.m(size),\n bem.is('border', border),\n bem.is('all-expanded', allExpanded),\n bem.is('text-ellipsis', textEllipsis)\n ]\"\n ref=\"scrollRef\"\n :content-class=\"cls.e('content')\"\n @scroll=\"handleScroll\"\n >\n <table :class=\"cls.e('wrap')\">\n <colgroup ref=\"colgroupRef\">\n <col\n v-for=\"column of leafColumns\"\n :key=\"column.key\"\n :class=\"column.fixed\"\n :style=\"{\n width: withUnit(column.width, 'px'),\n minWidth: withUnit(column.minWidth, 'px')\n }\"\n />\n </colgroup>\n <UTableHead />\n\n <u-table-body ref=\"tableBody\">\n <slot name=\"body\" :columns=\"leafColumns\" :rows=\"rows\" />\n\n <template #empty v-if=\"slots.empty\">\n <slot name=\"empty\" />\n </template>\n </u-table-body>\n\n <!-- 占用空间,用来撑开表格高度 -->\n <tbody v-if=\"virtualEnabled\" ref=\"spaceRef\" :style=\"{ width: '1px' }\"></tbody>\n\n <u-table-foot ref=\"tableFoot\">\n <slot name=\"foot\" :columns=\"leafColumns\" :rows=\"rows\" />\n </u-table-foot>\n </table>\n\n <slot name=\"append\" />\n\n <div v-if=\"showResizeLine\" :class=\"cls.e('resize-line')\" ref=\"resizeLineRef\"></div>\n\n <!-- <u-tip v-if=\"textEllipsis\" ref=\"tip\"> </u-tip> -->\n </u-scroll>\n</template>\n\n<script lang=\"ts\" setup>\nimport { useFallbackProps, useVirtual } from '@veltra/compositions'\nimport { bem, setStyles, withUnit } from '@veltra/utils'\nimport { computed, nextTick, provide, shallowRef, toRef, useTemplateRef, watch } from 'vue'\n\nimport type {\n TableProps,\n TableEmits,\n _TableExposed,\n TableColumnSlotsScope,\n ComponentSize,\n ScrollExposed,\n TableRowSlotsScope\n} from '../../types'\nimport { UScroll } from '../scroll'\nimport { TableDIKey } from './di'\nimport type { ColumnNode } from './node/col'\nimport type { TableRowNode } from './node/row'\nimport UTableBody from './table-body.vue'\nimport UTableFoot from './table-foot.vue'\n// import { UTip } from '../tip'\nimport UTableHead from './table-head'\nimport { useCheck } from './use-check'\nimport { useColResize } from './use-col-resize'\nimport { useColumns } from './use-columns'\nimport { useFixedColumns } from './use-fixed-columns'\nimport { useRows } from './use-rows'\nimport { useTable } from './use-table'\n\ndefineOptions({ name: 'Table' })\n\nconst props = withDefaults(defineProps<TableProps>(), {\n tree: false,\n stripe: true,\n textOverflow: 'line-break',\n virtualThreshold: 80\n})\nconst emit = defineEmits<TableEmits>()\n\nconst slots = defineSlots<{\n [key: `column:${string}`]: (props: TableColumnSlotsScope) => any\n [key: `header:${string}`]: (props: { column: ColumnNode }) => any\n 'row:expand'?: (props: TableRowSlotsScope) => any\n foot?: (props: { columns: ColumnNode[]; rows: TableRowNode[] }) => any\n body?: (props: { columns: ColumnNode[]; rows: TableRowNode[] }) => any\n empty?: () => any\n append?: () => any\n}>()\n\nconst cls = bem('table')\n\nconst { size } = useFallbackProps([props], { size: 'default' as ComponentSize })\n\n// 行\nconst {\n rows,\n toggleTreeRowExpand,\n allExpanded,\n toggleAllTreeRowExpand,\n rowForest,\n handleRowClick,\n handleCellClick,\n getRowByData\n} = useRows({ props, emit })\n\n// 选中\nconst { createCheckColumn, createSelectColumn, clearChecked, clearSelected, checkedRows } =\n useCheck({ size, props, rows, rowForest, emit, cls })\n\n// 列\nconst columnConfig = useColumns({\n props,\n createCheckColumn,\n createSelectColumn,\n toggleAllTreeRowExpand,\n cls\n})\n\nconst { leafColumns } = columnConfig\n\nconst { handleScroll, leftFixed, rightFixed } = useFixedColumns()\n\n// 在表格中提供的通用方法和属性\nconst {\n getColumnSlotsNode,\n getExpandRowSlotsNode,\n getHeaderSlotsNode,\n getCellClass,\n getCellCtx,\n getHeaderCellClass\n} = useTable({ props, cls, leftFixed, rightFixed })\n\nconst scrollRef = shallowRef<ScrollExposed>()\n\nconst { showResizeLine, resizeLineRef, colgroupRef } = useColResize({ scrollRef, leafColumns })\n\nconst virtualCtx = useVirtual({\n count: computed(() => rows.value.length),\n scrollEl: computed(() => scrollRef.value?.containerRef ?? null),\n estimateSize: () => 52,\n virtualThreshold: toRef(props, 'virtualThreshold')\n})\n\nconst { virtualList, totalHeight, virtualEnabled } = virtualCtx\n\nconst spaceHeight = computed(() => {\n if (virtualList.value.length) {\n return (\n totalHeight.value -\n virtualList.value[virtualList.value.length - 1]!.end +\n virtualList.value[0]!.start\n )\n }\n return totalHeight.value\n})\n\nconst spaceRef = shallowRef<HTMLElement>()\n\nwatch(\n spaceHeight,\n (spaceHeight) => {\n nextTick(() => {\n spaceRef.value &&\n setStyles(spaceRef.value, { height: spaceHeight ? `${spaceHeight}px` : undefined })\n })\n },\n { immediate: true }\n)\n\nconst tableBodyRef = useTemplateRef('tableBody')\nwatch(\n () => props.data,\n () => {\n tableBodyRef.value?.setBodyTransform(0)\n scrollRef.value?.scrollTo({ y: 0 })\n }\n)\n\n// const tipRef = useTemplateRef('tip')\n\nprovide(TableDIKey, {\n tableProps: props,\n tableSlots: slots,\n cls,\n rows,\n checkedRows,\n columnConfig,\n handleRowClick,\n handleCellClick,\n getColumnSlotsNode,\n getExpandRowSlotsNode,\n getHeaderSlotsNode,\n getCellClass,\n getCellCtx,\n getHeaderCellClass,\n // tipRef,\n toggleTreeRowExpand,\n ...virtualCtx\n})\n\nconst tableFootRef = useTemplateRef('tableFoot')\n\ndefineExpose<_TableExposed>({\n el: toRef(() => scrollRef.value?.el),\n clearChecked,\n clearSelected,\n getRowByData,\n getSummaryRow: () => tableFootRef.value!.getSummaryRow()\n})\n</script>\n"],"mappings":"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"}
1
+ {"version":3,"file":"table.js","names":[],"sources":["../../../src/components/table/table.vue"],"sourcesContent":["<template>\n <u-scroll\n :class=\"[\n cls.b,\n cls.m(size),\n bem.is('border', border),\n bem.is('all-expanded', allExpanded),\n bem.is('text-ellipsis', textEllipsis)\n ]\"\n ref=\"scrollRef\"\n :content-class=\"cls.e('content')\"\n @scroll=\"handleScroll\"\n >\n {{ console.log(1) }}\n <table :class=\"cls.e('wrap')\">\n <colgroup ref=\"colgroupRef\">\n <col\n v-for=\"column of leafColumns\"\n :key=\"column.key\"\n :class=\"column.fixed\"\n :style=\"{\n width: withUnit(column.width, 'px'),\n minWidth: withUnit(column.minWidth, 'px')\n }\"\n />\n </colgroup>\n <UTableHead />\n\n <!--\n 虚拟滚动上占位:替代 tbody transform,避免 table-layout: fixed 下列宽抖动。\n 高度由 useVirtualizer 命令式写入 `style.height`,不经过模板响应式绑定,\n 因此滚动窗口变化不会引起表格模板重渲染。\n -->\n <tbody v-if=\"virtualEnabled\" aria-hidden=\"true\">\n <tr ref=\"beforeSpacerRef\">\n <td :colspan=\"leafColumns.length\" style=\"padding: 0; border: none\"></td>\n </tr>\n </tbody>\n\n <u-table-body>\n <slot name=\"body\" :columns=\"leafColumns\" :rows=\"rows\" />\n\n <template #empty v-if=\"slots.empty\">\n <slot name=\"empty\" />\n </template>\n </u-table-body>\n\n <!-- 虚拟滚动下占位(高度由 useVirtualizer 命令式写入) -->\n <tbody v-if=\"virtualEnabled\" aria-hidden=\"true\">\n <tr ref=\"afterSpacerRef\">\n <td :colspan=\"leafColumns.length\" style=\"padding: 0; border: none\"></td>\n </tr>\n </tbody>\n\n <u-table-foot ref=\"tableFoot\">\n <slot name=\"foot\" :columns=\"leafColumns\" :rows=\"rows\" />\n </u-table-foot>\n </table>\n\n <slot name=\"append\" />\n\n <div v-if=\"showResizeLine\" :class=\"cls.e('resize-line')\" ref=\"resizeLineRef\"></div>\n\n <!-- <u-tip v-if=\"textEllipsis\" ref=\"tip\"> </u-tip> -->\n </u-scroll>\n</template>\n\n<script lang=\"ts\" setup>\nimport { useFallbackProps, useVirtualizer } from '@veltra/compositions'\nimport { bem, withUnit } from '@veltra/utils'\nimport { computed, provide, shallowRef, toRef, useTemplateRef, watch } from 'vue'\n\nimport type {\n TableProps,\n TableEmits,\n _TableExposed,\n TableColumnSlotsScope,\n ComponentSize,\n ScrollExposed,\n TableRowSlotsScope\n} from '../../types'\nimport { UScroll } from '../scroll'\nimport { TableDIKey } from './di'\nimport type { ColumnNode } from './node/col'\nimport type { TableRowNode } from './node/row'\nimport UTableBody from './table-body.vue'\nimport UTableFoot from './table-foot.vue'\n// import { UTip } from '../tip'\nimport UTableHead from './table-head'\nimport { useCheck } from './use-check'\nimport { useColResize } from './use-col-resize'\nimport { useColumns } from './use-columns'\nimport { useFixedColumns } from './use-fixed-columns'\nimport { useRows } from './use-rows'\nimport { useTable } from './use-table'\n\ndefineOptions({ name: 'Table' })\n\nconst props = withDefaults(defineProps<TableProps>(), {\n tree: false,\n stripe: true,\n textOverflow: 'line-break',\n virtualThreshold: 80\n})\nconst emit = defineEmits<TableEmits>()\n\nconst slots = defineSlots<{\n [key: `column:${string}`]: (props: TableColumnSlotsScope) => any\n [key: `header:${string}`]: (props: { column: ColumnNode }) => any\n 'row:expand'?: (props: TableRowSlotsScope) => any\n foot?: (props: { columns: ColumnNode[]; rows: TableRowNode[] }) => any\n body?: (props: { columns: ColumnNode[]; rows: TableRowNode[] }) => any\n empty?: () => any\n append?: () => any\n}>()\n\nconst cls = bem('table')\n\nconst { size } = useFallbackProps([props], { size: 'default' as ComponentSize })\n\nconst scrollRef = shallowRef<ScrollExposed>()\n\n// 占位 tr 的 DOM 句柄;交给 useVirtualizer 直接命令式写 style.height。\nconst beforeSpacerRef = useTemplateRef<HTMLElement>('beforeSpacerRef')\nconst afterSpacerRef = useTemplateRef<HTMLElement>('afterSpacerRef')\n\n// 行(useRows / useFixedColumns 需要 isScrolling,而虚拟化 hook 又依赖 rows.length;\n// 先用中继占位,下方 isScrolling 到位后 watch 回灌)。\nconst isScrollingRelay = shallowRef(false)\n\nconst {\n rows,\n toggleTreeRowExpand,\n allExpanded,\n toggleAllTreeRowExpand,\n rowForest,\n handleRowClick,\n handleCellClick,\n getRowByData\n} = useRows({ props, emit, isScrolling: isScrollingRelay })\n\nconst { createCheckColumn, createSelectColumn, clearChecked, clearSelected, checkedRows } =\n useCheck({ size, props, rows, rowForest, emit, cls })\n\nconst columnConfig = useColumns({\n props,\n createCheckColumn,\n createSelectColumn,\n toggleAllTreeRowExpand,\n cls\n})\n\nconst { leafColumns } = columnConfig\n\nconst { handleScroll, leftFixed, rightFixed } = useFixedColumns({\n isScrolling: isScrollingRelay\n})\n\nconst {\n getColumnSlotsNode,\n getExpandRowSlotsNode,\n getHeaderSlotsNode,\n getCellClass,\n getCellCtx,\n getHeaderCellClass\n} = useTable({ props, cls, leftFixed, rightFixed })\n\nconst { showResizeLine, resizeLineRef, colgroupRef } = useColResize({ scrollRef, leafColumns })\n\nconst { virtualizer, items, isScrolling } = useVirtualizer({\n count: computed(() => rows.value.length),\n scrollEl: () => scrollRef.value?.containerRef ?? null,\n beforeEl: beforeSpacerRef,\n afterEl: afterSpacerRef,\n estimateSize: () => 41,\n // 注意:getItemKey 在构造期一次性写入底层 Virtualizer;闭包每次读的是当前 rows.value,\n // rows 引用整体替换后 key 仍有效(无需再 setOptions)。\n getItemKey: (i) => rows.value[i]?.uid ?? i\n})\n\nconst virtualEnabled = computed(() => {\n const t = props.virtualThreshold\n return t ? rows.value.length > t : true\n})\n\n// 把虚拟化 isScrolling 回灌到中继,供 useRows / useFixedColumns 读取。\nwatch(isScrolling, (v) => {\n isScrollingRelay.value = v\n})\n\n/**\n * E1:数据替换时的条件置顶。\n *\n * 旧实现无差别地 `scrollTo({ y: 0 })`,在「行内增删 / 排序 / 单行更新」等\n * 场景会把用户当前阅读位置跳走;仅在「翻页 / 显著数据集替换」时才需要复位。\n *\n * 判定阈值:|Δlen| / max(newLen, oldLen) ≥ 0.5,即行数变化量超过更大侧的一半时视为显著变化。\n * 选择 0.5 是因为典型的分页/搜索场景会带来 50% 以上的行数差(通常完全替换数据集),\n * 而行内追加 / 删除个位数通常只带来 <5% 的变化;两者区分度足够宽。\n */\nconst LENGTH_DELTA_RATIO_TO_RESET_SCROLL = 0.5\n\nwatch(\n () => props.data,\n (next, prev) => {\n const nextLen = next?.length ?? 0\n const prevLen = prev?.length ?? 0\n const denom = Math.max(nextLen, prevLen, 1)\n const ratio = Math.abs(nextLen - prevLen) / denom\n if (ratio >= LENGTH_DELTA_RATIO_TO_RESET_SCROLL) {\n scrollRef.value?.scrollTo({ y: 0 })\n }\n }\n)\n\n// const tipRef = useTemplateRef('tip')\n\nprovide(TableDIKey, {\n tableProps: props,\n tableSlots: slots,\n cls,\n rows,\n checkedRows,\n columnConfig,\n handleRowClick,\n handleCellClick,\n getColumnSlotsNode,\n getExpandRowSlotsNode,\n getHeaderSlotsNode,\n getCellClass,\n getCellCtx,\n getHeaderCellClass,\n // tipRef,\n toggleTreeRowExpand,\n virtualList: items,\n virtualEnabled,\n measureElement: (index, el) => virtualizer.measureElement(index, el)\n})\n\nconst tableFootRef = useTemplateRef('tableFoot')\n\ndefineExpose<_TableExposed>({\n el: toRef(() => scrollRef.value?.el),\n clearChecked,\n clearSelected,\n getRowByData,\n getSummaryRow: () => tableFootRef.value!.getSummaryRow()\n})\n</script>\n"],"mappings":"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"}
@@ -1,22 +1,41 @@
1
- import { ref } from "vue";
1
+ import { ref, watch } from "vue";
2
2
  import { debounce } from "@cat-kit/core";
3
3
  //#region src/components/table/use-fixed-columns.ts
4
4
  /**
5
5
  * 固定列
6
6
  * @returns
7
7
  */
8
- function useFixedColumns() {
8
+ function useFixedColumns(options = {}) {
9
+ const { isScrolling } = options;
9
10
  const leftFixed = ref(false);
10
11
  const rightFixed = ref(false);
12
+ let pendingLeft = null;
13
+ let pendingRight = null;
14
+ const applyImmediate = (nextLeft, nextRight) => {
15
+ if (nextLeft !== leftFixed.value) leftFixed.value = nextLeft;
16
+ if (nextRight !== rightFixed.value) rightFixed.value = nextRight;
17
+ pendingLeft = null;
18
+ pendingRight = null;
19
+ };
20
+ const handleScroll = debounce((e) => {
21
+ const nextLeft = e.x > 0;
22
+ const nextRight = !(e.cw + e.x + 1 >= e.sw);
23
+ if (isScrolling?.value) {
24
+ pendingLeft = nextLeft;
25
+ pendingRight = nextRight;
26
+ return;
27
+ }
28
+ applyImmediate(nextLeft, nextRight);
29
+ }, 50);
30
+ if (isScrolling) watch(isScrolling, (v) => {
31
+ if (v) return;
32
+ if (pendingLeft === null && pendingRight === null) return;
33
+ applyImmediate(pendingLeft ?? leftFixed.value, pendingRight ?? rightFixed.value);
34
+ });
11
35
  return {
12
36
  leftFixed,
13
37
  rightFixed,
14
- handleScroll: debounce((e) => {
15
- if (e.x > 0) leftFixed.value = true;
16
- else leftFixed.value = false;
17
- if (e.cw + e.x + 1 >= e.sw) rightFixed.value = false;
18
- else rightFixed.value = true;
19
- }, 50)
38
+ handleScroll
20
39
  };
21
40
  }
22
41
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"use-fixed-columns.js","names":[],"sources":["../../../src/components/table/use-fixed-columns.ts"],"sourcesContent":["import { debounce } from '@cat-kit/core'\nimport { type ShallowRef, ref } from 'vue'\n\nimport type { ScrollPosition } from '../../types'\n\ninterface UseColumnFixedReturned {\n leftFixed: ShallowRef<boolean>\n rightFixed: ShallowRef<boolean>\n handleScroll: (e: Required<ScrollPosition>) => void\n}\n\n/**\n * 固定列\n * @returns\n */\nexport function useFixedColumns(): UseColumnFixedReturned {\n const leftFixed = ref(false)\n const rightFixed = ref(false)\n\n const handleScroll = debounce((e: Required<ScrollPosition>) => {\n if (e.x > 0) {\n leftFixed.value = true\n } else {\n leftFixed.value = false\n }\n\n // 精度修正\n if (e.cw + e.x + 1 >= e.sw) {\n rightFixed.value = false\n } else {\n rightFixed.value = true\n }\n }, 50)\n\n return { leftFixed, rightFixed, handleScroll }\n}\n"],"mappings":";;;;;;;AAeA,SAAgB,kBAA0C;CACxD,MAAM,YAAY,IAAI,MAAM;CAC5B,MAAM,aAAa,IAAI,MAAM;AAiB7B,QAAO;EAAE;EAAW;EAAY,cAfX,UAAU,MAAgC;AAC7D,OAAI,EAAE,IAAI,EACR,WAAU,QAAQ;OAElB,WAAU,QAAQ;AAIpB,OAAI,EAAE,KAAK,EAAE,IAAI,KAAK,EAAE,GACtB,YAAW,QAAQ;OAEnB,YAAW,QAAQ;KAEpB,GAAG;EAEwC"}
1
+ {"version":3,"file":"use-fixed-columns.js","names":[],"sources":["../../../src/components/table/use-fixed-columns.ts"],"sourcesContent":["import { debounce } from '@cat-kit/core'\nimport { type ShallowRef, type Ref, ref, watch } from 'vue'\n\nimport type { ScrollPosition } from '../../types'\n\ninterface UseColumnFixedReturned {\n leftFixed: ShallowRef<boolean>\n rightFixed: ShallowRef<boolean>\n handleScroll: (e: Required<ScrollPosition>) => void\n}\n\ninterface Options {\n /**\n * D1:滚动中 (`true`) 暂缓 `leftFixed` / `rightFixed` 的切换,滚动结束后\n * 一次性补算。\n *\n * 背景:固定列的阴影是通过在 cell 上切换 `is-last-fixed` / `is-first-fixed`\n * 等 class 实现的,这些 class 会穿透到 `useTable.getCellClass` 的缓存键,\n * 任何一次切换都会让全表的单元格类名失效重算。滚动本身并不需要实时更新阴影,\n * 因此把切换推迟到 `isScrolling` 归零时做一次即可。\n *\n * 未传入时退化到旧行为(实时切换)。\n */\n isScrolling?: Ref<boolean>\n}\n\n/**\n * 固定列\n * @returns\n */\nexport function useFixedColumns(options: Options = {}): UseColumnFixedReturned {\n const { isScrolling } = options\n\n const leftFixed = ref(false)\n const rightFixed = ref(false)\n\n let pendingLeft: boolean | null = null\n let pendingRight: boolean | null = null\n\n const applyImmediate = (nextLeft: boolean, nextRight: boolean): void => {\n if (nextLeft !== leftFixed.value) leftFixed.value = nextLeft\n if (nextRight !== rightFixed.value) rightFixed.value = nextRight\n pendingLeft = null\n pendingRight = null\n }\n\n const handleScroll = debounce((e: Required<ScrollPosition>) => {\n const nextLeft = e.x > 0\n // 精度修正:浏览器滚动条最后 1px 的误差\n const nextRight = !(e.cw + e.x + 1 >= e.sw)\n\n if (isScrolling?.value) {\n pendingLeft = nextLeft\n pendingRight = nextRight\n return\n }\n\n applyImmediate(nextLeft, nextRight)\n }, 50)\n\n if (isScrolling) {\n watch(isScrolling, (v) => {\n if (v) return\n if (pendingLeft === null && pendingRight === null) return\n applyImmediate(pendingLeft ?? leftFixed.value, pendingRight ?? rightFixed.value)\n })\n }\n\n return { leftFixed, rightFixed, handleScroll }\n}\n"],"mappings":";;;;;;;AA8BA,SAAgB,gBAAgB,UAAmB,EAAE,EAA0B;CAC7E,MAAM,EAAE,gBAAgB;CAExB,MAAM,YAAY,IAAI,MAAM;CAC5B,MAAM,aAAa,IAAI,MAAM;CAE7B,IAAI,cAA8B;CAClC,IAAI,eAA+B;CAEnC,MAAM,kBAAkB,UAAmB,cAA6B;AACtE,MAAI,aAAa,UAAU,MAAO,WAAU,QAAQ;AACpD,MAAI,cAAc,WAAW,MAAO,YAAW,QAAQ;AACvD,gBAAc;AACd,iBAAe;;CAGjB,MAAM,eAAe,UAAU,MAAgC;EAC7D,MAAM,WAAW,EAAE,IAAI;EAEvB,MAAM,YAAY,EAAE,EAAE,KAAK,EAAE,IAAI,KAAK,EAAE;AAExC,MAAI,aAAa,OAAO;AACtB,iBAAc;AACd,kBAAe;AACf;;AAGF,iBAAe,UAAU,UAAU;IAClC,GAAG;AAEN,KAAI,YACF,OAAM,cAAc,MAAM;AACxB,MAAI,EAAG;AACP,MAAI,gBAAgB,QAAQ,iBAAiB,KAAM;AACnD,iBAAe,eAAe,UAAU,OAAO,gBAAgB,WAAW,MAAM;GAChF;AAGJ,QAAO;EAAE;EAAW;EAAY;EAAc"}
@@ -4,7 +4,7 @@ import { Forest, o } from "@cat-kit/core";
4
4
  import { useModel as useModel$1 } from "@veltra/compositions";
5
5
  //#region src/components/table/use-rows.ts
6
6
  function useRows(options) {
7
- const { props, emit } = options;
7
+ const { props, emit, isScrolling } = options;
8
8
  /** 所有可见的行 */
9
9
  const rows = shallowRef([]);
10
10
  /** 数据行, 不包括展开和折叠的行 */
@@ -145,7 +145,7 @@ function useRows(options) {
145
145
  }
146
146
  }
147
147
  function handleRowClick(row, e) {
148
- currentRow.value = currentRow.value === row ? void 0 : row;
148
+ if (!isScrolling?.value) currentRow.value = currentRow.value === row ? void 0 : row;
149
149
  emit("row-click", row, e);
150
150
  }
151
151
  function handleCellClick(row, column, e) {
@@ -1 +1 @@
1
- {"version":3,"file":"use-rows.js","names":["useModel"],"sources":["../../../src/components/table/use-rows.ts"],"sourcesContent":["import { Forest, o } from '@cat-kit/core'\nimport { useModel } from '@veltra/compositions'\nimport { shallowRef, watch, type ShallowRef } from 'vue'\n\nimport type { TableColumn, TableEmits, TableProps, TableRow } from '../../types'\nimport { TableRowNode } from './node/row'\n\ninterface Options {\n props: TableProps\n emit: TableEmits\n}\n\ninterface UseRowsReturned {\n /** 所有的行森林(树形数据) */\n rowForest: ShallowRef<Forest<Record<string, unknown>, any> | undefined>\n /** 当前可见的展平行列表 */\n rows: ShallowRef<TableRowNode[]>\n /** 切换树形行节点的展开/折叠 */\n toggleTreeRowExpand: (node: TableRow) => void\n /** 是否全部展开 */\n allExpanded: ShallowRef<boolean>\n /** 切换全部展开/折叠 */\n toggleAllTreeRowExpand: () => void\n /** 行点击处理 */\n handleRowClick: (row: TableRow, e: MouseEvent) => void\n /** 单元格点击处理 */\n handleCellClick: (row: TableRow, column: TableColumn, e: MouseEvent) => void\n /** 根据原始数据对象获取对应的 TableRow */\n getRowByData: (data: Record<string, any>) => TableRow | undefined\n}\n\nexport function useRows(options: Options): UseRowsReturned {\n const { props, emit } = options\n\n // --- 状态定义 ---\n\n /** 所有可见的行 */\n const rows = shallowRef<TableRowNode[]>([])\n /** 数据行, 不包括展开和折叠的行 */\n const dataRows = shallowRef<TableRowNode[]>([])\n /** 行森林(数据结构树) */\n const rowForest = shallowRef<Forest<Record<string, unknown>, any>>()\n /** 树形节点是否全部展开 */\n const allExpanded = shallowRef(props.defaultExpandAll ?? false)\n\n /** 当前高亮的行 */\n const currentRow = useModel({\n props,\n emit,\n propName: 'current',\n shallow: true,\n local: () => !!props.highlightCurrent\n })\n\n // --- 行字典维护 (性能优化) ---\n\n /**\n * 用于优化增删改时的性能。\n * 原理:Vue 的组件在属性发生改变时会重新渲染,通过 WeakMap 缓存 data -> TableRow 的映射,\n * 可以在数据更新时复用已有的 TableRowNode 实例,避免重复创建和 DOM 抖动。\n */\n let rowDicts = new WeakMap<Record<string, any>, TableRowNode>()\n let tempRowDicts: null | WeakMap<Record<string, any>, TableRowNode> = null\n let uidSeed = 0\n\n /** 获取行唯一标识 */\n const getRowUID = props.rowKey\n ? (rowData: Record<string, any>) => rowData && o(rowData).get(props.rowKey!)\n : () => uidSeed++\n\n /** 创建或复用 TableRow 实例 */\n const createRow = (\n data: Record<string, any>,\n index: number,\n depth = 0,\n parent?: TableRowNode\n ) => {\n const existRow = data ? rowDicts.get(data) : undefined\n if (existRow) {\n existRow.index = index\n existRow.depth = depth\n existRow.parent = parent\n return existRow\n }\n return new TableRowNode({ data, index, depth, parent, uid: getRowUID(data) })\n }\n\n // --- 行处理逻辑 ---\n\n /** 处理常规扁平列表 */\n function getDataRows(data: Record<string, any>[]): TableRowNode[] {\n let i = data.length\n const result = Array.from({ length: i }) as TableRowNode[]\n tempRowDicts = new WeakMap()\n\n while (i--) {\n const dataItem = data[i]!\n const row = createRow(dataItem, i)\n result[i] = row\n tempRowDicts.set(dataItem, row)\n }\n\n rowDicts = tempRowDicts\n tempRowDicts = null\n return result\n }\n\n function expandDataRows(): void {\n const result: TableRowNode[] = []\n let i = -1\n while (++i < dataRows.value.length) {\n const row = dataRows.value[i]!\n result.push(row)\n if (row.expanded) {\n result.push(row.copy())\n }\n }\n rows.value = result\n }\n\n /** 处理树形森林结构 */\n function getRowForest(data: Record<string, any>[]): Forest<Record<string, unknown>, any> {\n tempRowDicts = new WeakMap()\n const childrenKey = typeof props.tree === 'string' ? props.tree : 'children'\n const ret = new Forest<Record<string, unknown>, any>({\n data,\n childrenKey,\n createNode(val, index, depth, _forest, parent) {\n const row = createRow(val, index, depth, parent)\n if (props.defaultExpandAll) {\n row.expanded = true\n }\n val && tempRowDicts!.set(val, row)\n return row\n }\n })\n\n rowDicts = tempRowDicts\n tempRowDicts = null\n return ret\n }\n\n /** 展平树形结构为可见行列表 */\n function updateFlattedRows(): void {\n if (!rowForest.value) return\n rows.value = rowForest.value.flattenVisible((n) => n.expanded)\n }\n\n // --- 副作用监听 ---\n\n // 监听当前行切换,维护 isCurrent 状态\n watch(\n () => currentRow.value,\n (row, oldRow) => {\n if (oldRow) oldRow.isCurrent = false\n if (row) row.isCurrent = true\n }\n )\n\n // 同步状态给外部\n watch(rows, (rows) => emit('update:rows', rows))\n watch(rowForest, (forest) => emit('update:forest', forest))\n\n // 核心:监听原始数据变化,重新构建行列表或树\n watch(\n [() => props.data, () => props.tree, () => props.defaultExpandAll],\n ([data, tree]) => {\n if (!data?.length) {\n rows.value = []\n rowDicts = new WeakMap()\n rowForest.value = undefined\n return\n }\n\n if (!tree) {\n rowForest.value = undefined\n dataRows.value = getDataRows(data)\n rows.value = dataRows.value\n } else {\n dataRows.value = []\n rowForest.value = getRowForest(data)\n updateFlattedRows()\n }\n },\n { immediate: true }\n )\n\n // --- 交互方法 ---\n\n function toggleTreeRowExpand(node: TableRow): void {\n node.expanded = !node.expanded\n\n if (props.tree) {\n return updateFlattedRows()\n }\n if (props.expandable) {\n expandDataRows()\n }\n }\n\n function toggleAllTreeRowExpand(): void {\n allExpanded.value = !allExpanded.value\n if (props.tree) {\n rowForest.value?.dfs((node) => {\n node.expanded = allExpanded.value\n })\n updateFlattedRows()\n return\n }\n if (props.expandable) {\n dataRows.value.forEach((row) => {\n row.expanded = allExpanded.value\n })\n expandDataRows()\n return\n }\n }\n\n function handleRowClick(row: TableRow, e: MouseEvent): void {\n currentRow.value = currentRow.value === row ? undefined : row\n emit('row-click', row, e)\n }\n\n function handleCellClick(row: TableRow, column: TableColumn, e: MouseEvent): void {\n emit('cell-click', row, column, e)\n }\n\n function getRowByData(data: Record<string, any>): TableRow | undefined {\n return rowDicts.get(data)\n }\n\n return {\n rowForest,\n rows,\n allExpanded,\n toggleTreeRowExpand,\n toggleAllTreeRowExpand,\n handleRowClick,\n handleCellClick,\n getRowByData\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-rows.js","names":["useModel"],"sources":["../../../src/components/table/use-rows.ts"],"sourcesContent":["import { Forest, o } from '@cat-kit/core'\nimport { useModel } from '@veltra/compositions'\nimport { shallowRef, watch, type Ref, type ShallowRef } from 'vue'\n\nimport type { TableColumn, TableEmits, TableProps, TableRow } from '../../types'\nimport { TableRowNode } from './node/row'\n\ninterface Options {\n props: TableProps\n emit: TableEmits\n /**\n * D2:滚动中 (`true`) 跳过 `currentRow` 高亮写入。\n *\n * 行点击处理会通过 `currentRow` watcher 把 `isCurrent` 切换到新行 / 旧行上,\n * 触发对应 `<tr>` 的 class 重绘。在滚动期间 (移动端/触控板惯性滚动常见)\n * 意外点击不应改变高亮,因此直接跳过;滚动结束后的正常点击仍会生效。\n */\n isScrolling?: Ref<boolean>\n}\n\ninterface UseRowsReturned {\n /** 所有的行森林(树形数据) */\n rowForest: ShallowRef<Forest<Record<string, unknown>, any> | undefined>\n /** 当前可见的展平行列表 */\n rows: ShallowRef<TableRowNode[]>\n /** 切换树形行节点的展开/折叠 */\n toggleTreeRowExpand: (node: TableRow) => void\n /** 是否全部展开 */\n allExpanded: ShallowRef<boolean>\n /** 切换全部展开/折叠 */\n toggleAllTreeRowExpand: () => void\n /** 行点击处理 */\n handleRowClick: (row: TableRow, e: MouseEvent) => void\n /** 单元格点击处理 */\n handleCellClick: (row: TableRow, column: TableColumn, e: MouseEvent) => void\n /** 根据原始数据对象获取对应的 TableRow */\n getRowByData: (data: Record<string, any>) => TableRow | undefined\n}\n\nexport function useRows(options: Options): UseRowsReturned {\n const { props, emit, isScrolling } = options\n\n // --- 状态定义 ---\n\n /** 所有可见的行 */\n const rows = shallowRef<TableRowNode[]>([])\n /** 数据行, 不包括展开和折叠的行 */\n const dataRows = shallowRef<TableRowNode[]>([])\n /** 行森林(数据结构树) */\n const rowForest = shallowRef<Forest<Record<string, unknown>, any>>()\n /** 树形节点是否全部展开 */\n const allExpanded = shallowRef(props.defaultExpandAll ?? false)\n\n /** 当前高亮的行 */\n const currentRow = useModel({\n props,\n emit,\n propName: 'current',\n shallow: true,\n local: () => !!props.highlightCurrent\n })\n\n // --- 行字典维护 (性能优化) ---\n\n /**\n * 用于优化增删改时的性能。\n * 原理:Vue 的组件在属性发生改变时会重新渲染,通过 WeakMap 缓存 data -> TableRow 的映射,\n * 可以在数据更新时复用已有的 TableRowNode 实例,避免重复创建和 DOM 抖动。\n */\n let rowDicts = new WeakMap<Record<string, any>, TableRowNode>()\n let tempRowDicts: null | WeakMap<Record<string, any>, TableRowNode> = null\n let uidSeed = 0\n\n /** 获取行唯一标识 */\n const getRowUID = props.rowKey\n ? (rowData: Record<string, any>) => rowData && o(rowData).get(props.rowKey!)\n : () => uidSeed++\n\n /** 创建或复用 TableRow 实例 */\n const createRow = (\n data: Record<string, any>,\n index: number,\n depth = 0,\n parent?: TableRowNode\n ) => {\n const existRow = data ? rowDicts.get(data) : undefined\n if (existRow) {\n existRow.index = index\n existRow.depth = depth\n existRow.parent = parent\n return existRow\n }\n return new TableRowNode({ data, index, depth, parent, uid: getRowUID(data) })\n }\n\n // --- 行处理逻辑 ---\n\n /** 处理常规扁平列表 */\n function getDataRows(data: Record<string, any>[]): TableRowNode[] {\n let i = data.length\n const result = Array.from({ length: i }) as TableRowNode[]\n tempRowDicts = new WeakMap()\n\n while (i--) {\n const dataItem = data[i]!\n const row = createRow(dataItem, i)\n result[i] = row\n tempRowDicts.set(dataItem, row)\n }\n\n rowDicts = tempRowDicts\n tempRowDicts = null\n return result\n }\n\n function expandDataRows(): void {\n const result: TableRowNode[] = []\n let i = -1\n while (++i < dataRows.value.length) {\n const row = dataRows.value[i]!\n result.push(row)\n if (row.expanded) {\n result.push(row.copy())\n }\n }\n rows.value = result\n }\n\n /** 处理树形森林结构 */\n function getRowForest(data: Record<string, any>[]): Forest<Record<string, unknown>, any> {\n tempRowDicts = new WeakMap()\n const childrenKey = typeof props.tree === 'string' ? props.tree : 'children'\n const ret = new Forest<Record<string, unknown>, any>({\n data,\n childrenKey,\n createNode(val, index, depth, _forest, parent) {\n const row = createRow(val, index, depth, parent)\n if (props.defaultExpandAll) {\n row.expanded = true\n }\n val && tempRowDicts!.set(val, row)\n return row\n }\n })\n\n rowDicts = tempRowDicts\n tempRowDicts = null\n return ret\n }\n\n /** 展平树形结构为可见行列表 */\n function updateFlattedRows(): void {\n if (!rowForest.value) return\n rows.value = rowForest.value.flattenVisible((n) => n.expanded)\n }\n\n // --- 副作用监听 ---\n\n // 监听当前行切换,维护 isCurrent 状态\n watch(\n () => currentRow.value,\n (row, oldRow) => {\n if (oldRow) oldRow.isCurrent = false\n if (row) row.isCurrent = true\n }\n )\n\n // 同步状态给外部\n watch(rows, (rows) => emit('update:rows', rows))\n watch(rowForest, (forest) => emit('update:forest', forest))\n\n // 核心:监听原始数据变化,重新构建行列表或树\n watch(\n [() => props.data, () => props.tree, () => props.defaultExpandAll],\n ([data, tree]) => {\n if (!data?.length) {\n rows.value = []\n rowDicts = new WeakMap()\n rowForest.value = undefined\n return\n }\n\n if (!tree) {\n rowForest.value = undefined\n dataRows.value = getDataRows(data)\n rows.value = dataRows.value\n } else {\n dataRows.value = []\n rowForest.value = getRowForest(data)\n updateFlattedRows()\n }\n },\n { immediate: true }\n )\n\n // --- 交互方法 ---\n\n function toggleTreeRowExpand(node: TableRow): void {\n node.expanded = !node.expanded\n\n if (props.tree) {\n return updateFlattedRows()\n }\n if (props.expandable) {\n expandDataRows()\n }\n }\n\n function toggleAllTreeRowExpand(): void {\n allExpanded.value = !allExpanded.value\n if (props.tree) {\n rowForest.value?.dfs((node) => {\n node.expanded = allExpanded.value\n })\n updateFlattedRows()\n return\n }\n if (props.expandable) {\n dataRows.value.forEach((row) => {\n row.expanded = allExpanded.value\n })\n expandDataRows()\n return\n }\n }\n\n function handleRowClick(row: TableRow, e: MouseEvent): void {\n // D2:滚动中的点击(多为惯性滚动误触)不应改变 currentRow 高亮。\n if (!isScrolling?.value) {\n currentRow.value = currentRow.value === row ? undefined : row\n }\n emit('row-click', row, e)\n }\n\n function handleCellClick(row: TableRow, column: TableColumn, e: MouseEvent): void {\n emit('cell-click', row, column, e)\n }\n\n function getRowByData(data: Record<string, any>): TableRow | undefined {\n return rowDicts.get(data)\n }\n\n return {\n rowForest,\n rows,\n allExpanded,\n toggleTreeRowExpand,\n toggleAllTreeRowExpand,\n handleRowClick,\n handleCellClick,\n getRowByData\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { useSlots } from "vue";
1
+ import { useSlots, watch } from "vue";
2
2
  import { o } from "@cat-kit/core";
3
3
  import { bem } from "@veltra/utils";
4
4
  //#region src/components/table/use-table.ts
@@ -33,11 +33,33 @@ function useTable(options) {
33
33
  if (column.fixed) classList.push(bem.is("fixed-" + column.fixed));
34
34
  return classList.join(" ");
35
35
  };
36
+ /**
37
+ * B1: 单元格类名按 ColumnNode 身份缓存。
38
+ *
39
+ * 对同一列,类名只取决于 (column.fixed, column.isLastFixed, column.isFirstFixed,
40
+ * column.align, leftFixed, rightFixed)。其中前四个绑定在 ColumnNode 实例上,
41
+ * 后两个在 leftFixed/rightFixed 变化时失效缓存即可。用 WeakMap 以 ColumnNode
42
+ * 为键可避免字符串 key 的拼接/哈希开销,且列树重建时节点随 GC 自动释放。
43
+ */
44
+ let cellClassCache = /* @__PURE__ */ new WeakMap();
45
+ let headerCellClassCache = /* @__PURE__ */ new WeakMap();
46
+ watch([leftFixed, rightFixed], () => {
47
+ cellClassCache = /* @__PURE__ */ new WeakMap();
48
+ headerCellClassCache = /* @__PURE__ */ new WeakMap();
49
+ });
36
50
  const getCellClass = (column) => {
37
- return getCommonClassName(column) + ` ${bem.is(column.align)}`;
51
+ const cached = cellClassCache.get(column);
52
+ if (cached !== void 0) return cached;
53
+ const result = getCommonClassName(column) + ` ${bem.is(column.align)}`;
54
+ cellClassCache.set(column, result);
55
+ return result;
38
56
  };
39
57
  const getHeaderCellClass = (column) => {
40
- return getCommonClassName(column) + ` ${bem.is(column.headerAlign)}`;
58
+ const cached = headerCellClassCache.get(column);
59
+ if (cached !== void 0) return cached;
60
+ const result = getCommonClassName(column) + ` ${bem.is(column.headerAlign)}`;
61
+ headerCellClassCache.set(column, result);
62
+ return result;
41
63
  };
42
64
  const getCellCtx = (row, column) => {
43
65
  const rowData = row.data;