niuma-ui 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/CHANGELOG.md +50 -1
  2. package/NOTICE +2 -2
  3. package/package.json +13 -5
  4. package/src/__tests__/RsButton.spec.ts +47 -0
  5. package/src/__tests__/RsCard.spec.ts +160 -1
  6. package/src/__tests__/RsConfirmDialog.spec.ts +127 -0
  7. package/src/__tests__/RsContextMenu.spec.ts +12 -0
  8. package/src/__tests__/RsDateTimePicker.spec.ts +11 -0
  9. package/src/__tests__/RsDescriptions.spec.ts +50 -0
  10. package/src/__tests__/RsDialog.spec.ts +117 -0
  11. package/src/__tests__/RsDrawer.spec.ts +96 -3
  12. package/src/__tests__/RsDynamicTags.spec.ts +52 -0
  13. package/src/__tests__/RsForm.spec.ts +245 -1
  14. package/src/__tests__/RsInput.spec.ts +15 -1
  15. package/src/__tests__/RsMenu.spec.ts +406 -14
  16. package/src/__tests__/RsPagination.spec.ts +67 -2
  17. package/src/__tests__/RsSelect.spec.ts +42 -1
  18. package/src/__tests__/RsSwitch.spec.ts +44 -0
  19. package/src/__tests__/RsTable.a11y.spec.ts +83 -0
  20. package/src/__tests__/RsTable.dom-baseline.spec.ts +87 -0
  21. package/src/__tests__/RsTable.perf.spec.ts +23 -4
  22. package/src/__tests__/RsTable.spec.ts +63 -8
  23. package/src/__tests__/RsTable.ssr.spec.ts +71 -0
  24. package/src/__tests__/RsTable.summary.spec.ts +45 -0
  25. package/src/__tests__/RsTabs.spec.ts +102 -4
  26. package/src/__tests__/RsTerminal.spec.ts +1 -1
  27. package/src/__tests__/RsTimePicker.spec.ts +11 -0
  28. package/src/__tests__/RsUpload.spec.ts +62 -6
  29. package/src/__tests__/createRsDialog.spec.ts +145 -0
  30. package/src/__tests__/form-rules.spec.ts +76 -0
  31. package/src/__tests__/perf-budgets.ts +32 -0
  32. package/src/__tests__/rs-table-api.spec.ts +206 -0
  33. package/src/__tests__/rs-table-compat-matrix.spec.ts +108 -0
  34. package/src/__tests__/rs-table-echarts-adapter.spec.ts +43 -0
  35. package/src/__tests__/rs-table-grid-nav.spec.ts +63 -0
  36. package/src/__tests__/rs-table-isolation.spec.ts +158 -0
  37. package/src/__tests__/split-pane-utils.spec.ts +21 -0
  38. package/src/__tests__/table-chart-utils.spec.ts +88 -0
  39. package/src/__tests__/table-summary-utils.spec.ts +62 -0
  40. package/src/__tests__/table-utils.spec.ts +124 -0
  41. package/src/__tests__/theme.spec.ts +25 -0
  42. package/src/__tests__/useRsTableColumnResize.spec.ts +25 -0
  43. package/src/__tests__/useRsTableCore.perf.spec.ts +170 -0
  44. package/src/components/RsAlert.vue +212 -0
  45. package/src/components/RsButton.vue +145 -21
  46. package/src/components/RsCard.vue +200 -16
  47. package/src/components/RsConfirmDialog.vue +215 -28
  48. package/src/components/RsContextMenu.vue +51 -13
  49. package/src/components/RsDatePicker.vue +267 -34
  50. package/src/components/RsDateTimePicker.vue +19 -2
  51. package/src/components/RsDescriptions.vue +186 -0
  52. package/src/components/RsDescriptionsItem.vue +32 -0
  53. package/src/components/RsDialog.vue +363 -51
  54. package/src/components/RsDivider.vue +89 -0
  55. package/src/components/RsDrawer.vue +558 -33
  56. package/src/components/RsDropdown.vue +33 -2
  57. package/src/components/RsDynamicTags.vue +517 -0
  58. package/src/components/RsForm.vue +97 -8
  59. package/src/components/RsInput.vue +80 -82
  60. package/src/components/RsInputNumber.vue +64 -23
  61. package/src/components/RsLabel.vue +20 -5
  62. package/src/components/RsLoadingBar.vue +151 -0
  63. package/src/components/RsMenu.vue +73 -15
  64. package/src/components/RsMenuItems.vue +147 -9
  65. package/src/components/RsPagination.vue +93 -14
  66. package/src/components/RsRadio.vue +81 -0
  67. package/src/components/RsRadioItem.vue +156 -0
  68. package/src/components/RsSelect.vue +67 -58
  69. package/src/components/RsSplitPane.vue +69 -1
  70. package/src/components/RsSwitch.vue +179 -0
  71. package/src/components/RsTable.vue +653 -2555
  72. package/src/components/RsTabs.vue +415 -83
  73. package/src/components/RsTag.vue +191 -0
  74. package/src/components/RsTerminal.vue +3 -3
  75. package/src/components/RsTimePicker.vue +44 -5
  76. package/src/components/RsTimePickerColumns.vue +136 -77
  77. package/src/components/RsToaster.vue +58 -18
  78. package/src/components/RsTree.vue +39 -21
  79. package/src/components/RsUpload.vue +145 -12
  80. package/src/components/RsVNodeHost.vue +20 -0
  81. package/src/components/button-utils.ts +39 -0
  82. package/src/components/context-menu-utils.ts +6 -0
  83. package/src/components/date-picker-utils.ts +84 -4
  84. package/src/components/descriptions-utils.ts +24 -0
  85. package/src/components/dialog-utils.ts +147 -0
  86. package/src/components/dialog-window.ts +74 -9
  87. package/src/components/drawer-utils.ts +73 -0
  88. package/src/components/form-rules.ts +257 -0
  89. package/src/components/form-utils.ts +52 -2
  90. package/src/components/menu-utils.ts +11 -0
  91. package/src/components/overlay-utils.ts +3 -0
  92. package/src/components/radio-utils.ts +12 -0
  93. package/src/components/reka.ts +5 -0
  94. package/src/components/select-utils.ts +21 -2
  95. package/src/components/split-pane-utils.ts +65 -12
  96. package/src/components/table/RsTableBody.vue +217 -0
  97. package/src/components/table/RsTableBodyRow.vue +90 -1
  98. package/src/components/table/RsTableColGroup.vue +41 -0
  99. package/src/components/table/RsTableHeader.vue +158 -0
  100. package/src/components/table/RsTableSummaryRow.vue +52 -0
  101. package/src/components/table/rs-table-api.ts +99 -0
  102. package/src/components/table/rs-table-compat-matrix.ts +71 -0
  103. package/src/components/table/rs-table-echarts-adapter.ts +81 -0
  104. package/src/components/table/rs-table-feature-host.ts +178 -0
  105. package/src/components/table/rs-table-grid-nav.ts +122 -0
  106. package/src/components/table/rs-table-header-types.ts +47 -0
  107. package/src/components/table/rs-table-module-registry.ts +72 -0
  108. package/src/components/table/rs-table-props.ts +274 -0
  109. package/src/components/table/rs-table-view-context.ts +258 -0
  110. package/src/components/table/rs-table.css +84 -6
  111. package/src/components/table/table-chart-utils.ts +165 -0
  112. package/src/components/table/table-features.ts +284 -0
  113. package/src/components/table/table-summary-utils.ts +194 -0
  114. package/src/components/table-utils.ts +452 -2
  115. package/src/components/tabs-utils.ts +142 -2
  116. package/src/components/terminal-utils.ts +1 -1
  117. package/src/components/terminal-wheel.ts +1 -1
  118. package/src/components/time-picker-utils.ts +76 -3
  119. package/src/components/upload-utils.ts +122 -0
  120. package/src/components/virtual-list-utils.ts +15 -2
  121. package/src/composables/assembleRsTableApi.ts +79 -0
  122. package/src/composables/bindRsTableViewContext.ts +269 -0
  123. package/src/composables/createRsDialog.ts +357 -0
  124. package/src/composables/createRsTableEditEmitBridge.ts +38 -0
  125. package/src/composables/useRsLoadingBar.ts +36 -0
  126. package/src/composables/useRsTable.ts +134 -0
  127. package/src/composables/useRsTableA11y.ts +52 -0
  128. package/src/composables/useRsTableChartBridge.ts +79 -0
  129. package/src/composables/useRsTableColumnLayout.ts +320 -0
  130. package/src/composables/useRsTableColumnResize.ts +244 -0
  131. package/src/composables/useRsTableColumnVirtual.ts +180 -0
  132. package/src/composables/useRsTableColumns.ts +200 -0
  133. package/src/composables/useRsTableContextMenu.ts +257 -0
  134. package/src/composables/useRsTableCore.ts +256 -0
  135. package/src/composables/useRsTableEditActions.ts +606 -0
  136. package/src/composables/useRsTableEditLayer.ts +251 -0
  137. package/src/composables/useRsTableEngine.ts +357 -0
  138. package/src/composables/useRsTableGridKeyboard.ts +123 -0
  139. package/src/composables/useRsTableHeadless.ts +137 -0
  140. package/src/composables/useRsTableInteraction.ts +433 -0
  141. package/src/composables/useRsTableScrollHost.ts +75 -0
  142. package/src/composables/useRsTableScrollLayout.ts +255 -0
  143. package/src/composables/useRsTableSelectionSource.ts +89 -0
  144. package/src/composables/useRsTableShell.ts +416 -0
  145. package/src/composables/useRsTableShellChrome.ts +170 -0
  146. package/src/composables/useRsTableSummary.ts +113 -0
  147. package/src/composables/useRsTableViewProvide.ts +263 -0
  148. package/src/composables/useRsTableVirtual.ts +154 -0
  149. package/src/composables/useRsTabsNav.ts +4 -0
  150. package/src/composables/useTableEdit.ts +1 -1
  151. package/src/icons/lucide.ts +2 -2
  152. package/src/index.ts +309 -9
  153. package/src/locale/messages.ts +30 -0
  154. package/src/styles.css +305 -52
  155. package/src/theme/brand.example.css +21 -2
  156. package/src/theme/presets.ts +4 -0
  157. package/src/theme/types.ts +67 -0
  158. package/src/components/RsTableColgroup.vue +0 -17
  159. package/src/components/RsTableHeaderRow.vue +0 -30
