niuma-ui 1.0.0

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 (220) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +25 -0
  4. package/README.en.md +99 -0
  5. package/README.md +161 -0
  6. package/package.json +106 -0
  7. package/src/__tests__/RsBadge.spec.ts +40 -0
  8. package/src/__tests__/RsBreadcrumb.spec.ts +172 -0
  9. package/src/__tests__/RsButton.spec.ts +108 -0
  10. package/src/__tests__/RsCalendarGrid.spec.ts +82 -0
  11. package/src/__tests__/RsCard.spec.ts +97 -0
  12. package/src/__tests__/RsCheckbox.spec.ts +36 -0
  13. package/src/__tests__/RsCodeEditor.spec.ts +111 -0
  14. package/src/__tests__/RsConfirmDialog.spec.ts +158 -0
  15. package/src/__tests__/RsContainer.spec.ts +89 -0
  16. package/src/__tests__/RsContextMenu.spec.ts +118 -0
  17. package/src/__tests__/RsDatePicker.spec.ts +88 -0
  18. package/src/__tests__/RsDateTimePicker.spec.ts +69 -0
  19. package/src/__tests__/RsDialog.spec.ts +404 -0
  20. package/src/__tests__/RsDrawer.spec.ts +145 -0
  21. package/src/__tests__/RsDropdown.spec.ts +135 -0
  22. package/src/__tests__/RsEmpty.spec.ts +99 -0
  23. package/src/__tests__/RsForm.spec.ts +173 -0
  24. package/src/__tests__/RsIcon.spec.ts +244 -0
  25. package/src/__tests__/RsInput.spec.ts +269 -0
  26. package/src/__tests__/RsInputNumber.spec.ts +118 -0
  27. package/src/__tests__/RsLabel.spec.ts +106 -0
  28. package/src/__tests__/RsLink.spec.ts +49 -0
  29. package/src/__tests__/RsLoading.spec.ts +84 -0
  30. package/src/__tests__/RsMarkdown.spec.ts +143 -0
  31. package/src/__tests__/RsMenu.spec.ts +119 -0
  32. package/src/__tests__/RsNewComponents.spec.ts +17 -0
  33. package/src/__tests__/RsPagination.spec.ts +158 -0
  34. package/src/__tests__/RsPopover.spec.ts +126 -0
  35. package/src/__tests__/RsScrollbar.spec.ts +53 -0
  36. package/src/__tests__/RsSelect.spec.ts +291 -0
  37. package/src/__tests__/RsSidebar.spec.ts +119 -0
  38. package/src/__tests__/RsSplitPane.spec.ts +177 -0
  39. package/src/__tests__/RsStatCard.spec.ts +79 -0
  40. package/src/__tests__/RsSteps.spec.ts +125 -0
  41. package/src/__tests__/RsTable.perf.spec.ts +103 -0
  42. package/src/__tests__/RsTable.spec.ts +1135 -0
  43. package/src/__tests__/RsTabs.spec.ts +328 -0
  44. package/src/__tests__/RsTerminal.spec.ts +218 -0
  45. package/src/__tests__/RsTimePicker.spec.ts +77 -0
  46. package/src/__tests__/RsToaster.spec.ts +95 -0
  47. package/src/__tests__/RsToolbar.spec.ts +75 -0
  48. package/src/__tests__/RsTooltip.spec.ts +111 -0
  49. package/src/__tests__/RsTree.spec.ts +572 -0
  50. package/src/__tests__/RsUpload.spec.ts +160 -0
  51. package/src/__tests__/RsVirtualList.spec.ts +77 -0
  52. package/src/__tests__/date-picker-utils.spec.ts +70 -0
  53. package/src/__tests__/dialog-viewport.spec.ts +38 -0
  54. package/src/__tests__/iso-local-datetime.spec.ts +58 -0
  55. package/src/__tests__/rs-clipboard.spec.ts +142 -0
  56. package/src/__tests__/setup.ts +12 -0
  57. package/src/__tests__/split-pane-utils.spec.ts +136 -0
  58. package/src/__tests__/table-drag.spec.ts +35 -0
  59. package/src/__tests__/table-edit-utils.spec.ts +194 -0
  60. package/src/__tests__/table-utils.spec.ts +286 -0
  61. package/src/__tests__/theme.spec.ts +22 -0
  62. package/src/__tests__/tree-utils.spec.ts +234 -0
  63. package/src/__tests__/useTableCellTooltip.spec.ts +101 -0
  64. package/src/components/RsAvatar.vue +115 -0
  65. package/src/components/RsBadge.vue +59 -0
  66. package/src/components/RsBreadcrumb.vue +65 -0
  67. package/src/components/RsBreadcrumbItems.vue +36 -0
  68. package/src/components/RsButton.vue +393 -0
  69. package/src/components/RsCalendarGrid.vue +284 -0
  70. package/src/components/RsCard.vue +237 -0
  71. package/src/components/RsCheckbox.vue +189 -0
  72. package/src/components/RsCodeBlock.vue +278 -0
  73. package/src/components/RsCodeEditor.vue +597 -0
  74. package/src/components/RsConfigProvider.vue +92 -0
  75. package/src/components/RsConfirmDialog.vue +178 -0
  76. package/src/components/RsContainer.vue +235 -0
  77. package/src/components/RsContextMenu.vue +238 -0
  78. package/src/components/RsContextMenuItems.vue +77 -0
  79. package/src/components/RsDatePicker.vue +756 -0
  80. package/src/components/RsDateTimePicker.vue +42 -0
  81. package/src/components/RsDialog.vue +486 -0
  82. package/src/components/RsDrawer.vue +175 -0
  83. package/src/components/RsDropdown.vue +252 -0
  84. package/src/components/RsDropdownItems.vue +90 -0
  85. package/src/components/RsEmpty.vue +107 -0
  86. package/src/components/RsForm.vue +131 -0
  87. package/src/components/RsIcon.vue +104 -0
  88. package/src/components/RsInput.vue +1101 -0
  89. package/src/components/RsInputNumber.vue +532 -0
  90. package/src/components/RsLabel.vue +54 -0
  91. package/src/components/RsLink.vue +106 -0
  92. package/src/components/RsLoading.vue +171 -0
  93. package/src/components/RsMarkdown.vue +329 -0
  94. package/src/components/RsMenu.vue +256 -0
  95. package/src/components/RsMenuItems.vue +125 -0
  96. package/src/components/RsMonacoEditor.vue +541 -0
  97. package/src/components/RsPagination.vue +184 -0
  98. package/src/components/RsPopover.vue +77 -0
  99. package/src/components/RsProseEditor.vue +160 -0
  100. package/src/components/RsScrollbar.vue +171 -0
  101. package/src/components/RsSelect.vue +1045 -0
  102. package/src/components/RsSidebar.vue +121 -0
  103. package/src/components/RsSidebarGroup.vue +32 -0
  104. package/src/components/RsSidebarItem.vue +72 -0
  105. package/src/components/RsSplitPane.vue +619 -0
  106. package/src/components/RsStatCard.vue +85 -0
  107. package/src/components/RsSteps.vue +138 -0
  108. package/src/components/RsTable.vue +2664 -0
  109. package/src/components/RsTableColgroup.vue +17 -0
  110. package/src/components/RsTableHeaderRow.vue +30 -0
  111. package/src/components/RsTabs.vue +951 -0
  112. package/src/components/RsTerminal.vue +697 -0
  113. package/src/components/RsTimePicker.vue +554 -0
  114. package/src/components/RsTimePickerColumns.vue +226 -0
  115. package/src/components/RsToaster.vue +396 -0
  116. package/src/components/RsToolbar.vue +135 -0
  117. package/src/components/RsTooltip.vue +142 -0
  118. package/src/components/RsTooltipProvider.vue +20 -0
  119. package/src/components/RsTree.vue +1248 -0
  120. package/src/components/RsUpload.vue +279 -0
  121. package/src/components/RsVirtualList.vue +217 -0
  122. package/src/components/avatar-utils.ts +19 -0
  123. package/src/components/breadcrumb-utils.ts +19 -0
  124. package/src/components/class-utils.ts +29 -0
  125. package/src/components/code-editor-utils.ts +106 -0
  126. package/src/components/code-mirror-completion.ts +40 -0
  127. package/src/components/code-mirror-diagnostics.ts +52 -0
  128. package/src/components/code-mirror-ghost.ts +158 -0
  129. package/src/components/code-mirror-inline-edit.ts +55 -0
  130. package/src/components/code-mirror-intel.ts +81 -0
  131. package/src/components/code-mirror-lang.ts +128 -0
  132. package/src/components/context-menu-utils.ts +20 -0
  133. package/src/components/date-picker-utils.ts +284 -0
  134. package/src/components/date-range-validation.ts +29 -0
  135. package/src/components/date-validation.ts +38 -0
  136. package/src/components/dialog-viewport.ts +48 -0
  137. package/src/components/dialog-window.ts +253 -0
  138. package/src/components/dropdown-utils.ts +31 -0
  139. package/src/components/form-utils.ts +77 -0
  140. package/src/components/input-number-utils.ts +128 -0
  141. package/src/components/input-rules.ts +84 -0
  142. package/src/components/loading-utils.ts +8 -0
  143. package/src/components/markdown-utils.ts +126 -0
  144. package/src/components/menu-utils.ts +48 -0
  145. package/src/components/overlay-utils.ts +47 -0
  146. package/src/components/pagination-utils.ts +38 -0
  147. package/src/components/popover-utils.ts +8 -0
  148. package/src/components/reka.ts +106 -0
  149. package/src/components/resolve-radius.ts +22 -0
  150. package/src/components/resolve-size.ts +29 -0
  151. package/src/components/scrollbar-utils.ts +10 -0
  152. package/src/components/select-utils.ts +72 -0
  153. package/src/components/split-pane-utils.ts +187 -0
  154. package/src/components/steps-utils.ts +22 -0
  155. package/src/components/table/RsTableBodyRow.vue +287 -0
  156. package/src/components/table/RsTableCell.vue +239 -0
  157. package/src/components/table/RsTableCellContent.ts +18 -0
  158. package/src/components/table/RsTableCellEditor.vue +351 -0
  159. package/src/components/table/RsTableEditGutter.vue +54 -0
  160. package/src/components/table/RsTableHeaderFilter.vue +107 -0
  161. package/src/components/table/RsTableRowStatus.vue +69 -0
  162. package/src/components/table/rs-table.css +1141 -0
  163. package/src/components/table/table-context-menu.ts +53 -0
  164. package/src/components/table/table-edit-utils.ts +496 -0
  165. package/src/components/table/table-keys.ts +12 -0
  166. package/src/components/table-drag.ts +215 -0
  167. package/src/components/table-utils.ts +950 -0
  168. package/src/components/tabs-utils.ts +107 -0
  169. package/src/components/terminal-utils.ts +301 -0
  170. package/src/components/terminal-wheel.ts +49 -0
  171. package/src/components/time-picker-utils.ts +167 -0
  172. package/src/components/time-range-validation.ts +23 -0
  173. package/src/components/time-validation.ts +20 -0
  174. package/src/components/tree-utils.ts +518 -0
  175. package/src/components/upload-utils.ts +55 -0
  176. package/src/components/virtual-list-utils.ts +17 -0
  177. package/src/composables/useClickOutside.ts +22 -0
  178. package/src/composables/useRsConfig.ts +91 -0
  179. package/src/composables/useRsI18n.ts +22 -0
  180. package/src/composables/useRsTabsNav.ts +187 -0
  181. package/src/composables/useRsToast.ts +48 -0
  182. package/src/composables/useTableCellTooltip.ts +239 -0
  183. package/src/composables/useTableEdit.ts +271 -0
  184. package/src/composables/useTableRowHighlight.ts +50 -0
  185. package/src/dev/vite-codemirror-deps.ts +20 -0
  186. package/src/icons/custom/api.ts +54 -0
  187. package/src/icons/custom/clickhouse.ts +52 -0
  188. package/src/icons/custom/dameng.ts +83 -0
  189. package/src/icons/custom/ftp.ts +61 -0
  190. package/src/icons/custom/kingbase.ts +52 -0
  191. package/src/icons/custom/mongodb.ts +50 -0
  192. package/src/icons/custom/mysql.ts +51 -0
  193. package/src/icons/custom/oracle.ts +52 -0
  194. package/src/icons/custom/redis.ts +51 -0
  195. package/src/icons/custom/sqlite.ts +50 -0
  196. package/src/icons/custom/sqlserver.ts +52 -0
  197. package/src/icons/custom/vastbase.ts +57 -0
  198. package/src/icons/lucide.ts +78 -0
  199. package/src/icons/registry.ts +35 -0
  200. package/src/index.ts +425 -0
  201. package/src/lib/iso-local-datetime.ts +152 -0
  202. package/src/lib/rs-dayjs.ts +56 -0
  203. package/src/locale/messages.ts +362 -0
  204. package/src/locale/types.ts +5 -0
  205. package/src/monaco/debug-decorations.css +20 -0
  206. package/src/monaco/debug-decorations.ts +70 -0
  207. package/src/monaco/index.ts +22 -0
  208. package/src/monaco/languages/index.ts +21 -0
  209. package/src/monaco/languages/mongodb-shell.ts +125 -0
  210. package/src/monaco/languages/types.ts +55 -0
  211. package/src/monaco/setup.ts +101 -0
  212. package/src/styles.css +1040 -0
  213. package/src/theme/apply.ts +12 -0
  214. package/src/theme/brand.example.css +37 -0
  215. package/src/theme/presets.ts +69 -0
  216. package/src/theme/types.ts +81 -0
  217. package/src/utils/rs-clipboard.ts +272 -0
  218. package/src/vite-env.d.ts +7 -0
  219. package/vite-plugins/monaco-zh-nls.ts +101 -0
  220. package/vite-plugins/silence-antlr-parse-console.ts +29 -0
