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,123 @@
1
+ /**
2
+ * RsTable ARIA Grid 键盘漫游(与编辑键盘解耦)。
3
+ *
4
+ * - 始终可在壳层焦点下移动 focusCell(只读表也可用)
5
+ * - 编辑中 / 表单控件内不抢键
6
+ * - 箭头 / Home / End / Ctrl+Home|End / PageUp|PageDown
7
+ * - 移动后将焦点格 scrollIntoView(nearest)
8
+ */
9
+
10
+ import { nextTick, type Ref } from 'vue'
11
+ import type { useTableEdit } from './useTableEdit'
12
+ import {
13
+ navigateGridCell,
14
+ resolveGridNavDirection,
15
+ type RsTableGridCellRef,
16
+ } from '../components/table/rs-table-grid-nav'
17
+ import type { RsTableColumn, RsTableRowData, RsTableRowEntry } from '../components/table-utils'
18
+
19
+ type TableEditApi = ReturnType<typeof useTableEdit>
20
+
21
+ export interface UseRsTableGridKeyboardOptions<T extends RsTableRowData> {
22
+ tableEdit: TableEditApi
23
+ /** 默认 true;可关以兼容旧行为 */
24
+ enabled?: () => boolean
25
+ dataRows: () => Array<Extract<RsTableRowEntry<T>, { type: 'row' }>>
26
+ rowKeyFor: (entry: Extract<RsTableRowEntry<T>, { type: 'row' }>) => string
27
+ /** 可见数据列(与虚拟列切片一致) */
28
+ colKeys: () => string[]
29
+ displayColumns?: () => RsTableColumn<T>[]
30
+ applyRowClickHighlight: (rowKey: string) => void
31
+ /** PageUp/Down 跨越行数,默认 10 */
32
+ pageSize?: () => number
33
+ /** 滚动容器:用于焦点格滚入视口 */
34
+ scrollContainerRef?: Ref<HTMLElement | null | undefined>
35
+ }
36
+
37
+ function escapeAttr(value: string): string {
38
+ if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {
39
+ return CSS.escape(value)
40
+ }
41
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
42
+ }
43
+
44
+ function scrollFocusCellIntoView(
45
+ container: HTMLElement | null | undefined,
46
+ cell: RsTableGridCellRef,
47
+ ): void {
48
+ if (!container) return
49
+ const selector = `tr[data-row-key="${escapeAttr(cell.rowKey)}"] td[data-col-key="${escapeAttr(cell.colKey)}"]`
50
+ const el = container.querySelector(selector) as HTMLElement | null
51
+ // jsdom 无 scrollIntoView;浏览器 / happy-dom 才滚
52
+ if (el && typeof el.scrollIntoView === 'function') {
53
+ el.scrollIntoView({ block: 'nearest', inline: 'nearest' })
54
+ }
55
+ }
56
+
57
+ /**
58
+ * 网格键盘漫游 API。
59
+ */
60
+ export function useRsTableGridKeyboard<T extends RsTableRowData>(
61
+ options: UseRsTableGridKeyboardOptions<T>,
62
+ ) {
63
+ function buildRows(): Array<{ rowKey: string; rowIndex: number }> {
64
+ return options.dataRows().map((entry) => ({
65
+ rowKey: options.rowKeyFor(entry),
66
+ rowIndex: entry.rowIndex,
67
+ }))
68
+ }
69
+
70
+ function resolveColKeys(): string[] {
71
+ const keys = options.colKeys()
72
+ if (keys.length) return keys
73
+ return (options.displayColumns?.() ?? []).map((c) => c.key)
74
+ }
75
+
76
+ /**
77
+ * 处理网格导航键。已处理返回 true(调用方应停止后续编辑键逻辑中的箭头分支)。
78
+ */
79
+ function onGridKeydown(event: KeyboardEvent): boolean {
80
+ if (options.enabled && !options.enabled()) return false
81
+ if (options.tableEdit.editingCell.value) return false
82
+
83
+ const target = event.target as HTMLElement | null
84
+ if (target?.closest('input, textarea, select, [contenteditable="true"]')) {
85
+ return false
86
+ }
87
+
88
+ const direction = resolveGridNavDirection(event)
89
+ if (!direction) return false
90
+
91
+ const rows = buildRows()
92
+ const colKeys = resolveColKeys()
93
+ if (!rows.length || !colKeys.length) return false
94
+
95
+ const focus = options.tableEdit.focusCell.value
96
+ const current: RsTableGridCellRef | null = focus
97
+ ? {
98
+ rowKey: focus.rowKey,
99
+ colKey: focus.colKey,
100
+ rowIndex: focus.rowIndex,
101
+ }
102
+ : null
103
+
104
+ const next = navigateGridCell(
105
+ rows,
106
+ colKeys,
107
+ current,
108
+ direction,
109
+ options.pageSize?.() ?? 10,
110
+ )
111
+ if (!next) return false
112
+
113
+ event.preventDefault()
114
+ options.tableEdit.setFocusCell(next)
115
+ options.applyRowClickHighlight(next.rowKey)
116
+ void nextTick(() => {
117
+ scrollFocusCellIntoView(options.scrollContainerRef?.value, next)
118
+ })
119
+ return true
120
+ }
121
+
122
+ return { onGridKeydown }
123
+ }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * RsTable 无头驱动入口(无模板也能跑 Engine / Features / Api)。
3
+ *
4
+ * 适用:服务端预计算、自定义渲染器、图表只消费 snapshot。
5
+ * 不含列宽 DOM / 右键 / 编辑交互(那些依赖视图壳)。
6
+ *
7
+ * 多表:每次调用产生独立 Core + FeatureHost;互不共享状态。
8
+ * 唯一全局点是 RsTableModuleRegistry(显式 register 才会影响所有实例)。
9
+ */
10
+
11
+ import type { Ref } from 'vue'
12
+ import type { RsTableColumn, RsTableRowData, RsTableRowKey } from '../components/table-utils'
13
+ import type { RsTableFeature } from '../components/table/table-features'
14
+ import { useRsTableCore, type UseRsTableCoreOptions } from './useRsTableCore'
15
+
16
+ export interface UseRsTableHeadlessOptions<T extends RsTableRowData> {
17
+ columns: Ref<RsTableColumn<T>[]> | (() => RsTableColumn<T>[])
18
+ data: Ref<T[]> | (() => T[])
19
+ rowKey?: RsTableRowKey<T> | (() => RsTableRowKey<T> | undefined)
20
+ selectable?: boolean
21
+ features?: RsTableFeature<T>[] | (() => RsTableFeature<T>[] | undefined)
22
+ /** 最小 emit 桩;受控同步时可传入 */
23
+ emit?: UseRsTableCoreOptions<T>['emit']
24
+ }
25
+
26
+ function readRefOrGetter<T>(source: Ref<T> | (() => T)): () => T {
27
+ return typeof source === 'function' ? (source as () => T) : () => (source as Ref<T>).value
28
+ }
29
+
30
+ /**
31
+ * 无头表格:复用 useRsTableCore(已含 Feature 绑定),再装配 Api。
32
+ */
33
+ export function useRsTableHeadless<T extends RsTableRowData>(
34
+ options: UseRsTableHeadlessOptions<T>,
35
+ ) {
36
+ const getColumns = readRefOrGetter(options.columns)
37
+ const getData = readRefOrGetter(options.data)
38
+ const getRowKey = () =>
39
+ typeof options.rowKey === 'function' ? options.rowKey() : options.rowKey
40
+ const getFeatures = () =>
41
+ typeof options.features === 'function' ? options.features() : options.features
42
+
43
+ const noopEmit = ((() => undefined) as unknown) as UseRsTableCoreOptions<T>['emit']
44
+
45
+ const core = useRsTableCore<T>({
46
+ columns: getColumns,
47
+ data: getData,
48
+ rowKey: getRowKey,
49
+ compact: () => false,
50
+ size: () => 'md',
51
+ columnOrder: () => undefined,
52
+ defaultColumnOrder: [],
53
+ columnFilters: () => undefined,
54
+ defaultColumnFilters: {},
55
+ initialColumnWidths: () => undefined,
56
+ resizable: () => false,
57
+ columnLayout: () => 'auto',
58
+ sort: () => undefined,
59
+ defaultSort: null,
60
+ sorts: () => undefined,
61
+ defaultSorts: [],
62
+ multiSort: () => false,
63
+ remoteSort: () => false,
64
+ filterText: () => undefined,
65
+ filterKeys: () => undefined,
66
+ groupBy: () => undefined,
67
+ groupLabel: () => undefined,
68
+ treeConfig: () => undefined,
69
+ expandable: () => false,
70
+ rowExpandable: () => undefined,
71
+ selectedRowKeys: () => undefined,
72
+ defaultSelectedRowKeys: [],
73
+ expandedRowKeys: () => undefined,
74
+ defaultExpandedRowKeys: [],
75
+ rowSelectable: () => undefined,
76
+ height: () => 320,
77
+ layoutActive: () => true,
78
+ viewKey: () => undefined,
79
+ virtual: () => false,
80
+ virtualAutoThreshold: () => 50,
81
+ fill: () => false,
82
+ infinite: () => false,
83
+ virtualOnInfinite: () => true,
84
+ overscan: () => 4,
85
+ expandRowHeight: () => undefined,
86
+ rowHeight: () => undefined,
87
+ selectable: () => Boolean(options.selectable),
88
+ editable: () => false,
89
+ contextMenu: () => false,
90
+ summaryFeatureEnabled: () => false,
91
+ features: getFeatures,
92
+ emit: options.emit ?? noopEmit,
93
+ })
94
+
95
+ const api = core.tableSurface.createApi(
96
+ {
97
+ getRowByKey: (rowKey) => {
98
+ const entry = core.dataRows.value.find(
99
+ (item) => core.rowKeyByIndex.value.get(item.rowIndex) === rowKey,
100
+ )
101
+ return entry?.row
102
+ },
103
+ getRowByIndex: (index) =>
104
+ core.dataRows.value.find((item) => item.rowIndex === index)?.row,
105
+ getColumnValues: () => [],
106
+ getCellValue: () => undefined,
107
+ getViewRows: () => core.viewRows.value,
108
+ getSelectedRows: () => core.selectedRows.value,
109
+ getSelectedRowKeys: () => [...core.selectedRowKeys.value],
110
+ getAnalyticsSnapshot: () => core.analyticsSource.getSnapshot(),
111
+ subscribeAnalytics: (listener) => core.analyticsSource.subscribe(listener),
112
+ getHighlightedRowKey: () => undefined,
113
+ setHighlightedRowKey: () => undefined,
114
+ cancelCellEdit: () => undefined,
115
+ cancelAllEdits: () => undefined,
116
+ getDirtyCellKeys: () => [],
117
+ getCellError: () => undefined,
118
+ setCellError: () => undefined,
119
+ stageCell: () => undefined,
120
+ rejectRowEdit: () => undefined,
121
+ undoEdit: () => undefined,
122
+ redoEdit: () => undefined,
123
+ commitRowEdits: () => undefined,
124
+ rollbackRowEdits: () => undefined,
125
+ getToolbarItems: () => core.tableSurface.getToolbarItems(),
126
+ getOverlayContributions: () => core.tableSurface.getOverlayContributions(),
127
+ },
128
+ { provide: false },
129
+ )
130
+
131
+ return {
132
+ api,
133
+ core,
134
+ getToolbarItems: () => core.tableSurface.getToolbarItems(),
135
+ getOverlayContributions: () => core.tableSurface.getOverlayContributions(),
136
+ }
137
+ }
@@ -0,0 +1,433 @@
1
+ /**
2
+ * RsTable 行/列交互:排序点击、列拖拽排序、行选/展开、行拖拽封装。
3
+ *
4
+ * 性能约定:
5
+ * - 不引入对 data 的 deep watch
6
+ * - rowKeyFor 走 Engine 的 rowKeyByIndex 缓存,避免热路径重复 resolve
7
+ */
8
+
9
+ import { ref, type ComputedRef, type Ref } from 'vue'
10
+ import {
11
+ createTableRowDragHandlers,
12
+ createTableRowDragState,
13
+ type RsTableRowDragTrigger,
14
+ type RsTableRowDropMode,
15
+ } from '../components/table-drag'
16
+ import {
17
+ getSortOrderForKey,
18
+ getSortPriorityForKey,
19
+ getTableTreeChildren,
20
+ hasTableTreeChildren,
21
+ isTableRowDisabled,
22
+ reorderColumnKeys,
23
+ resolveRowKey,
24
+ resolveTableTreeCheckState,
25
+ selectRowKeys,
26
+ selectRowKeysByClick,
27
+ toggleExpandedRowKeys,
28
+ toggleMultiSortState,
29
+ toggleSelectAll,
30
+ toggleSortState,
31
+ toggleTableTreeCheck,
32
+ type RsTableColumn,
33
+ type RsTableRowData,
34
+ type RsTableRowEntry,
35
+ type RsTableRowKey,
36
+ type RsTableSelectionType,
37
+ type RsTableSortOrder,
38
+ type RsTableSortState,
39
+ type RsTableTreeConfig,
40
+ type RsTableTreeNodeIndex,
41
+ type RsTableRowDropPosition,
42
+ } from '../components/table-utils'
43
+
44
+ function read<T>(source: ComputedRef<T> | (() => T) | Ref<T>): T {
45
+ if (typeof source === 'function') return (source as () => T)()
46
+ return (source as Ref<T> | ComputedRef<T>).value
47
+ }
48
+
49
+ export interface UseRsTableInteractionOptions<T extends RsTableRowData> {
50
+ multiSort: () => boolean
51
+ maxSort: () => number | undefined
52
+ columnDraggable: () => boolean
53
+ selectable: () => boolean
54
+ selectionType: () => RsTableSelectionType
55
+ rowSelectable?: (row: T, index: number) => boolean
56
+ rowDraggable: () => boolean
57
+ rowDragTrigger: () => RsTableRowDragTrigger
58
+ rowDropMode: () => RsTableRowDropMode
59
+ rowDraggableWhen?: (row: T, index: number) => boolean
60
+ rowDropTargetWhen?: (row: T, index: number) => boolean
61
+ canRowDrop?: (dragKeys: string[], dropKey: string) => boolean
62
+ rowKey: () => RsTableRowKey<T> | undefined
63
+ rowKeyByIndex: () => Map<number, string>
64
+ selectedRowKeys: {
65
+ get: () => string[]
66
+ set: (keys: string[]) => void
67
+ }
68
+ selectedKeySet: () => Set<string>
69
+ selectableRowKeys: () => string[]
70
+ selectAllState: () => 'checked' | 'indeterminate' | 'unchecked'
71
+ expandedRowKeys: {
72
+ get: () => string[]
73
+ set: (keys: string[]) => void
74
+ }
75
+ expandedKeySet: () => Set<string>
76
+ treeMode: () => boolean
77
+ treeCheckStrictly: () => boolean
78
+ treeLazy: () => boolean
79
+ treeChildrenField: () => string
80
+ treeIsLeafField: () => string
81
+ treeLoadingKeys: {
82
+ get: () => string[]
83
+ set: (keys: string[]) => void
84
+ }
85
+ treeLoadingKeySet: () => Set<string>
86
+ tableTreeNodeIndex: () => Map<string, RsTableTreeNodeIndex> | null
87
+ treeConfig: () => RsTableTreeConfig<T> | undefined
88
+ expandable: () => boolean
89
+ rowExpandable?: (row: T, index: number) => boolean
90
+ sortState: {
91
+ get: () => RsTableSortState | null
92
+ set: (value: RsTableSortState | null) => void
93
+ }
94
+ sortsState: {
95
+ get: () => RsTableSortState[]
96
+ set: (value: RsTableSortState[]) => void
97
+ }
98
+ columnOrderState: {
99
+ get: () => string[]
100
+ set: (value: string[]) => void
101
+ }
102
+ /** 行选时若正在编辑则不改选中 */
103
+ isEditingAny: () => boolean
104
+ applyRowClickHighlight: (rowKey: string) => void
105
+ onRowClickEmit: (row: T, index: number) => void
106
+ onRowDblclickEmit: (row: T, index: number) => void
107
+ onRowDragStartEmit: (dragKeys: string[], event: DragEvent) => void
108
+ onRowDropEmit: (dragKeys: string[], dropKey: string, position: RsTableRowDropPosition) => void
109
+ }
110
+
111
+ /**
112
+ * 表格交互 API(排序 / 列拖 / 行选展开 / 行拖)。
113
+ */
114
+ export function useRsTableInteraction<T extends RsTableRowData>(
115
+ options: UseRsTableInteractionOptions<T>,
116
+ ) {
117
+ const dragColumnKey = ref<string | null>(null)
118
+ const dropColumnKey = ref<string | null>(null)
119
+ const selectionAnchorKey = ref<string | null>(null)
120
+ const rowDragState = createTableRowDragState()
121
+ const { dragRowKeys, dropRowTargetKey, dropRowPosition } = rowDragState
122
+
123
+ function rowKeyFor(entry: Extract<RsTableRowEntry<T>, { type: 'row' }>): string {
124
+ return (
125
+ options.rowKeyByIndex().get(entry.rowIndex) ??
126
+ entry.treeKey ??
127
+ resolveRowKey(entry.row, entry.rowIndex, options.rowKey())
128
+ )
129
+ }
130
+
131
+ function sortOrderFor(key: string): RsTableSortOrder {
132
+ if (options.multiSort()) return getSortOrderForKey(options.sortsState.get(), key)
133
+ const sort = options.sortState.get()
134
+ return sort?.key === key ? sort.order : null
135
+ }
136
+
137
+ function sortPriorityFor(key: string): number {
138
+ if (!options.multiSort()) return 0
139
+ return getSortPriorityForKey(options.sortsState.get(), key)
140
+ }
141
+
142
+ function sortIconName(key: string): string {
143
+ const order = sortOrderFor(key)
144
+ if (order === 'asc') return 'arrow-up'
145
+ if (order === 'desc') return 'arrow-down'
146
+ return 'arrow-up-down'
147
+ }
148
+
149
+ function onHeaderClick(column: RsTableColumn<T>): void {
150
+ if (!column.sortable) return
151
+ if (options.multiSort()) {
152
+ options.sortsState.set(
153
+ toggleMultiSortState(options.sortsState.get(), column.key, options.maxSort()),
154
+ )
155
+ return
156
+ }
157
+ options.sortState.set(toggleSortState(options.sortState.get(), column.key))
158
+ }
159
+
160
+ function onColumnDragStart(key: string, event: DragEvent): void {
161
+ if (!options.columnDraggable()) return
162
+ dragColumnKey.value = key
163
+ dropColumnKey.value = null
164
+ if (!event.dataTransfer) return
165
+ event.dataTransfer.setData('text/plain', key)
166
+ event.dataTransfer.effectAllowed = 'move'
167
+ }
168
+
169
+ function onColumnDragOver(key: string, event: DragEvent): void {
170
+ if (!options.columnDraggable() || !dragColumnKey.value) return
171
+ if (dragColumnKey.value === key) {
172
+ if (dropColumnKey.value !== null) dropColumnKey.value = null
173
+ return
174
+ }
175
+ event.preventDefault()
176
+ if (dropColumnKey.value !== key) dropColumnKey.value = key
177
+ }
178
+
179
+ function onColumnDrop(key: string, event: DragEvent): void {
180
+ if (!options.columnDraggable() || !dragColumnKey.value) return
181
+ event.preventDefault()
182
+ if (dragColumnKey.value === key) return
183
+ options.columnOrderState.set(
184
+ reorderColumnKeys(options.columnOrderState.get(), dragColumnKey.value, key),
185
+ )
186
+ dragColumnKey.value = null
187
+ dropColumnKey.value = null
188
+ }
189
+
190
+ function onColumnDragEnd(): void {
191
+ dragColumnKey.value = null
192
+ dropColumnKey.value = null
193
+ }
194
+
195
+ const isRowSelection = () =>
196
+ options.selectable() && options.selectionType() === 'row'
197
+
198
+ function canSelectRow(entry: Extract<RsTableRowEntry<T>, { type: 'row' }>): boolean {
199
+ if (isTableRowDisabled(entry.row)) return false
200
+ return options.rowSelectable ? options.rowSelectable(entry.row, entry.rowIndex) : true
201
+ }
202
+
203
+ function isRowSelected(entry: Extract<RsTableRowEntry<T>, { type: 'row' }>): boolean {
204
+ const key = rowKeyFor(entry)
205
+ const index = options.tableTreeNodeIndex()
206
+ if (index && !options.treeCheckStrictly()) {
207
+ return resolveTableTreeCheckState(key, options.selectedKeySet(), index, false) === 'checked'
208
+ }
209
+ return options.selectedKeySet().has(key)
210
+ }
211
+
212
+ function isRowIndeterminate(entry: Extract<RsTableRowEntry<T>, { type: 'row' }>): boolean {
213
+ const index = options.tableTreeNodeIndex()
214
+ if (!index || options.treeCheckStrictly()) return false
215
+ return (
216
+ resolveTableTreeCheckState(rowKeyFor(entry), options.selectedKeySet(), index, false) ===
217
+ 'indeterminate'
218
+ )
219
+ }
220
+
221
+ function onToggleRow(entry: Extract<RsTableRowEntry<T>, { type: 'row' }>): void {
222
+ if (!canSelectRow(entry)) return
223
+ const key = rowKeyFor(entry)
224
+ const index = options.tableTreeNodeIndex()
225
+ if (index && !options.treeCheckStrictly() && options.selectionType() !== 'radio') {
226
+ options.selectedRowKeys.set(
227
+ toggleTableTreeCheck(key, options.selectedKeySet(), index, false),
228
+ )
229
+ return
230
+ }
231
+ options.selectedRowKeys.set(
232
+ selectRowKeys(options.selectedRowKeys.get(), key, options.selectionType()),
233
+ )
234
+ }
235
+
236
+ function onToggleSelectAll(): void {
237
+ const select = options.selectAllState() !== 'checked'
238
+ options.selectedRowKeys.set(
239
+ toggleSelectAll(options.selectedRowKeys.get(), options.selectableRowKeys(), select),
240
+ )
241
+ }
242
+
243
+ function canExpandRow(entry: Extract<RsTableRowEntry<T>, { type: 'row' }>): boolean {
244
+ if (options.treeMode()) {
245
+ if (entry.hasChildren != null) return entry.hasChildren
246
+ return hasTableTreeChildren(entry.row, {
247
+ childrenField: options.treeChildrenField(),
248
+ isLeafField: options.treeIsLeafField(),
249
+ lazy: options.treeLazy(),
250
+ })
251
+ }
252
+ if (!options.expandable()) return false
253
+ if (options.rowExpandable) return options.rowExpandable(entry.row, entry.rowIndex)
254
+ return true
255
+ }
256
+
257
+ function isRowExpanded(entry: Extract<RsTableRowEntry<T>, { type: 'row' }>): boolean {
258
+ return options.expandedKeySet().has(rowKeyFor(entry))
259
+ }
260
+
261
+ function treeDepthOf(entry: Extract<RsTableRowEntry<T>, { type: 'row' }>): number {
262
+ return entry.depth ?? 0
263
+ }
264
+
265
+ async function onToggleExpand(
266
+ entry: Extract<RsTableRowEntry<T>, { type: 'row' }>,
267
+ ): Promise<void> {
268
+ if (!canExpandRow(entry)) return
269
+ const key = rowKeyFor(entry)
270
+ const expanding = !options.expandedKeySet().has(key)
271
+ const tree = options.treeConfig()
272
+
273
+ if (
274
+ options.treeMode() &&
275
+ expanding &&
276
+ options.treeLazy() &&
277
+ tree?.loadData &&
278
+ getTableTreeChildren(entry.row, options.treeChildrenField()).length === 0
279
+ ) {
280
+ if (options.treeLoadingKeySet().has(key)) return
281
+ options.treeLoadingKeys.set([...options.treeLoadingKeys.get(), key])
282
+ try {
283
+ const loaded = await tree.loadData(entry.row, key)
284
+ if (Array.isArray(loaded)) {
285
+ ;(entry.row as Record<string, unknown>)[options.treeChildrenField()] = loaded
286
+ }
287
+ } finally {
288
+ options.treeLoadingKeys.set(options.treeLoadingKeys.get().filter((item) => item !== key))
289
+ }
290
+ }
291
+
292
+ options.expandedRowKeys.set(toggleExpandedRowKeys(options.expandedRowKeys.get(), key))
293
+ }
294
+
295
+ function onRowClick(entry: RsTableRowEntry<T>, event?: MouseEvent): void {
296
+ if (entry.type !== 'row') return
297
+ const rowKey = rowKeyFor(entry)
298
+ if (isRowSelection() && canSelectRow(entry) && !options.isEditingAny()) {
299
+ const toggle = Boolean(event && (event.ctrlKey || event.metaKey))
300
+ const range = Boolean(event?.shiftKey)
301
+ options.selectedRowKeys.set(
302
+ selectRowKeysByClick(options.selectedRowKeys.get(), rowKey, {
303
+ toggle,
304
+ range,
305
+ orderedKeys: options.selectableRowKeys(),
306
+ anchorKey: selectionAnchorKey.value,
307
+ }),
308
+ )
309
+ if (!range) selectionAnchorKey.value = rowKey
310
+ if (typeof window !== 'undefined') {
311
+ window.getSelection()?.removeAllRanges()
312
+ }
313
+ }
314
+ options.applyRowClickHighlight(rowKey)
315
+ options.onRowClickEmit(entry.row, entry.rowIndex)
316
+ }
317
+
318
+ function onRowSelectMouseDown(event: MouseEvent): void {
319
+ if (!isRowSelection()) return
320
+ if (event.shiftKey || event.ctrlKey || event.metaKey) {
321
+ event.preventDefault()
322
+ }
323
+ }
324
+
325
+ function onRowDblclick(entry: RsTableRowEntry<T>, _event?: MouseEvent): void {
326
+ if (entry.type !== 'row') return
327
+ options.onRowDblclickEmit(entry.row, entry.rowIndex)
328
+ }
329
+
330
+ const rowDrag = createTableRowDragHandlers<T>({
331
+ state: rowDragState,
332
+ getRowDraggable: () => options.rowDraggable(),
333
+ getRowDragTrigger: () => options.rowDragTrigger(),
334
+ getRowDropMode: () => options.rowDropMode(),
335
+ rowDraggableWhen: options.rowDraggableWhen,
336
+ rowDropTargetWhen: options.rowDropTargetWhen,
337
+ canRowDrop: options.canRowDrop,
338
+ rowKeyFor: (row, index) => resolveRowKey(row, index, options.rowKey()),
339
+ isRowDisabled: isTableRowDisabled,
340
+ getSelectedKeys: () => options.selectedRowKeys.get(),
341
+ onDragStart: (dragKeys, event) => options.onRowDragStartEmit(dragKeys, event),
342
+ onDrop: (dragKeys, dropKey, position) =>
343
+ options.onRowDropEmit(dragKeys, dropKey, position),
344
+ })
345
+
346
+ function onRowDragStart(
347
+ entry: Extract<RsTableRowEntry<T>, { type: 'row' }>,
348
+ event: DragEvent,
349
+ ): void {
350
+ rowDrag.onRowDragStart(entry.row, entry.rowIndex, event)
351
+ }
352
+
353
+ function onRowDragOver(
354
+ entry: Extract<RsTableRowEntry<T>, { type: 'row' }>,
355
+ event: DragEvent,
356
+ ): void {
357
+ rowDrag.onRowDragOver(entry.row, entry.rowIndex, event)
358
+ }
359
+
360
+ function onRowDragLeave(event: DragEvent): void {
361
+ rowDrag.onRowDragLeave(event)
362
+ }
363
+
364
+ function onRowDrop(
365
+ entry: Extract<RsTableRowEntry<T>, { type: 'row' }>,
366
+ event: DragEvent,
367
+ ): void {
368
+ rowDrag.onRowDrop(entry.row, entry.rowIndex, event)
369
+ }
370
+
371
+ function onRowDragEnd(): void {
372
+ rowDrag.onRowDragEnd()
373
+ }
374
+
375
+ function isRowDropTarget(entry: Extract<RsTableRowEntry<T>, { type: 'row' }>): boolean {
376
+ return rowDrag.isRowDropTarget(rowKeyFor(entry))
377
+ }
378
+
379
+ function isRowDragging(entry: Extract<RsTableRowEntry<T>, { type: 'row' }>): boolean {
380
+ return rowDrag.isRowDragging(rowKeyFor(entry))
381
+ }
382
+
383
+ function canDragRow(entry: Extract<RsTableRowEntry<T>, { type: 'row' }>): boolean {
384
+ return rowDrag.canDragRow(entry.row, entry.rowIndex)
385
+ }
386
+
387
+ function isRowDragByRow(entry: Extract<RsTableRowEntry<T>, { type: 'row' }>): boolean {
388
+ return options.rowDraggable() && options.rowDragTrigger() === 'row' && canDragRow(entry)
389
+ }
390
+
391
+ return {
392
+ dragColumnKey,
393
+ dropColumnKey,
394
+ selectionAnchorKey,
395
+ dragRowKeys,
396
+ dropRowTargetKey,
397
+ dropRowPosition,
398
+ rowKeyFor,
399
+ sortOrderFor,
400
+ sortPriorityFor,
401
+ sortIconName,
402
+ onHeaderClick,
403
+ onColumnDragStart,
404
+ onColumnDragOver,
405
+ onColumnDrop,
406
+ onColumnDragEnd,
407
+ canSelectRow,
408
+ isRowSelected,
409
+ isRowIndeterminate,
410
+ onToggleRow,
411
+ onToggleSelectAll,
412
+ canExpandRow,
413
+ isRowExpanded,
414
+ treeDepthOf,
415
+ onToggleExpand,
416
+ onRowClick,
417
+ onRowSelectMouseDown,
418
+ onRowDblclick,
419
+ onRowDragStart,
420
+ onRowDragOver,
421
+ onRowDragLeave,
422
+ onRowDrop,
423
+ onRowDragEnd,
424
+ isRowDropTarget,
425
+ isRowDragging,
426
+ canDragRow,
427
+ isRowDragByRow,
428
+ }
429
+ }
430
+
431
+ export type RsTableInteractionApi<T extends RsTableRowData> = ReturnType<
432
+ typeof useRsTableInteraction<T>
433
+ >