@@ -63,9 +63,21 @@ function onSelect(value: AcceptableValue) {
63
63
  <template>
64
64
  <DropdownMenuRoot
65
65
  class="rs-dropdown"
66
- :class="{ 'rs-dropdown--action': !showSelected }"
66
+ :class="{ 'rs-dropdown--action': !showSelected || Boolean($slots.trigger) }"
67
67
  >
68
- <DropdownMenuTrigger class="rs-dropdown__trigger" :disabled="disabled">
68
+ <DropdownMenuTrigger
69
+ v-if="$slots.trigger"
70
+ class="rs-dropdown__trigger-slot"
71
+ :disabled="disabled"
72
+ as-child
73
+ >
74
+ <slot name="trigger" />
75
+ </DropdownMenuTrigger>
76
+ <DropdownMenuTrigger
77
+ v-else
78
+ class="rs-dropdown__trigger"
79
+ :disabled="disabled"
80
+ >
69
81
  <span class="rs-dropdown__value">
70
82
  <span
71
83
  class="rs-dropdown__label"
@@ -102,6 +114,25 @@ function onSelect(value: AcceptableValue) {
102
114
  min-width: auto;
103
115
  }
104
116
 
117
+ /* 自定义 #trigger:不套默认输入框铬边,由业务控制外观 */
118
+ .rs-dropdown__trigger-slot {
119
+ display: inline-flex;
120
+ max-width: 100%;
121
+ border: 0;
122
+ padding: 0;
123
+ margin: 0;
124
+ background: transparent;
125
+ color: inherit;
126
+ font: inherit;
127
+ line-height: inherit;
128
+ cursor: pointer;
129
+ }
130
+
131
+ .rs-dropdown__trigger-slot[data-disabled] {
132
+ opacity: 0.38;
133
+ cursor: not-allowed;
134
+ }
135
+
105
136
  .rs-dropdown__trigger {
106
137
  display: flex;
107
138
  align-items: center;
@@ -0,0 +1,517 @@
1
+ <script setup lang="ts">
2
+ import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
3
+ import { useRsI18n } from '../composables/useRsI18n'
4
+ import type { RsComponentSize, RsRadius } from '../theme/types'
5
+ import { useRsFormContext, useRsFormField } from './form-utils'
6
+ import {
7
+ buildLocalInputRules,
8
+ runFormFieldRules,
9
+ type RsFormRuleTrigger,
10
+ } from './form-rules'
11
+ import { rsRadiusCss, useResolvedRsRadius } from './resolve-radius'
12
+ import { useResolvedRsComponentSize } from './resolve-size'
13
+ import type { RsTagVariant } from './RsTag.vue'
14
+ import RsIcon from './RsIcon.vue'
15
+ import RsTag from './RsTag.vue'
16
+
17
+ export type RsDynamicTagsInputMode = 'trigger' | 'always'
18
+ export type RsDynamicTagsRejectReason = 'duplicate' | 'max'
19
+
20
+ const model = defineModel<string[]>({ default: () => [] })
21
+
22
+ const props = withDefaults(
23
+ defineProps<{
24
+ /** 字段名:匹配 RsForm.rules[name] */
25
+ name?: string
26
+ placeholder?: string
27
+ disabled?: boolean
28
+ required?: boolean
29
+ max?: number
30
+ /** 输入内容最大长度 */
31
+ maxlength?: number
32
+ /** 允许重复标签 */
33
+ allowDuplicate?: boolean
34
+ /**
35
+ * trigger:点击「+」后再输入(对齐 Naive DynamicTags)
36
+ * always:始终显示输入框(对齐 InputTag 常驻输入)
37
+ */
38
+ inputMode?: RsDynamicTagsInputMode
39
+ /** 失焦时是否提交当前输入 */
40
+ commitOnBlur?: boolean
41
+ size?: RsComponentSize
42
+ radius?: RsRadius
43
+ /** 标签视觉变体 */
44
+ tagVariant?: RsTagVariant
45
+ round?: boolean
46
+ }>(),
47
+ {
48
+ disabled: false,
49
+ required: false,
50
+ allowDuplicate: false,
51
+ inputMode: 'trigger',
52
+ commitOnBlur: true,
53
+ tagVariant: 'default',
54
+ round: false,
55
+ },
56
+ )
57
+
58
+ const emit = defineEmits<{
59
+ create: [value: string]
60
+ remove: [value: string, index: number]
61
+ reject: [reason: RsDynamicTagsRejectReason, value: string]
62
+ }>()
63
+
64
+ const { t } = useRsI18n()
65
+ const formContext = useRsFormContext()
66
+ const draft = ref('')
67
+ const editing = ref(props.inputMode === 'always')
68
+ const composing = ref(false)
69
+ const feedback = ref<RsDynamicTagsRejectReason | null>(null)
70
+ const autoMessage = ref('')
71
+ const inputRef = ref<HTMLInputElement | null>(null)
72
+ let feedbackTimer: ReturnType<typeof setTimeout> | null = null
73
+
74
+ const resolvedSize = useResolvedRsComponentSize(() => props.size)
75
+ const resolvedRadius = useResolvedRsRadius(() => props.radius, 'md')
76
+ const resolvedPlaceholder = computed(
77
+ () => props.placeholder ?? t('dynamicTags.placeholder'),
78
+ )
79
+
80
+ const canAddMore = computed(
81
+ () => props.max === undefined || model.value.length < props.max,
82
+ )
83
+
84
+ const showInput = computed(
85
+ () => !props.disabled && canAddMore.value && (props.inputMode === 'always' || editing.value),
86
+ )
87
+
88
+ const showTrigger = computed(
89
+ () =>
90
+ !props.disabled &&
91
+ canAddMore.value &&
92
+ props.inputMode === 'trigger' &&
93
+ !editing.value,
94
+ )
95
+
96
+ const hasError = computed(() => Boolean(autoMessage.value))
97
+
98
+ const rootClass = computed(() => [
99
+ 'rs-dynamic-tags',
100
+ `rs-dynamic-tags--${resolvedSize.value}`,
101
+ {
102
+ 'rs-dynamic-tags--disabled': props.disabled,
103
+ 'rs-dynamic-tags--editing': showInput.value,
104
+ 'rs-dynamic-tags--feedback': feedback.value != null,
105
+ 'rs-dynamic-tags--invalid': hasError.value,
106
+ },
107
+ ])
108
+
109
+ const rootStyle = computed(() => ({
110
+ '--rs-dynamic-tags-radius-local': rsRadiusCss(resolvedRadius.value),
111
+ }))
112
+
113
+ function clearFeedbackTimer(): void {
114
+ if (feedbackTimer) {
115
+ clearTimeout(feedbackTimer)
116
+ feedbackTimer = null
117
+ }
118
+ }
119
+
120
+ function flashFeedback(reason: RsDynamicTagsRejectReason, value: string): void {
121
+ feedback.value = reason
122
+ emit('reject', reason, value)
123
+ clearFeedbackTimer()
124
+ feedbackTimer = setTimeout(() => {
125
+ feedback.value = null
126
+ feedbackTimer = null
127
+ }, 520)
128
+ }
129
+
130
+ async function focusInput(): Promise<void> {
131
+ await nextTick()
132
+ inputRef.value?.focus()
133
+ }
134
+
135
+ function startEdit(): void {
136
+ if (props.disabled || !canAddMore.value) {
137
+ if (!canAddMore.value) flashFeedback('max', draft.value)
138
+ return
139
+ }
140
+ editing.value = true
141
+ void focusInput()
142
+ }
143
+
144
+ function stopEdit(): void {
145
+ draft.value = ''
146
+ if (props.inputMode === 'trigger') editing.value = false
147
+ }
148
+
149
+ function addTag(): boolean {
150
+ if (props.disabled) return false
151
+ if (!canAddMore.value) {
152
+ flashFeedback('max', draft.value)
153
+ return false
154
+ }
155
+ const value = draft.value.trim()
156
+ if (!value) return false
157
+ if (!props.allowDuplicate && model.value.includes(value)) {
158
+ flashFeedback('duplicate', value)
159
+ draft.value = ''
160
+ return false
161
+ }
162
+ model.value = [...model.value, value]
163
+ emit('create', value)
164
+ draft.value = ''
165
+ void runValidate('change')
166
+ return true
167
+ }
168
+
169
+ function commit(): void {
170
+ if (composing.value) return
171
+ const value = draft.value.trim()
172
+ if (!value) {
173
+ if (props.inputMode === 'trigger') stopEdit()
174
+ return
175
+ }
176
+ const ok = addTag()
177
+ if (ok) {
178
+ if (props.inputMode === 'trigger' && !canAddMore.value) stopEdit()
179
+ else void focusInput()
180
+ }
181
+ }
182
+
183
+ function removeTag(index: number): void {
184
+ if (props.disabled) return
185
+ const next = [...model.value]
186
+ const [removed] = next.splice(index, 1)
187
+ model.value = next
188
+ if (removed !== undefined) emit('remove', removed, index)
189
+ void runValidate('change')
190
+ }
191
+
192
+ function onKeydown(event: KeyboardEvent): void {
193
+ if (event.isComposing || composing.value) return
194
+ if (event.key === 'Enter') {
195
+ event.preventDefault()
196
+ commit()
197
+ } else if (event.key === 'Escape') {
198
+ event.preventDefault()
199
+ stopEdit()
200
+ } else if (event.key === 'Backspace' && !draft.value && model.value.length > 0) {
201
+ removeTag(model.value.length - 1)
202
+ }
203
+ }
204
+
205
+ function onBlur(): void {
206
+ if (composing.value) return
207
+ if (props.commitOnBlur && draft.value.trim()) {
208
+ commit()
209
+ } else if (props.inputMode === 'trigger') {
210
+ stopEdit()
211
+ }
212
+ void runValidate('blur')
213
+ }
214
+
215
+ async function runValidate(trigger: RsFormRuleTrigger = 'submit') {
216
+ const formRules = formContext?.getFieldRules(props.name) ?? []
217
+ const localRules = buildLocalInputRules({ required: props.required })
218
+ const rules = [...formRules, ...localRules]
219
+ if (!rules.length) {
220
+ autoMessage.value = ''
221
+ return { valid: true as const, name: props.name }
222
+ }
223
+ const result = await runFormFieldRules(model.value, rules, { trigger })
224
+ autoMessage.value = result.message ?? ''
225
+ return { valid: result.valid, message: result.message, name: props.name }
226
+ }
227
+
228
+ function setValue(value: unknown): void {
229
+ if (Array.isArray(value)) {
230
+ model.value = value.map((item) => String(item ?? ''))
231
+ return
232
+ }
233
+ if (value == null || value === '') {
234
+ model.value = []
235
+ return
236
+ }
237
+ model.value = [String(value)]
238
+ }
239
+
240
+ function clearValidation(): void {
241
+ autoMessage.value = ''
242
+ }
243
+
244
+ useRsFormField(() => ({
245
+ get name() {
246
+ return props.name
247
+ },
248
+ getValue: () => model.value,
249
+ setValue,
250
+ validate: (trigger) => runValidate(trigger ?? 'submit'),
251
+ clearValidation,
252
+ setError: (message: string) => {
253
+ autoMessage.value = message
254
+ },
255
+ }))
256
+
257
+ watch(
258
+ () => model.value,
259
+ () => {
260
+ if (autoMessage.value) void runValidate('change')
261
+ },
262
+ )
263
+
264
+ defineExpose({
265
+ validate: runValidate,
266
+ clearValidation,
267
+ setValue,
268
+ })
269
+
270
+ function onRootPointerDown(event: MouseEvent): void {
271
+ if (props.disabled) return
272
+ const target = event.target as HTMLElement | null
273
+ if (!target) return
274
+ // 关闭按钮、触发器、输入框自身不抢焦点逻辑
275
+ if (target.closest('.rs-tag__close, .rs-dynamic-tags__trigger, .rs-dynamic-tags__input')) {
276
+ return
277
+ }
278
+ if (showInput.value) {
279
+ void focusInput()
280
+ } else if (showTrigger.value) {
281
+ startEdit()
282
+ }
283
+ }
284
+
285
+ onBeforeUnmount(() => {
286
+ clearFeedbackTimer()
287
+ })
288
+ </script>
289
+
290
+ <template>
291
+ <div class="rs-dynamic-tags-field">
292
+ <div
293
+ :class="rootClass"
294
+ :style="rootStyle"
295
+ role="group"
296
+ :aria-disabled="disabled || undefined"
297
+ :aria-invalid="hasError || undefined"
298
+ :aria-label="t('dynamicTags.label')"
299
+ @pointerdown="onRootPointerDown"
300
+ >
301
+ <RsTag
302
+ v-for="(tag, index) in model"
303
+ :key="`${tag}-${index}`"
304
+ closable
305
+ :disabled="disabled"
306
+ :size="resolvedSize"
307
+ :radius="resolvedRadius"
308
+ :variant="tagVariant"
309
+ :round="round"
310
+ @close="removeTag(index)"
311
+ >
312
+ {{ tag }}
313
+ </RsTag>
314
+
315
+ <input
316
+ v-if="showInput"
317
+ ref="inputRef"
318
+ v-model="draft"
319
+ class="rs-dynamic-tags__input"
320
+ type="text"
321
+ :placeholder="resolvedPlaceholder"
322
+ :disabled="disabled"
323
+ :maxlength="maxlength"
324
+ :aria-label="resolvedPlaceholder"
325
+ @keydown="onKeydown"
326
+ @blur="onBlur"
327
+ @compositionstart="composing = true"
328
+ @compositionend="composing = false"
329
+ >
330
+
331
+ <button
332
+ v-else-if="showTrigger"
333
+ type="button"
334
+ class="rs-dynamic-tags__trigger"
335
+ :aria-label="t('dynamicTags.add')"
336
+ @click.stop="startEdit"
337
+ >
338
+ <RsIcon name="plus" :size="14" />
339
+ </button>
340
+ </div>
341
+ <p
342
+ v-if="autoMessage"
343
+ class="rs-dynamic-tags-field__error"
344
+ role="alert"
345
+ >
346
+ {{ autoMessage }}
347
+ </p>
348
+ </div>
349
+ </template>
350
+
351
+ <style scoped>
352
+ .rs-dynamic-tags-field {
353
+ display: flex;
354
+ flex-direction: column;
355
+ width: 100%;
356
+ min-width: 0;
357
+ }
358
+
359
+ .rs-dynamic-tags-field__error {
360
+ margin: 0.25rem 0 0;
361
+ font-size: var(--rs-font-size-xs);
362
+ line-height: var(--rs-line-height-tight);
363
+ color: var(--rs-danger);
364
+ }
365
+
366
+ .rs-dynamic-tags {
367
+ --rs-dynamic-tags-chip-height: 1.5rem;
368
+ display: flex;
369
+ flex-wrap: wrap;
370
+ align-items: center;
371
+ gap: var(--rs-dynamic-tags-gap, var(--rs-space-xs));
372
+ /* padding + chip + 1px*2 边框,避免 trigger/input 切换或输入时高度跳动 */
373
+ min-height: calc(
374
+ var(--rs-dynamic-tags-chip-height) + 2 * var(--rs-dynamic-tags-padding, var(--rs-space-xs)) +
375
+ 2px
376
+ );
377
+ padding: var(--rs-dynamic-tags-padding, var(--rs-space-xs));
378
+ border: 1px solid var(--rs-dynamic-tags-border, var(--rs-input-border, var(--rs-border)));
379
+ border-radius: var(
380
+ --rs-dynamic-tags-radius-local,
381
+ var(--rs-dynamic-tags-radius, var(--rs-radius))
382
+ );
383
+ background: var(--rs-dynamic-tags-bg, var(--rs-input-bg, var(--rs-surface)));
384
+ box-shadow: var(--rs-dynamic-tags-shadow, var(--rs-input-shadow, none));
385
+ box-sizing: border-box;
386
+ cursor: text;
387
+ transition:
388
+ border-color var(--rs-transition-fast),
389
+ box-shadow var(--rs-transition-fast),
390
+ background-color var(--rs-transition-fast);
391
+ }
392
+
393
+ .rs-dynamic-tags:hover:not(.rs-dynamic-tags--disabled) {
394
+ border-color: var(
395
+ --rs-dynamic-tags-border-hover,
396
+ var(--rs-input-border-hover, var(--rs-border))
397
+ );
398
+ }
399
+
400
+ .rs-dynamic-tags--ssm {
401
+ --rs-dynamic-tags-chip-height: 1.125rem;
402
+ }
403
+
404
+ .rs-dynamic-tags--sm {
405
+ --rs-dynamic-tags-chip-height: 1.25rem;
406
+ }
407
+
408
+ .rs-dynamic-tags--md {
409
+ --rs-dynamic-tags-chip-height: 1.5rem;
410
+ }
411
+
412
+ .rs-dynamic-tags--lg {
413
+ --rs-dynamic-tags-chip-height: 1.75rem;
414
+ }
415
+
416
+ .rs-dynamic-tags--editing:not(.rs-dynamic-tags--disabled),
417
+ .rs-dynamic-tags:focus-within:not(.rs-dynamic-tags--disabled) {
418
+ border-color: var(--rs-focus-border, var(--rs-primary));
419
+ box-shadow:
420
+ var(--rs-dynamic-tags-shadow, var(--rs-input-shadow, none)),
421
+ 0 0 0 var(--rs-focus-ring-width, 2px) var(--rs-focus-ring);
422
+ }
423
+
424
+ .rs-dynamic-tags--feedback,
425
+ .rs-dynamic-tags--invalid {
426
+ border-color: var(--rs-danger);
427
+ }
428
+
429
+ .rs-dynamic-tags--feedback {
430
+ animation: rs-dynamic-tags-shake 0.45s ease;
431
+ }
432
+
433
+ .rs-dynamic-tags--disabled {
434
+ opacity: 0.55;
435
+ cursor: not-allowed;
436
+ }
437
+
438
+ .rs-dynamic-tags__input {
439
+ flex: 1 1 6rem;
440
+ min-width: 4rem;
441
+ width: 0;
442
+ height: var(--rs-dynamic-tags-chip-height);
443
+ margin: 0;
444
+ padding: 0 var(--rs-space-xs);
445
+ border: 0;
446
+ outline: none;
447
+ background: transparent;
448
+ color: var(--rs-text);
449
+ font: inherit;
450
+ font-size: var(--rs-font-size-sm);
451
+ line-height: var(--rs-dynamic-tags-chip-height);
452
+ box-sizing: border-box;
453
+ box-shadow: none;
454
+ appearance: none;
455
+ }
456
+
457
+ .rs-dynamic-tags--ssm .rs-dynamic-tags__input,
458
+ .rs-dynamic-tags--sm .rs-dynamic-tags__input {
459
+ font-size: var(--rs-font-size-xs);
460
+ }
461
+
462
+ .rs-dynamic-tags__input::placeholder {
463
+ color: var(--rs-placeholder);
464
+ line-height: var(--rs-dynamic-tags-chip-height);
465
+ }
466
+
467
+ .rs-dynamic-tags__trigger {
468
+ display: inline-flex;
469
+ flex-shrink: 0;
470
+ align-items: center;
471
+ justify-content: center;
472
+ width: var(--rs-dynamic-tags-chip-height);
473
+ height: var(--rs-dynamic-tags-chip-height);
474
+ margin: 0;
475
+ padding: 0;
476
+ border: 1px dashed var(--rs-dynamic-tags-trigger-border, var(--rs-border));
477
+ border-radius: var(--rs-radius-sm);
478
+ background: transparent;
479
+ color: var(--rs-muted);
480
+ cursor: pointer;
481
+ outline: none;
482
+ box-sizing: border-box;
483
+ transition:
484
+ color var(--rs-transition-fast),
485
+ border-color var(--rs-transition-fast),
486
+ background-color var(--rs-transition-fast);
487
+ }
488
+
489
+ .rs-dynamic-tags__trigger:hover {
490
+ color: var(--rs-primary);
491
+ border-color: var(--rs-primary);
492
+ background: color-mix(in srgb, var(--rs-primary) 8%, transparent);
493
+ }
494
+
495
+ .rs-dynamic-tags__trigger:focus-visible {
496
+ box-shadow: 0 0 0 var(--rs-focus-ring-width, 2px) var(--rs-focus-ring);
497
+ }
498
+
499
+ @keyframes rs-dynamic-tags-shake {
500
+ 0%,
501
+ 100% {
502
+ transform: translateX(0);
503
+ }
504
+ 20% {
505
+ transform: translateX(-3px);
506
+ }
507
+ 40% {
508
+ transform: translateX(3px);
509
+ }
510
+ 60% {
511
+ transform: translateX(-2px);
512
+ }
513
+ 80% {
514
+ transform: translateX(2px);
515
+ }
516
+ }
517
+ </style>