niuma-ui 1.0.0 → 1.0.2

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 (159) hide show
  1. package/CHANGELOG.md +50 -1
  2. package/NOTICE +2 -2
  3. package/package.json +13 -5
  4. package/src/__tests__/RsButton.spec.ts +47 -0
  5. package/src/__tests__/RsCard.spec.ts +160 -1
  6. package/src/__tests__/RsConfirmDialog.spec.ts +127 -0
  7. package/src/__tests__/RsContextMenu.spec.ts +12 -0
  8. package/src/__tests__/RsDateTimePicker.spec.ts +11 -0
  9. package/src/__tests__/RsDescriptions.spec.ts +50 -0
  10. package/src/__tests__/RsDialog.spec.ts +117 -0
  11. package/src/__tests__/RsDrawer.spec.ts +96 -3
  12. package/src/__tests__/RsDynamicTags.spec.ts +52 -0
  13. package/src/__tests__/RsForm.spec.ts +245 -1
  14. package/src/__tests__/RsInput.spec.ts +15 -1
  15. package/src/__tests__/RsMenu.spec.ts +406 -14
  16. package/src/__tests__/RsPagination.spec.ts +67 -2
  17. package/src/__tests__/RsSelect.spec.ts +42 -1
  18. package/src/__tests__/RsSwitch.spec.ts +44 -0
  19. package/src/__tests__/RsTable.a11y.spec.ts +83 -0
  20. package/src/__tests__/RsTable.dom-baseline.spec.ts +87 -0
  21. package/src/__tests__/RsTable.perf.spec.ts +23 -4
  22. package/src/__tests__/RsTable.spec.ts +63 -8
  23. package/src/__tests__/RsTable.ssr.spec.ts +71 -0
  24. package/src/__tests__/RsTable.summary.spec.ts +45 -0
  25. package/src/__tests__/RsTabs.spec.ts +102 -4
  26. package/src/__tests__/RsTerminal.spec.ts +1 -1
  27. package/src/__tests__/RsTimePicker.spec.ts +11 -0
  28. package/src/__tests__/RsUpload.spec.ts +62 -6
  29. package/src/__tests__/createRsDialog.spec.ts +145 -0
  30. package/src/__tests__/form-rules.spec.ts +76 -0
  31. package/src/__tests__/perf-budgets.ts +32 -0
  32. package/src/__tests__/rs-table-api.spec.ts +206 -0
  33. package/src/__tests__/rs-table-compat-matrix.spec.ts +108 -0
  34. package/src/__tests__/rs-table-echarts-adapter.spec.ts +43 -0
  35. package/src/__tests__/rs-table-grid-nav.spec.ts +63 -0
  36. package/src/__tests__/rs-table-isolation.spec.ts +158 -0
  37. package/src/__tests__/split-pane-utils.spec.ts +21 -0
  38. package/src/__tests__/table-chart-utils.spec.ts +88 -0
  39. package/src/__tests__/table-summary-utils.spec.ts +62 -0
  40. package/src/__tests__/table-utils.spec.ts +124 -0
  41. package/src/__tests__/theme.spec.ts +25 -0
  42. package/src/__tests__/useRsTableColumnResize.spec.ts +25 -0
  43. package/src/__tests__/useRsTableCore.perf.spec.ts +170 -0
  44. package/src/components/RsAlert.vue +212 -0
  45. package/src/components/RsButton.vue +145 -21
  46. package/src/components/RsCard.vue +200 -16
  47. package/src/components/RsConfirmDialog.vue +215 -28
  48. package/src/components/RsContextMenu.vue +51 -13
  49. package/src/components/RsDatePicker.vue +267 -34
  50. package/src/components/RsDateTimePicker.vue +19 -2
  51. package/src/components/RsDescriptions.vue +186 -0
  52. package/src/components/RsDescriptionsItem.vue +32 -0
  53. package/src/components/RsDialog.vue +363 -51
  54. package/src/components/RsDivider.vue +89 -0
  55. package/src/components/RsDrawer.vue +558 -33
  56. package/src/components/RsDropdown.vue +33 -2
  57. package/src/components/RsDynamicTags.vue +517 -0
  58. package/src/components/RsForm.vue +97 -8
  59. package/src/components/RsInput.vue +80 -82
  60. package/src/components/RsInputNumber.vue +64 -23
  61. package/src/components/RsLabel.vue +20 -5
  62. package/src/components/RsLoadingBar.vue +151 -0
  63. package/src/components/RsMenu.vue +73 -15
  64. package/src/components/RsMenuItems.vue +147 -9
  65. package/src/components/RsPagination.vue +93 -14
  66. package/src/components/RsRadio.vue +81 -0
  67. package/src/components/RsRadioItem.vue +156 -0
  68. package/src/components/RsSelect.vue +67 -58
  69. package/src/components/RsSplitPane.vue +69 -1
  70. package/src/components/RsSwitch.vue +179 -0
  71. package/src/components/RsTable.vue +653 -2555
  72. package/src/components/RsTabs.vue +415 -83
  73. package/src/components/RsTag.vue +191 -0
  74. package/src/components/RsTerminal.vue +3 -3
  75. package/src/components/RsTimePicker.vue +44 -5
  76. package/src/components/RsTimePickerColumns.vue +136 -77
  77. package/src/components/RsToaster.vue +58 -18
  78. package/src/components/RsTree.vue +39 -21
  79. package/src/components/RsUpload.vue +145 -12
  80. package/src/components/RsVNodeHost.vue +20 -0
  81. package/src/components/button-utils.ts +39 -0
  82. package/src/components/context-menu-utils.ts +6 -0
  83. package/src/components/date-picker-utils.ts +84 -4
  84. package/src/components/descriptions-utils.ts +24 -0
  85. package/src/components/dialog-utils.ts +147 -0
  86. package/src/components/dialog-window.ts +74 -9
  87. package/src/components/drawer-utils.ts +73 -0
  88. package/src/components/form-rules.ts +257 -0
  89. package/src/components/form-utils.ts +52 -2
  90. package/src/components/menu-utils.ts +11 -0
  91. package/src/components/overlay-utils.ts +3 -0
  92. package/src/components/radio-utils.ts +12 -0
  93. package/src/components/reka.ts +5 -0
  94. package/src/components/select-utils.ts +21 -2
  95. package/src/components/split-pane-utils.ts +65 -12
  96. package/src/components/table/RsTableBody.vue +217 -0
  97. package/src/components/table/RsTableBodyRow.vue +90 -1
  98. package/src/components/table/RsTableColGroup.vue +41 -0
  99. package/src/components/table/RsTableHeader.vue +158 -0
  100. package/src/components/table/RsTableSummaryRow.vue +52 -0
  101. package/src/components/table/rs-table-api.ts +99 -0
  102. package/src/components/table/rs-table-compat-matrix.ts +71 -0
  103. package/src/components/table/rs-table-echarts-adapter.ts +81 -0
  104. package/src/components/table/rs-table-feature-host.ts +178 -0
  105. package/src/components/table/rs-table-grid-nav.ts +122 -0
  106. package/src/components/table/rs-table-header-types.ts +47 -0
  107. package/src/components/table/rs-table-module-registry.ts +72 -0
  108. package/src/components/table/rs-table-props.ts +274 -0
  109. package/src/components/table/rs-table-view-context.ts +258 -0
  110. package/src/components/table/rs-table.css +84 -6
  111. package/src/components/table/table-chart-utils.ts +165 -0
  112. package/src/components/table/table-features.ts +284 -0
  113. package/src/components/table/table-summary-utils.ts +194 -0
  114. package/src/components/table-utils.ts +452 -2
  115. package/src/components/tabs-utils.ts +142 -2
  116. package/src/components/terminal-utils.ts +1 -1
  117. package/src/components/terminal-wheel.ts +1 -1
  118. package/src/components/time-picker-utils.ts +76 -3
  119. package/src/components/upload-utils.ts +122 -0
  120. package/src/components/virtual-list-utils.ts +15 -2
  121. package/src/composables/assembleRsTableApi.ts +79 -0
  122. package/src/composables/bindRsTableViewContext.ts +269 -0
  123. package/src/composables/createRsDialog.ts +357 -0
  124. package/src/composables/createRsTableEditEmitBridge.ts +38 -0
  125. package/src/composables/useRsLoadingBar.ts +36 -0
  126. package/src/composables/useRsTable.ts +134 -0
  127. package/src/composables/useRsTableA11y.ts +52 -0
  128. package/src/composables/useRsTableChartBridge.ts +79 -0
  129. package/src/composables/useRsTableColumnLayout.ts +320 -0
  130. package/src/composables/useRsTableColumnResize.ts +244 -0
  131. package/src/composables/useRsTableColumnVirtual.ts +180 -0
  132. package/src/composables/useRsTableColumns.ts +200 -0
  133. package/src/composables/useRsTableContextMenu.ts +257 -0
  134. package/src/composables/useRsTableCore.ts +256 -0
  135. package/src/composables/useRsTableEditActions.ts +606 -0
  136. package/src/composables/useRsTableEditLayer.ts +251 -0
  137. package/src/composables/useRsTableEngine.ts +357 -0
  138. package/src/composables/useRsTableGridKeyboard.ts +123 -0
  139. package/src/composables/useRsTableHeadless.ts +137 -0
  140. package/src/composables/useRsTableInteraction.ts +433 -0
  141. package/src/composables/useRsTableScrollHost.ts +75 -0
  142. package/src/composables/useRsTableScrollLayout.ts +255 -0
  143. package/src/composables/useRsTableSelectionSource.ts +89 -0
  144. package/src/composables/useRsTableShell.ts +416 -0
  145. package/src/composables/useRsTableShellChrome.ts +170 -0
  146. package/src/composables/useRsTableSummary.ts +113 -0
  147. package/src/composables/useRsTableViewProvide.ts +263 -0
  148. package/src/composables/useRsTableVirtual.ts +154 -0
  149. package/src/composables/useRsTabsNav.ts +4 -0
  150. package/src/composables/useTableEdit.ts +1 -1
  151. package/src/icons/lucide.ts +2 -2
  152. package/src/index.ts +309 -9
  153. package/src/locale/messages.ts +30 -0
  154. package/src/styles.css +305 -52
  155. package/src/theme/brand.example.css +21 -2
  156. package/src/theme/presets.ts +4 -0
  157. package/src/theme/types.ts +67 -0
  158. package/src/components/RsTableColgroup.vue +0 -17
  159. package/src/components/RsTableHeaderRow.vue +0 -30
