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,597 @@
1
+ <script setup lang="ts">
2
+ import { ref, shallowRef, onMounted, onUnmounted, watch, computed, nextTick } from 'vue'
3
+ import { basicSetup } from 'codemirror'
4
+ import { EditorState, Compartment } from '@codemirror/state'
5
+ import { EditorView, placeholder as cmPlaceholder, tooltips } from '@codemirror/view'
6
+ import { oneDark } from '@codemirror/theme-one-dark'
7
+ import type {
8
+ RsCodeEditorDiagnostic,
9
+ RsCodeEditorLanguage,
10
+ RsCodeEditorSqlConfig,
11
+ RsCodeEditorTheme,
12
+ } from './code-editor-utils'
13
+ import {
14
+ codeEditorLanguageLabel,
15
+ resolveCodeEditorLanguage,
16
+ resolveCodeEditorSize,
17
+ resolveCodeEditorTheme,
18
+ } from './code-editor-utils'
19
+ import { isCodeMirrorLightTheme, resolveCodeMirrorLanguage } from './code-mirror-lang'
20
+ import {
21
+ diagnosticExtensions,
22
+ setEditorDiagnostics,
23
+ } from './code-mirror-diagnostics'
24
+ import { codeMirrorIntelExtensions, goToPosition } from './code-mirror-intel'
25
+ import { codeMirrorCompletionExtensions } from './code-mirror-completion'
26
+ import { codeMirrorGhostExtensions, clearGhostCompletion } from './code-mirror-ghost'
27
+ import {
28
+ codeMirrorInlineEditExtension,
29
+ extractSurroundingContext,
30
+ replaceEditorRange,
31
+ type InlineEditTrigger,
32
+ } from './code-mirror-inline-edit'
33
+
34
+ const model = defineModel<string>({ default: '' })
35
+
36
+ const emit = defineEmits<{
37
+ 'goto-definition': [loc: { file: string; line: number; column?: number }]
38
+ ready: []
39
+ }>()
40
+
41
+ const props = withDefaults(
42
+ defineProps<{
43
+ language?: RsCodeEditorLanguage
44
+ theme?: RsCodeEditorTheme
45
+ height?: number | string
46
+ readonly?: boolean
47
+ disabled?: boolean
48
+ /** 是否显示内置语言标签栏(文件工作台使用外部 FileEditorToolbar 时应关闭) */
49
+ showToolbar?: boolean
50
+ /**
51
+ * 嵌入父容器:去边框,直角,铺满父级高度。
52
+ * 嵌套在已有描边/分割条的面板(如过滤条)时使用,避免业务侧 :deep 改样式。
53
+ */
54
+ embedded?: boolean
55
+ /** 外框圆角;embedded 时强制直角 */
56
+ rounded?: boolean
57
+ /**
58
+ * 行号 gutter 最小宽度(px)。
59
+ * 用于与表格 editGutterWidth / indexWidth 对齐。
60
+ */
61
+ gutterWidth?: number
62
+ /** 是否显示代码折叠 gutter;过滤片段等场景可关以便与表格行号宽对齐 */
63
+ foldGutter?: boolean
64
+ diagnostics?: RsCodeEditorDiagnostic[]
65
+ placeholder?: string
66
+ /** SQL 表/字段补全(language=sql 时生效) */
67
+ sqlConfig?: RsCodeEditorSqlConfig
68
+ filePath?: string
69
+ hoverRequest?: (line: number, column: number) => Promise<string | null>
70
+ definitionRequest?: (line: number, column: number) => Promise<{
71
+ file: string
72
+ line: number
73
+ column?: number
74
+ } | null>
75
+ completionRequest?: (prefix: string, suffix: string, line: number, column: number) => Promise<string | null>
76
+ inlineEditRequest?: (params: {
77
+ selection: string
78
+ instruction: string
79
+ from: number
80
+ to: number
81
+ surroundingContext: string
82
+ }) => Promise<string | null>
83
+ }>(),
84
+ {
85
+ language: 'plaintext',
86
+ theme: 'auto',
87
+ readonly: false,
88
+ disabled: false,
89
+ showToolbar: true,
90
+ embedded: false,
91
+ rounded: true,
92
+ foldGutter: true,
93
+ diagnostics: () => [],
94
+ },
95
+ )
96
+
97
+ const rootStyle = computed(() => {
98
+ const style: Record<string, string> = {
99
+ height: resolveCodeEditorSize(props.height),
100
+ }
101
+ if (props.gutterWidth != null && props.gutterWidth > 0) {
102
+ style['--rs-code-editor-gutter-width'] = `${props.gutterWidth}px`
103
+ }
104
+ return style
105
+ })
106
+
107
+ const resolvedLanguage = computed(() => resolveCodeEditorLanguage(props.language))
108
+ const resolvedLanguageLabel = computed(() => codeEditorLanguageLabel(props.language))
109
+ const resolvedTheme = computed(() => resolveCodeEditorTheme(props.theme))
110
+
111
+ const editorEl = ref<HTMLElement | null>(null)
112
+ const view = shallowRef<EditorView | null>(null)
113
+ const editableCompartment = new Compartment()
114
+ let themeObserver: MutationObserver | null = null
115
+ let unmounted = false
116
+ let syncingFromModel = false
117
+ let ghostTimer: ReturnType<typeof setTimeout> | null = null
118
+ let initSeq = 0
119
+ const inlineEditOpen = ref(false)
120
+ const inlineEditInstruction = ref('')
121
+ const inlineEditLoading = ref(false)
122
+ const inlineEditPreview = ref('')
123
+ const inlineEditError = ref('')
124
+ const inlineEditInputRef = ref<HTMLInputElement | null>(null)
125
+ let pendingInlineEdit: InlineEditTrigger | null = null
126
+
127
+ function closeInlineEdit() {
128
+ inlineEditOpen.value = false
129
+ inlineEditInstruction.value = ''
130
+ inlineEditPreview.value = ''
131
+ inlineEditError.value = ''
132
+ pendingInlineEdit = null
133
+ }
134
+
135
+ function onInlineEditTrigger(_view: EditorView, info: InlineEditTrigger) {
136
+ if (!props.inlineEditRequest || props.readonly || props.disabled) return
137
+ if (view.value) clearGhostCompletion(view.value)
138
+ pendingInlineEdit = info
139
+ inlineEditPreview.value = info.selection.length > 120
140
+ ? `${info.selection.slice(0, 120)}…`
141
+ : info.selection
142
+ inlineEditOpen.value = true
143
+ inlineEditInstruction.value = ''
144
+ inlineEditError.value = ''
145
+ void nextTick(() => inlineEditInputRef.value?.focus())
146
+ }
147
+
148
+ async function submitInlineEdit() {
149
+ const range = pendingInlineEdit
150
+ const instruction = inlineEditInstruction.value.trim()
151
+ if (!range || !instruction || !props.inlineEditRequest || !view.value || inlineEditLoading.value) return
152
+ inlineEditLoading.value = true
153
+ inlineEditError.value = ''
154
+ try {
155
+ const doc = view.value.state.doc.toString()
156
+ const edited = await props.inlineEditRequest({
157
+ selection: range.selection,
158
+ instruction,
159
+ from: range.from,
160
+ to: range.to,
161
+ surroundingContext: extractSurroundingContext(doc, range.from, range.to),
162
+ })
163
+ if (edited == null) {
164
+ inlineEditError.value = '未能生成替换代码,请调整指令后重试'
165
+ return
166
+ }
167
+ replaceEditorRange(view.value, range.from, range.to, edited)
168
+ closeInlineEdit()
169
+ } finally {
170
+ inlineEditLoading.value = false
171
+ }
172
+ }
173
+
174
+ function scheduleGhostCompletion(fn: () => void) {
175
+ if (ghostTimer) clearTimeout(ghostTimer)
176
+ ghostTimer = setTimeout(fn, 900)
177
+ }
178
+
179
+ function cancelGhostCompletionSchedule() {
180
+ if (ghostTimer) {
181
+ clearTimeout(ghostTimer)
182
+ ghostTimer = null
183
+ }
184
+ }
185
+
186
+ async function initEditor() {
187
+ if (!editorEl.value || unmounted) return
188
+ const seq = ++initSeq
189
+ const targetEl = editorEl.value
190
+
191
+ view.value?.destroy()
192
+ view.value = null
193
+
194
+ const langExts = await resolveCodeMirrorLanguage(resolvedLanguage.value, {
195
+ sql: props.sqlConfig,
196
+ })
197
+ if (unmounted || !targetEl.isConnected || seq !== initSeq) return
198
+
199
+ const editable = !props.readonly && !props.disabled
200
+ const state = EditorState.create({
201
+ doc: model.value,
202
+ extensions: [
203
+ basicSetup,
204
+ // 嵌入 overflow:hidden 容器(如浏览过滤条)时,补全浮层挂到 body,避免被裁切后回车无法接受选项
205
+ ...(props.embedded ? [tooltips({ parent: document.body })] : []),
206
+ EditorView.lineWrapping,
207
+ editableCompartment.of(EditorView.editable.of(editable)),
208
+ ...(props.placeholder ? [cmPlaceholder(props.placeholder)] : []),
209
+ ...langExts,
210
+ diagnosticExtensions(),
211
+ ...codeMirrorIntelExtensions(() => ({
212
+ hoverRequest: props.hoverRequest,
213
+ definitionRequest: props.definitionRequest,
214
+ currentFilePath: props.filePath,
215
+ onGotoDefinition: (loc) => emit('goto-definition', loc),
216
+ })),
217
+ ...codeMirrorCompletionExtensions(() => ({
218
+ completionRequest: props.completionRequest,
219
+ })),
220
+ ...codeMirrorGhostExtensions(
221
+ () => ({ completionRequest: props.completionRequest }),
222
+ scheduleGhostCompletion,
223
+ cancelGhostCompletionSchedule,
224
+ ),
225
+ ...(props.inlineEditRequest
226
+ ? [codeMirrorInlineEditExtension(onInlineEditTrigger)]
227
+ : []),
228
+ ...(isCodeMirrorLightTheme() ? [] : [oneDark]),
229
+ EditorView.updateListener.of((update) => {
230
+ if (!update.docChanged || syncingFromModel) return
231
+ const next = update.state.doc.toString()
232
+ if (next !== model.value) model.value = next
233
+ }),
234
+ ],
235
+ })
236
+
237
+ view.value = new EditorView({ state, parent: targetEl })
238
+ setEditorDiagnostics(view.value, props.diagnostics ?? [])
239
+ emit('ready')
240
+ }
241
+
242
+ onMounted(() => {
243
+ unmounted = false
244
+ void initEditor()
245
+ themeObserver = new MutationObserver(() => { void initEditor() })
246
+ themeObserver.observe(document.documentElement, {
247
+ attributes: true,
248
+ attributeFilter: ['data-rs-theme'],
249
+ })
250
+ })
251
+
252
+ onUnmounted(() => {
253
+ unmounted = true
254
+ if (ghostTimer) clearTimeout(ghostTimer)
255
+ view.value?.destroy()
256
+ view.value = null
257
+ themeObserver?.disconnect()
258
+ themeObserver = null
259
+ })
260
+
261
+ watch(() => props.language, () => { void initEditor() })
262
+ watch(() => props.inlineEditRequest, () => { void initEditor() })
263
+ watch(
264
+ () => props.sqlConfig,
265
+ () => {
266
+ if (resolvedLanguage.value === 'sql') void initEditor()
267
+ },
268
+ { deep: true },
269
+ )
270
+
271
+ watch(
272
+ () => model.value,
273
+ (newCode) => {
274
+ if (!view.value) return
275
+ const current = view.value.state.doc.toString()
276
+ if (current === newCode) return
277
+ syncingFromModel = true
278
+ if (view.value) clearGhostCompletion(view.value)
279
+ view.value.dispatch({
280
+ changes: { from: 0, to: view.value.state.doc.length, insert: newCode },
281
+ })
282
+ syncingFromModel = false
283
+ },
284
+ )
285
+
286
+ watch(
287
+ () => [props.readonly, props.disabled] as const,
288
+ ([readonly, disabled]) => {
289
+ if (!view.value) return
290
+ const editable = !readonly && !disabled
291
+ view.value.dispatch({
292
+ effects: editableCompartment.reconfigure(EditorView.editable.of(editable)),
293
+ })
294
+ },
295
+ )
296
+
297
+ watch(
298
+ () => props.diagnostics,
299
+ (diags) => {
300
+ if (!view.value) return
301
+ setEditorDiagnostics(view.value, diags ?? [])
302
+ },
303
+ { deep: true },
304
+ )
305
+
306
+ defineExpose({
307
+ goToPosition(line: number, column = 1) {
308
+ if (!view.value) return
309
+ goToPosition(view.value, line, column)
310
+ },
311
+ })
312
+ </script>
313
+
314
+ <template>
315
+ <div
316
+ class="rs-code-editor"
317
+ :class="[
318
+ `rs-code-editor--${resolvedTheme}`,
319
+ {
320
+ 'rs-code-editor--embedded': embedded,
321
+ 'rs-code-editor--square': !rounded || embedded,
322
+ 'rs-code-editor--no-fold': !foldGutter,
323
+ 'rs-code-editor--gutter-fixed': gutterWidth != null && gutterWidth > 0,
324
+ },
325
+ ]"
326
+ :style="rootStyle"
327
+ >
328
+ <div v-if="showToolbar" class="rs-code-editor__toolbar">
329
+ <span>{{ resolvedLanguageLabel }}</span>
330
+ <slot name="toolbar" />
331
+ </div>
332
+ <div class="rs-code-editor__body">
333
+ <div
334
+ ref="editorEl"
335
+ class="rs-code-editor__surface"
336
+ :class="{ 'rs-code-editor__surface--disabled': disabled }"
337
+ />
338
+ <div v-if="inlineEditOpen" class="rs-code-editor__inline-edit">
339
+ <div class="rs-code-editor__inline-edit-head">
340
+ <span>Inline Edit</span>
341
+ <kbd>Ctrl+K</kbd>
342
+ </div>
343
+ <p class="rs-code-editor__inline-preview">{{ inlineEditPreview }}</p>
344
+ <input
345
+ ref="inlineEditInputRef"
346
+ v-model="inlineEditInstruction"
347
+ class="rs-code-editor__inline-input"
348
+ type="text"
349
+ placeholder="描述要如何修改选区…"
350
+ :disabled="inlineEditLoading"
351
+ @keydown.enter.prevent="submitInlineEdit"
352
+ @keydown.esc.prevent="closeInlineEdit"
353
+ />
354
+ <p v-if="inlineEditError" class="rs-code-editor__inline-error">{{ inlineEditError }}</p>
355
+ <div class="rs-code-editor__inline-actions">
356
+ <button type="button" class="rs-code-editor__inline-btn" :disabled="inlineEditLoading" @click="closeInlineEdit">
357
+ 取消
358
+ </button>
359
+ <button
360
+ type="button"
361
+ class="rs-code-editor__inline-btn rs-code-editor__inline-btn--primary"
362
+ :disabled="inlineEditLoading || !inlineEditInstruction.trim()"
363
+ @click="submitInlineEdit"
364
+ >
365
+ {{ inlineEditLoading ? '生成中…' : '应用' }}
366
+ </button>
367
+ </div>
368
+ </div>
369
+ </div>
370
+ <ul v-if="diagnostics.length" class="rs-code-editor__diagnostics">
371
+ <li
372
+ v-for="(diagnostic, index) in diagnostics"
373
+ :key="index"
374
+ :class="`rs-code-editor__diagnostic--${diagnostic.severity ?? 'error'}`"
375
+ >
376
+ <span v-if="diagnostic.line">{{ diagnostic.line }}:{{ diagnostic.column ?? 1 }}</span>
377
+ {{ diagnostic.message }}
378
+ </li>
379
+ </ul>
380
+ </div>
381
+ </template>
382
+
383
+ <style scoped>
384
+ .rs-code-editor {
385
+ display: flex;
386
+ flex-direction: column;
387
+ overflow: hidden;
388
+ border: 1px solid var(--rs-border);
389
+ border-radius: var(--rs-radius);
390
+ background: var(--rs-input-bg);
391
+ color: var(--rs-text);
392
+ }
393
+ .rs-code-editor--square {
394
+ border-radius: 0;
395
+ }
396
+ .rs-code-editor--embedded {
397
+ border: 0;
398
+ border-radius: 0;
399
+ background: transparent;
400
+ flex: 1 1 auto;
401
+ min-height: 0;
402
+ width: 100%;
403
+ }
404
+ .rs-code-editor__toolbar {
405
+ display: flex;
406
+ align-items: center;
407
+ justify-content: space-between;
408
+ padding: var(--rs-space-xs) var(--rs-space-sm);
409
+ border-bottom: 1px solid var(--rs-border-subtle);
410
+ color: var(--rs-muted);
411
+ font-size: var(--rs-font-size-xs);
412
+ }
413
+ .rs-code-editor__body {
414
+ position: relative;
415
+ flex: 1;
416
+ min-height: 0;
417
+ display: flex;
418
+ flex-direction: column;
419
+ }
420
+ .rs-code-editor__surface {
421
+ flex: 1;
422
+ min-height: 0;
423
+ overflow: hidden;
424
+ }
425
+ .rs-code-editor--embedded .rs-code-editor__body,
426
+ .rs-code-editor--embedded .rs-code-editor__surface {
427
+ height: 100%;
428
+ }
429
+ .rs-code-editor__inline-edit {
430
+ position: absolute;
431
+ left: 50%;
432
+ top: 12%;
433
+ z-index: 5;
434
+ width: min(28rem, calc(100% - 2rem));
435
+ transform: translateX(-50%);
436
+ padding: 0.75rem;
437
+ border: 1px solid var(--rs-border);
438
+ border-radius: var(--rs-radius);
439
+ background: var(--rs-surface-elevated);
440
+ box-shadow: 0 8px 28px rgba(0, 0, 0, 0.14);
441
+ }
442
+ .rs-code-editor__inline-edit-head {
443
+ display: flex;
444
+ align-items: center;
445
+ justify-content: space-between;
446
+ margin-bottom: 0.5rem;
447
+ font-size: var(--rs-font-size-xs);
448
+ font-weight: 600;
449
+ color: var(--rs-foreground);
450
+ }
451
+ .rs-code-editor__inline-edit-head kbd {
452
+ padding: 0.1rem 0.35rem;
453
+ border-radius: 4px;
454
+ border: 1px solid var(--rs-border-subtle);
455
+ font-size: 0.7rem;
456
+ color: var(--rs-muted);
457
+ }
458
+ .rs-code-editor__inline-preview {
459
+ margin: 0 0 0.5rem;
460
+ padding: 0.35rem 0.5rem;
461
+ border-radius: 4px;
462
+ background: color-mix(in srgb, var(--rs-muted) 8%, transparent);
463
+ color: var(--rs-muted);
464
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
465
+ font-size: 0.72rem;
466
+ line-height: 1.4;
467
+ white-space: pre-wrap;
468
+ max-height: 4.5rem;
469
+ overflow: hidden;
470
+ }
471
+ .rs-code-editor__inline-input {
472
+ width: 100%;
473
+ box-sizing: border-box;
474
+ margin-bottom: 0.5rem;
475
+ padding: 0.45rem 0.55rem;
476
+ border: 1px solid var(--rs-border);
477
+ border-radius: var(--rs-radius-sm, 4px);
478
+ background: var(--rs-input-bg);
479
+ color: var(--rs-foreground);
480
+ font-size: var(--rs-font-size-sm);
481
+ }
482
+ .rs-code-editor__inline-input:focus {
483
+ outline: 2px solid color-mix(in srgb, var(--rs-primary) 35%, transparent);
484
+ outline-offset: 1px;
485
+ }
486
+ .rs-code-editor__inline-error {
487
+ margin: -0.25rem 0 0.5rem;
488
+ font-size: var(--rs-font-size-xs);
489
+ color: var(--rs-danger);
490
+ }
491
+ .rs-code-editor__inline-actions {
492
+ display: flex;
493
+ justify-content: flex-end;
494
+ gap: 0.35rem;
495
+ }
496
+ .rs-code-editor__inline-btn {
497
+ padding: 0.3rem 0.65rem;
498
+ border: 1px solid var(--rs-border);
499
+ border-radius: var(--rs-radius-sm, 4px);
500
+ background: transparent;
501
+ color: var(--rs-foreground);
502
+ font-size: var(--rs-font-size-xs);
503
+ cursor: pointer;
504
+ }
505
+ .rs-code-editor__inline-btn--primary {
506
+ border-color: var(--rs-primary);
507
+ background: var(--rs-primary);
508
+ color: var(--rs-primary-foreground, #fff);
509
+ }
510
+ .rs-code-editor__inline-btn:disabled {
511
+ opacity: 0.55;
512
+ cursor: not-allowed;
513
+ }
514
+ .rs-code-editor__surface--disabled {
515
+ opacity: 0.6;
516
+ pointer-events: none;
517
+ }
518
+ .rs-code-editor__surface :deep(.cm-editor) {
519
+ height: 100%;
520
+ background: var(--rs-surface-elevated) !important;
521
+ }
522
+ .rs-code-editor__surface :deep(.cm-scroller) {
523
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
524
+ font-size: var(--rs-font-size-sm);
525
+ line-height: 1.6;
526
+ }
527
+ .rs-code-editor__surface :deep(.cm-gutters) {
528
+ background: color-mix(in srgb, var(--rs-surface-elevated) 85%, var(--rs-border) 15%) !important;
529
+ border-right-color: var(--rs-border-subtle) !important;
530
+ color: var(--rs-muted);
531
+ }
532
+ .rs-code-editor--embedded .rs-code-editor__surface :deep(.cm-editor) {
533
+ height: 100%;
534
+ background: transparent !important;
535
+ }
536
+ .rs-code-editor--no-fold .rs-code-editor__surface :deep(.cm-foldGutter) {
537
+ display: none !important;
538
+ width: 0 !important;
539
+ min-width: 0 !important;
540
+ }
541
+ .rs-code-editor--gutter-fixed .rs-code-editor__surface :deep(.cm-gutters) {
542
+ min-width: var(--rs-code-editor-gutter-width);
543
+ }
544
+ .rs-code-editor--gutter-fixed.rs-code-editor--no-fold .rs-code-editor__surface :deep(.cm-lineNumbers) {
545
+ min-width: var(--rs-code-editor-gutter-width);
546
+ }
547
+ .rs-code-editor--gutter-fixed.rs-code-editor--no-fold .rs-code-editor__surface :deep(.cm-lineNumbers .cm-gutterElement) {
548
+ min-width: calc(var(--rs-code-editor-gutter-width) - 8px);
549
+ }
550
+ .rs-code-editor__surface :deep(.cm-content) {
551
+ color: var(--rs-foreground);
552
+ }
553
+ .rs-code-editor__surface :deep(.cm-lintRange-error) {
554
+ background: color-mix(in srgb, var(--rs-danger) 12%, transparent);
555
+ }
556
+ .rs-code-editor__surface :deep(.cm-lintRange-warning) {
557
+ background: color-mix(in srgb, var(--rs-warning) 12%, transparent);
558
+ }
559
+ .rs-code-editor__surface :deep(.cm-lintRange-info) {
560
+ background: color-mix(in srgb, var(--rs-info) 10%, transparent);
561
+ }
562
+ .rs-code-editor__surface :deep(.rs-code-editor__ghost) {
563
+ color: var(--rs-muted);
564
+ opacity: 0.55;
565
+ pointer-events: none;
566
+ }
567
+ .rs-code-editor__surface :deep(.rs-code-editor__hover) {
568
+ max-width: 28rem;
569
+ padding: 0.45rem 0.65rem;
570
+ border-radius: var(--rs-radius-sm, 4px);
571
+ border: 1px solid var(--rs-border);
572
+ background: var(--rs-surface-elevated);
573
+ color: var(--rs-foreground);
574
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
575
+ font-size: var(--rs-font-size-xs);
576
+ line-height: 1.45;
577
+ white-space: pre-wrap;
578
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
579
+ }
580
+ .rs-code-editor__diagnostics {
581
+ margin: 0;
582
+ padding: var(--rs-space-sm) var(--rs-space-md);
583
+ border-top: 1px solid var(--rs-border-subtle);
584
+ color: var(--rs-muted);
585
+ font-size: var(--rs-font-size-xs);
586
+ list-style: none;
587
+ }
588
+ .rs-code-editor__diagnostic--error {
589
+ color: var(--rs-danger);
590
+ }
591
+ .rs-code-editor__diagnostic--warning {
592
+ color: var(--rs-warning);
593
+ }
594
+ .rs-code-editor__diagnostic--info {
595
+ color: var(--rs-info);
596
+ }
597
+ </style>
@@ -0,0 +1,92 @@
1
+ <script setup lang="ts">
2
+ import { ref, watch } from 'vue'
3
+ import { createRsConfigState, provideRsConfig } from '../composables/useRsConfig'
4
+ import { applyTheme } from '../theme/apply'
5
+ import { defaultLocale, type RsLocale } from '../locale/types'
6
+ import type { RsComponentSize, RsRadius, RsThemeMode } from '../theme/types'
7
+
8
+ const props = withDefaults(
9
+ defineProps<{
10
+ theme?: RsThemeMode
11
+ locale?: RsLocale
12
+ /** 全局默认控件尺寸:ssm 极小 / sm 小号 / md 中号 / lg 大号 */
13
+ controlSize?: RsComponentSize
14
+ /**
15
+ * 全局默认圆角:none 直角 / xs~lg / full。
16
+ * 未传时各组件保留自身默认;传入后覆盖未显式指定 radius 的控件。
17
+ */
18
+ controlRadius?: RsRadius
19
+ /**
20
+ * global:data-rs-theme 写到 document(默认,Portal 弹出层同步)
21
+ * local:写到 Provider 根节点(子树隔离,业务 CSS 可 scoped 到 .rs-app-*)
22
+ */
23
+ themeScope?: 'global' | 'local'
24
+ }>(),
25
+ {
26
+ theme: 'light',
27
+ locale: defaultLocale,
28
+ controlSize: 'md',
29
+ themeScope: 'global',
30
+ },
31
+ )
32
+
33
+ const rootEl = ref<HTMLElement | null>(null)
34
+ const config = createRsConfigState(
35
+ props.theme,
36
+ props.locale,
37
+ props.controlSize,
38
+ props.controlRadius,
39
+ )
40
+ provideRsConfig(config)
41
+
42
+ function syncTheme() {
43
+ if (typeof document === 'undefined') return
44
+ const el =
45
+ props.themeScope === 'local' && rootEl.value
46
+ ? rootEl.value
47
+ : document.documentElement
48
+ applyTheme(config.theme.value, el)
49
+ }
50
+
51
+ watch(
52
+ () => props.theme,
53
+ (value) => {
54
+ if (value !== config.theme.value) config.setTheme(value)
55
+ },
56
+ )
57
+ watch(
58
+ () => props.locale,
59
+ (value) => {
60
+ if (value !== config.locale.value) config.setLocale(value)
61
+ },
62
+ )
63
+ watch(
64
+ () => props.controlSize,
65
+ (value) => {
66
+ if (value && value !== config.controlSize.value) config.setControlSize(value)
67
+ },
68
+ )
69
+ watch(
70
+ () => props.controlRadius,
71
+ (value) => {
72
+ if (value !== config.controlRadius.value) config.setControlRadius(value)
73
+ },
74
+ )
75
+ watch(() => config.theme.value, syncTheme, { immediate: true })
76
+ watch(() => props.themeScope, syncTheme)
77
+ </script>
78
+
79
+ <template>
80
+ <div ref="rootEl" class="rs-config-provider">
81
+ <slot />
82
+ </div>
83
+ </template>
84
+
85
+ <style scoped>
86
+ .rs-config-provider {
87
+ color: var(--rs-text);
88
+ background: var(--rs-bg);
89
+ height: 100%;
90
+ min-height: 0;
91
+ }
92
+ </style>