@@ -0,0 +1,29 @@
1
+ import { computed, type ComputedRef } from 'vue'
2
+ import type { RsComponentSize } from '../theme/types'
3
+ import { useRsConfigOptional } from '../composables/useRsConfig'
4
+ import { useRsFormContext, type RsFormContext } from './form-utils'
5
+
6
+ /**
7
+ * 解析控件尺寸:props → Form → ConfigProvider → md。
8
+ * 业务应传 `size` 或依赖全局/表单上下文,避免 :deep 改高度。
9
+ */
10
+ export function resolveRsComponentSize(
11
+ size: RsComponentSize | undefined,
12
+ form?: RsFormContext | null,
13
+ ): ComputedRef<RsComponentSize> {
14
+ const config = useRsConfigOptional()
15
+ return computed(
16
+ () => size ?? form?.size.value ?? config?.controlSize.value ?? 'md',
17
+ )
18
+ }
19
+
20
+ /** 在 setup 内便捷解析(自动读 Form / Config)。 */
21
+ export function useResolvedRsComponentSize(
22
+ size: () => RsComponentSize | undefined,
23
+ ): ComputedRef<RsComponentSize> {
24
+ const form = useRsFormContext()
25
+ const config = useRsConfigOptional()
26
+ return computed(
27
+ () => size() ?? form?.size.value ?? config?.controlSize.value ?? 'md',
28
+ )
29
+ }
@@ -0,0 +1,10 @@
1
+ export type RsScrollbarType = 'auto' | 'always' | 'scroll' | 'hover'
2
+
3
+ export type RsScrollbarOrientation = 'vertical' | 'horizontal' | 'both'
4
+
5
+ export function resolveScrollbarSize(size: number | string | undefined): string | undefined {
6
+ if (size === undefined) {
7
+ return undefined
8
+ }
9
+ return typeof size === 'number' ? `${size}px` : size
10
+ }
@@ -0,0 +1,72 @@
1
+ export interface RsSelectOption {
2
+ label: string
3
+ value: string
4
+ disabled?: boolean
5
+ }
6
+
7
+ export interface RsSelectOptionGroup {
8
+ label: string
9
+ options: RsSelectOption[]
10
+ }
11
+
12
+ export type RsSelectOptions = ReadonlyArray<RsSelectOption | RsSelectOptionGroup>
13
+
14
+ export function isSelectOptionGroup(
15
+ item: RsSelectOption | RsSelectOptionGroup,
16
+ ): item is RsSelectOptionGroup {
17
+ return 'options' in item && Array.isArray(item.options)
18
+ }
19
+
20
+ export function flattenSelectOptions(options: RsSelectOptions): RsSelectOption[] {
21
+ const result: RsSelectOption[] = []
22
+ for (const item of options) {
23
+ if (isSelectOptionGroup(item)) {
24
+ result.push(...item.options)
25
+ } else {
26
+ result.push(item)
27
+ }
28
+ }
29
+ return result
30
+ }
31
+
32
+ export function buildOptionLabelMap(options: RsSelectOptions): Map<string, string> {
33
+ const map = new Map<string, string>()
34
+ for (const opt of flattenSelectOptions(options)) {
35
+ map.set(opt.value, opt.label)
36
+ }
37
+ return map
38
+ }
39
+
40
+ export function buildOptionDisabledMap(options: RsSelectOptions): Map<string, boolean> {
41
+ const map = new Map<string, boolean>()
42
+ for (const opt of flattenSelectOptions(options)) {
43
+ map.set(opt.value, Boolean(opt.disabled))
44
+ }
45
+ return map
46
+ }
47
+
48
+ export function filterSelectOptions(
49
+ options: RsSelectOptions,
50
+ query: string,
51
+ contains: (text: string, search: string) => boolean,
52
+ ): RsSelectOptions {
53
+ const trimmed = query.trim()
54
+ if (!trimmed) return options
55
+
56
+ const result: Array<RsSelectOption | RsSelectOptionGroup> = []
57
+ for (const item of options) {
58
+ if (isSelectOptionGroup(item)) {
59
+ const filtered = item.options.filter((opt) => contains(opt.label, trimmed))
60
+ if (filtered.length > 0) {
61
+ result.push({ label: item.label, options: filtered })
62
+ }
63
+ } else if (contains(item.label, trimmed)) {
64
+ result.push(item)
65
+ }
66
+ }
67
+ return result as RsSelectOptions
68
+ }
69
+
70
+ export function flattenSelectValues(options: RsSelectOptions): string[] {
71
+ return flattenSelectOptions(options).map((opt) => opt.value)
72
+ }
@@ -0,0 +1,187 @@
1
+ /** 面板分割方向:horizontal 左右排布(拖动横向)· vertical 上下排布(拖动纵向) */
2
+ export type RsSplitOrientation = 'horizontal' | 'vertical'
3
+
4
+ /** 单个面板定义;尺寸单位统一为「占比百分比」(0~100) */
5
+ export interface RsSplitPaneItem {
6
+ /** 唯一标识,用于插槽名与 v-model:sizes 的顺序映射 */
7
+ key: string
8
+ /** 初始尺寸(百分比);缺省时在剩余空间内均分 */
9
+ size?: number
10
+ /** 最小尺寸(百分比),默认 0 */
11
+ min?: number
12
+ /** 最大尺寸(百分比),默认 100 */
13
+ max?: number
14
+ /** 是否可折叠:拖动越过阈值时吸附到 collapsedSize */
15
+ collapsible?: boolean
16
+ /** 折叠后的尺寸(百分比),默认 0;会被限制在 [0, min] */
17
+ collapsedSize?: number
18
+ /** 本面板右侧分隔条是否显示抓手;缺省时继承 RsSplitPane.withHandle */
19
+ resizerHandle?: boolean
20
+ }
21
+
22
+ /** 由 RsSplitPaneItem 解析出的规范化约束 */
23
+ export interface RsSplitConstraint {
24
+ min: number
25
+ max: number
26
+ collapsible: boolean
27
+ collapsedSize: number
28
+ }
29
+
30
+ const clamp = (value: number, lower: number, upper: number): number =>
31
+ Math.min(Math.max(value, lower), upper)
32
+
33
+ /** 保留有限位小数,消除浮点毛刺 */
34
+ export function roundSize(value: number, precision = 4): number {
35
+ const factor = 10 ** precision
36
+ return Math.round(value * factor) / factor
37
+ }
38
+
39
+ /** 将面板定义解析为规范化约束,保证 0 ≤ min ≤ max ≤ 100 且 collapsedSize ≤ min */
40
+ export function resolveSplitConstraints(panes: RsSplitPaneItem[]): RsSplitConstraint[] {
41
+ return panes.map((pane) => {
42
+ const rawMin = clamp(pane.min ?? 0, 0, 100)
43
+ const rawMax = clamp(pane.max ?? 100, 0, 100)
44
+ const min = Math.min(rawMin, rawMax)
45
+ const max = Math.max(rawMin, rawMax)
46
+ const collapsedSize = clamp(pane.collapsedSize ?? 0, 0, min)
47
+ return {
48
+ min,
49
+ max,
50
+ collapsible: pane.collapsible ?? false,
51
+ collapsedSize,
52
+ }
53
+ })
54
+ }
55
+
56
+ /** 按比例把一组尺寸缩放到总和为 100 */
57
+ function rescaleTo100(sizes: number[]): number[] {
58
+ if (!sizes.length) return []
59
+ const sum = sizes.reduce((acc, value) => acc + Math.max(0, value), 0)
60
+ if (sum <= 0) {
61
+ const each = 100 / sizes.length
62
+ return sizes.map(() => each)
63
+ }
64
+ return sizes.map((value) => (Math.max(0, value) / sum) * 100)
65
+ }
66
+
67
+ /**
68
+ * 计算初始尺寸数组(总和 100)。
69
+ * 优先使用 provided(受控值),其次面板自身的 size,未指定的项在剩余空间内均分。
70
+ */
71
+ export function normalizeSplitSizes(
72
+ panes: RsSplitPaneItem[],
73
+ provided?: number[] | null,
74
+ ): number[] {
75
+ const count = panes.length
76
+ if (count === 0) return []
77
+
78
+ const seed: (number | null)[] = panes.map((pane, index) => {
79
+ const candidate = provided?.[index] ?? pane.size
80
+ return typeof candidate === 'number' && Number.isFinite(candidate)
81
+ ? Math.max(0, candidate)
82
+ : null
83
+ })
84
+
85
+ const missing = seed.filter((value) => value === null).length
86
+ const knownSum = seed.reduce<number>((acc, value) => acc + (value ?? 0), 0)
87
+ const each = missing > 0 ? Math.max(0, 100 - knownSum) / missing : 0
88
+ const filled = seed.map((value) => value ?? each)
89
+ return rescaleTo100(filled).map((value) => roundSize(value))
90
+ }
91
+
92
+ /** 判断某面板当前是否处于折叠态 */
93
+ export function isSplitPaneCollapsed(size: number, constraint: RsSplitConstraint): boolean {
94
+ if (!constraint.collapsible) return false
95
+ return size <= constraint.collapsedSize + 0.01
96
+ }
97
+
98
+ /**
99
+ * 在第 index 个分隔条(介于 pane[index] 与 pane[index+1] 之间)上拖动 deltaPercent。
100
+ * 仅调整相邻两个面板,遵守各自 min/max,并在可折叠时吸附到 collapsedSize。
101
+ * 返回新的尺寸数组(引用不同,总和保持不变)。
102
+ */
103
+ export function applySplitResize(
104
+ sizes: number[],
105
+ constraints: RsSplitConstraint[],
106
+ index: number,
107
+ deltaPercent: number,
108
+ ): number[] {
109
+ if (index < 0 || index >= sizes.length - 1) return sizes
110
+ const a = sizes[index]
111
+ const b = sizes[index + 1]
112
+ const ca = constraints[index]
113
+ const cb = constraints[index + 1]
114
+ if (a === undefined || b === undefined || !ca || !cb) return sizes
115
+
116
+ const total = a + b
117
+ let nextA = a + deltaPercent
118
+
119
+ // 收缩 A:越过阈值吸附折叠
120
+ if (ca.collapsible && deltaPercent < 0 && nextA < ca.min) {
121
+ nextA = nextA <= (ca.min + ca.collapsedSize) / 2 ? ca.collapsedSize : ca.min
122
+ }
123
+
124
+ // 收缩 B(即放大 A):越过阈值吸附折叠 B
125
+ if (cb.collapsible && deltaPercent > 0) {
126
+ const nextB = total - nextA
127
+ if (nextB < cb.min) {
128
+ const snappedB = nextB <= (cb.min + cb.collapsedSize) / 2 ? cb.collapsedSize : cb.min
129
+ nextA = total - snappedB
130
+ }
131
+ }
132
+
133
+ // 硬约束:A 的可行区间需同时满足 A 与 B 的 min/max
134
+ const lowerA = Math.max(ca.collapsible ? ca.collapsedSize : ca.min, total - cb.max)
135
+ const upperA = Math.min(ca.max, total - (cb.collapsible ? cb.collapsedSize : cb.min))
136
+ nextA = clamp(nextA, Math.min(lowerA, upperA), Math.max(lowerA, upperA))
137
+
138
+ const next = sizes.slice()
139
+ next[index] = roundSize(nextA)
140
+ next[index + 1] = roundSize(total - nextA)
141
+ return next
142
+ }
143
+
144
+ /** 把 pane[index] 设为 target,差额从相邻面板(优先右侧)补偿 */
145
+ function redistribute(sizes: number[], index: number, target: number): number[] {
146
+ const current = sizes[index]
147
+ if (current === undefined) return sizes
148
+ const neighbor = index + 1 < sizes.length ? index + 1 : index - 1
149
+ if (neighbor < 0) return sizes
150
+ const next = sizes.slice()
151
+ const delta = target - current
152
+ next[index] = roundSize(target)
153
+ next[neighbor] = roundSize(Math.max(0, (next[neighbor] ?? 0) - delta))
154
+ return next
155
+ }
156
+
157
+ /** 折叠 pane[index](可折叠→collapsedSize,否则→min) */
158
+ export function collapseSplitPane(
159
+ sizes: number[],
160
+ constraints: RsSplitConstraint[],
161
+ index: number,
162
+ ): number[] {
163
+ const constraint = constraints[index]
164
+ if (!constraint || sizes[index] === undefined) return sizes
165
+ const target = constraint.collapsible ? constraint.collapsedSize : constraint.min
166
+ return redistribute(sizes, index, target)
167
+ }
168
+
169
+ /** 展开 pane[index](toSize 缺省时回到 min 或均分值),并夹紧到 [min, max] */
170
+ export function expandSplitPane(
171
+ sizes: number[],
172
+ constraints: RsSplitConstraint[],
173
+ index: number,
174
+ toSize?: number,
175
+ ): number[] {
176
+ const constraint = constraints[index]
177
+ if (!constraint || sizes[index] === undefined) return sizes
178
+ const fallback = constraint.min > 0 ? constraint.min : 100 / Math.max(1, sizes.length)
179
+ const target = clamp(toSize ?? fallback, constraint.min, constraint.max)
180
+ return redistribute(sizes, index, target)
181
+ }
182
+
183
+ /** 两组尺寸是否近似相等(用于受控回写时避免抖动) */
184
+ export function splitSizesEqual(a: number[], b: number[], epsilon = 0.01): boolean {
185
+ if (a.length !== b.length) return false
186
+ return a.every((value, index) => Math.abs(value - (b[index] ?? 0)) <= epsilon)
187
+ }
@@ -0,0 +1,22 @@
1
+ export type RsStepStatus = 'wait' | 'process' | 'finish' | 'error'
2
+ export type RsStepsOrientation = 'horizontal' | 'vertical'
3
+ export type RsStepsSize = 'sm' | 'md'
4
+
5
+ export interface RsStepItem {
6
+ value: string
7
+ title: string
8
+ description?: string
9
+ status?: RsStepStatus
10
+ disabled?: boolean
11
+ }
12
+
13
+ export function resolveStepStatus(index: number, activeIndex: number, explicit?: RsStepStatus): RsStepStatus {
14
+ if (explicit) return explicit
15
+ if (index < activeIndex) return 'finish'
16
+ if (index === activeIndex) return 'process'
17
+ return 'wait'
18
+ }
19
+
20
+ export function isStepSeparatorCompleted(status: RsStepStatus): boolean {
21
+ return status === 'finish'
22
+ }
@@ -0,0 +1,287 @@
1
+ <script setup lang="ts" generic="T extends import('../table-utils').RsTableRowData">
2
+ import type { RsTableColumn, RsTableRowDropPosition, RsTableSelectionType } from '../table-utils'
3
+ import type { RsTableCellEditTrigger } from './table-edit-utils'
4
+ import RsTableCell from './RsTableCell.vue'
5
+ import RsTableEditGutter from './RsTableEditGutter.vue'
6
+ import RsTableRowStatus from './RsTableRowStatus.vue'
7
+
8
+ defineProps<{
9
+ row: T
10
+ rowIndex: number
11
+ rowKey: string
12
+ columns: RsTableColumn<T>[]
13
+ /** 列虚拟化左侧占位宽(px) */
14
+ columnPadLeft?: number
15
+ /** 列虚拟化右侧占位宽(px) */
16
+ columnPadRight?: number
17
+ columnTdClassMap: Map<string, string[]>
18
+ columnStyleMap: Map<string, Record<string, string> | undefined>
19
+ showRowDragHandle: boolean
20
+ expandable: boolean
21
+ selectable: boolean
22
+ showIndex: boolean
23
+ showEditGutter: boolean
24
+ striped: boolean
25
+ selectionType: RsTableSelectionType
26
+ isRadioSelection: boolean
27
+ tableEditable: boolean
28
+ editTrigger: RsTableCellEditTrigger
29
+ rowCommit: boolean
30
+ allowNull: boolean
31
+ focusMode: import('./table-edit-utils').RsTableCellEditFocusMode
32
+ nullLabel: string
33
+ showRowStatus: boolean
34
+ selected: boolean
35
+ highlighted: boolean
36
+ expanded: boolean
37
+ disabled: boolean
38
+ rowDragByRow: boolean
39
+ dragging: boolean
40
+ dropTarget: boolean
41
+ dropPosition: RsTableRowDropPosition | null
42
+ dragLeadStyle: Record<string, string>
43
+ expandLeadStyle: Record<string, string>
44
+ selectLeadStyle: Record<string, string>
45
+ indexLeadStyle: Record<string, string>
46
+ gutterLeadStyle: Record<string, string>
47
+ canExpand: boolean
48
+ canSelect: boolean
49
+ rowEditPending: boolean
50
+ rowDirty: boolean
51
+ /** gutter 待提交时是否显示撤销按钮 */
52
+ showGutterRollback?: boolean
53
+ editingColKey: string | null
54
+ focusColKey: string | null
55
+ hasColumnSlot: (key: string) => boolean
56
+ hasEditSlot: (key: string) => boolean
57
+ cellTooltipEnabled: (column: RsTableColumn<T>, rowIndex: number) => boolean
58
+ cellTooltipMode: (column: RsTableColumn<T>, rowIndex: number) => string | undefined
59
+ cellTooltipText: (column: RsTableColumn<T>, row: T, rowIndex: number) => string | undefined
60
+ cellTooltipFallbackTitle: (column: RsTableColumn<T>, row: T, rowIndex: number) => string | undefined
61
+ getCellDraft: (rowKey: string, colKey: string) => string | undefined
62
+ isCellDirty: (rowKey: string, colKey: string) => boolean
63
+ getCellError: (rowKey: string, colKey: string) => string | undefined
64
+ isCellValidating: (rowKey: string, colKey: string) => boolean
65
+ dragRowLabel: string
66
+ expandRowLabel: string
67
+ collapseRowLabel: string
68
+ selectRowLabel: string
69
+ rowCommitLabel: string
70
+ rowRollbackLabel: string
71
+ gutterCommitHint: string
72
+ }>()
73
+
74
+ const emit = defineEmits<{
75
+ click: [event: MouseEvent]
76
+ cellClick: [colKey: string, event: MouseEvent]
77
+ cellContextmenu: [colKey: string, event: MouseEvent]
78
+ /** 数据单元格双击(.stop 后由父级决定进入编辑或转发 rowDblclick) */
79
+ cellDblclick: [colKey: string, event: MouseEvent]
80
+ dblclick: [event: MouseEvent]
81
+ contextmenu: [event: MouseEvent]
82
+ dragover: [event: DragEvent]
83
+ dragleave: [event: DragEvent]
84
+ drop: [event: DragEvent]
85
+ rowDragStart: [event: DragEvent]
86
+ rowDragEnd: []
87
+ toggleExpand: []
88
+ toggleSelect: []
89
+ cellStartEdit: [colKey: string]
90
+ cellCommit: [colKey: string, value: string]
91
+ cellCancel: [colKey: string]
92
+ cellUpdateDraft: [colKey: string, value: string]
93
+ cellNavigate: [colKey: string, direction: import('./table-edit-utils').RsTableCellNavigateDirection]
94
+ rowCommit: []
95
+ rowRollback: []
96
+ gutterCommit: []
97
+ }>()
98
+ </script>
99
+
100
+ <template>
101
+ <tr
102
+ class="rs-table__row"
103
+ :class="{
104
+ 'rs-table__row--selected': selected,
105
+ 'rs-table__row--highlighted': highlighted && !selected,
106
+ 'rs-table__row--striped': striped && rowIndex % 2 === 1,
107
+ 'rs-table__row--disabled': disabled,
108
+ 'rs-table__row--draggable': rowDragByRow,
109
+ 'rs-table__row--dragging': dragging,
110
+ 'rs-table__row--drop-before': dropTarget && dropPosition === 'before',
111
+ 'rs-table__row--drop-after': dropTarget && dropPosition === 'after',
112
+ 'rs-table__row--drop-into': dropTarget && dropPosition === 'into',
113
+ 'rs-table__row--dirty': rowDirty || rowEditPending,
114
+ }"
115
+ @click="emit('click', $event)"
116
+ @dblclick="emit('dblclick', $event)"
117
+ @contextmenu="emit('contextmenu', $event)"
118
+ @dragover="emit('dragover', $event)"
119
+ @dragleave="emit('dragleave', $event)"
120
+ @drop="emit('drop', $event)"
121
+ >
122
+ <td
123
+ v-if="showRowDragHandle"
124
+ class="rs-table__td rs-table__td--drag"
125
+ :style="dragLeadStyle"
126
+ @click.stop
127
+ >
128
+ <span
129
+ v-if="!disabled"
130
+ class="rs-table__row-drag-handle"
131
+ draggable="true"
132
+ :aria-label="dragRowLabel"
133
+ @dragstart="emit('rowDragStart', $event)"
134
+ @dragend="emit('rowDragEnd')"
135
+ />
136
+ </td>
137
+ <td
138
+ v-if="expandable"
139
+ class="rs-table__td rs-table__td--expand"
140
+ :style="expandLeadStyle"
141
+ @click.stop
142
+ >
143
+ <button
144
+ v-if="canExpand"
145
+ type="button"
146
+ class="rs-table__expand-btn"
147
+ :class="{ 'rs-table__expand-btn--expanded': expanded }"
148
+ :aria-label="expanded ? collapseRowLabel : expandRowLabel"
149
+ @click="emit('toggleExpand')"
150
+ >
151
+
152
+ </button>
153
+ </td>
154
+ <td
155
+ v-if="selectable"
156
+ class="rs-table__td rs-table__td--selection"
157
+ :style="selectLeadStyle"
158
+ @click.stop
159
+ >
160
+ <label class="rs-table__checkbox" :class="{ 'rs-table__checkbox--checked': selected }">
161
+ <input
162
+ :type="selectionType"
163
+ class="rs-table__checkbox-input"
164
+ :name="isRadioSelection ? 'rs-table-radio' : undefined"
165
+ :checked="selected"
166
+ :disabled="!canSelect"
167
+ :aria-label="selectRowLabel"
168
+ @change="emit('toggleSelect')"
169
+ >
170
+ <span
171
+ class="rs-table__checkbox-box"
172
+ :class="{ 'rs-table__checkbox-box--radio': isRadioSelection }"
173
+ aria-hidden="true"
174
+ />
175
+ </label>
176
+ </td>
177
+ <td
178
+ v-if="showEditGutter"
179
+ class="rs-table__td rs-table__td--gutter rs-table__cell--center"
180
+ :style="gutterLeadStyle"
181
+ >
182
+ <RsTableEditGutter
183
+ :line-number="rowIndex + 1"
184
+ :pending="rowEditPending"
185
+ :commit-label="rowCommitLabel"
186
+ :commit-hint="gutterCommitHint"
187
+ :rollback-label="rowRollbackLabel"
188
+ :show-rollback="showGutterRollback ?? rowCommit"
189
+ @commit="emit('gutterCommit')"
190
+ @rollback="emit('rowRollback')"
191
+ />
192
+ </td>
193
+ <td
194
+ v-else-if="showIndex"
195
+ class="rs-table__td rs-table__td--index rs-table__cell--center"
196
+ :style="indexLeadStyle"
197
+ >
198
+ {{ rowIndex + 1 }}
199
+ </td>
200
+ <td
201
+ v-if="showRowStatus"
202
+ class="rs-table__td rs-table__td--status rs-table__cell--center"
203
+ @click.stop
204
+ >
205
+ <RsTableRowStatus
206
+ :dirty="rowDirty"
207
+ :commit-label="rowCommitLabel"
208
+ :rollback-label="rowRollbackLabel"
209
+ @commit="emit('rowCommit')"
210
+ @rollback="emit('rowRollback')"
211
+ />
212
+ </td>
213
+ <td
214
+ v-if="(columnPadLeft ?? 0) > 0"
215
+ class="rs-table__td rs-table__col-pad"
216
+ :style="{ width: `${columnPadLeft}px`, minWidth: `${columnPadLeft}px` }"
217
+ />
218
+ <td
219
+ v-for="column in columns"
220
+ :key="column.key"
221
+ class="rs-table__td rs-table__td--data"
222
+ :class="[
223
+ columnTdClassMap.get(column.key),
224
+ {
225
+ 'rs-table__td--row-draggable': rowDragByRow,
226
+ 'rs-table__td--editing': editingColKey === column.key,
227
+ 'rs-table__td--focused': focusColKey === column.key && editingColKey !== column.key,
228
+ 'rs-table__td--invalid': !!getCellError(rowKey, column.key),
229
+ },
230
+ ]"
231
+ :style="columnStyleMap.get(column.key)"
232
+ :draggable="rowDragByRow"
233
+ :aria-selected="focusColKey === column.key || editingColKey === column.key ? 'true' : undefined"
234
+ @click.stop="emit('cellClick', column.key, $event)"
235
+ @dblclick.stop="emit('cellDblclick', column.key, $event)"
236
+ @contextmenu="emit('cellContextmenu', column.key, $event)"
237
+ @dragstart="rowDragByRow ? emit('rowDragStart', $event) : undefined"
238
+ @dragend="rowDragByRow ? emit('rowDragEnd') : undefined"
239
+ >
240
+ <span
241
+ :class="[
242
+ { 'rs-table__cell-tip': cellTooltipEnabled(column, rowIndex) },
243
+ { 'rs-table__cell-tip--editing': editingColKey === column.key },
244
+ ]"
245
+ :data-rs-table-tip-mode="cellTooltipEnabled(column, rowIndex) ? cellTooltipMode(column, rowIndex) : undefined"
246
+ :data-rs-table-tip-text="cellTooltipText(column, row, rowIndex)"
247
+ :title="getCellError(rowKey, column.key) || cellTooltipFallbackTitle(column, row, rowIndex)"
248
+ >
249
+ <RsTableCell
250
+ :column="column"
251
+ :row="row"
252
+ :row-index="rowIndex"
253
+ :row-key="rowKey"
254
+ :table-editable="tableEditable"
255
+ :editing="editingColKey === column.key"
256
+ :dirty="isCellDirty(rowKey, column.key)"
257
+ :focused="focusColKey === column.key"
258
+ :draft="getCellDraft(rowKey, column.key)"
259
+ :has-custom-slot="hasColumnSlot(column.key)"
260
+ :has-edit-slot="hasEditSlot(column.key)"
261
+ :edit-trigger="editTrigger"
262
+ :row-commit="rowCommit"
263
+ :allow-null="allowNull"
264
+ :focus-mode="focusMode"
265
+ :null-label="nullLabel"
266
+ :error-message="getCellError(rowKey, column.key) ?? null"
267
+ :validating="isCellValidating(rowKey, column.key)"
268
+ @start-edit="emit('cellStartEdit', column.key)"
269
+ @commit="emit('cellCommit', column.key, $event)"
270
+ @cancel="emit('cellCancel', column.key)"
271
+ @update-draft="emit('cellUpdateDraft', column.key, $event)"
272
+ @navigate="emit('cellNavigate', column.key, $event)"
273
+ >
274
+ <slot :name="column.key" :row="row" :column="column" :index="rowIndex" />
275
+ <template v-if="hasEditSlot(column.key)" #editor="scope">
276
+ <slot :name="`edit-${column.key}`" v-bind="scope" />
277
+ </template>
278
+ </RsTableCell>
279
+ </span>
280
+ </td>
281
+ <td
282
+ v-if="(columnPadRight ?? 0) > 0"
283
+ class="rs-table__td rs-table__col-pad"
284
+ :style="{ width: `${columnPadRight}px`, minWidth: `${columnPadRight}px` }"
285
+ />
286
+ </tr>
287
+ </template>