@@ -0,0 +1,416 @@
1
+ /**
2
+ * RsTable 壳层装配:前缀列几何 + 列宽/列虚拟/列布局 + 交互 + 右键菜单。
3
+ *
4
+ * 性能约定(不可破):
5
+ * - 列宽拖拽仍走 useRsTableColumnResize 的 DOM 直写(禁止拖拽中写 reactive 全表宽)
6
+ * - columnVirtual 在 isColumnResizing 时冻结切片,避免拖宽时重算窗口
7
+ * - 不引入对 data 的 deep watch;状态经 getter / 受控 ref 读写
8
+ *
9
+ * 不做:Engine/Virtual 行切片(useRsTableCore)、编辑提交(useRsTableEditActions)、模板。
10
+ */
11
+
12
+ import { computed, type ComputedRef, type Ref } from 'vue'
13
+ import type { RsContextMenuItem } from '../components/context-menu-utils'
14
+ import type { RsTableRowDragTrigger, RsTableRowDropMode } from '../components/table-drag'
15
+ import type {
16
+ RsTableColumn,
17
+ RsTableRowData,
18
+ RsTableRowEntry,
19
+ RsTableRowKey,
20
+ RsTableSelectionType,
21
+ RsTableSortState,
22
+ RsTableTreeConfig,
23
+ RsTableTreeNodeIndex,
24
+ RsTableRowDropPosition,
25
+ } from '../components/table-utils'
26
+ import { resolveScrollWidth, selectRowKeys } from '../components/table-utils'
27
+ import {
28
+ measureRsTablePrefixWidth,
29
+ RS_TABLE_PREFIX_COL_WIDTH,
30
+ useRsTableColumnVirtual,
31
+ } from './useRsTableColumnVirtual'
32
+ import { useRsTableColumnLayout } from './useRsTableColumnLayout'
33
+ import { useRsTableColumnResize } from './useRsTableColumnResize'
34
+ import { useRsTableContextMenu } from './useRsTableContextMenu'
35
+ import { useRsTableInteraction } from './useRsTableInteraction'
36
+ import { resolveVirtualListHeight } from '../components/virtual-list-utils'
37
+
38
+ /** 壳层依赖的 Core 状态切片(显式列出,避免吞整个 core) */
39
+ export interface UseRsTableShellCoreSlice<T extends RsTableRowData> {
40
+ displayColumns: ComputedRef<RsTableColumn<T>[]>
41
+ resolvedColumnWidths: {
42
+ get: () => Record<string, number>
43
+ set: (value: Record<string, number>) => void
44
+ } | Ref<Record<string, number>>
45
+ useStableColumnWidths: ComputedRef<boolean>
46
+ scrollLeft: Ref<number>
47
+ measuredViewportWidth: Ref<number>
48
+ virtualScrollEnabled: ComputedRef<boolean>
49
+ dataRows: ComputedRef<Array<Extract<RsTableRowEntry<T>, { type: 'row' }>>>
50
+ selectedRows: ComputedRef<T[]>
51
+ rowKeyByIndex: ComputedRef<Map<number, string>>
52
+ selectedRowKeys: {
53
+ get: () => string[]
54
+ set: (keys: string[]) => void
55
+ } | Ref<string[]>
56
+ selectedKeySet: ComputedRef<Set<string>>
57
+ selectableRowKeys: ComputedRef<string[]>
58
+ selectAllState: ComputedRef<'checked' | 'indeterminate' | 'unchecked'>
59
+ expandedRowKeys: {
60
+ get: () => string[]
61
+ set: (keys: string[]) => void
62
+ } | Ref<string[]>
63
+ expandedKeySet: ComputedRef<Set<string>>
64
+ detailExpandable: ComputedRef<boolean>
65
+ treeMode: ComputedRef<boolean>
66
+ treeCheckStrictly: ComputedRef<boolean>
67
+ treeLazy: ComputedRef<boolean>
68
+ treeChildrenField: ComputedRef<string>
69
+ treeIsLeafField: ComputedRef<string>
70
+ treeLoadingKeys: {
71
+ get: () => string[]
72
+ set: (keys: string[]) => void
73
+ } | Ref<string[]>
74
+ treeLoadingKeySet: ComputedRef<Set<string>>
75
+ tableTreeNodeIndex: ComputedRef<Map<string, RsTableTreeNodeIndex> | null>
76
+ sortState: {
77
+ get: () => RsTableSortState | null
78
+ set: (value: RsTableSortState | null) => void
79
+ } | Ref<RsTableSortState | null>
80
+ sortsState: {
81
+ get: () => RsTableSortState[]
82
+ set: (value: RsTableSortState[]) => void
83
+ } | Ref<RsTableSortState[]>
84
+ columnOrderState: {
85
+ get: () => string[]
86
+ set: (value: string[]) => void
87
+ } | Ref<string[]>
88
+ }
89
+
90
+ function asAccessor<T>(source: { get: () => T; set: (v: T) => void } | Ref<T>): {
91
+ get: () => T
92
+ set: (v: T) => void
93
+ } {
94
+ if ('get' in source && typeof source.get === 'function') return source
95
+ const r = source as Ref<T>
96
+ return {
97
+ get: () => r.value,
98
+ set: (v) => {
99
+ r.value = v
100
+ },
101
+ }
102
+ }
103
+
104
+ export interface UseRsTableShellOptions<T extends RsTableRowData> {
105
+ core: UseRsTableShellCoreSlice<T>
106
+ // chrome / layout props
107
+ rowDraggable: () => boolean
108
+ rowDragTrigger: () => RsTableRowDragTrigger
109
+ selectable: () => boolean
110
+ selectionType: () => RsTableSelectionType
111
+ scrollX: () => number | string | undefined
112
+ resizable: () => boolean
113
+ minColumnWidth: () => number | undefined
114
+ maxColumnWidth: () => number | undefined
115
+ columnLayout: () => 'auto' | 'fixed'
116
+ virtualColumns: () => boolean | 'auto' | undefined
117
+ virtualColumnsAutoThreshold: () => number | undefined
118
+ virtualColumnOverscan: () => number | undefined
119
+ fill: () => boolean
120
+ infinite: () => boolean
121
+ height: () => number | string | undefined
122
+ showEditGutterColumn: () => boolean
123
+ showIndexColumn: () => boolean
124
+ showRowStatusColumn: () => boolean
125
+ resolvedGutterWidth: () => number
126
+ resolvedIndexWidth: () => number
127
+ // interaction
128
+ multiSort: () => boolean
129
+ maxSort: () => number | undefined
130
+ columnDraggable: () => boolean
131
+ rowSelectable?: (row: T, index: number) => boolean
132
+ rowDropMode: () => RsTableRowDropMode
133
+ rowDraggableWhen?: (row: T, index: number) => boolean
134
+ rowDropTargetWhen?: (row: T, index: number) => boolean
135
+ canRowDrop?: (dragKeys: string[], dropKey: string) => boolean
136
+ rowKey: () => RsTableRowKey<T> | undefined
137
+ treeConfig: () => RsTableTreeConfig<T> | undefined
138
+ expandable: () => boolean
139
+ rowExpandable?: (row: T, index: number) => boolean
140
+ isEditingAny: () => boolean
141
+ applyRowClickHighlight: (key: string) => void
142
+ onRowClickEmit: (row: T, index: number) => void
143
+ onRowDblclickEmit: (row: T, index: number) => void
144
+ onRowDragStartEmit: (dragKeys: string[], event: DragEvent) => void
145
+ onRowDropEmit: (dragKeys: string[], dropKey: string, position: RsTableRowDropPosition) => void
146
+ onColumnResize: (key: string, width: number) => void
147
+ // context menu
148
+ contextMenuEnabled: ComputedRef<boolean> | (() => boolean)
149
+ copyCellLabel: () => string
150
+ copyRowLabel: () => string
151
+ contextMenuItems?: (row: T | null, selectedRows: T[]) => RsContextMenuItem[]
152
+ mergeFeatureMenuItems?: (
153
+ items: RsContextMenuItem[],
154
+ ctx: { row: T | null; selectedRows: T[] },
155
+ ) => RsContextMenuItem[]
156
+ selectOnContextmenu: () => boolean
157
+ getDraft: (rowKey: string, colKey: string) => string | undefined
158
+ onContextMenuSelect: (key: string, row: T | null, selected: T[]) => void
159
+ onRowContextmenuEmit: (row: T, rowIndex: number, event: MouseEvent) => void
160
+ }
161
+
162
+ /**
163
+ * 装配壳层:列几何/布局/交互/右键。
164
+ */
165
+ export function useRsTableShell<T extends RsTableRowData>(options: UseRsTableShellOptions<T>) {
166
+ const { core } = options
167
+ const PREFIX_COL_WIDTH = RS_TABLE_PREFIX_COL_WIDTH
168
+
169
+ const showRowDragHandle = computed(
170
+ () => options.rowDraggable() && options.rowDragTrigger() === 'handle',
171
+ )
172
+ const dragColumnOffset = computed(() => (showRowDragHandle.value ? 40 : 0))
173
+ const expandColumnOffset = computed(
174
+ () => dragColumnOffset.value + (core.detailExpandable.value ? 40 : 0),
175
+ )
176
+ const showSelectColumn = computed(
177
+ () => options.selectable() && options.selectionType() !== 'row',
178
+ )
179
+ const isRowSelection = computed(
180
+ () => options.selectable() && options.selectionType() === 'row',
181
+ )
182
+ const isRadioSelection = computed(() => options.selectionType() === 'radio')
183
+ const selectColumnOffset = computed(
184
+ () => expandColumnOffset.value + (showSelectColumn.value ? 40 : 0),
185
+ )
186
+ const tableMinWidth = computed(() => resolveScrollWidth(options.scrollX()))
187
+
188
+ const selectedRowKeysAcc = asAccessor(core.selectedRowKeys)
189
+ const expandedRowKeysAcc = asAccessor(core.expandedRowKeys)
190
+ const treeLoadingKeysAcc = asAccessor(core.treeLoadingKeys)
191
+ const sortStateAcc = asAccessor(core.sortState)
192
+ const sortsStateAcc = asAccessor(core.sortsState)
193
+ const columnOrderStateAcc = asAccessor(core.columnOrderState)
194
+ const resolvedWidthsAcc = asAccessor(core.resolvedColumnWidths)
195
+
196
+ const {
197
+ tableRef,
198
+ isColumnResizing,
199
+ resizePaintWidths,
200
+ onResizeStart,
201
+ } = useRsTableColumnResize<T>({
202
+ resizable: () => options.resizable(),
203
+ minColumnWidth: () => options.minColumnWidth(),
204
+ maxColumnWidth: () => options.maxColumnWidth(),
205
+ displayColumns: () => core.displayColumns.value,
206
+ resolvedColumnWidths: resolvedWidthsAcc,
207
+ measurePrefixColumnWidth: () =>
208
+ measureRsTablePrefixWidth({
209
+ showRowDragHandle: showRowDragHandle.value,
210
+ detailExpandable: core.detailExpandable.value,
211
+ showSelectColumn: showSelectColumn.value,
212
+ showEditGutterColumn: options.showEditGutterColumn(),
213
+ showIndexColumn: options.showIndexColumn(),
214
+ gutterWidth: options.resolvedGutterWidth(),
215
+ indexWidth: options.resolvedIndexWidth(),
216
+ }),
217
+ tableMinWidth: () => tableMinWidth.value,
218
+ onColumnResize: options.onColumnResize,
219
+ })
220
+
221
+ /** 拖拽帧用快照,否则持久化宽 —— 供布局/列虚拟共用,避免双份计算 */
222
+ const effectiveColumnWidths = computed(
223
+ () => resizePaintWidths.value ?? resolvedWidthsAcc.get(),
224
+ )
225
+
226
+ const {
227
+ virtualColumnsEnabled,
228
+ visibleDataColumns,
229
+ columnPadLeft,
230
+ columnPadRight,
231
+ } = useRsTableColumnVirtual<T>({
232
+ displayColumns: () => core.displayColumns.value,
233
+ virtualColumns: () => options.virtualColumns(),
234
+ virtualColumnsAutoThreshold: () => options.virtualColumnsAutoThreshold(),
235
+ virtualColumnOverscan: () => options.virtualColumnOverscan(),
236
+ isColumnResizing: () => isColumnResizing.value,
237
+ scrollLeft: () => core.scrollLeft.value,
238
+ measuredViewportWidth: () => core.measuredViewportWidth.value,
239
+ columnWidths: () => effectiveColumnWidths.value,
240
+ showRowDragHandle: () => showRowDragHandle.value,
241
+ detailExpandable: () => core.detailExpandable.value,
242
+ showSelectColumn: () => showSelectColumn.value,
243
+ showEditGutterColumn: () => options.showEditGutterColumn(),
244
+ showIndexColumn: () => options.showIndexColumn(),
245
+ showRowStatusColumn: () => options.showRowStatusColumn(),
246
+ resolvedGutterWidth: () => options.resolvedGutterWidth(),
247
+ resolvedIndexWidth: () => options.resolvedIndexWidth(),
248
+ })
249
+
250
+ const layout = useRsTableColumnLayout<T>({
251
+ displayColumns: () => core.displayColumns.value,
252
+ effectiveColumnWidths: () => effectiveColumnWidths.value,
253
+ measuredViewportWidth: () => core.measuredViewportWidth.value,
254
+ tableMinWidth: () => tableMinWidth.value,
255
+ resizable: () => options.resizable(),
256
+ columnLayout: () => options.columnLayout(),
257
+ useStableColumnWidths: () => core.useStableColumnWidths.value,
258
+ isColumnResizing: () => isColumnResizing.value,
259
+ virtualColumnsEnabled: () => virtualColumnsEnabled.value,
260
+ scrollX: () => options.scrollX(),
261
+ showRowDragHandle: () => showRowDragHandle.value,
262
+ detailExpandable: () => core.detailExpandable.value,
263
+ showSelectColumn: () => showSelectColumn.value,
264
+ showEditGutterColumn: () => options.showEditGutterColumn(),
265
+ showIndexColumn: () => options.showIndexColumn(),
266
+ showRowStatusColumn: () => options.showRowStatusColumn(),
267
+ resolvedGutterWidth: () => options.resolvedGutterWidth(),
268
+ resolvedIndexWidth: () => options.resolvedIndexWidth(),
269
+ dragColumnOffset: () => dragColumnOffset.value,
270
+ expandColumnOffset: () => expandColumnOffset.value,
271
+ selectColumnOffset: () => selectColumnOffset.value,
272
+ resizePaintWidths: () => resizePaintWidths.value,
273
+ })
274
+
275
+ const useScrollContainer = computed(
276
+ () =>
277
+ core.virtualScrollEnabled.value ||
278
+ options.infinite() ||
279
+ virtualColumnsEnabled.value,
280
+ )
281
+
282
+ const scrollContainerStyle = computed(() => {
283
+ const style: Record<string, string> = {}
284
+ if (useScrollContainer.value && !options.fill()) {
285
+ const resolvedHeight = resolveVirtualListHeight(options.height(), 320)
286
+ if (resolvedHeight) {
287
+ style.height = resolvedHeight
288
+ style.maxHeight = resolvedHeight
289
+ }
290
+ }
291
+ if (options.showEditGutterColumn()) {
292
+ style['--rs-table-gutter-width'] = `${options.resolvedGutterWidth()}px`
293
+ }
294
+ if (showSelectColumn.value) {
295
+ style['--rs-table-select-width'] = `${PREFIX_COL_WIDTH.select}px`
296
+ }
297
+ if (options.showIndexColumn()) {
298
+ style['--rs-table-index-width'] = `${options.resolvedIndexWidth()}px`
299
+ }
300
+ return Object.keys(style).length ? style : undefined
301
+ })
302
+
303
+ const bodyColspan = computed(() => {
304
+ let count = visibleDataColumns.value.length
305
+ if (columnPadLeft.value > 0) count += 1
306
+ if (columnPadRight.value > 0) count += 1
307
+ if (showRowDragHandle.value) count += 1
308
+ if (core.detailExpandable.value) count += 1
309
+ if (showSelectColumn.value) count += 1
310
+ if (options.showEditGutterColumn() || options.showIndexColumn()) count += 1
311
+ if (options.showRowStatusColumn()) count += 1
312
+ return count
313
+ })
314
+
315
+ const emptyContentStyle = computed(() => {
316
+ const w = core.measuredViewportWidth.value
317
+ if (w <= 0) return undefined
318
+ return { width: `${w}px` }
319
+ })
320
+
321
+ const interaction = useRsTableInteraction<T>({
322
+ multiSort: () => options.multiSort(),
323
+ maxSort: () => options.maxSort(),
324
+ columnDraggable: () => options.columnDraggable(),
325
+ selectable: () => options.selectable(),
326
+ selectionType: () => options.selectionType(),
327
+ rowSelectable: options.rowSelectable,
328
+ rowDraggable: () => options.rowDraggable(),
329
+ rowDragTrigger: () => options.rowDragTrigger(),
330
+ rowDropMode: () => options.rowDropMode(),
331
+ rowDraggableWhen: options.rowDraggableWhen,
332
+ rowDropTargetWhen: options.rowDropTargetWhen,
333
+ canRowDrop: options.canRowDrop,
334
+ rowKey: () => options.rowKey(),
335
+ rowKeyByIndex: () => core.rowKeyByIndex.value,
336
+ selectedRowKeys: selectedRowKeysAcc,
337
+ selectedKeySet: () => core.selectedKeySet.value,
338
+ selectableRowKeys: () => core.selectableRowKeys.value,
339
+ selectAllState: () => core.selectAllState.value,
340
+ expandedRowKeys: expandedRowKeysAcc,
341
+ expandedKeySet: () => core.expandedKeySet.value,
342
+ treeMode: () => core.treeMode.value,
343
+ treeCheckStrictly: () => core.treeCheckStrictly.value,
344
+ treeLazy: () => core.treeLazy.value,
345
+ treeChildrenField: () => core.treeChildrenField.value,
346
+ treeIsLeafField: () => core.treeIsLeafField.value,
347
+ treeLoadingKeys: treeLoadingKeysAcc,
348
+ treeLoadingKeySet: () => core.treeLoadingKeySet.value,
349
+ tableTreeNodeIndex: () => core.tableTreeNodeIndex.value,
350
+ treeConfig: () => options.treeConfig(),
351
+ expandable: () => options.expandable(),
352
+ rowExpandable: options.rowExpandable,
353
+ sortState: sortStateAcc,
354
+ sortsState: sortsStateAcc,
355
+ columnOrderState: columnOrderStateAcc,
356
+ isEditingAny: () => options.isEditingAny(),
357
+ applyRowClickHighlight: options.applyRowClickHighlight,
358
+ onRowClickEmit: options.onRowClickEmit,
359
+ onRowDblclickEmit: options.onRowDblclickEmit,
360
+ onRowDragStartEmit: options.onRowDragStartEmit,
361
+ onRowDropEmit: options.onRowDropEmit,
362
+ })
363
+
364
+ const contextMenu = useRsTableContextMenu<T>({
365
+ enabled: options.contextMenuEnabled,
366
+ dataRows: () => core.dataRows.value,
367
+ displayColumns: () => core.displayColumns.value,
368
+ selectedRows: () => core.selectedRows.value,
369
+ rowKey: () => options.rowKey(),
370
+ rowKeyByIndex: () => core.rowKeyByIndex.value,
371
+ copyCellLabel: () => options.copyCellLabel(),
372
+ copyRowLabel: () => options.copyRowLabel(),
373
+ contextMenuItems: (row, selected) => options.contextMenuItems?.(row, selected) ?? [],
374
+ mergeFeatureMenuItems: options.mergeFeatureMenuItems,
375
+ shouldSelectOnContextmenu: () => options.selectOnContextmenu(),
376
+ canSelectRow: interaction.canSelectRow,
377
+ isRowSelected: interaction.isRowSelected,
378
+ selectionType: () => options.selectionType(),
379
+ getSelectedRowKeys: () => selectedRowKeysAcc.get(),
380
+ setSelectedRowKeys: (keys) => selectedRowKeysAcc.set(keys),
381
+ selectRowKeys,
382
+ getDraft: options.getDraft,
383
+ onSelect: options.onContextMenuSelect,
384
+ onRowContextmenu: options.onRowContextmenuEmit,
385
+ })
386
+
387
+ return {
388
+ PREFIX_COL_WIDTH,
389
+ showRowDragHandle,
390
+ dragColumnOffset,
391
+ expandColumnOffset,
392
+ showSelectColumn,
393
+ isRowSelection,
394
+ isRadioSelection,
395
+ selectColumnOffset,
396
+ tableMinWidth,
397
+ tableRef,
398
+ isColumnResizing,
399
+ resizePaintWidths,
400
+ onResizeStart,
401
+ effectiveColumnWidths,
402
+ virtualColumnsEnabled,
403
+ visibleDataColumns,
404
+ columnPadLeft,
405
+ columnPadRight,
406
+ ...layout,
407
+ useScrollContainer,
408
+ scrollContainerStyle,
409
+ bodyColspan,
410
+ emptyContentStyle,
411
+ ...interaction,
412
+ ...contextMenu,
413
+ }
414
+ }
415
+
416
+ export type RsTableShellApi<T extends RsTableRowData> = ReturnType<typeof useRsTableShell<T>>
@@ -0,0 +1,170 @@
1
+ /**
2
+ * RsTable 壳层编排:i18n 文案、a11y 绑定、Grid 键盘、滚动宿主 class。
3
+ * 从 RsTable.vue 下沉,SFC 只保留接线。
4
+ */
5
+
6
+ import { computed, useAttrs, useSlots, type ComputedRef, type Ref } from 'vue'
7
+ import type { useTableEdit } from './useTableEdit'
8
+ import { useRsI18n } from './useRsI18n'
9
+ import { useRsTableA11y } from './useRsTableA11y'
10
+ import { useRsTableGridKeyboard } from './useRsTableGridKeyboard'
11
+ import type { RsTableColumn, RsTableRowData, RsTableRowEntry } from '../components/table-utils'
12
+
13
+ type TableEditApi = ReturnType<typeof useTableEdit>
14
+
15
+ export interface UseRsTableShellChromeOptions<T extends RsTableRowData> {
16
+ ariaLabel: () => string | undefined
17
+ loading: () => boolean
18
+ dataRows:
19
+ | Ref<Array<Extract<RsTableRowEntry<T>, { type: 'row' }>>>
20
+ | ComputedRef<Array<Extract<RsTableRowEntry<T>, { type: 'row' }>>>
21
+ visibleDataColumns: Ref<RsTableColumn<T>[]> | ComputedRef<RsTableColumn<T>[]>
22
+ displayColumns: () => RsTableColumn<T>[]
23
+ tableEdit: TableEditApi
24
+ rowKeyFor: (entry: Extract<RsTableRowEntry<T>, { type: 'row' }>) => string
25
+ applyRowClickHighlight: (rowKey: string) => void
26
+ pageSize: () => number
27
+ onTableKeydown: (event: KeyboardEvent) => void
28
+ scrollContainerRef?: Ref<HTMLElement | null | undefined>
29
+ /** 滚动宿主 class 所需状态 */
30
+ hostClass: {
31
+ bordered: () => boolean
32
+ columnBordered: () => boolean
33
+ rounded: () => boolean
34
+ fill: () => boolean
35
+ compact: () => boolean
36
+ resolvedSize: () => string
37
+ virtualScrollEnabled: () => boolean
38
+ virtualColumnsEnabled: () => boolean
39
+ dataLength: () => number
40
+ infinite: () => boolean
41
+ resizable: () => boolean
42
+ columnLayout: () => string
43
+ useFixedColumnLayout: () => boolean
44
+ striped: () => boolean
45
+ selectable: () => boolean
46
+ isRowSelection: () => boolean
47
+ detailExpandable: () => boolean
48
+ treeMode: () => boolean
49
+ rowDraggable: () => boolean
50
+ columnDraggable: () => boolean
51
+ scrollX: () => number | string | undefined
52
+ overflowXEnabled: () => boolean
53
+ contextMenuEnabled: () => boolean
54
+ showEditGutterColumn: () => boolean
55
+ }
56
+ }
57
+
58
+ /**
59
+ * 壳层 chrome:文案 / a11y / 键盘 / 宿主 class。
60
+ */
61
+ export function useRsTableShellChrome<T extends RsTableRowData>(
62
+ options: UseRsTableShellChromeOptions<T>,
63
+ ) {
64
+ const { t } = useRsI18n()
65
+ const slots = useSlots()
66
+ const attrs = useAttrs()
67
+
68
+ const hasData = computed(() => options.dataRows.value.length > 0)
69
+
70
+ const headerLabels = computed(() => ({
71
+ selectAll: t('table.selectAll'),
72
+ index: t('table.index'),
73
+ rowStatus: t('table.rowStatus'),
74
+ dragColumn: t('table.dragColumn'),
75
+ filterColumn: t('table.filterColumn'),
76
+ filterPlaceholder: t('table.filterPlaceholder'),
77
+ filterClear: t('table.filterClear'),
78
+ filterApply: t('table.filterApply'),
79
+ }))
80
+
81
+ const bodyLabels = computed(() => ({
82
+ loading: t('table.loading'),
83
+ empty: t('table.empty'),
84
+ loadingMore: t('table.loadingMore'),
85
+ dragRow: t('table.dragRow'),
86
+ expandRow: t('table.expandRow'),
87
+ collapseRow: t('table.collapseRow'),
88
+ selectRow: t('table.selectRow'),
89
+ rowCommit: t('table.rowCommit'),
90
+ rowRollback: t('table.rowRollback'),
91
+ gutterCommit: t('table.gutterCommit'),
92
+ }))
93
+
94
+ function hasColumnSlot(key: string): boolean {
95
+ return Boolean(slots[key])
96
+ }
97
+
98
+ function hasEditSlot(key: string): boolean {
99
+ return Boolean(slots[`edit-${key}`])
100
+ }
101
+
102
+ const { shellA11y, tableA11y } = useRsTableA11y({
103
+ ariaLabel: options.ariaLabel,
104
+ defaultLabel: () => t('table.a11yLabel'),
105
+ loading: options.loading,
106
+ rowCount: () => options.dataRows.value.length,
107
+ colCount: () => options.visibleDataColumns.value.length,
108
+ })
109
+
110
+ const shellBind = computed(() => ({
111
+ ...shellA11y.value,
112
+ ...attrs,
113
+ }))
114
+
115
+ const { onGridKeydown } = useRsTableGridKeyboard<T>({
116
+ tableEdit: options.tableEdit,
117
+ dataRows: () => options.dataRows.value,
118
+ rowKeyFor: options.rowKeyFor,
119
+ colKeys: () => options.visibleDataColumns.value.map((c) => c.key),
120
+ displayColumns: options.displayColumns,
121
+ applyRowClickHighlight: options.applyRowClickHighlight,
122
+ pageSize: options.pageSize,
123
+ scrollContainerRef: options.scrollContainerRef,
124
+ })
125
+
126
+ function onShellKeydown(event: KeyboardEvent): void {
127
+ if (onGridKeydown(event)) return
128
+ options.onTableKeydown(event)
129
+ }
130
+
131
+ const h = options.hostClass
132
+ const scrollHostClass = computed(() => ({
133
+ 'rs-table--bordered': h.bordered(),
134
+ 'rs-table--column-bordered': h.columnBordered(),
135
+ 'rs-table--rounded': h.rounded(),
136
+ 'rs-table--fill': h.fill(),
137
+ 'rs-table--compact': h.compact(),
138
+ [`rs-table--${h.resolvedSize()}`]: true,
139
+ 'rs-table--virtual': h.virtualScrollEnabled(),
140
+ 'rs-table--virtual-cols': h.virtualColumnsEnabled(),
141
+ 'rs-table--content-visibility': !h.virtualScrollEnabled() && h.dataLength() >= 30,
142
+ 'rs-table--infinite': h.infinite(),
143
+ 'rs-table--resizable': h.resizable(),
144
+ 'rs-table--col-auto': h.resizable() && h.columnLayout() === 'auto',
145
+ 'rs-table--col-fixed': h.useFixedColumnLayout(),
146
+ 'rs-table--striped': h.striped(),
147
+ 'rs-table--selectable': h.selectable(),
148
+ 'rs-table--row-select': h.isRowSelection(),
149
+ 'rs-table--expandable': h.detailExpandable(),
150
+ 'rs-table--tree': h.treeMode(),
151
+ 'rs-table--draggable': h.rowDraggable() || h.columnDraggable(),
152
+ 'rs-table--scroll-x': !!h.scrollX(),
153
+ 'rs-table--overflow-x': h.overflowXEnabled(),
154
+ 'rs-table--ctx': h.contextMenuEnabled(),
155
+ 'rs-table--edit-gutter': h.showEditGutterColumn(),
156
+ }))
157
+
158
+ return {
159
+ t,
160
+ hasData,
161
+ headerLabels,
162
+ bodyLabels,
163
+ hasColumnSlot,
164
+ hasEditSlot,
165
+ tableA11y,
166
+ shellBind,
167
+ onShellKeydown,
168
+ scrollHostClass,
169
+ }
170
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * RsTable 汇总行编排:是否启用、footer 单元格、前缀 colspan。
3
+ *
4
+ * 纯聚合算法在 table-summary-utils;本 composable 对接 props / slots / 前缀列状态。
5
+ */
6
+
7
+ import { computed, watch, type ComputedRef } from 'vue'
8
+ import type { RsTableColumn, RsTableRowData } from '../components/table-utils'
9
+ import {
10
+ buildTableSummaryCells,
11
+ hasTableSummaryConfig,
12
+ warnSummaryCompatibility,
13
+ type RsTableColumnSummary,
14
+ type RsTableSummaryData,
15
+ type RsTableSummaryMode,
16
+ } from '../components/table/table-summary-utils'
17
+
18
+ function read<T>(source: ComputedRef<T> | (() => T)): T {
19
+ return typeof source === 'function' ? (source as () => T)() : source.value
20
+ }
21
+
22
+ type ColumnWithSummary<T extends RsTableRowData> = RsTableColumn<T> & {
23
+ summary?: RsTableColumnSummary<T>
24
+ }
25
+
26
+ export interface UseRsTableSummaryOptions<T extends RsTableRowData> {
27
+ /** 显式开启汇总行 */
28
+ showSummary: ComputedRef<boolean | undefined> | (() => boolean | undefined)
29
+ summaryMode: ComputedRef<RsTableSummaryMode> | (() => RsTableSummaryMode)
30
+ summaryData: ComputedRef<RsTableSummaryData | null | undefined> | (() => RsTableSummaryData | null | undefined)
31
+ /** 原始列(判断是否配置 summary) */
32
+ columns: ComputedRef<ColumnWithSummary<T>[]> | (() => ColumnWithSummary<T>[])
33
+ /** 展示列(footer 与表体对齐) */
34
+ displayColumns: ComputedRef<ColumnWithSummary<T>[]> | (() => ColumnWithSummary<T>[])
35
+ viewRows: ComputedRef<T[]> | (() => T[])
36
+ /** 是否存在 #summary 插槽(有则不渲染内置行) */
37
+ hasSummarySlot: ComputedRef<boolean> | (() => boolean)
38
+ treeMode: ComputedRef<boolean> | (() => boolean)
39
+ detailExpandable: ComputedRef<boolean> | (() => boolean)
40
+ showRowDragHandle: ComputedRef<boolean> | (() => boolean)
41
+ showSelectColumn: ComputedRef<boolean> | (() => boolean)
42
+ showEditGutterOrIndex: ComputedRef<boolean> | (() => boolean)
43
+ showRowStatusColumn: ComputedRef<boolean> | (() => boolean)
44
+ }
45
+
46
+ /**
47
+ * 汇总 feature 状态。
48
+ *
49
+ * @returns enabled / cells / showBuiltinSummaryRow / prefixColspan
50
+ */
51
+ export function useRsTableSummary<T extends RsTableRowData>(options: UseRsTableSummaryOptions<T>) {
52
+ const summaryFeatureEnabled = computed(() => {
53
+ if (read(options.showSummary)) return true
54
+ return hasTableSummaryConfig({
55
+ columns: read(options.columns),
56
+ mode: read(options.summaryMode),
57
+ summaryData: read(options.summaryData),
58
+ })
59
+ })
60
+
61
+ const summaryCells = computed(() => {
62
+ if (!summaryFeatureEnabled.value) return []
63
+ return buildTableSummaryCells({
64
+ columns: read(options.displayColumns),
65
+ rows: read(options.viewRows),
66
+ mode: read(options.summaryMode),
67
+ summaryData: read(options.summaryData),
68
+ })
69
+ })
70
+
71
+ const showBuiltinSummaryRow = computed(
72
+ () => summaryFeatureEnabled.value && !read(options.hasSummarySlot),
73
+ )
74
+
75
+ const summaryPrefixColspan = computed(() => {
76
+ let n = 0
77
+ if (read(options.showRowDragHandle)) n += 1
78
+ if (read(options.detailExpandable)) n += 1
79
+ if (read(options.showSelectColumn)) n += 1
80
+ if (read(options.showEditGutterOrIndex)) n += 1
81
+ if (read(options.showRowStatusColumn)) n += 1
82
+ return n
83
+ })
84
+
85
+ if (import.meta.env.DEV) {
86
+ watch(
87
+ () =>
88
+ [
89
+ summaryFeatureEnabled.value,
90
+ read(options.treeMode),
91
+ read(options.detailExpandable),
92
+ ] as const,
93
+ ([summaryEnabled, tree, detail]) => {
94
+ warnSummaryCompatibility({
95
+ summaryEnabled,
96
+ tree,
97
+ detailExpandable: detail,
98
+ })
99
+ },
100
+ { immediate: true },
101
+ )
102
+ }
103
+
104
+ return {
105
+ summaryFeatureEnabled,
106
+ summaryCells,
107
+ showBuiltinSummaryRow,
108
+ summaryPrefixColspan,
109
+ }
110
+ }
111
+
112
+ /** 供 feature 注册表识别:summary 已由 useRsTableSummary 承载 */
113
+ export const RS_TABLE_SUMMARY_FEATURE_ID = 'summary' as const