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,99 @@
1
+ /**
2
+ * RsTable 公开 API 契约(对标 AG Grid GridApi / TanStack table 实例)。
3
+ *
4
+ * 设计原则(大厂共性):
5
+ * - 业务与第三方只依赖本接口,不依赖 SFC 内部变量
6
+ * - 加性演进:新增方法用可选字段或 bump RS_TABLE_API_VERSION
7
+ * - 查询与命令分离:getXxx 无副作用;setXxx / commitXxx 为命令
8
+ *
9
+ * 兼容承诺:同主版本内不删除、不改语义已有方法签名。
10
+ */
11
+
12
+ import type { RsTableStagedCell } from '../../composables/useTableEdit'
13
+ import type { RsTableAnalyticsSnapshot } from '../../composables/useRsTableSelectionSource'
14
+ import type { RsTableColumn, RsTableRowData } from '../table-utils'
15
+
16
+ /** 公开 API 语义版本(与 package 版本解耦,专指 TableApi 契约) */
17
+ export const RS_TABLE_API_VERSION = '1.0.0' as const
18
+
19
+ /**
20
+ * 表格实例 API:ref / expose / provide / inject 共用同一形状。
21
+ */
22
+ export interface RsTableApi<T extends RsTableRowData = RsTableRowData> {
23
+ readonly apiVersion: typeof RS_TABLE_API_VERSION
24
+
25
+ // -- 行查询 --
26
+ getRowByKey: (rowKey: string) => T | undefined
27
+ getRowByIndex: (index: number) => T | undefined
28
+ getColumnValues: (colKey: string) => unknown[]
29
+ getCellValue: (rowKey: string, colKey: string) => unknown
30
+
31
+ // -- 视图 / 选中(分析与图表真源)--
32
+ /** 筛选/排序后的业务行(不含 group/expand 伪行) */
33
+ getViewRows: () => T[]
34
+ getSelectedRows: () => T[]
35
+ getSelectedRowKeys: () => string[]
36
+ getAnalyticsSnapshot: () => RsTableAnalyticsSnapshot<T>
37
+ subscribeAnalytics: (listener: (snap: RsTableAnalyticsSnapshot<T>) => void) => () => void
38
+
39
+ // -- 高亮 --
40
+ getHighlightedRowKey: () => string | undefined
41
+ setHighlightedRowKey: (key: string | undefined) => void
42
+
43
+ // -- 编辑 --
44
+ cancelCellEdit: () => void
45
+ cancelAllEdits: () => void
46
+ getDirtyCellKeys: () => string[]
47
+ getCellError: (rowKey: string, colKey: string) => string | undefined
48
+ setCellError: (rowKey: string, colKey: string, message: string | null) => void
49
+ stageCell: (cell: RsTableStagedCell) => void
50
+ rejectRowEdit: (rowKey: string, reason?: string) => void
51
+ undoEdit: () => void
52
+ redoEdit: () => void
53
+ commitRowEdits: (rowKey: string) => void
54
+ rollbackRowEdits: (rowKey: string) => void
55
+
56
+ // -- 扩展元数据(可选,便于遥测)--
57
+ getActiveFeatureIds?: () => string[]
58
+ /** 实例 Feature 贡献的工具条项 */
59
+ getToolbarItems?: () => import('./rs-table-feature-host').RsTableToolbarItem[]
60
+ /** 实例 Feature 贡献的 overlay 描述 */
61
+ getOverlayContributions?: () => import('./rs-table-feature-host').RsTableOverlayContribution[]
62
+ }
63
+
64
+ /** provide/inject 键:子组件或插槽内拿表格 API */
65
+ export const RS_TABLE_API_KEY: unique symbol = Symbol('rsTableApi')
66
+
67
+ /**
68
+ * 由 RsTable 内部装配公开 API(单一出口,避免 expose 与 inject 漂移)。
69
+ */
70
+ export function createRsTableApi<T extends RsTableRowData>(
71
+ impl: Omit<RsTableApi<T>, 'apiVersion'> & { apiVersion?: typeof RS_TABLE_API_VERSION },
72
+ ): RsTableApi<T> {
73
+ return {
74
+ apiVersion: impl.apiVersion ?? RS_TABLE_API_VERSION,
75
+ ...impl,
76
+ }
77
+ }
78
+
79
+ /** 类型守卫:判断未知对象是否像 RsTableApi */
80
+ export function isRsTableApi<T extends RsTableRowData = RsTableRowData>(
81
+ value: unknown,
82
+ ): value is RsTableApi<T> {
83
+ if (!value || typeof value !== 'object') return false
84
+ const api = value as Partial<RsTableApi<T>>
85
+ return (
86
+ typeof api.getViewRows === 'function' &&
87
+ typeof api.getSelectedRows === 'function' &&
88
+ typeof api.getAnalyticsSnapshot === 'function'
89
+ )
90
+ }
91
+
92
+ /** 列定义辅助:标记某列参与图表分类(文档/约定,不强制运行时) */
93
+ export type RsTableColumnChartMeta = {
94
+ chartCategory?: boolean
95
+ chartValue?: boolean
96
+ }
97
+
98
+ export type RsTableColumnWithChartMeta<T extends RsTableRowData = RsTableRowData> = RsTableColumn<T> &
99
+ RsTableColumnChartMeta
@@ -0,0 +1,71 @@
1
+ /**
2
+ * RsTable 公开契约兼容矩阵(单一真源)。
3
+ *
4
+ * - 测试锁定这些键:删除/改名 = 失败,须 bump RS_TABLE_API_VERSION
5
+ * - 文档与 CI 共用,避免矩阵与实现漂移
6
+ */
7
+
8
+ import { RS_TABLE_API_VERSION } from './rs-table-api'
9
+ import { RS_TABLE_PROP_DEFAULTS } from './rs-table-props'
10
+
11
+ /** 当前锁定的 API 契约版本(与 createRsTableApi 一致) */
12
+ export const RS_TABLE_COMPAT_API_VERSION = RS_TABLE_API_VERSION
13
+
14
+ /**
15
+ * 1.0.x 必选 Api 方法(加性可选方法不在此列)。
16
+ * 删减任一项视为 breaking。
17
+ */
18
+ export const RS_TABLE_API_REQUIRED_METHODS = [
19
+ 'getRowByKey',
20
+ 'getRowByIndex',
21
+ 'getColumnValues',
22
+ 'getCellValue',
23
+ 'getViewRows',
24
+ 'getSelectedRows',
25
+ 'getSelectedRowKeys',
26
+ 'getAnalyticsSnapshot',
27
+ 'subscribeAnalytics',
28
+ 'getHighlightedRowKey',
29
+ 'setHighlightedRowKey',
30
+ 'cancelCellEdit',
31
+ 'cancelAllEdits',
32
+ 'getDirtyCellKeys',
33
+ 'getCellError',
34
+ 'setCellError',
35
+ 'stageCell',
36
+ 'rejectRowEdit',
37
+ 'undoEdit',
38
+ 'redoEdit',
39
+ 'commitRowEdits',
40
+ 'rollbackRowEdits',
41
+ ] as const
42
+
43
+ /** 1.0.x 加性可选 Api(可缺省,但若存在须为函数) */
44
+ export const RS_TABLE_API_OPTIONAL_METHODS = [
45
+ 'getActiveFeatureIds',
46
+ 'getToolbarItems',
47
+ 'getOverlayContributions',
48
+ ] as const
49
+
50
+ /**
51
+ * 稳定 emit 名(同主版本不删)。
52
+ * 新增 emit 可追加到测试期望的超集校验。
53
+ */
54
+ export const RS_TABLE_STABLE_EMITS = [
55
+ 'rowClick',
56
+ 'rowDblclick',
57
+ 'cellView',
58
+ 'update:selectedRowKeys',
59
+ 'selectionChange',
60
+ 'loadMore',
61
+ 'cellEditCommit',
62
+ 'cellEditCancel',
63
+ 'rowEditCommit',
64
+ 'rowEditRollback',
65
+ ] as const
66
+
67
+ /** 带默认值的 props 键(与 RS_TABLE_PROP_DEFAULTS 同步校验) */
68
+ export const RS_TABLE_DEFAULTED_PROP_KEYS = Object.keys(RS_TABLE_PROP_DEFAULTS).sort()
69
+
70
+ export type RsTableApiRequiredMethod = (typeof RS_TABLE_API_REQUIRED_METHODS)[number]
71
+ export type RsTableApiOptionalMethod = (typeof RS_TABLE_API_OPTIONAL_METHODS)[number]
@@ -0,0 +1,81 @@
1
+ /**
2
+ * RsTable 系列 → ECharts option 纯映射(不依赖 echarts 包)。
3
+ *
4
+ * 业务侧:`import('echarts').then(e => e.init(el).setOption(option))`
5
+ * 内核保持库无关;本文件只产出普通 JSON 可序列化结构。
6
+ */
7
+
8
+ import type { RsTableChartSeries } from './table-chart-utils'
9
+
10
+ /** 最小 ECharts option 面(刻意放宽,避免绑定具体 echarts 类型版本) */
11
+ export type RsTableEChartsOption = {
12
+ tooltip?: { trigger?: string }
13
+ legend?: { data?: string[] }
14
+ xAxis?: { type?: string; data?: string[] } | Array<{ type?: string; data?: string[] }>
15
+ yAxis?: { type?: string } | Array<{ type?: string }>
16
+ series: Array<{
17
+ id?: string
18
+ name?: string
19
+ type: 'bar' | 'line' | 'pie'
20
+ data: Array<number | { name: string; value: number }>
21
+ }>
22
+ }
23
+
24
+ export interface MapRsTableSeriesToEChartsOptions {
25
+ /** 多系列时是否共用类目轴(默认 true;饼图忽略) */
26
+ sharedCategoryAxis?: boolean
27
+ /** tooltip.trigger,默认按首系列推断 */
28
+ tooltipTrigger?: 'item' | 'axis'
29
+ }
30
+
31
+ function mapKind(kind: RsTableChartSeries['kind']): 'bar' | 'line' | 'pie' {
32
+ if (kind === 'pie') return 'pie'
33
+ if (kind === 'line') return 'line'
34
+ return 'bar'
35
+ }
36
+
37
+ /**
38
+ * 将库无关 series 映射为可直接 setOption 的 ECharts option。
39
+ */
40
+ export function mapRsTableSeriesToEChartsOption(
41
+ seriesList: readonly RsTableChartSeries[],
42
+ options: MapRsTableSeriesToEChartsOptions = {},
43
+ ): RsTableEChartsOption {
44
+ if (!seriesList.length) {
45
+ return { series: [] }
46
+ }
47
+
48
+ const allPie = seriesList.every((s) => s.kind === 'pie')
49
+ const tooltipTrigger =
50
+ options.tooltipTrigger ?? (allPie ? 'item' : 'axis')
51
+
52
+ if (allPie) {
53
+ return {
54
+ tooltip: { trigger: tooltipTrigger },
55
+ legend: { data: seriesList.flatMap((s) => s.categories) },
56
+ series: seriesList.map((s) => ({
57
+ id: s.id,
58
+ name: s.id,
59
+ type: 'pie' as const,
60
+ data: s.points.map((p) => ({ name: p.name, value: p.value })),
61
+ })),
62
+ }
63
+ }
64
+
65
+ const shared = options.sharedCategoryAxis !== false
66
+ const primary = seriesList[0]!
67
+ return {
68
+ tooltip: { trigger: tooltipTrigger },
69
+ legend: { data: seriesList.map((s) => s.id) },
70
+ xAxis: shared
71
+ ? { type: 'category', data: primary.categories }
72
+ : seriesList.map((s) => ({ type: 'category', data: s.categories })),
73
+ yAxis: { type: 'value' },
74
+ series: seriesList.map((s) => ({
75
+ id: s.id,
76
+ name: s.id,
77
+ type: mapKind(s.kind),
78
+ data: s.values,
79
+ })),
80
+ }
81
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Feature 运行时宿主:贡献袋 + 生命周期(对标可插拔中间件总线)。
3
+ *
4
+ * 贡献点(实例级,多表互不串扰):
5
+ * - contextMenu:右键菜单
6
+ * - toolbar:表头工具条项
7
+ * - overlay:浮层/旁路 UI 标记(图表容器挂载点等)
8
+ *
9
+ * 每个 RsTable 各自 createRsTableFeatureHost();不要跨实例复用 host。
10
+ */
11
+
12
+ import type { RsContextMenuItem } from '../context-menu-utils'
13
+ import type { RsTableRowData } from '../table-utils'
14
+ import type { RsTableAnalyticsSnapshot } from '../../composables/useRsTableSelectionSource'
15
+ import type { RsTableFeature, RsTableFeatureContext } from './table-features'
16
+ import { setupTableFeatures } from './table-features'
17
+
18
+ /** 动态右键菜单贡献函数 */
19
+ export type RsTableContextMenuContributor<T extends RsTableRowData> = (
20
+ items: RsContextMenuItem[],
21
+ ctx: { row: T | null; selectedRows: T[] },
22
+ ) => void
23
+
24
+ /** 工具条贡献项 */
25
+ export interface RsTableToolbarItem {
26
+ key: string
27
+ label: string
28
+ icon?: string
29
+ disabled?: boolean
30
+ onClick?: () => void
31
+ }
32
+
33
+ export type RsTableToolbarContributor = (
34
+ items: RsTableToolbarItem[],
35
+ ) => void
36
+
37
+ /** Overlay 贡献(旁路挂载描述,不直接操作 DOM) */
38
+ export interface RsTableOverlayContribution {
39
+ key: string
40
+ /** 业务自定义渲染/挂载标识 */
41
+ slot?: string
42
+ meta?: Record<string, unknown>
43
+ }
44
+
45
+ export type RsTableOverlayContributor = (
46
+ items: RsTableOverlayContribution[],
47
+ ) => void
48
+
49
+ /**
50
+ * 单次 setup 周期内的贡献收集器(实例私有)。
51
+ */
52
+ export interface RsTableFeatureHost<T extends RsTableRowData = RsTableRowData> {
53
+ createContext: (base: {
54
+ getViewRows: () => T[]
55
+ getSelectedRows: () => T[]
56
+ getAnalyticsSnapshot: () => RsTableAnalyticsSnapshot<T>
57
+ subscribeAnalytics: (listener: (snap: RsTableAnalyticsSnapshot<T>) => void) => () => void
58
+ }) => RsTableFeatureContext<T>
59
+ setup: (features: RsTableFeature<T>[], ctx: RsTableFeatureContext<T>) => () => void
60
+ mergeContextMenuItems: (
61
+ baseItems: RsContextMenuItem[],
62
+ menuCtx: { row: T | null; selectedRows: T[] },
63
+ ) => RsContextMenuItem[]
64
+ /** 合并 toolbar 贡献 */
65
+ getToolbarItems: () => RsTableToolbarItem[]
66
+ /** 合并 overlay 贡献 */
67
+ getOverlayContributions: () => RsTableOverlayContribution[]
68
+ getActiveFeatureIds: () => string[]
69
+ }
70
+
71
+ /**
72
+ * 创建 Feature 宿主(每个 RsTable 实例一个 —— 多表请各建各的)。
73
+ */
74
+ export function createRsTableFeatureHost<T extends RsTableRowData>(): RsTableFeatureHost<T> {
75
+ let menuContributors: Array<RsTableContextMenuContributor<T>> = []
76
+ let toolbarContributors: RsTableToolbarContributor[] = []
77
+ let overlayContributors: RsTableOverlayContributor[] = []
78
+ let activeIds: string[] = []
79
+
80
+ function createContext(base: {
81
+ getViewRows: () => T[]
82
+ getSelectedRows: () => T[]
83
+ getAnalyticsSnapshot: () => RsTableAnalyticsSnapshot<T>
84
+ subscribeAnalytics: (listener: (snap: RsTableAnalyticsSnapshot<T>) => void) => () => void
85
+ }): RsTableFeatureContext<T> {
86
+ return {
87
+ getViewRows: base.getViewRows,
88
+ getSelectedRows: base.getSelectedRows,
89
+ getAnalyticsSnapshot: base.getAnalyticsSnapshot,
90
+ subscribeAnalytics: base.subscribeAnalytics,
91
+ contributeContextMenuItems(contributor) {
92
+ if (typeof contributor === 'function') {
93
+ menuContributors.push(contributor)
94
+ return
95
+ }
96
+ const staticItems = contributor
97
+ menuContributors.push((items) => {
98
+ items.push(...staticItems)
99
+ })
100
+ },
101
+ contributeToolbarItems(contributor) {
102
+ if (typeof contributor === 'function') {
103
+ toolbarContributors.push(contributor)
104
+ return
105
+ }
106
+ const staticItems = contributor
107
+ toolbarContributors.push((items) => {
108
+ items.push(...staticItems)
109
+ })
110
+ },
111
+ contributeOverlay(contributor) {
112
+ if (typeof contributor === 'function') {
113
+ overlayContributors.push(contributor)
114
+ return
115
+ }
116
+ const staticItems = contributor
117
+ overlayContributors.push((items) => {
118
+ items.push(...staticItems)
119
+ })
120
+ },
121
+ }
122
+ }
123
+
124
+ function setup(features: RsTableFeature<T>[], ctx: RsTableFeatureContext<T>): () => void {
125
+ menuContributors = []
126
+ toolbarContributors = []
127
+ overlayContributors = []
128
+ activeIds = features.map((f) => f.id)
129
+ const dispose = setupTableFeatures(features, ctx)
130
+ return () => {
131
+ dispose()
132
+ menuContributors = []
133
+ toolbarContributors = []
134
+ overlayContributors = []
135
+ activeIds = []
136
+ }
137
+ }
138
+
139
+ function mergeContextMenuItems(
140
+ baseItems: RsContextMenuItem[],
141
+ menuCtx: { row: T | null; selectedRows: T[] },
142
+ ): RsContextMenuItem[] {
143
+ const items = [...baseItems]
144
+ const before = items.length
145
+ for (const contribute of menuContributors) {
146
+ contribute(items, menuCtx)
147
+ }
148
+ if (before > 0 && items.length > before && !items[before]?.separator) {
149
+ items.splice(before, 0, { key: '__ctx-feature-sep', label: '', separator: true })
150
+ }
151
+ return items
152
+ }
153
+
154
+ function getToolbarItems(): RsTableToolbarItem[] {
155
+ const items: RsTableToolbarItem[] = []
156
+ for (const contribute of toolbarContributors) contribute(items)
157
+ return items
158
+ }
159
+
160
+ function getOverlayContributions(): RsTableOverlayContribution[] {
161
+ const items: RsTableOverlayContribution[] = []
162
+ for (const contribute of overlayContributors) contribute(items)
163
+ return items
164
+ }
165
+
166
+ function getActiveFeatureIds(): string[] {
167
+ return [...activeIds]
168
+ }
169
+
170
+ return {
171
+ createContext,
172
+ setup,
173
+ mergeContextMenuItems,
174
+ getToolbarItems,
175
+ getOverlayContributions,
176
+ getActiveFeatureIds,
177
+ }
178
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * ARIA Grid 单元格导航(矩形行列模型,不限于可编辑格)。
3
+ * 对标 Excel / AG Grid 漫游:箭头、Home/End、Ctrl+Home/End、PageUp/PageDown。
4
+ */
5
+
6
+ export type RsTableGridNavDirection =
7
+ | 'left'
8
+ | 'right'
9
+ | 'up'
10
+ | 'down'
11
+ | 'home'
12
+ | 'end'
13
+ | 'first'
14
+ | 'last'
15
+ | 'pageUp'
16
+ | 'pageDown'
17
+
18
+ export interface RsTableGridCellRef {
19
+ rowKey: string
20
+ colKey: string
21
+ rowIndex: number
22
+ }
23
+
24
+ /**
25
+ * 在「行列表 × 列 key」矩形上移动焦点。
26
+ * rows 按视觉顺序;colKeys 为可见数据列顺序。
27
+ */
28
+ export function navigateGridCell(
29
+ rows: Array<{ rowKey: string; rowIndex: number }>,
30
+ colKeys: string[],
31
+ current: RsTableGridCellRef | null,
32
+ direction: RsTableGridNavDirection,
33
+ pageSize = 10,
34
+ ): RsTableGridCellRef | null {
35
+ if (!rows.length || !colKeys.length) return null
36
+
37
+ const seed = current ?? {
38
+ rowKey: rows[0]!.rowKey,
39
+ colKey: colKeys[0]!,
40
+ rowIndex: rows[0]!.rowIndex,
41
+ }
42
+
43
+ let rowPos = rows.findIndex((r) => r.rowKey === seed.rowKey)
44
+ if (rowPos < 0) rowPos = 0
45
+ let colPos = colKeys.indexOf(seed.colKey)
46
+ if (colPos < 0) colPos = 0
47
+
48
+ const lastRow = rows.length - 1
49
+ const lastCol = colKeys.length - 1
50
+ const step = Math.max(1, pageSize)
51
+
52
+ switch (direction) {
53
+ case 'left':
54
+ colPos = Math.max(0, colPos - 1)
55
+ break
56
+ case 'right':
57
+ colPos = Math.min(lastCol, colPos + 1)
58
+ break
59
+ case 'up':
60
+ rowPos = Math.max(0, rowPos - 1)
61
+ break
62
+ case 'down':
63
+ rowPos = Math.min(lastRow, rowPos + 1)
64
+ break
65
+ case 'home':
66
+ colPos = 0
67
+ break
68
+ case 'end':
69
+ colPos = lastCol
70
+ break
71
+ case 'first':
72
+ rowPos = 0
73
+ colPos = 0
74
+ break
75
+ case 'last':
76
+ rowPos = lastRow
77
+ colPos = lastCol
78
+ break
79
+ case 'pageUp':
80
+ rowPos = Math.max(0, rowPos - step)
81
+ break
82
+ case 'pageDown':
83
+ rowPos = Math.min(lastRow, rowPos + step)
84
+ break
85
+ default:
86
+ break
87
+ }
88
+
89
+ const row = rows[rowPos]!
90
+ return {
91
+ rowKey: row.rowKey,
92
+ colKey: colKeys[colPos]!,
93
+ rowIndex: row.rowIndex,
94
+ }
95
+ }
96
+
97
+ /**
98
+ * 将 KeyboardEvent 映射为网格导航方向;无法识别则返回 null。
99
+ */
100
+ export function resolveGridNavDirection(event: KeyboardEvent): RsTableGridNavDirection | null {
101
+ const mod = event.ctrlKey || event.metaKey
102
+ switch (event.key) {
103
+ case 'ArrowLeft':
104
+ return 'left'
105
+ case 'ArrowRight':
106
+ return 'right'
107
+ case 'ArrowUp':
108
+ return 'up'
109
+ case 'ArrowDown':
110
+ return 'down'
111
+ case 'Home':
112
+ return mod ? 'first' : 'home'
113
+ case 'End':
114
+ return mod ? 'last' : 'end'
115
+ case 'PageUp':
116
+ return 'pageUp'
117
+ case 'PageDown':
118
+ return 'pageDown'
119
+ default:
120
+ return null
121
+ }
122
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * RsTableHeader 视图契约(对标列头渲染层,与 Engine 解耦)。
3
+ */
4
+
5
+ import type { CSSProperties } from 'vue'
6
+ import type { RsTableColumn, RsTableRowData } from '../table-utils'
7
+
8
+ export interface RsTableHeaderProps<T extends RsTableRowData = RsTableRowData> {
9
+ showRowDragHandle: boolean
10
+ detailExpandable: boolean
11
+ showSelectColumn: boolean
12
+ isRadioSelection: boolean
13
+ selectAllState: 'checked' | 'indeterminate' | 'unchecked'
14
+ showEditGutterColumn: boolean
15
+ showIndexColumn: boolean
16
+ showRowStatusColumn: boolean
17
+ columnPadLeft: number
18
+ columnPadRight: number
19
+ visibleDataColumns: RsTableColumn<T>[]
20
+ columnDraggable: boolean
21
+ resizable: boolean
22
+ multiSort: boolean
23
+ columnFiltersState: Record<string, string>
24
+ dragColumnKey: string | null
25
+ dropColumnKey: string | null
26
+ dragLeadHeaderStyle: CSSProperties | Record<string, string> | undefined
27
+ expandLeadHeaderStyle: CSSProperties | Record<string, string> | undefined
28
+ selectLeadHeaderStyle: CSSProperties | Record<string, string> | undefined
29
+ gutterLeadHeaderStyle: CSSProperties | Record<string, string> | undefined
30
+ indexLeadHeaderStyle: CSSProperties | Record<string, string> | undefined
31
+ columnHeaderStyleMap: Map<string, Record<string, string> | undefined>
32
+ columnFilterValue: (key: string) => string
33
+ isColumnFilterActive: (filters: Record<string, string>, key: string) => boolean
34
+ sortOrderFor: (key: string) => string | null | undefined
35
+ sortPriorityFor: (key: string) => number
36
+ sortIconName: (key: string) => string
37
+ labels: {
38
+ selectAll: string
39
+ index: string
40
+ rowStatus: string
41
+ dragColumn: string
42
+ filterColumn: string
43
+ filterPlaceholder: string
44
+ filterClear: string
45
+ filterApply: string
46
+ }
47
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * RsTable 模块注册表(对标 AG Grid ModuleRegistry)。
3
+ *
4
+ * - 全局 register:所有表格实例默认挂载(同页多表会共享这些模块)
5
+ * - 实例 props.features:仅该实例;同 id 覆盖全局
6
+ * - 不改内核渲染;模块通过 Feature.setup 订阅/贡献
7
+ * - 多表隔离要点:选中/编辑/ViewContext 仍是实例私有;仅 Registry 是全局点
8
+ */
9
+
10
+ import type { RsTableFeature } from './table-features'
11
+ import type { RsTableRowData } from '../table-utils'
12
+
13
+ const globalModules: RsTableFeature<RsTableRowData>[] = []
14
+
15
+ /**
16
+ * 全局模块注册表。
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * RsTableModuleRegistry.register(
21
+ * createAnalyticsTableFeature({ onSnapshot: (s) => syncCharts(s) }),
22
+ * )
23
+ * ```
24
+ */
25
+ export const RsTableModuleRegistry = {
26
+ /**
27
+ * 注册全局 feature(幂等:同 id 覆盖)。
28
+ */
29
+ register<T extends RsTableRowData>(feature: RsTableFeature<T>): void {
30
+ const id = feature.id
31
+ const idx = globalModules.findIndex((item) => item.id === id)
32
+ const next = feature as RsTableFeature<RsTableRowData>
33
+ if (idx >= 0) globalModules[idx] = next
34
+ else globalModules.push(next)
35
+ },
36
+
37
+ /** 按 id 注销全局模块 */
38
+ unregister(id: string): void {
39
+ const idx = globalModules.findIndex((item) => item.id === id)
40
+ if (idx >= 0) globalModules.splice(idx, 1)
41
+ },
42
+
43
+ /** 清空(测试用) */
44
+ clear(): void {
45
+ globalModules.length = 0
46
+ },
47
+
48
+ /** 当前全局模块快照(浅拷贝) */
49
+ getModules<T extends RsTableRowData = RsTableRowData>(): RsTableFeature<T>[] {
50
+ return [...globalModules] as RsTableFeature<T>[]
51
+ },
52
+
53
+ listIds(): string[] {
54
+ return globalModules.map((item) => item.id)
55
+ },
56
+ }
57
+
58
+ /**
59
+ * 合并全局模块 + 实例 features(实例同 id 覆盖全局)。
60
+ */
61
+ export function resolveInstanceFeatures<T extends RsTableRowData>(
62
+ instanceFeatures?: RsTableFeature<T>[] | null,
63
+ ): RsTableFeature<T>[] {
64
+ const map = new Map<string, RsTableFeature<T>>()
65
+ for (const feature of RsTableModuleRegistry.getModules<T>()) {
66
+ map.set(feature.id, feature)
67
+ }
68
+ for (const feature of instanceFeatures ?? []) {
69
+ map.set(feature.id, feature)
70
+ }
71
+ return [...map.values()]
72
+ }