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,251 @@
1
+ /**
2
+ * RsTable 编辑编排层:状态机 + 高亮 + 动作 + Api 编辑切片。
3
+ *
4
+ * 分层:
5
+ * - useRsTableEditLayer:早期挂载(须在 Shell 之前,供 isEditingAny)
6
+ * - bindActions:Shell 交互就绪后绑定提交/键盘/粘贴
7
+ * - toApiEditSlice:供 createRsTableApi 拼装,避免 expose 与内部漂移
8
+ *
9
+ * 性能:草稿/undo 仍用 shallow Map;不在此对 data 做 deep watch。
10
+ */
11
+
12
+ import { computed, type ComputedRef, type Ref } from 'vue'
13
+ import type { RsTableColumn, RsTableRowData, RsTableRowEntry } from '../components/table-utils'
14
+ import {
15
+ columnUsesSharedTooltip,
16
+ resolveCellTooltipMode,
17
+ resolveCellTooltipText,
18
+ } from '../components/table-utils'
19
+ import { resolveColumnRawValue } from '../components/table/table-edit-utils'
20
+ import type { RsTableApi } from '../components/table/rs-table-api'
21
+ import { useTableEdit } from './useTableEdit'
22
+ import { useTableRowHighlight } from './useTableRowHighlight'
23
+ import {
24
+ useRsTableEditActions,
25
+ type UseRsTableEditActionsOptions,
26
+ } from './useRsTableEditActions'
27
+
28
+ export interface UseRsTableEditLayerOptions {
29
+ editable: () => boolean
30
+ editGutter: () => boolean
31
+ editGutterWidth: () => number | undefined
32
+ showIndex: () => boolean
33
+ indexWidth: () => number | undefined
34
+ rowCommit: () => boolean
35
+ showRowStatus: () => boolean
36
+ editUndoLimit: () => number | undefined
37
+ highlightRow: () => boolean
38
+ highlightRowOnClick: () => boolean
39
+ highlightedRowKey: () => string | undefined
40
+ defaultHighlightedRowKey: () => string | undefined
41
+ onHighlightUpdate: (value: string | undefined) => void
42
+ /** 共享 tooltip / 插槽探测 */
43
+ cellTooltip: () => boolean
44
+ hasColumnSlot: (key: string) => boolean
45
+ }
46
+
47
+ /**
48
+ * Api 中与编辑/高亮相关的方法切片(不含行查询与 analytics)。
49
+ */
50
+ export type RsTableApiEditSlice<T extends RsTableRowData> = Pick<
51
+ RsTableApi<T>,
52
+ | 'getHighlightedRowKey'
53
+ | 'setHighlightedRowKey'
54
+ | 'cancelCellEdit'
55
+ | 'cancelAllEdits'
56
+ | 'getDirtyCellKeys'
57
+ | 'getCellError'
58
+ | 'setCellError'
59
+ | 'stageCell'
60
+ | 'rejectRowEdit'
61
+ | 'undoEdit'
62
+ | 'redoEdit'
63
+ | 'commitRowEdits'
64
+ | 'rollbackRowEdits'
65
+ >
66
+
67
+ /**
68
+ * 编辑层:早期状态 + 延迟 bindActions。
69
+ */
70
+ export function useRsTableEditLayer<T extends RsTableRowData>(
71
+ options: UseRsTableEditLayerOptions,
72
+ ) {
73
+ const showEditGutterColumn = computed(() => options.editable() && options.editGutter())
74
+ const showIndexColumn = computed(() => options.showIndex() && !showEditGutterColumn.value)
75
+ const showRowStatusColumn = computed(
76
+ () => options.rowCommit() && options.showRowStatus() && !showEditGutterColumn.value,
77
+ )
78
+ const resolvedGutterWidth = computed(() => Math.max(24, options.editGutterWidth() || 32))
79
+ const resolvedIndexWidth = computed(() => Math.max(24, options.indexWidth() || 48))
80
+
81
+ const tableEdit = useTableEdit({
82
+ enabled: () => options.editable(),
83
+ undoLimit: () => options.editUndoLimit(),
84
+ })
85
+
86
+ const activeEditCellKey = computed(() => {
87
+ const cell = tableEdit.editingCell.value
88
+ return cell ? `${cell.rowKey}:${cell.colKey}` : ''
89
+ })
90
+ const activeFocusCellKey = computed(() => {
91
+ const cell = tableEdit.focusCell.value
92
+ return cell ? `${cell.rowKey}:${cell.colKey}` : ''
93
+ })
94
+ const activeErrorMapSize = computed(() => tableEdit.errorMap.value.size)
95
+ const activeValidatingMapSize = computed(() => tableEdit.validatingMap.value.size)
96
+
97
+ const isHighlightControlled = computed(() => options.highlightedRowKey() !== undefined)
98
+ const rowHighlight = useTableRowHighlight({
99
+ enabled: () => options.highlightRow(),
100
+ highlightRowOnClick: () => options.highlightRowOnClick(),
101
+ isControlled: () => isHighlightControlled.value,
102
+ highlightedRowKey: () => options.highlightedRowKey(),
103
+ defaultHighlightedRowKey: () => options.defaultHighlightedRowKey(),
104
+ onUpdate: options.onHighlightUpdate,
105
+ })
106
+
107
+ function cellTooltipEnabled(column: RsTableColumn<T>, _rowIndex: number): boolean {
108
+ return options.cellTooltip() && columnUsesSharedTooltip(column) && !options.hasColumnSlot(column.key)
109
+ }
110
+
111
+ function cellTooltipMode(column: RsTableColumn<T>, rowIndex: number): string | undefined {
112
+ if (!cellTooltipEnabled(column, rowIndex)) return undefined
113
+ return resolveCellTooltipMode(column) ?? undefined
114
+ }
115
+
116
+ function cellTooltipText(column: RsTableColumn<T>, row: T, rowIndex: number): string | undefined {
117
+ if (!cellTooltipEnabled(column, rowIndex)) return undefined
118
+ const text = resolveCellTooltipText(column, row, rowIndex)
119
+ return text || undefined
120
+ }
121
+
122
+ function cellTooltipFallbackTitle(
123
+ column: RsTableColumn<T>,
124
+ row: T,
125
+ rowIndex: number,
126
+ ): string | undefined {
127
+ if (options.cellTooltip() || !column.ellipsis || options.hasColumnSlot(column.key)) {
128
+ return undefined
129
+ }
130
+ const text = resolveCellTooltipText(column, row, rowIndex)
131
+ return text || undefined
132
+ }
133
+
134
+ /**
135
+ * Shell 就绪后绑定编辑动作(依赖 rowKeyFor / onRowClick)。
136
+ */
137
+ function bindActions(
138
+ actionOptions: Omit<UseRsTableEditActionsOptions<T>, 'tableEdit' | 'applyRowClickHighlight'>,
139
+ ) {
140
+ const actions = useRsTableEditActions<T>({
141
+ ...actionOptions,
142
+ tableEdit,
143
+ applyRowClickHighlight: (key) => rowHighlight.applyRowClickHighlight(key),
144
+ })
145
+
146
+ function getRowByKey(
147
+ rowKey: string,
148
+ dataRows: Array<Extract<RsTableRowEntry<T>, { type: 'row' }>>,
149
+ rowKeyByIndex: Map<number, string>,
150
+ ): T | undefined {
151
+ const entry = dataRows.find((item) => rowKeyByIndex.get(item.rowIndex) === rowKey)
152
+ return entry?.row
153
+ }
154
+
155
+ function getRowByIndex(
156
+ index: number,
157
+ dataRows: Array<Extract<RsTableRowEntry<T>, { type: 'row' }>>,
158
+ ): T | undefined {
159
+ return dataRows.find((item) => item.rowIndex === index)?.row
160
+ }
161
+
162
+ function getColumnValues(
163
+ colKey: string,
164
+ dataRows: Array<Extract<RsTableRowEntry<T>, { type: 'row' }>>,
165
+ ): unknown[] {
166
+ const column = actions.resolveColumnByKey(colKey)
167
+ if (!column) return []
168
+ return dataRows.map((entry) => resolveColumnRawValue(entry.row, column, entry.rowIndex))
169
+ }
170
+
171
+ function getCellValue(
172
+ rowKey: string,
173
+ colKey: string,
174
+ dataRows: Array<Extract<RsTableRowEntry<T>, { type: 'row' }>>,
175
+ rowKeyByIndex: Map<number, string>,
176
+ ): unknown {
177
+ const row = getRowByKey(rowKey, dataRows, rowKeyByIndex)
178
+ const column = actions.resolveColumnByKey(colKey)
179
+ if (!row || !column) return undefined
180
+ const entry = dataRows.find((item) => rowKeyByIndex.get(item.rowIndex) === rowKey)
181
+ if (!entry) return undefined
182
+ return resolveColumnRawValue(entry.row, column, entry.rowIndex)
183
+ }
184
+
185
+ function toApiEditSlice(ctx: {
186
+ dataRows:
187
+ | Ref<Array<Extract<RsTableRowEntry<T>, { type: 'row' }>>>
188
+ | ComputedRef<Array<Extract<RsTableRowEntry<T>, { type: 'row' }>>>
189
+ rowKeyByIndex: Ref<Map<number, string>> | ComputedRef<Map<number, string>>
190
+ }): RsTableApiEditSlice<T> {
191
+ return {
192
+ getHighlightedRowKey: () => rowHighlight.highlightedKey.value,
193
+ setHighlightedRowKey: rowHighlight.setHighlightedKey,
194
+ cancelCellEdit: () => tableEdit.cancelEdit(),
195
+ cancelAllEdits: () => tableEdit.cancelAll(),
196
+ getDirtyCellKeys: () => [...tableEdit.stagedMap.value.keys()],
197
+ getCellError: tableEdit.getCellError,
198
+ setCellError: tableEdit.setCellError,
199
+ stageCell: tableEdit.stageCell,
200
+ rejectRowEdit: actions.rejectRowEdit,
201
+ undoEdit: actions.onEditUndo,
202
+ redoEdit: actions.onEditRedo,
203
+ commitRowEdits: (rowKey: string) => {
204
+ const entry = ctx.dataRows.value.find(
205
+ (item) => ctx.rowKeyByIndex.value.get(item.rowIndex) === rowKey,
206
+ )
207
+ if (entry) void actions.onRowCommitEdit(entry)
208
+ },
209
+ rollbackRowEdits: (rowKey: string) => {
210
+ const entry = ctx.dataRows.value.find(
211
+ (item) => ctx.rowKeyByIndex.value.get(item.rowIndex) === rowKey,
212
+ )
213
+ if (entry) actions.onRowRollbackEdit(entry)
214
+ },
215
+ }
216
+ }
217
+
218
+ return {
219
+ ...actions,
220
+ getRowByKey,
221
+ getRowByIndex,
222
+ getColumnValues,
223
+ getCellValue,
224
+ toApiEditSlice,
225
+ }
226
+ }
227
+
228
+ return {
229
+ tableEdit,
230
+ rowHighlight,
231
+ showEditGutterColumn,
232
+ showIndexColumn,
233
+ showRowStatusColumn,
234
+ resolvedGutterWidth,
235
+ resolvedIndexWidth,
236
+ activeEditCellKey,
237
+ activeFocusCellKey,
238
+ activeErrorMapSize,
239
+ activeValidatingMapSize,
240
+ isEditingAny: () => Boolean(tableEdit.editingCell.value),
241
+ cellTooltipEnabled,
242
+ cellTooltipMode,
243
+ cellTooltipText,
244
+ cellTooltipFallbackTitle,
245
+ bindActions,
246
+ }
247
+ }
248
+
249
+ export type RsTableEditLayerApi<T extends RsTableRowData> = ReturnType<
250
+ typeof useRsTableEditLayer<T>
251
+ >
@@ -0,0 +1,357 @@
1
+ /**
2
+ * RsTable 数据内核:排序 / 筛选 / 分组后的 entries,以及选择、展开、树索引。
3
+ *
4
+ * 职责边界:
5
+ * - 做:受控/非受控 sort、selection、expand;构建 tableEntries / viewRows
6
+ * - 不做:虚拟滚动、列宽、右键菜单 DOM、行内编辑提交(由 RsTable + 其它 composable 负责)
7
+ *
8
+ * `viewRows` 供 summary / 表外 analytics 使用(仅业务行,不含 group/expand 伪行)。
9
+ */
10
+
11
+ import { computed, ref, watch, type ComputedRef, type Ref } from 'vue'
12
+ import {
13
+ buildTableEntries,
14
+ buildTableTreeEntries,
15
+ buildTableTreeNodeIndex,
16
+ collectTableTreeExpandableKeys,
17
+ hasStableTableTreeRowKey,
18
+ injectExpandRows,
19
+ isTableRowDisabled,
20
+ resolveRowKey,
21
+ resolveSelectAllState,
22
+ resolveTableTreeIndent,
23
+ type RsTableColumn,
24
+ type RsTableGroupBy,
25
+ type RsTableRowData,
26
+ type RsTableRowEntry,
27
+ type RsTableRowKey,
28
+ type RsTableSize,
29
+ type RsTableSortState,
30
+ type RsTableTreeConfig,
31
+ } from '../components/table-utils'
32
+
33
+ /** Engine 向外发出的状态同步事件(与 RsTable emit 子集对齐) */
34
+ export interface UseRsTableEngineEmit {
35
+ (e: 'update:selectedRowKeys', value: string[]): void
36
+ (e: 'selectionChange', value: string[]): void
37
+ (e: 'update:expandedRowKeys', value: string[]): void
38
+ (e: 'expandChange', value: string[]): void
39
+ (e: 'update:sort', value: RsTableSortState | null): void
40
+ (e: 'update:sorts', value: RsTableSortState[]): void
41
+ (e: 'sortsChange', value: RsTableSortState[]): void
42
+ }
43
+
44
+ /**
45
+ * useRsTableEngine 入参。
46
+ * 数据源与列定义通过 getter/ComputedRef 注入,避免在 Engine 内直接依赖整个 props 对象。
47
+ */
48
+ export interface UseRsTableEngineOptions<T extends RsTableRowData> {
49
+ data: ComputedRef<T[]> | (() => T[])
50
+ /** 已排序/钉住展开列后的展示列(通常来自 useRsTableColumns.displayColumns) */
51
+ displayColumns: ComputedRef<RsTableColumn<T>[]> | (() => RsTableColumn<T>[])
52
+ rowKey: ComputedRef<RsTableRowKey<T> | undefined> | (() => RsTableRowKey<T> | undefined)
53
+ sort: ComputedRef<RsTableSortState | null | undefined> | (() => RsTableSortState | null | undefined)
54
+ defaultSort: RsTableSortState | null
55
+ sorts: ComputedRef<RsTableSortState[] | undefined> | (() => RsTableSortState[] | undefined)
56
+ defaultSorts: RsTableSortState[]
57
+ multiSort: ComputedRef<boolean> | (() => boolean)
58
+ remoteSort: ComputedRef<boolean> | (() => boolean)
59
+ filterText: ComputedRef<string | undefined> | (() => string | undefined)
60
+ filterKeys: ComputedRef<string[] | undefined> | (() => string[] | undefined)
61
+ columnFilters: ComputedRef<Record<string, string>> | (() => Record<string, string>)
62
+ groupBy: ComputedRef<RsTableGroupBy<T> | undefined> | (() => RsTableGroupBy<T> | undefined)
63
+ groupLabel: ComputedRef<((key: string) => string) | undefined> | (() => ((key: string) => string) | undefined)
64
+ treeConfig: ComputedRef<RsTableTreeConfig<T> | undefined> | (() => RsTableTreeConfig<T> | undefined)
65
+ expandable: ComputedRef<boolean> | (() => boolean)
66
+ rowExpandable: ComputedRef<((row: T, index: number) => boolean) | undefined> | (() => ((row: T, index: number) => boolean) | undefined)
67
+ selectedRowKeys: ComputedRef<string[] | undefined> | (() => string[] | undefined)
68
+ defaultSelectedRowKeys: string[]
69
+ expandedRowKeys: ComputedRef<string[] | undefined> | (() => string[] | undefined)
70
+ defaultExpandedRowKeys: string[]
71
+ rowSelectable: ComputedRef<((row: T, index: number) => boolean) | undefined> | (() => ((row: T, index: number) => boolean) | undefined)
72
+ size: ComputedRef<RsTableSize> | (() => RsTableSize)
73
+ emit: UseRsTableEngineEmit
74
+ }
75
+
76
+ /** 统一读取 ComputedRef / Ref / getter */
77
+ function read<T>(source: ComputedRef<T> | (() => T) | Ref<T>): T {
78
+ if (typeof source === 'function') return (source as () => T)()
79
+ return (source as Ref<T> | ComputedRef<T>).value
80
+ }
81
+
82
+ /**
83
+ * 表格数据内核。
84
+ *
85
+ * @returns 排序状态、选择/展开、树字段、tableEntries、dataRows、viewRows 等;
86
+ * 行勾选/展开的「交互处理函数」仍留在 RsTable(依赖 DOM 与其它 UI 状态)。
87
+ */
88
+ export function useRsTableEngine<T extends RsTableRowData>(options: UseRsTableEngineOptions<T>) {
89
+ const internalSort = ref<RsTableSortState | null>(options.defaultSort ?? null)
90
+ const internalSorts = ref<RsTableSortState[]>([...options.defaultSorts])
91
+ const internalSelectedRowKeys = ref<string[]>([...options.defaultSelectedRowKeys])
92
+ const internalExpandedRowKeys = ref<string[]>([...options.defaultExpandedRowKeys])
93
+ /** 树懒加载中的行 key(UI loading 用) */
94
+ const treeLoadingKeys = ref<string[]>([])
95
+
96
+ const isSortControlled = computed(() => read(options.sort) !== undefined)
97
+
98
+ /** 单列排序状态(受控或内部) */
99
+ const sortState = computed({
100
+ get: () => (isSortControlled.value ? read(options.sort) ?? null : internalSort.value),
101
+ set: (value: RsTableSortState | null) => {
102
+ if (isSortControlled.value) options.emit('update:sort', value)
103
+ else internalSort.value = value
104
+ },
105
+ })
106
+
107
+ const isSortsControlled = computed(() => read(options.sorts) !== undefined)
108
+
109
+ /** 多列排序状态 */
110
+ const sortsState = computed({
111
+ get: () => (isSortsControlled.value ? read(options.sorts) ?? [] : internalSorts.value),
112
+ set: (value: RsTableSortState[]) => {
113
+ if (isSortsControlled.value) options.emit('update:sorts', value)
114
+ else internalSorts.value = value
115
+ options.emit('sortsChange', value)
116
+ },
117
+ })
118
+
119
+ const isSelectionControlled = computed(() => read(options.selectedRowKeys) !== undefined)
120
+
121
+ /** 已选行 key 列表;写入时同步 selectionChange */
122
+ const selectedRowKeys = computed({
123
+ get: () =>
124
+ isSelectionControlled.value ? read(options.selectedRowKeys) ?? [] : internalSelectedRowKeys.value,
125
+ set: (value: string[]) => {
126
+ if (isSelectionControlled.value) options.emit('update:selectedRowKeys', value)
127
+ else internalSelectedRowKeys.value = value
128
+ options.emit('selectionChange', value)
129
+ },
130
+ })
131
+ const selectedKeySet = computed(() => new Set(selectedRowKeys.value))
132
+
133
+ const isExpandedControlled = computed(() => read(options.expandedRowKeys) !== undefined)
134
+
135
+ /** 展开行 key(明细展开或树展开共用) */
136
+ const expandedRowKeys = computed({
137
+ get: () =>
138
+ isExpandedControlled.value ? read(options.expandedRowKeys) ?? [] : internalExpandedRowKeys.value,
139
+ set: (value: string[]) => {
140
+ if (isExpandedControlled.value) options.emit('update:expandedRowKeys', value)
141
+ else internalExpandedRowKeys.value = value
142
+ options.emit('expandChange', value)
143
+ },
144
+ })
145
+ const expandedKeySet = computed(() => new Set(expandedRowKeys.value))
146
+
147
+ const treeMode = computed(() => Boolean(read(options.treeConfig)))
148
+ /** 明细行展开:与 tree 互斥,树表下强制关闭 */
149
+ const detailExpandable = computed(() => read(options.expandable) && !treeMode.value)
150
+ const treeChildrenField = computed(() => read(options.treeConfig)?.childrenField ?? 'children')
151
+ const treeIsLeafField = computed(() => read(options.treeConfig)?.isLeafField ?? 'isLeaf')
152
+ const treeLazy = computed(() => Boolean(read(options.treeConfig)?.lazy))
153
+ /** 默认严格勾选(父子不联动);checkStrictly === false 时启用级联 */
154
+ const treeCheckStrictly = computed(() => read(options.treeConfig)?.checkStrictly !== false)
155
+ const treeIndentPx = computed(() =>
156
+ resolveTableTreeIndent(read(options.size), read(options.treeConfig)?.indent),
157
+ )
158
+ const treeExpandColumnKey = computed(() => {
159
+ const tree = read(options.treeConfig)
160
+ if (!tree) return null
161
+ return tree.expandColumnKey ?? read(options.displayColumns)[0]?.key ?? null
162
+ })
163
+ const treeFixExpandColumn = computed(() => read(options.treeConfig)?.fixExpandColumn !== false)
164
+ const treeLoadingKeySet = computed(() => new Set(treeLoadingKeys.value))
165
+
166
+ /** 全树索引:仅非严格勾选时构建,供级联半选等使用 */
167
+ const tableTreeNodeIndex = computed(() => {
168
+ const tree = read(options.treeConfig)
169
+ if (!tree || treeCheckStrictly.value) return null
170
+ return buildTableTreeNodeIndex(read(options.data), {
171
+ childrenField: treeChildrenField.value,
172
+ isLeafField: treeIsLeafField.value,
173
+ lazy: treeLazy.value,
174
+ rowKey: read(options.rowKey),
175
+ })
176
+ })
177
+
178
+ if (import.meta.env.DEV) {
179
+ watch(
180
+ () => [read(options.treeConfig), read(options.expandable), read(options.groupBy)] as const,
181
+ ([tree, expandable, groupBy]) => {
182
+ if (!tree) return
183
+ if (expandable) {
184
+ console.warn('[RsTable] treeConfig 与 expandable(明细展开)互斥,已优先使用 treeConfig')
185
+ }
186
+ if (groupBy) {
187
+ console.warn('[RsTable] treeConfig 与 groupBy 互斥,已忽略 groupBy')
188
+ }
189
+ },
190
+ { immediate: true },
191
+ )
192
+ watch(
193
+ () => [read(options.treeConfig), read(options.rowKey)] as const,
194
+ ([tree, rowKey]) => {
195
+ if (!tree) return
196
+ if (!hasStableTableTreeRowKey(rowKey)) {
197
+ console.warn(
198
+ '[RsTable] 树表必须提供稳定的 rowKey(字段名或函数),否则跨分支节点可能 key 碰撞;当前将回退为路径 key。',
199
+ )
200
+ }
201
+ },
202
+ { immediate: true },
203
+ )
204
+ }
205
+
206
+ /** defaultExpandAll 是否已种过种子(避免空数据把 seeded 提前锁死) */
207
+ const treeExpandAllSeeded = ref(false)
208
+ watch(
209
+ () => read(options.data),
210
+ (data) => {
211
+ const tree = read(options.treeConfig)
212
+ if (!tree?.defaultExpandAll) return
213
+ if (isExpandedControlled.value) return
214
+ if (treeExpandAllSeeded.value) return
215
+ if (options.defaultExpandedRowKeys.length > 0) {
216
+ treeExpandAllSeeded.value = true
217
+ return
218
+ }
219
+ if (!data.length) return
220
+ internalExpandedRowKeys.value = collectTableTreeExpandableKeys(data, {
221
+ childrenField: treeChildrenField.value,
222
+ isLeafField: treeIsLeafField.value,
223
+ lazy: treeLazy.value,
224
+ rowKey: read(options.rowKey),
225
+ })
226
+ treeExpandAllSeeded.value = true
227
+ },
228
+ { immediate: true },
229
+ )
230
+
231
+ /**
232
+ * 渲染用 entry 流水线:
233
+ * - 树表:buildTableTreeEntries
234
+ * - 普通:buildTableEntries(sort/filter/group)再 injectExpandRows
235
+ */
236
+ const tableEntries = computed(() => {
237
+ const tree = read(options.treeConfig)
238
+ const columns = read(options.displayColumns)
239
+ const data = read(options.data)
240
+ const rowKey = read(options.rowKey)
241
+ if (tree) {
242
+ return buildTableTreeEntries(data, columns, expandedKeySet.value, {
243
+ tree,
244
+ rowKey,
245
+ sort: sortState.value,
246
+ sorts: sortsState.value,
247
+ multiSort: read(options.multiSort),
248
+ filterText: read(options.filterText),
249
+ filterKeys: read(options.filterKeys),
250
+ columnFilters: read(options.columnFilters),
251
+ remoteSort: read(options.remoteSort),
252
+ })
253
+ }
254
+ return injectExpandRows(
255
+ buildTableEntries(data, columns, {
256
+ sort: sortState.value,
257
+ sorts: sortsState.value,
258
+ multiSort: read(options.multiSort),
259
+ filterText: read(options.filterText),
260
+ filterKeys: read(options.filterKeys),
261
+ columnFilters: read(options.columnFilters),
262
+ groupBy: read(options.groupBy),
263
+ groupLabel: read(options.groupLabel),
264
+ remoteSort: read(options.remoteSort),
265
+ }),
266
+ expandedKeySet.value,
267
+ rowKey,
268
+ read(options.rowExpandable),
269
+ )
270
+ })
271
+
272
+ /**
273
+ * 单次遍历 tableEntries,产出 dataRows / viewRows / selectableRowKeys / rowKeyByIndex。
274
+ * 不依赖 selectedKeySet,避免选中变化触发全量重遍历。
275
+ */
276
+ const rowDerivedState = computed(() => {
277
+ const rows: Extract<RsTableRowEntry<T>, { type: 'row' }>[] = []
278
+ const view: T[] = []
279
+ const selKeys: string[] = []
280
+ const keyByIndex = new Map<number, string>()
281
+ const rowKey = read(options.rowKey)
282
+ const rowSelectable = read(options.rowSelectable)
283
+ for (const entry of tableEntries.value) {
284
+ if (entry.type !== 'row') continue
285
+ rows.push(entry)
286
+ view.push(entry.row)
287
+ const key = entry.treeKey ?? resolveRowKey(entry.row, entry.rowIndex, rowKey)
288
+ keyByIndex.set(entry.rowIndex, key)
289
+ const disabled = isTableRowDisabled(entry.row)
290
+ if (!disabled && (rowSelectable ? rowSelectable(entry.row, entry.rowIndex) : true)) {
291
+ selKeys.push(key)
292
+ }
293
+ }
294
+ return { rows, view, selKeys, keyByIndex }
295
+ })
296
+
297
+ const dataRows = computed(() => rowDerivedState.value.rows)
298
+ const selectableRowKeys = computed(() => rowDerivedState.value.selKeys)
299
+ /** rowIndex → rowKey,供模板热路径 O(1) 查询 */
300
+ const rowKeyByIndex = computed(() => rowDerivedState.value.keyByIndex)
301
+ /** 当前筛选/排序后的业务行(不含 group/expand 伪行),与 dataRows 同趟产出 */
302
+ const viewRows = computed(() => rowDerivedState.value.view)
303
+
304
+ /** 当前已选行对象(依赖 dataRows + selectedKeySet;选中变化不重扫 tableEntries) */
305
+ const selectedRows = computed<T[]>(() => {
306
+ const keySet = selectedKeySet.value
307
+ const keyMap = rowKeyByIndex.value
308
+ const out: T[] = []
309
+ for (const entry of dataRows.value) {
310
+ if (keySet.has(keyMap.get(entry.rowIndex) ?? '')) out.push(entry.row)
311
+ }
312
+ return out
313
+ })
314
+
315
+ const selectAllState = computed(() =>
316
+ resolveSelectAllState(selectedRowKeys.value, selectableRowKeys.value),
317
+ )
318
+
319
+ return {
320
+ internalSort,
321
+ internalSorts,
322
+ internalSelectedRowKeys,
323
+ internalExpandedRowKeys,
324
+ treeLoadingKeys,
325
+ isSortControlled,
326
+ sortState,
327
+ isSortsControlled,
328
+ sortsState,
329
+ isSelectionControlled,
330
+ selectedRowKeys,
331
+ selectedKeySet,
332
+ isExpandedControlled,
333
+ expandedRowKeys,
334
+ expandedKeySet,
335
+ treeMode,
336
+ detailExpandable,
337
+ treeChildrenField,
338
+ treeIsLeafField,
339
+ treeLazy,
340
+ treeCheckStrictly,
341
+ treeIndentPx,
342
+ treeExpandColumnKey,
343
+ treeFixExpandColumn,
344
+ treeLoadingKeySet,
345
+ tableTreeNodeIndex,
346
+ tableEntries,
347
+ dataRows,
348
+ selectableRowKeys,
349
+ rowKeyByIndex,
350
+ selectedRows,
351
+ selectAllState,
352
+ viewRows,
353
+ }
354
+ }
355
+
356
+ /** useRsTableEngine 返回值类型 */
357
+ export type RsTableEngineApi<T extends RsTableRowData> = ReturnType<typeof useRsTableEngine<T>>