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,1101 @@
1
+ <script setup lang="ts">
2
+
3
+ import { computed, ref, useAttrs, useId, useSlots } from 'vue'
4
+
5
+ import { useRsI18n } from '../composables/useRsI18n'
6
+
7
+ import type { RsComponentSize, RsRadius } from '../theme/types'
8
+ import { useResolvedRsComponentSize } from './resolve-size'
9
+ import { rsRadiusCss, useResolvedRsRadius } from './resolve-radius'
10
+
11
+ import {
12
+
13
+ runInputValidation,
14
+
15
+ type RsInputRule,
16
+
17
+ type RsInputValidateTrigger,
18
+
19
+ } from './input-rules'
20
+
21
+ import {
22
+
23
+ useRsFormContext,
24
+
25
+ useRsFormField,
26
+
27
+ type RsFormLabelPosition,
28
+
29
+ } from './form-utils'
30
+
31
+ import RsIcon from './RsIcon.vue'
32
+
33
+
34
+
35
+ const { t } = useRsI18n()
36
+
37
+
38
+
39
+ defineOptions({ inheritAttrs: false })
40
+
41
+
42
+
43
+ /**
44
+ * modelValue 契约恒为 string(对齐 Ant Input / 原生 input.value)。
45
+ * get/set 变换兜住外部误传 number;原生控件用 :value + el.value,避免 type=number 的类型漂移。
46
+ */
47
+ const model = defineModel<string>({
48
+ default: '',
49
+ get(value) {
50
+ return value == null ? '' : String(value)
51
+ },
52
+ set(value) {
53
+ return value == null ? '' : String(value)
54
+ },
55
+ })
56
+
57
+
58
+
59
+ const props = withDefaults(
60
+
61
+ defineProps<{
62
+
63
+ id?: string
64
+
65
+ type?: 'text' | 'email' | 'password' | 'search' | 'tel' | 'url' | 'number' | 'date' | 'datetime-local'
66
+
67
+ placeholder?: string
68
+
69
+ disabled?: boolean
70
+
71
+ readonly?: boolean
72
+
73
+ /** 手动标记错误(优先级高于自动校验) */
74
+
75
+ invalid?: boolean
76
+
77
+ errorMessage?: string
78
+
79
+ size?: RsComponentSize
80
+
81
+ /** 圆角档位;默认 sm。直角 UI 传 `none`。 */
82
+
83
+ radius?: RsRadius
84
+
85
+ /** 预设格式规则 */
86
+
87
+ rule?: RsInputRule
88
+
89
+ /** 自定义校验,返回 true / false / 错误文案 */
90
+
91
+ validator?: (value: string) => boolean | string
92
+
93
+ required?: boolean
94
+
95
+ validateTrigger?: RsInputValidateTrigger
96
+
97
+ /** 文本前缀(也可用 prefix 插槽) */
98
+
99
+ prefix?: string
100
+
101
+ /** 文本后缀(也可用 suffix 插槽) */
102
+
103
+ suffix?: string
104
+
105
+ /** 有值时展示清除按钮(对齐 Ant allowClear / Element clearable) */
106
+
107
+ clearable?: boolean
108
+
109
+ /** 最大字符数,配合 showCount 展示字数统计 */
110
+
111
+ maxlength?: number
112
+
113
+ /** 展示字数统计,需配合 maxlength */
114
+
115
+ showCount?: boolean
116
+
117
+ /** 密码框显隐按钮;password 类型默认展示 */
118
+
119
+ visibilityToggle?: boolean
120
+
121
+ /** 为 false 时不展示自动校验错误 */
122
+
123
+ showValidateMessage?: boolean
124
+
125
+ /** 字段标签(与 RsDatePicker / RsTimePicker 一致) */
126
+
127
+ label?: string
128
+
129
+ /** 辅助说明,展示在控件下方 */
130
+
131
+ hint?: string
132
+
133
+ labelPosition?: RsFormLabelPosition
134
+
135
+ }>(),
136
+
137
+ {
138
+
139
+ type: 'text',
140
+
141
+ disabled: false,
142
+
143
+ readonly: false,
144
+
145
+ invalid: false,
146
+
147
+ required: false,
148
+
149
+ validateTrigger: 'blur',
150
+
151
+ clearable: false,
152
+
153
+ showCount: false,
154
+
155
+ visibilityToggle: true,
156
+
157
+ showValidateMessage: true,
158
+
159
+ labelPosition: 'top',
160
+
161
+ },
162
+
163
+ )
164
+
165
+
166
+
167
+ const emit = defineEmits<{
168
+
169
+ validate: [payload: { valid: boolean; message?: string }]
170
+
171
+ blur: [event: FocusEvent]
172
+
173
+ focus: [event: FocusEvent]
174
+
175
+ pressEnter: [event: KeyboardEvent]
176
+
177
+ clear: []
178
+
179
+ }>()
180
+
181
+
182
+
183
+ const attrs = useAttrs()
184
+
185
+ const slots = useSlots()
186
+
187
+ const formContext = useRsFormContext()
188
+
189
+ const fieldId = useId()
190
+
191
+
192
+
193
+ const touched = ref(false)
194
+
195
+ const autoInvalid = ref(false)
196
+
197
+ const autoMessage = ref('')
198
+
199
+ const passwordVisible = ref(false)
200
+
201
+
202
+
203
+ const hasPrefix = computed(() => Boolean(props.prefix || slots.prefix))
204
+
205
+
206
+
207
+ const resolvedId = computed(() => props.id ?? fieldId)
208
+
209
+ const resolvedLabelPosition = computed(
210
+
211
+ () => props.labelPosition ?? formContext?.labelPosition.value ?? 'top',
212
+
213
+ )
214
+
215
+ const resolvedLabelAlign = computed(() => formContext?.labelAlign.value ?? 'start')
216
+
217
+ const errorId = computed(() => `${resolvedId.value}-error`)
218
+
219
+
220
+
221
+ const displayMessage = computed(() => {
222
+
223
+ if (props.errorMessage) return props.errorMessage
224
+
225
+ if (props.showValidateMessage && autoInvalid.value) return autoMessage.value
226
+
227
+ return ''
228
+
229
+ })
230
+
231
+
232
+
233
+ const isInvalid = computed(() => props.invalid || autoInvalid.value)
234
+
235
+ const resolvedDisabled = computed(() => props.disabled || formContext?.disabled.value || false)
236
+
237
+ const resolvedReadonly = computed(() => props.readonly)
238
+
239
+ const resolvedSize = useResolvedRsComponentSize(() => props.size)
240
+
241
+ const resolvedRadius = useResolvedRsRadius(() => props.radius, 'sm')
242
+
243
+ const radiusStyle = computed(() => ({
244
+ '--rs-input-radius': rsRadiusCss(resolvedRadius.value),
245
+ }))
246
+
247
+ const resolvedShowPassword = computed(
248
+
249
+ () => props.type === 'password' && props.visibilityToggle,
250
+
251
+ )
252
+
253
+ const resolvedInputType = computed(() => {
254
+
255
+ if (props.type === 'password' && passwordVisible.value) return 'text'
256
+
257
+ return props.type
258
+
259
+ })
260
+
261
+
262
+
263
+ const hasValue = computed(() => model.value.length > 0)
264
+
265
+ const showClearButton = computed(
266
+
267
+ () => props.clearable && hasValue.value && !resolvedDisabled.value && !resolvedReadonly.value,
268
+
269
+ )
270
+
271
+ const showPasswordToggle = computed(
272
+
273
+ () => props.type === 'password' && resolvedShowPassword.value && !resolvedDisabled.value,
274
+
275
+ )
276
+
277
+ const showCountDisplay = computed(() => props.showCount && props.maxlength != null)
278
+
279
+ const countText = computed(() => `${model.value.length} / ${props.maxlength}`)
280
+
281
+
282
+
283
+ const hasSuffix = computed(
284
+
285
+ () =>
286
+
287
+ Boolean(
288
+
289
+ props.suffix ||
290
+
291
+ slots.suffix ||
292
+
293
+ showClearButton.value ||
294
+
295
+ showPasswordToggle.value ||
296
+
297
+ showCountDisplay.value,
298
+
299
+ ),
300
+
301
+ )
302
+
303
+
304
+
305
+ const fieldStyle = computed(() => {
306
+
307
+ if (resolvedLabelPosition.value !== 'left') return undefined
308
+
309
+ const labelWidth = formContext?.labelWidth.value
310
+
311
+ if (!labelWidth) return undefined
312
+
313
+ return { '--rs-field-label-width': labelWidth } as Record<string, string>
314
+
315
+ })
316
+
317
+
318
+
319
+ function runValidate() {
320
+
321
+ if (!props.rule && !props.validator && !props.required) {
322
+
323
+ autoInvalid.value = false
324
+
325
+ autoMessage.value = ''
326
+
327
+ emit('validate', { valid: true })
328
+
329
+ return true
330
+
331
+ }
332
+
333
+
334
+
335
+ const result = runInputValidation(
336
+
337
+ model.value,
338
+
339
+ {
340
+
341
+ rule: props.rule,
342
+
343
+ required: props.required,
344
+
345
+ validator: props.validator,
346
+
347
+ },
348
+
349
+ t,
350
+
351
+ )
352
+
353
+
354
+
355
+ autoInvalid.value = !result.valid
356
+
357
+ autoMessage.value = result.message ?? ''
358
+
359
+ emit('validate', result)
360
+
361
+ return result.valid
362
+
363
+ }
364
+
365
+
366
+
367
+ function onInput() {
368
+
369
+ if (
370
+
371
+ touched.value &&
372
+
373
+ (props.validateTrigger === 'input' || props.validateTrigger === 'both')
374
+
375
+ ) {
376
+
377
+ runValidate()
378
+
379
+ }
380
+
381
+ }
382
+
383
+ /** 受控写入:始终取 DOM string value,不依赖 v-model 对 type=number 的隐式行为。 */
384
+ function onNativeInput(event: Event) {
385
+ const el = event.target as HTMLInputElement | null
386
+ model.value = el?.value ?? ''
387
+ onInput()
388
+ }
389
+
390
+
391
+
392
+ function onBlur(event: FocusEvent) {
393
+
394
+ touched.value = true
395
+
396
+ if (props.validateTrigger === 'blur' || props.validateTrigger === 'both') {
397
+
398
+ runValidate()
399
+
400
+ }
401
+
402
+ emit('blur', event)
403
+
404
+ }
405
+
406
+
407
+
408
+ function onFocus(event: FocusEvent) {
409
+
410
+ emit('focus', event)
411
+
412
+ }
413
+
414
+
415
+
416
+ function onKeydown(event: KeyboardEvent) {
417
+
418
+ if (event.key === 'Enter') {
419
+
420
+ emit('pressEnter', event)
421
+
422
+ }
423
+
424
+ }
425
+
426
+
427
+
428
+ function onClear() {
429
+
430
+ model.value = ''
431
+
432
+ emit('clear')
433
+
434
+ if (touched.value) runValidate()
435
+
436
+ }
437
+
438
+
439
+
440
+ function togglePasswordVisible() {
441
+
442
+ passwordVisible.value = !passwordVisible.value
443
+
444
+ }
445
+
446
+
447
+
448
+ function setValue(value: unknown): void {
449
+
450
+ model.value = String(value ?? '')
451
+
452
+ }
453
+
454
+
455
+
456
+ function clearValidation(): void {
457
+
458
+ touched.value = false
459
+
460
+ autoInvalid.value = false
461
+
462
+ autoMessage.value = ''
463
+
464
+ }
465
+
466
+
467
+
468
+ useRsFormField(() => ({
469
+
470
+ getValue: () => model.value,
471
+
472
+ setValue,
473
+
474
+ validate: () => ({ valid: runValidate() }),
475
+
476
+ clearValidation,
477
+
478
+ }))
479
+
480
+
481
+
482
+ defineExpose({
483
+
484
+ validate: runValidate,
485
+
486
+ clearValidation,
487
+
488
+ setValue,
489
+
490
+ })
491
+
492
+ </script>
493
+
494
+
495
+
496
+ <template>
497
+
498
+ <div
499
+
500
+ class="rs-field"
501
+
502
+ :class="[
503
+
504
+ `rs-field--label-${resolvedLabelPosition}`,
505
+
506
+ resolvedLabelPosition === 'left' && resolvedLabelAlign === 'end'
507
+
508
+ ? 'rs-field--label-align-end'
509
+
510
+ : undefined,
511
+
512
+ `rs-input-field--${resolvedSize}`,
513
+
514
+ ]"
515
+
516
+ :style="[fieldStyle, radiusStyle]"
517
+
518
+ >
519
+
520
+ <span v-if="label" class="rs-field__label">
521
+
522
+ <label :for="resolvedId">{{ label }}</label>
523
+
524
+ <span v-if="required" class="rs-field__required" aria-hidden="true">*</span>
525
+
526
+ </span>
527
+
528
+
529
+
530
+ <div class="rs-input-field">
531
+
532
+ <div
533
+
534
+ class="rs-input-group"
535
+
536
+ :class="{
537
+
538
+ 'rs-input-group--invalid': isInvalid,
539
+
540
+ 'rs-input-group--disabled': resolvedDisabled,
541
+
542
+ 'rs-input-group--readonly': resolvedReadonly,
543
+
544
+ [`rs-input-group--${resolvedSize}`]: true,
545
+
546
+ 'rs-input-group--has-prefix': hasPrefix,
547
+
548
+ 'rs-input-group--has-suffix': hasSuffix,
549
+
550
+ }"
551
+
552
+ >
553
+
554
+ <span
555
+
556
+ v-if="hasPrefix"
557
+
558
+ class="rs-input-group__affix rs-input-group__affix--prefix"
559
+
560
+ >
561
+
562
+ <slot name="prefix">{{ prefix }}</slot>
563
+
564
+ </span>
565
+
566
+ <input
567
+
568
+ v-bind="attrs"
569
+
570
+ :id="resolvedId"
571
+
572
+ :value="model"
573
+
574
+ class="rs-input-group__control"
575
+
576
+ :class="`rs-input-group__control--${resolvedSize}`"
577
+
578
+ :type="resolvedInputType"
579
+
580
+ :placeholder="placeholder"
581
+
582
+ :disabled="resolvedDisabled"
583
+
584
+ :readonly="resolvedReadonly"
585
+
586
+ :maxlength="maxlength"
587
+
588
+ :aria-invalid="isInvalid || undefined"
589
+
590
+ :aria-describedby="displayMessage ? errorId : undefined"
591
+
592
+ @input="onNativeInput"
593
+
594
+ @blur="onBlur"
595
+
596
+ @focus="onFocus"
597
+
598
+ @keydown="onKeydown"
599
+
600
+ />
601
+
602
+ <span
603
+
604
+ v-if="hasSuffix"
605
+
606
+ class="rs-input-group__affix rs-input-group__affix--suffix"
607
+
608
+ >
609
+
610
+ <slot name="suffix">{{ suffix }}</slot>
611
+
612
+ <span
613
+
614
+ v-if="showCountDisplay"
615
+
616
+ class="rs-input-group__count"
617
+
618
+ aria-hidden="true"
619
+
620
+ >
621
+
622
+ {{ countText }}
623
+
624
+ </span>
625
+
626
+ <button
627
+
628
+ v-if="showClearButton"
629
+
630
+ type="button"
631
+
632
+ class="rs-input-group__action"
633
+
634
+ :aria-label="t('input.clear')"
635
+
636
+ @pointerdown.prevent
637
+
638
+ @click="onClear"
639
+
640
+ >
641
+
642
+ <RsIcon name="x" :size="14" />
643
+
644
+ </button>
645
+
646
+ <button
647
+
648
+ v-if="showPasswordToggle"
649
+
650
+ type="button"
651
+
652
+ class="rs-input-group__action"
653
+
654
+ :aria-label="passwordVisible ? t('input.hidePassword') : t('input.showPassword')"
655
+
656
+ :aria-pressed="passwordVisible"
657
+
658
+ @pointerdown.prevent
659
+
660
+ @click="togglePasswordVisible"
661
+
662
+ >
663
+
664
+ <RsIcon :name="passwordVisible ? 'eye-off' : 'eye'" :size="14" />
665
+
666
+ </button>
667
+
668
+ </span>
669
+
670
+ </div>
671
+
672
+ </div>
673
+
674
+
675
+
676
+ <p
677
+
678
+ v-if="displayMessage"
679
+
680
+ :id="errorId"
681
+
682
+ class="rs-input-field__error"
683
+
684
+ role="alert"
685
+
686
+ >
687
+
688
+ {{ displayMessage }}
689
+
690
+ </p>
691
+
692
+
693
+
694
+ <span v-if="hint" class="rs-field__hint">{{ hint }}</span>
695
+
696
+ </div>
697
+
698
+ </template>
699
+
700
+
701
+
702
+ <style scoped>
703
+
704
+ .rs-input-field {
705
+
706
+ width: 100%;
707
+
708
+ }
709
+
710
+ .rs-field--label-left .rs-input-field {
711
+
712
+ grid-column: 2;
713
+
714
+ min-width: 0;
715
+
716
+ }
717
+
718
+ .rs-field--label-left .rs-input-field__error {
719
+
720
+ grid-column: 2;
721
+
722
+ }
723
+
724
+ .rs-field--label-left .rs-field__label {
725
+
726
+ display: flex;
727
+
728
+ align-items: center;
729
+
730
+ min-height: var(--rs-control-height-md);
731
+
732
+ }
733
+
734
+ .rs-input-field--ssm.rs-field--label-left .rs-field__label {
735
+
736
+ min-height: var(--rs-control-height-ssm);
737
+
738
+ }
739
+
740
+ .rs-input-field--sm.rs-field--label-left .rs-field__label {
741
+
742
+ min-height: var(--rs-control-height-sm);
743
+
744
+ }
745
+
746
+ .rs-input-field--lg.rs-field--label-left .rs-field__label {
747
+
748
+ min-height: var(--rs-control-height-lg);
749
+
750
+ }
751
+
752
+ .rs-input-group {
753
+
754
+ display: flex;
755
+
756
+ align-items: center;
757
+
758
+ width: 100%;
759
+
760
+ min-height: var(--rs-control-height-md);
761
+
762
+ border-radius: var(--rs-input-radius, var(--rs-radius-sm));
763
+
764
+ border: 1px solid var(--rs-input-border, var(--rs-border));
765
+
766
+ background: var(--rs-input-bg);
767
+
768
+ box-shadow: var(--rs-input-shadow, none);
769
+
770
+ /* 裁切 autofill 矩形 inset,避免盖住圆角边框 */
771
+
772
+ overflow: hidden;
773
+
774
+ transition:
775
+
776
+ border-color var(--rs-transition-fast),
777
+
778
+ box-shadow var(--rs-transition-fast),
779
+
780
+ background var(--rs-transition-fast);
781
+
782
+ }
783
+
784
+ .rs-input-group--ssm {
785
+
786
+ min-height: var(--rs-control-height-ssm);
787
+
788
+ }
789
+
790
+ .rs-input-group--sm {
791
+
792
+ min-height: var(--rs-control-height-sm);
793
+
794
+ }
795
+
796
+ .rs-input-group--lg {
797
+
798
+ min-height: var(--rs-control-height-lg);
799
+
800
+ }
801
+
802
+ .rs-input-group:hover:not(.rs-input-group--disabled):not(.rs-input-group--readonly):not(:focus-within) {
803
+
804
+ border-color: var(--rs-input-border-hover, var(--rs-border));
805
+
806
+ }
807
+
808
+ .rs-input-group:focus-within {
809
+
810
+ border-color: var(--rs-focus-border, var(--rs-primary));
811
+
812
+ background: var(--rs-input-bg);
813
+
814
+ box-shadow:
815
+
816
+ var(--rs-input-shadow, none),
817
+
818
+ 0 0 0 var(--rs-focus-ring-width, 2px) var(--rs-focus-ring);
819
+
820
+ }
821
+
822
+ .rs-input-group--invalid {
823
+
824
+ border-color: var(--rs-danger);
825
+
826
+ }
827
+
828
+ .rs-input-group--invalid:focus-within {
829
+
830
+ border-color: var(--rs-danger);
831
+
832
+ box-shadow:
833
+
834
+ var(--rs-input-shadow, none),
835
+
836
+ 0 0 0 var(--rs-focus-ring-width, 2px)
837
+
838
+ color-mix(in srgb, var(--rs-danger) 14%, transparent);
839
+
840
+ }
841
+
842
+ .rs-input-group--disabled {
843
+
844
+ opacity: 0.38;
845
+
846
+ cursor: not-allowed;
847
+
848
+ background: var(--rs-surface-hover);
849
+
850
+ }
851
+
852
+ .rs-input-group--readonly {
853
+
854
+ background: var(--rs-surface-hover);
855
+
856
+ cursor: default;
857
+
858
+ }
859
+
860
+ .rs-input-group__affix {
861
+
862
+ display: inline-flex;
863
+
864
+ align-items: center;
865
+
866
+ flex-shrink: 0;
867
+
868
+ color: var(--rs-muted);
869
+
870
+ font-size: var(--rs-font-size-sm);
871
+
872
+ white-space: nowrap;
873
+
874
+ user-select: none;
875
+
876
+ }
877
+
878
+ .rs-input-group__affix--prefix {
879
+
880
+ padding-left: var(--rs-space-md);
881
+
882
+ }
883
+
884
+ .rs-input-group__affix--suffix {
885
+
886
+ gap: var(--rs-space-xs);
887
+
888
+ padding-right: var(--rs-space-sm);
889
+
890
+ }
891
+
892
+ .rs-input-group--ssm .rs-input-group__affix--prefix {
893
+
894
+ padding-left: var(--rs-space-xs);
895
+
896
+ }
897
+
898
+ .rs-input-group--ssm .rs-input-group__affix--suffix {
899
+
900
+ padding-right: var(--rs-space-xs);
901
+
902
+ }
903
+
904
+ .rs-input-group--sm .rs-input-group__affix--prefix {
905
+
906
+ padding-left: var(--rs-space-sm);
907
+
908
+ }
909
+
910
+ .rs-input-group--sm .rs-input-group__affix--suffix {
911
+
912
+ padding-right: var(--rs-space-xs);
913
+
914
+ }
915
+
916
+ .rs-input-group--lg .rs-input-group__affix--prefix {
917
+
918
+ padding-left: var(--rs-space-lg);
919
+
920
+ }
921
+
922
+ .rs-input-group--lg .rs-input-group__affix--suffix {
923
+
924
+ padding-right: var(--rs-space-md);
925
+
926
+ }
927
+
928
+ .rs-input-group__count {
929
+
930
+ font-size: var(--rs-font-size-xs);
931
+
932
+ color: var(--rs-muted);
933
+
934
+ font-variant-numeric: tabular-nums;
935
+
936
+ }
937
+
938
+ .rs-input-group__action {
939
+
940
+ display: inline-flex;
941
+
942
+ align-items: center;
943
+
944
+ justify-content: center;
945
+
946
+ width: 1.25rem;
947
+
948
+ height: 1.25rem;
949
+
950
+ padding: 0;
951
+
952
+ border: none;
953
+
954
+ border-radius: var(--rs-radius-xs);
955
+
956
+ background: transparent;
957
+
958
+ color: var(--rs-muted);
959
+
960
+ cursor: pointer;
961
+
962
+ transition:
963
+
964
+ color var(--rs-transition-fast),
965
+
966
+ background var(--rs-transition-fast);
967
+
968
+ }
969
+
970
+ .rs-input-group__action:hover {
971
+
972
+ color: var(--rs-text);
973
+
974
+ background: var(--rs-item-hover);
975
+
976
+ }
977
+
978
+ .rs-input-group__action:focus-visible {
979
+
980
+ outline: none;
981
+
982
+ box-shadow: 0 0 0 var(--rs-focus-ring-width, 2px) var(--rs-focus-ring);
983
+
984
+ }
985
+
986
+ .rs-input-group__control {
987
+
988
+ flex: 1;
989
+
990
+ min-width: 0;
991
+
992
+ width: 100%;
993
+
994
+ min-height: inherit;
995
+
996
+ padding: 0 var(--rs-space-md);
997
+
998
+ border: none;
999
+
1000
+ outline: none;
1001
+
1002
+ background: transparent;
1003
+
1004
+ color: var(--rs-text);
1005
+
1006
+ font-size: var(--rs-font-size-sm);
1007
+
1008
+ line-height: var(--rs-line-height-tight);
1009
+
1010
+ }
1011
+
1012
+ .rs-input-group__control--ssm {
1013
+
1014
+ padding: 0 var(--rs-space-xs);
1015
+
1016
+ font-size: var(--rs-font-size-xs);
1017
+
1018
+ }
1019
+
1020
+ .rs-input-group__control--sm {
1021
+
1022
+ padding: 0 var(--rs-space-sm);
1023
+
1024
+ font-size: var(--rs-font-size-xs);
1025
+
1026
+ }
1027
+
1028
+ .rs-input-group__control--lg {
1029
+
1030
+ padding: 0 var(--rs-space-lg);
1031
+
1032
+ font-size: var(--rs-font-size-base);
1033
+
1034
+ }
1035
+
1036
+ .rs-input-group--has-prefix .rs-input-group__control {
1037
+
1038
+ padding-left: var(--rs-space-sm);
1039
+
1040
+ }
1041
+
1042
+ .rs-input-group--has-suffix .rs-input-group__control {
1043
+
1044
+ padding-right: var(--rs-space-xs);
1045
+
1046
+ }
1047
+
1048
+ .rs-input-group__control::placeholder {
1049
+
1050
+ color: var(--rs-placeholder);
1051
+
1052
+ }
1053
+
1054
+ .rs-input-group__control:disabled {
1055
+
1056
+ cursor: not-allowed;
1057
+
1058
+ }
1059
+
1060
+ .rs-input-group__control:read-only {
1061
+
1062
+ cursor: default;
1063
+
1064
+ }
1065
+
1066
+ /* 浏览器 autofill 会给 input 注入独立背景色,与外层圆角容器不一致 */
1067
+
1068
+ .rs-input-group__control:-webkit-autofill,
1069
+
1070
+ .rs-input-group__control:-webkit-autofill:hover,
1071
+
1072
+ .rs-input-group__control:-webkit-autofill:focus,
1073
+
1074
+ .rs-input-group__control:-webkit-autofill:active {
1075
+
1076
+ -webkit-box-shadow: 0 0 0 1000px var(--rs-input-bg) inset;
1077
+
1078
+ box-shadow: 0 0 0 1000px var(--rs-input-bg) inset;
1079
+
1080
+ -webkit-text-fill-color: var(--rs-text);
1081
+
1082
+ caret-color: var(--rs-text);
1083
+
1084
+ transition: background-color 99999s ease-out 0s;
1085
+
1086
+ }
1087
+
1088
+ .rs-input-field__error {
1089
+
1090
+ margin: 0.25rem 0 0;
1091
+
1092
+ font-size: var(--rs-font-size-xs);
1093
+
1094
+ line-height: var(--rs-line-height-tight);
1095
+
1096
+ color: var(--rs-danger);
1097
+
1098
+ }
1099
+
1100
+ </style>
1101
+