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,75 @@
1
+ /**
2
+ * RsTable 滚动宿主生命周期 + 无限加载触底。
3
+ */
4
+
5
+ import { onActivated, onDeactivated, onMounted, ref, watch, type Ref } from 'vue'
6
+ import { isNearScrollBottom } from '../components/table-utils'
7
+
8
+ export interface UseRsTableScrollHostOptions {
9
+ scrollContainerRef: Ref<HTMLElement | null | undefined>
10
+ scheduleVirtualLayoutSync: () => void
11
+ bindScrollElement: (el: HTMLElement | null | undefined) => void
12
+ captureScrollFromDom: () => void
13
+ onScrollFrame: (element: HTMLElement) => void
14
+ hideCellTooltip: () => void
15
+ infinite: () => boolean
16
+ loading: () => boolean
17
+ loadingMore: () => boolean
18
+ hasMore: () => boolean
19
+ infiniteDistance: () => number
20
+ onLoadMore: () => void
21
+ }
22
+
23
+ /**
24
+ * 绑定滚动生命周期与无限加载。
25
+ */
26
+ export function useRsTableScrollHost(options: UseRsTableScrollHostOptions) {
27
+ const loadMoreLocked = ref(false)
28
+
29
+ onMounted(() => {
30
+ options.scheduleVirtualLayoutSync()
31
+ options.bindScrollElement(options.scrollContainerRef.value)
32
+ })
33
+
34
+ onActivated(() => {
35
+ options.scheduleVirtualLayoutSync()
36
+ })
37
+
38
+ onDeactivated(() => {
39
+ options.captureScrollFromDom()
40
+ })
41
+
42
+ function onScroll(event: Event): void {
43
+ options.hideCellTooltip()
44
+ const element = event.target as HTMLElement
45
+ if (
46
+ options.infinite() &&
47
+ !options.loading() &&
48
+ !options.loadingMore() &&
49
+ options.hasMore() &&
50
+ !loadMoreLocked.value
51
+ ) {
52
+ if (
53
+ isNearScrollBottom(
54
+ element.scrollTop,
55
+ element.scrollHeight,
56
+ element.clientHeight,
57
+ options.infiniteDistance(),
58
+ )
59
+ ) {
60
+ loadMoreLocked.value = true
61
+ options.onLoadMore()
62
+ }
63
+ }
64
+ options.onScrollFrame(element)
65
+ }
66
+
67
+ watch(
68
+ () => [options.loadingMore(), options.hasMore()] as const,
69
+ ([loadingMore, hasMore]) => {
70
+ if (!loadingMore || !hasMore) loadMoreLocked.value = false
71
+ },
72
+ )
73
+
74
+ return { onScroll, loadMoreLocked }
75
+ }
@@ -0,0 +1,255 @@
1
+ /**
2
+ * RsTable 滚动与视口布局:测量、节流同步、keep-alive / layoutActive 恢复。
3
+ *
4
+ * 职责边界:
5
+ * - 做:scrollTop/Left、视口宽高、写回 DOM、ResizeObserver、RAF 节流滚动
6
+ * - 不做:虚拟切片本身、无限加载业务判定(由调用方在 onScroll 钩子中处理)
7
+ */
8
+
9
+ import {
10
+ computed,
11
+ nextTick,
12
+ onUnmounted,
13
+ ref,
14
+ watch,
15
+ type ComputedRef,
16
+ type Ref,
17
+ } from 'vue'
18
+
19
+ /** 统一读取 ComputedRef / getter */
20
+ function read<T>(source: ComputedRef<T> | (() => T)): T {
21
+ return typeof source === 'function' ? (source as () => T)() : source.value
22
+ }
23
+
24
+ export interface UseRsTableScrollLayoutOptions {
25
+ /** 显式高度(number / px 字符串优先于测量值) */
26
+ height: ComputedRef<number | string | undefined> | (() => number | string | undefined)
27
+ /** 外层 keep-alive 可见性;false→快照,true→恢复 */
28
+ layoutActive: ComputedRef<boolean | undefined> | (() => boolean | undefined)
29
+ /** 视图切换标识;变化时重置滚动(跳过首次) */
30
+ viewKey: ComputedRef<string | number | undefined> | (() => string | number | undefined)
31
+ /** 无测量值时的回退视口高度 */
32
+ fallbackViewportHeight?: number
33
+ }
34
+
35
+ /**
36
+ * 表格滚动容器与视口状态。
37
+ *
38
+ * @returns 滚动状态、视口尺寸、恢复/重置 API、bindScrollElement / onScrollFrame
39
+ */
40
+ export function useRsTableScrollLayout(options: UseRsTableScrollLayoutOptions) {
41
+ const scrollTop = ref(0)
42
+ const scrollLeft = ref(0)
43
+ const scrollContainerRef = ref<HTMLElement | null>(null)
44
+ const measuredViewportHeight = ref(0)
45
+ const measuredViewportWidth = ref(0)
46
+
47
+ let viewportResizeObserver: ResizeObserver | null = null
48
+ let preservedScrollTop = 0
49
+ let preservedScrollLeft = 0
50
+ let restoringScroll = false
51
+ let restoreGeneration = 0
52
+ let scrollRafId = 0
53
+
54
+ const fallback = options.fallbackViewportHeight ?? 320
55
+
56
+ /** 虚拟切片用视口高度:显式 height > 测量值 > DOM > fallback */
57
+ const viewportHeight = computed(() => {
58
+ const h = read(options.height)
59
+ if (typeof h === 'number' && h > 0) return h
60
+ if (typeof h === 'string') {
61
+ const parsed = Number.parseInt(h, 10)
62
+ if (Number.isFinite(parsed) && parsed > 0 && !h.includes('%')) return parsed
63
+ }
64
+ if (measuredViewportHeight.value > 0) return measuredViewportHeight.value
65
+ const el = scrollContainerRef.value
66
+ if (el && el.clientHeight > 0) return el.clientHeight
67
+ return fallback
68
+ })
69
+
70
+ function syncMeasuredViewportSize(): void {
71
+ const el = scrollContainerRef.value
72
+ if (!el) return
73
+ const nextH = el.clientHeight
74
+ if (nextH > 0 && nextH !== measuredViewportHeight.value) {
75
+ measuredViewportHeight.value = nextH
76
+ }
77
+ const nextW = el.clientWidth
78
+ if (nextW > 0 && nextW !== measuredViewportWidth.value) {
79
+ measuredViewportWidth.value = nextW
80
+ }
81
+ }
82
+
83
+ /**
84
+ * 失活前快照滚动。
85
+ * DOM 脱离文档后 scrollTop 常为 0——切勿用 0 覆盖 onScroll 已写入的位置。
86
+ */
87
+ function captureScrollFromDom(): void {
88
+ const el = scrollContainerRef.value
89
+ if (el) {
90
+ if (el.scrollTop > 0) scrollTop.value = el.scrollTop
91
+ if (el.scrollLeft > 0) scrollLeft.value = el.scrollLeft
92
+ }
93
+ if (scrollTop.value > 0) preservedScrollTop = scrollTop.value
94
+ if (scrollLeft.value > 0) preservedScrollLeft = scrollLeft.value
95
+ }
96
+
97
+ /** 将内部保存的滚动写回 DOM,并同步 reactive(驱动虚拟切片)。 */
98
+ function restoreScrollToDom(): void {
99
+ const el = scrollContainerRef.value
100
+ if (!el) return
101
+ syncMeasuredViewportSize()
102
+ const top = preservedScrollTop
103
+ const left = preservedScrollLeft
104
+ if (scrollTop.value !== top) scrollTop.value = top
105
+ if (scrollLeft.value !== left) scrollLeft.value = left
106
+ if (el.scrollTop !== top) el.scrollTop = top
107
+ if (el.scrollLeft !== left) el.scrollLeft = left
108
+ }
109
+
110
+ /**
111
+ * 布局稳定后多次写回滚动:keep-alive 切回首帧 clientHeight 常为 0,
112
+ * 需等 nextTick / rAF / 视口测稳后再设 scrollTop,否则会被浏览器钳成 0。
113
+ */
114
+ function scheduleVirtualLayoutSync(): void {
115
+ restoreGeneration += 1
116
+ const gen = restoreGeneration
117
+ restoringScroll = true
118
+ const apply = (): void => {
119
+ if (gen !== restoreGeneration) return
120
+ restoreScrollToDom()
121
+ }
122
+ apply()
123
+ void nextTick(() => {
124
+ apply()
125
+ requestAnimationFrame(() => {
126
+ apply()
127
+ requestAnimationFrame(() => {
128
+ apply()
129
+ if (gen === restoreGeneration) restoringScroll = false
130
+ })
131
+ })
132
+ })
133
+ }
134
+
135
+ function resetScrollPosition(): void {
136
+ preservedScrollTop = 0
137
+ preservedScrollLeft = 0
138
+ scrollTop.value = 0
139
+ scrollLeft.value = 0
140
+ const el = scrollContainerRef.value
141
+ if (el) {
142
+ el.scrollTop = 0
143
+ el.scrollLeft = 0
144
+ }
145
+ scheduleVirtualLayoutSync()
146
+ }
147
+
148
+ function isRestoringScroll(): boolean {
149
+ return restoringScroll
150
+ }
151
+
152
+ /**
153
+ * RAF 节流写入滚动位置;恢复窗口内忽略伪 scrollTop=0。
154
+ * @returns true 表示本帧已调度或处于恢复中(调用方仍可先做无限加载判断)
155
+ */
156
+ function onScrollFrame(element: HTMLElement): void {
157
+ if (scrollRafId) return
158
+ scrollRafId = requestAnimationFrame(() => {
159
+ if (restoringScroll) {
160
+ scrollRafId = 0
161
+ return
162
+ }
163
+ scrollTop.value = element.scrollTop
164
+ scrollLeft.value = element.scrollLeft
165
+ preservedScrollTop = element.scrollTop
166
+ preservedScrollLeft = element.scrollLeft
167
+ scrollRafId = 0
168
+ })
169
+ }
170
+
171
+ /**
172
+ * 绑定滚动容器 ResizeObserver:视口从 0→实高时补一次恢复写回。
173
+ */
174
+ function bindScrollElement(el: HTMLElement | null | undefined): void {
175
+ viewportResizeObserver?.disconnect()
176
+ viewportResizeObserver = null
177
+ if (!el || typeof ResizeObserver === 'undefined') return
178
+ viewportResizeObserver = new ResizeObserver(() => {
179
+ const prevH = measuredViewportHeight.value
180
+ syncMeasuredViewportSize()
181
+ if (
182
+ prevH === 0 &&
183
+ measuredViewportHeight.value > 0 &&
184
+ (preservedScrollTop > 0 || preservedScrollLeft > 0)
185
+ ) {
186
+ restoringScroll = true
187
+ restoreScrollToDom()
188
+ requestAnimationFrame(() => {
189
+ restoringScroll = false
190
+ })
191
+ }
192
+ })
193
+ viewportResizeObserver.observe(el)
194
+ }
195
+
196
+ function disposeScrollLayout(): void {
197
+ viewportResizeObserver?.disconnect()
198
+ viewportResizeObserver = null
199
+ if (scrollRafId) {
200
+ cancelAnimationFrame(scrollRafId)
201
+ scrollRafId = 0
202
+ }
203
+ }
204
+
205
+ watch(
206
+ () => read(options.layoutActive),
207
+ (active, prev) => {
208
+ if (active === false && prev !== false) {
209
+ captureScrollFromDom()
210
+ }
211
+ },
212
+ { flush: 'sync' },
213
+ )
214
+
215
+ watch(
216
+ () => read(options.layoutActive),
217
+ (active) => {
218
+ if (active) scheduleVirtualLayoutSync()
219
+ },
220
+ { flush: 'post' },
221
+ )
222
+
223
+ watch(
224
+ () => read(options.viewKey),
225
+ (_next, prev) => {
226
+ if (prev === undefined) return
227
+ resetScrollPosition()
228
+ },
229
+ )
230
+
231
+ onUnmounted(() => {
232
+ disposeScrollLayout()
233
+ })
234
+
235
+ return {
236
+ scrollTop,
237
+ scrollLeft,
238
+ scrollContainerRef,
239
+ measuredViewportHeight,
240
+ measuredViewportWidth,
241
+ viewportHeight,
242
+ syncMeasuredViewportSize,
243
+ captureScrollFromDom,
244
+ restoreScrollToDom,
245
+ scheduleVirtualLayoutSync,
246
+ resetScrollPosition,
247
+ isRestoringScroll,
248
+ onScrollFrame,
249
+ bindScrollElement,
250
+ disposeScrollLayout,
251
+ }
252
+ }
253
+
254
+ /** useRsTableScrollLayout 返回值类型 */
255
+ export type RsTableScrollLayoutApi = ReturnType<typeof useRsTableScrollLayout>
@@ -0,0 +1,89 @@
1
+ /**
2
+ * 表格分析/图表数据源:viewRows + selectedRows → 稳定 sourceRows。
3
+ *
4
+ * 约定(便于柱状图/饼图等后期扩展):
5
+ * - 有选中行时 sourceMode='selected',图表跟随勾选
6
+ * - 无选中时回退 viewRows(筛选/排序后的业务行)
7
+ * - subscribe 仅在浅层引用变化时通知,不做 deep watch
8
+ */
9
+
10
+ import { computed, watch, type ComputedRef, type Ref } from 'vue'
11
+ import type { RsTableRowData } from '../components/table-utils'
12
+
13
+ export type RsTableAnalyticsSourceMode = 'selected' | 'view'
14
+
15
+ /** 分析快照:图表 / 指标卡 / 服务端请求共用 */
16
+ export interface RsTableAnalyticsSnapshot<T extends RsTableRowData = RsTableRowData> {
17
+ viewRows: readonly T[]
18
+ selectedRows: readonly T[]
19
+ sourceRows: readonly T[]
20
+ sourceMode: RsTableAnalyticsSourceMode
21
+ selectedCount: number
22
+ viewCount: number
23
+ }
24
+
25
+ export interface UseRsTableSelectionSourceOptions<T extends RsTableRowData> {
26
+ viewRows: Ref<T[]> | ComputedRef<T[]>
27
+ selectedRows?: Ref<T[]> | ComputedRef<T[]>
28
+ }
29
+
30
+ /**
31
+ * 选中优先的分析数据源。
32
+ *
33
+ * @returns sourceRows / sourceMode / getSnapshot / subscribe
34
+ */
35
+ export function useRsTableSelectionSource<T extends RsTableRowData>(
36
+ options: UseRsTableSelectionSourceOptions<T>,
37
+ ) {
38
+ const viewRows = computed(() => options.viewRows.value ?? [])
39
+ const selectedRows = computed(() => options.selectedRows?.value ?? [])
40
+
41
+ const sourceMode = computed<RsTableAnalyticsSourceMode>(() =>
42
+ selectedRows.value.length > 0 ? 'selected' : 'view',
43
+ )
44
+
45
+ const sourceRows = computed(() =>
46
+ sourceMode.value === 'selected' ? selectedRows.value : viewRows.value,
47
+ )
48
+
49
+ function getSnapshot(): RsTableAnalyticsSnapshot<T> {
50
+ return {
51
+ viewRows: viewRows.value,
52
+ selectedRows: selectedRows.value,
53
+ sourceRows: sourceRows.value,
54
+ sourceMode: sourceMode.value,
55
+ selectedCount: selectedRows.value.length,
56
+ viewCount: viewRows.value.length,
57
+ }
58
+ }
59
+
60
+ /**
61
+ * 订阅快照变化。
62
+ * 依赖:viewRows 引用、selectedRows 引用、sourceMode。
63
+ * @returns teardown
64
+ */
65
+ function subscribe(listener: (snap: RsTableAnalyticsSnapshot<T>) => void): () => void {
66
+ const stop = watch(
67
+ () => [viewRows.value, selectedRows.value, sourceMode.value] as const,
68
+ () => {
69
+ listener(getSnapshot())
70
+ },
71
+ { flush: 'post' },
72
+ )
73
+ listener(getSnapshot())
74
+ return stop
75
+ }
76
+
77
+ return {
78
+ viewRows,
79
+ selectedRows,
80
+ sourceRows,
81
+ sourceMode,
82
+ getSnapshot,
83
+ subscribe,
84
+ }
85
+ }
86
+
87
+ export type RsTableSelectionSourceApi<T extends RsTableRowData> = ReturnType<
88
+ typeof useRsTableSelectionSource<T>
89
+ >