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,1045 @@
1
+ <script setup lang="ts">
2
+
3
+ import { computed, ref, watch } from 'vue'
4
+
5
+ import { useRsI18n } from '../composables/useRsI18n'
6
+ import type { RsComponentSize, RsRadius } from '../theme/types'
7
+ import { RS_COMPONENT_SIZE_ICON_PX } from '../theme/types'
8
+
9
+ import RsIcon from './RsIcon.vue'
10
+ import { useRsFormContext, useRsFormField } from './form-utils'
11
+ import { useResolvedRsComponentSize } from './resolve-size'
12
+ import { rsRadiusCss, useResolvedRsRadius } from './resolve-radius'
13
+
14
+ import {
15
+
16
+ buildOptionDisabledMap,
17
+
18
+ buildOptionLabelMap,
19
+
20
+ filterSelectOptions,
21
+
22
+ flattenSelectOptions,
23
+
24
+ flattenSelectValues,
25
+
26
+ isSelectOptionGroup,
27
+ type RsSelectOptions,
28
+ } from './select-utils'
29
+
30
+ import {
31
+
32
+ ComboboxAnchor,
33
+
34
+ ComboboxContent,
35
+
36
+ ComboboxEmpty,
37
+
38
+ ComboboxGroup,
39
+
40
+ ComboboxInput,
41
+
42
+ ComboboxItem,
43
+
44
+ ComboboxItemIndicator,
45
+
46
+ ComboboxLabel,
47
+
48
+ ComboboxPortal,
49
+
50
+ ComboboxRoot,
51
+
52
+ ComboboxTrigger,
53
+
54
+ ComboboxViewport,
55
+
56
+ ComboboxVirtualizer,
57
+
58
+ useFilter,
59
+
60
+ } from './reka'
61
+
62
+
63
+
64
+ const model = defineModel<string | string[]>({ default: '' })
65
+
66
+
67
+
68
+ const props = withDefaults(
69
+
70
+ defineProps<{
71
+
72
+ options: RsSelectOptions
73
+
74
+ placeholder?: string
75
+
76
+ disabled?: boolean
77
+
78
+ id?: string
79
+
80
+ /** 下拉内搜索过滤 */
81
+
82
+ searchable?: boolean
83
+
84
+ /**
85
+ * 允许把搜索框内容作为自定义值提交(Enter 或点「使用 xxx」)。
86
+ * 专业工具常见:类型名 / 枚举等不在预设列表时仍可手输。
87
+ * 开启后若未显式关 searchable,将自动启用搜索框。
88
+ */
89
+ creatable?: boolean
90
+
91
+ /** 多选,v-model 为 string[] */
92
+
93
+ multiple?: boolean
94
+ required?: boolean
95
+
96
+ /** 显示清空按钮 */
97
+
98
+ clearable?: boolean
99
+
100
+ /** 启用虚拟滚动(长列表) */
101
+
102
+ virtual?: boolean
103
+
104
+ /** 超过该数量自动启用虚拟滚动 */
105
+
106
+ virtualThreshold?: number
107
+
108
+ /** 远程搜索:关闭内置过滤,通过 @search 拉取 options */
109
+
110
+ remote?: boolean
111
+
112
+ /** 远程加载中 */
113
+
114
+ loading?: boolean
115
+
116
+ searchPlaceholder?: string
117
+
118
+ emptyText?: string
119
+
120
+ loadingText?: string
121
+
122
+ size?: RsComponentSize
123
+
124
+ /** 圆角档位;默认 sm。直角 UI 传 `none`。 */
125
+
126
+ radius?: RsRadius
127
+
128
+ /**
129
+ * 下拉面板是否与触发器等宽。
130
+ *
131
+ * - 默认 `false`:面板 `min-width` 对齐触发器,可随选项文案变宽(受 max-width 限制),避免长文案大量省略。
132
+ * - `true`:面板强制与触发器同宽,适合表单栅格、筛选条等需要对齐的场景;长文案仍会 ellipsis。
133
+ *
134
+ * 注意:开启虚拟滚动时,列表项多为绝对定位,无法可靠把面板撑到「全局最长文案」宽度,
135
+ * 宽度可能随可见项变化或退回到触发器宽度;长列表若强依赖内容撑宽,请关闭 virtual,
136
+ * 或改用 `matchTriggerWidth` + 截断项 Tooltip。
137
+ */
138
+ matchTriggerWidth?: boolean
139
+
140
+ /** 根节点占满父级宽度(与按钮并排时用)。 */
141
+ block?: boolean
142
+
143
+ }>(),
144
+
145
+ {
146
+
147
+ disabled: false,
148
+
149
+ searchable: false,
150
+
151
+ creatable: false,
152
+
153
+ multiple: false,
154
+ required: false,
155
+
156
+ clearable: false,
157
+
158
+ virtual: false,
159
+
160
+ virtualThreshold: 50,
161
+
162
+ remote: false,
163
+
164
+ loading: false,
165
+
166
+ matchTriggerWidth: false,
167
+
168
+ block: false,
169
+
170
+ },
171
+
172
+ )
173
+
174
+
175
+
176
+ const emit = defineEmits<{
177
+
178
+ search: [query: string]
179
+
180
+ }>()
181
+
182
+
183
+
184
+ const { t } = useRsI18n()
185
+ const formContext = useRsFormContext()
186
+
187
+ const { contains } = useFilter({ sensitivity: 'base' })
188
+
189
+
190
+
191
+ const searchQuery = ref('')
192
+
193
+ /** 下拉显隐;支持 v-model:open(如表格多选关闭时提交) */
194
+ const open = defineModel<boolean>('open', { default: false })
195
+
196
+
197
+
198
+ const resolvedPlaceholder = computed(() => props.placeholder ?? t('select.placeholder'))
199
+
200
+ const resolvedSearchPlaceholder = computed(
201
+
202
+ () => props.searchPlaceholder ?? t('select.searchPlaceholder'),
203
+
204
+ )
205
+
206
+ const resolvedEmptyText = computed(() => props.emptyText ?? t('select.empty'))
207
+
208
+ const resolvedLoadingText = computed(() => props.loadingText ?? t('select.loading'))
209
+
210
+ /** creatable 默认带搜索框,便于手输 */
211
+ const isSearchable = computed(() => props.searchable || props.creatable)
212
+
213
+
214
+
215
+ const labelMap = computed(() => buildOptionLabelMap(props.options))
216
+
217
+ const disabledMap = computed(() => buildOptionDisabledMap(props.options))
218
+
219
+ const flatOptions = computed(() => flattenSelectOptions(props.options))
220
+
221
+ const flatCount = computed(() => flatOptions.value.length)
222
+
223
+
224
+
225
+ const useVirtual = computed(
226
+
227
+ () => props.virtual || flatCount.value > props.virtualThreshold,
228
+
229
+ )
230
+
231
+ const useManualFilter = computed(() => props.remote || useVirtual.value)
232
+
233
+
234
+ const displayOptions = computed(() => {
235
+ if (props.remote) return props.options
236
+ if (!useManualFilter.value) return props.options
237
+ return filterSelectOptions(props.options, searchQuery.value, contains)
238
+ })
239
+
240
+ const createValue = computed(() => searchQuery.value.trim())
241
+
242
+ const canCreate = computed(() => {
243
+ if (!props.creatable) return false
244
+ const q = createValue.value
245
+ if (!q) return false
246
+ const lower = q.toLowerCase()
247
+ return !flatOptions.value.some(
248
+ (opt) => opt.value.toLowerCase() === lower || String(opt.label).toLowerCase() === lower,
249
+ )
250
+ })
251
+
252
+ const createOptionLabel = computed(() =>
253
+ t('select.createOption', '使用 “{query}”').replace('{query}', createValue.value),
254
+ )
255
+
256
+ const virtualValues = computed(() => {
257
+ const values = flattenSelectValues(displayOptions.value)
258
+ if (!canCreate.value) return values
259
+ return [createValue.value, ...values.filter((v) => String(v) !== createValue.value)]
260
+ })
261
+
262
+
263
+
264
+ const selectedValues = computed<string[]>(() => {
265
+
266
+ if (props.multiple) {
267
+
268
+ const value = model.value
269
+
270
+ if (Array.isArray(value)) return value.map(String)
271
+
272
+ return value ? [String(value)] : []
273
+
274
+ }
275
+
276
+ const value = model.value
277
+
278
+ return value !== '' && value !== undefined && value !== null ? [String(value)] : []
279
+
280
+ })
281
+
282
+
283
+
284
+ const hasValue = computed(() => selectedValues.value.length > 0)
285
+ const resolvedDisabled = computed(() => props.disabled || formContext?.disabled.value || false)
286
+ const resolvedSize = useResolvedRsComponentSize(() => props.size)
287
+ const resolvedRadius = useResolvedRsRadius(() => props.radius, 'sm')
288
+ const triggerIconSize = computed(() => RS_COMPONENT_SIZE_ICON_PX[resolvedSize.value])
289
+ const rootStyle = computed(() => ({
290
+ '--rs-select-radius': rsRadiusCss(resolvedRadius.value),
291
+ }))
292
+
293
+
294
+
295
+ const singleDisplayLabel = computed(() => {
296
+
297
+ if (props.multiple || !hasValue.value) return ''
298
+
299
+ return labelMap.value.get(selectedValues.value[0]!) ?? selectedValues.value[0]!
300
+
301
+ })
302
+
303
+
304
+
305
+ const comboboxModel = computed<string | string[] | undefined>({
306
+
307
+ get() {
308
+
309
+ if (props.multiple) return selectedValues.value
310
+
311
+ return hasValue.value ? selectedValues.value[0] : undefined
312
+
313
+ },
314
+
315
+ set(value) {
316
+
317
+ if (props.multiple) {
318
+
319
+ model.value = Array.isArray(value) ? value.map(String) : []
320
+
321
+ } else {
322
+
323
+ model.value = value == null || value === '' ? '' : String(value)
324
+
325
+ }
326
+
327
+ },
328
+
329
+ })
330
+
331
+
332
+
333
+ watch(searchQuery, (query) => {
334
+
335
+ if (props.remote) emit('search', query)
336
+
337
+ })
338
+
339
+
340
+
341
+ watch(open, (isOpen) => {
342
+ if (isOpen) searchQuery.value = ''
343
+ })
344
+
345
+ function commitCreatedValue(value: string): void {
346
+ const next = value.trim()
347
+ if (!next) return
348
+ if (props.multiple) {
349
+ const current = selectedValues.value
350
+ model.value = current.includes(next) ? current : [...current, next]
351
+ } else {
352
+ model.value = next
353
+ open.value = false
354
+ }
355
+ searchQuery.value = ''
356
+ }
357
+
358
+ function onSearchKeydown(event: KeyboardEvent): void {
359
+ if (event.key !== 'Enter' || event.isComposing) return
360
+ if (!canCreate.value) return
361
+ // 无精确匹配时 Enter 提交手输值;有匹配时交给 Combobox 默认选中行为
362
+ event.preventDefault()
363
+ event.stopPropagation()
364
+ commitCreatedValue(createValue.value)
365
+ }
366
+
367
+
368
+
369
+ function onClear(event: MouseEvent) {
370
+
371
+ event.preventDefault()
372
+
373
+ event.stopPropagation()
374
+
375
+ model.value = props.multiple ? [] : ''
376
+
377
+ }
378
+
379
+
380
+
381
+ function removeTag(value: string, event: MouseEvent) {
382
+
383
+ event.preventDefault()
384
+
385
+ event.stopPropagation()
386
+
387
+ if (!props.multiple) return
388
+
389
+ model.value = selectedValues.value.filter((item) => item !== value)
390
+
391
+ }
392
+
393
+ function setValue(value: unknown): void {
394
+ if (props.multiple) {
395
+ if (Array.isArray(value)) {
396
+ model.value = value.map(String)
397
+ } else if (value) {
398
+ model.value = [String(value)]
399
+ } else {
400
+ model.value = []
401
+ }
402
+ return
403
+ }
404
+ model.value = value == null ? '' : String(value)
405
+ }
406
+
407
+ function clearValidation(): void {
408
+ searchQuery.value = ''
409
+ }
410
+
411
+ useRsFormField(() => ({
412
+ getValue: () => model.value,
413
+ setValue,
414
+ validate: () => ({ valid: props.multiple ? selectedValues.value.length > 0 || !props.required : Boolean(model.value) || !props.required }),
415
+ clearValidation,
416
+ }))
417
+
418
+ defineExpose({
419
+ setValue,
420
+ clearValidation,
421
+ })
422
+
423
+ </script>
424
+
425
+
426
+
427
+ <template>
428
+
429
+ <ComboboxRoot
430
+
431
+ v-model="comboboxModel"
432
+
433
+ v-model:open="open"
434
+
435
+ class="rs-select"
436
+
437
+ :class="{
438
+
439
+ 'rs-select--multiple': multiple,
440
+
441
+ 'rs-select--searchable': isSearchable,
442
+ 'rs-select--creatable': creatable,
443
+
444
+ 'rs-select--block': block,
445
+
446
+ [`rs-select--${resolvedSize}`]: true,
447
+
448
+ }"
449
+
450
+ :style="rootStyle"
451
+
452
+ :multiple="multiple"
453
+
454
+ :disabled="resolvedDisabled"
455
+
456
+ :ignore-filter="useManualFilter"
457
+
458
+ :reset-search-term-on-select="!multiple"
459
+
460
+ open-on-click
461
+
462
+ >
463
+
464
+ <ComboboxAnchor as-child>
465
+
466
+ <ComboboxTrigger :id="id" class="rs-select__trigger">
467
+
468
+ <span v-if="multiple" class="rs-select__value rs-select__value--multiple">
469
+
470
+ <template v-if="hasValue">
471
+
472
+ <span
473
+
474
+ v-for="value in selectedValues"
475
+
476
+ :key="value"
477
+
478
+ class="rs-select__tag"
479
+
480
+ >
481
+
482
+ <span class="rs-select__tag-label">{{ labelMap.get(value) ?? value }}</span>
483
+
484
+ <button
485
+
486
+ type="button"
487
+
488
+ class="rs-select__tag-remove"
489
+
490
+ :aria-label="t('select.clear')"
491
+
492
+ @pointerdown.stop
493
+
494
+ @click="removeTag(value, $event)"
495
+
496
+ >
497
+
498
+ <RsIcon name="x" :size="12" />
499
+
500
+ </button>
501
+
502
+ </span>
503
+
504
+ </template>
505
+
506
+ <span v-else class="rs-select__placeholder">{{ resolvedPlaceholder }}</span>
507
+
508
+ </span>
509
+
510
+ <span v-else class="rs-select__value">
511
+
512
+ <span v-if="hasValue" class="rs-select__single-label">{{ singleDisplayLabel }}</span>
513
+
514
+ <span v-else class="rs-select__placeholder">{{ resolvedPlaceholder }}</span>
515
+
516
+ </span>
517
+
518
+
519
+
520
+ <span class="rs-select__actions">
521
+
522
+ <button
523
+
524
+ v-if="clearable && hasValue && !resolvedDisabled"
525
+
526
+ type="button"
527
+
528
+ class="rs-select__clear"
529
+
530
+ :aria-label="t('select.clear')"
531
+
532
+ @pointerdown.stop
533
+
534
+ @click="onClear"
535
+
536
+ >
537
+
538
+ <RsIcon name="x" :size="14" />
539
+
540
+ </button>
541
+
542
+ <RsIcon name="chevron-down" :size="triggerIconSize" class="rs-select__icon" />
543
+
544
+ </span>
545
+
546
+ </ComboboxTrigger>
547
+
548
+ </ComboboxAnchor>
549
+
550
+
551
+
552
+ <ComboboxPortal>
553
+
554
+ <ComboboxContent
555
+ class="rs-select__content"
556
+ :class="{ 'rs-select__content--match-trigger': matchTriggerWidth }"
557
+ align="start"
558
+ :side-offset="4"
559
+ position="popper"
560
+ >
561
+
562
+ <div v-if="isSearchable" class="rs-select__search-bar">
563
+ <div class="rs-select__search-wrap">
564
+ <RsIcon name="search" :size="14" class="rs-select__search-icon" aria-hidden="true" />
565
+ <ComboboxInput
566
+ v-model="searchQuery"
567
+ class="rs-select__search"
568
+ :placeholder="resolvedSearchPlaceholder"
569
+ auto-focus
570
+ @keydown="onSearchKeydown"
571
+ />
572
+ </div>
573
+ </div>
574
+
575
+
576
+
577
+ <div v-if="loading" class="rs-select__status">{{ resolvedLoadingText }}</div>
578
+
579
+
580
+
581
+ <ComboboxEmpty v-if="!loading && !canCreate" class="rs-select__empty">
582
+
583
+ {{ resolvedEmptyText }}
584
+
585
+ </ComboboxEmpty>
586
+
587
+
588
+
589
+ <ComboboxViewport class="rs-select__viewport">
590
+
591
+ <ComboboxVirtualizer
592
+
593
+ v-if="useVirtual"
594
+
595
+ v-slot="{ option }"
596
+
597
+ :options="virtualValues"
598
+
599
+ :text-content="(value) => labelMap.get(String(value)) ?? String(value)"
600
+
601
+ :estimate-size="36"
602
+
603
+ >
604
+
605
+ <ComboboxItem
606
+ :value="option"
607
+ :disabled="disabledMap.get(String(option))"
608
+ :text-value="labelMap.get(String(option)) ?? String(option)"
609
+ class="rs-select__item"
610
+ :class="{ 'rs-select__item--create': canCreate && String(option) === createValue }"
611
+ >
612
+ <span class="rs-select__item-label">
613
+ {{
614
+ canCreate && String(option) === createValue
615
+ ? createOptionLabel
616
+ : (labelMap.get(String(option)) ?? option)
617
+ }}
618
+ </span>
619
+ <ComboboxItemIndicator class="rs-select__item-check">
620
+ <RsIcon name="check" :size="14" />
621
+ </ComboboxItemIndicator>
622
+ </ComboboxItem>
623
+
624
+ </ComboboxVirtualizer>
625
+
626
+
627
+
628
+ <template v-else>
629
+
630
+ <ComboboxItem
631
+ v-if="canCreate"
632
+ :value="createValue"
633
+ :text-value="createValue"
634
+ class="rs-select__item rs-select__item--create"
635
+ >
636
+ <span class="rs-select__item-label">{{ createOptionLabel }}</span>
637
+ <ComboboxItemIndicator class="rs-select__item-check">
638
+ <RsIcon name="check" :size="14" />
639
+ </ComboboxItemIndicator>
640
+ </ComboboxItem>
641
+
642
+ <template v-for="(entry, index) in displayOptions" :key="index">
643
+
644
+ <ComboboxGroup v-if="isSelectOptionGroup(entry)" class="rs-select__group">
645
+
646
+ <ComboboxLabel class="rs-select__group-label">
647
+
648
+ {{ entry.label }}
649
+
650
+ </ComboboxLabel>
651
+
652
+ <ComboboxItem
653
+ v-for="opt in entry.options"
654
+ :key="opt.value"
655
+ :value="opt.value"
656
+ :disabled="opt.disabled"
657
+ :text-value="opt.label"
658
+ class="rs-select__item"
659
+ >
660
+ <span class="rs-select__item-label">{{ opt.label }}</span>
661
+ <ComboboxItemIndicator class="rs-select__item-check">
662
+ <RsIcon name="check" :size="14" />
663
+ </ComboboxItemIndicator>
664
+ </ComboboxItem>
665
+
666
+ </ComboboxGroup>
667
+
668
+
669
+
670
+ <ComboboxItem
671
+ v-else
672
+ :key="entry.value"
673
+ :value="entry.value"
674
+ :disabled="entry.disabled"
675
+ :text-value="entry.label"
676
+ class="rs-select__item"
677
+ >
678
+ <span class="rs-select__item-label">{{ entry.label }}</span>
679
+ <ComboboxItemIndicator class="rs-select__item-check">
680
+ <RsIcon name="check" :size="14" />
681
+ </ComboboxItemIndicator>
682
+ </ComboboxItem>
683
+
684
+ </template>
685
+
686
+ </template>
687
+
688
+ </ComboboxViewport>
689
+
690
+ </ComboboxContent>
691
+
692
+ </ComboboxPortal>
693
+
694
+ </ComboboxRoot>
695
+
696
+ </template>
697
+
698
+
699
+
700
+ <style scoped>
701
+
702
+ .rs-select {
703
+
704
+ display: inline-flex;
705
+
706
+ width: 100%;
707
+
708
+ max-width: 20rem;
709
+
710
+ }
711
+
712
+ .rs-select__trigger {
713
+
714
+ display: inline-flex;
715
+
716
+ align-items: center;
717
+
718
+ justify-content: space-between;
719
+
720
+ gap: var(--rs-space-sm);
721
+
722
+ width: 100%;
723
+
724
+ min-height: var(--rs-control-height-md);
725
+
726
+ padding: 0 var(--rs-space-md);
727
+
728
+ border-radius: var(--rs-select-radius, var(--rs-radius-sm));
729
+
730
+ border: 1px solid var(--rs-input-border, var(--rs-border));
731
+
732
+ background: var(--rs-input-bg);
733
+
734
+ color: var(--rs-text);
735
+
736
+ font-size: var(--rs-font-size-sm);
737
+
738
+ line-height: var(--rs-line-height-normal);
739
+
740
+ text-align: left;
741
+
742
+ cursor: pointer;
743
+
744
+ box-shadow: var(--rs-input-shadow, none);
745
+
746
+ transition:
747
+
748
+ border-color var(--rs-transition-fast),
749
+
750
+ box-shadow var(--rs-transition-fast),
751
+
752
+ background var(--rs-transition-fast);
753
+
754
+ }
755
+
756
+ .rs-select--multiple .rs-select__trigger {
757
+
758
+ min-height: var(--rs-control-height-md);
759
+
760
+ height: auto;
761
+
762
+ padding-top: var(--rs-space-xs);
763
+
764
+ padding-bottom: var(--rs-space-xs);
765
+
766
+ }
767
+
768
+ .rs-select--block {
769
+ display: flex;
770
+ flex-direction: column;
771
+ width: 100%;
772
+ min-width: 0;
773
+ }
774
+
775
+ .rs-select--block .rs-select__anchor,
776
+ .rs-select--block .rs-select__trigger {
777
+ width: 100%;
778
+ }
779
+
780
+ .rs-select--ssm .rs-select__trigger {
781
+ min-height: var(--rs-control-height-ssm);
782
+ padding: 0 var(--rs-space-xs);
783
+ font-size: var(--rs-font-size-xs);
784
+ }
785
+
786
+ .rs-select--ssm.rs-select--multiple .rs-select__trigger {
787
+ min-height: var(--rs-control-height-ssm);
788
+ }
789
+
790
+ .rs-select--sm .rs-select__trigger {
791
+ min-height: var(--rs-control-height-sm);
792
+ padding: 0 var(--rs-space-sm);
793
+ font-size: var(--rs-font-size-xs);
794
+ }
795
+
796
+ .rs-select--sm.rs-select--multiple .rs-select__trigger {
797
+ min-height: var(--rs-control-height-sm);
798
+ }
799
+
800
+ .rs-select--lg .rs-select__trigger {
801
+ min-height: var(--rs-control-height-lg);
802
+ padding: 0 var(--rs-space-lg);
803
+ font-size: var(--rs-font-size-base);
804
+ }
805
+
806
+ .rs-select--lg.rs-select--multiple .rs-select__trigger {
807
+ min-height: var(--rs-control-height-lg);
808
+ }
809
+
810
+ .rs-select__trigger:hover:not([data-disabled]) {
811
+
812
+ border-color: var(--rs-input-border-hover, var(--rs-border));
813
+
814
+ }
815
+
816
+ .rs-select__trigger:focus-visible {
817
+
818
+ outline: none;
819
+
820
+ border-color: var(--rs-focus-border, var(--rs-primary));
821
+
822
+ background: var(--rs-input-bg);
823
+
824
+ box-shadow:
825
+
826
+ var(--rs-input-shadow, none),
827
+
828
+ 0 0 0 var(--rs-focus-ring-width, 2px) var(--rs-focus-ring);
829
+
830
+ }
831
+
832
+ .rs-select__trigger[data-disabled] {
833
+
834
+ opacity: 0.38;
835
+
836
+ cursor: not-allowed;
837
+
838
+ background: var(--rs-surface-hover);
839
+
840
+ }
841
+
842
+ .rs-select__value {
843
+
844
+ flex: 1;
845
+
846
+ min-width: 0;
847
+
848
+ text-align: left;
849
+
850
+ overflow: hidden;
851
+
852
+ text-overflow: ellipsis;
853
+
854
+ white-space: nowrap;
855
+
856
+ }
857
+
858
+ .rs-select__value--multiple {
859
+
860
+ display: flex;
861
+
862
+ flex-wrap: nowrap;
863
+
864
+ gap: var(--rs-space-xs);
865
+
866
+ align-items: center;
867
+
868
+ white-space: nowrap;
869
+
870
+ overflow: hidden;
871
+
872
+ }
873
+
874
+ .rs-select__placeholder {
875
+
876
+ color: var(--rs-placeholder);
877
+
878
+ }
879
+
880
+ .rs-select__single-label {
881
+
882
+ display: block;
883
+
884
+ overflow: hidden;
885
+
886
+ text-overflow: ellipsis;
887
+
888
+ white-space: nowrap;
889
+
890
+ }
891
+
892
+ .rs-select__tag {
893
+
894
+ display: inline-flex;
895
+
896
+ align-items: center;
897
+
898
+ gap: 0.125rem;
899
+
900
+ flex: 0 0 auto;
901
+
902
+ max-width: 10rem;
903
+
904
+ padding: 0.125rem 0.25rem 0.125rem 0.5rem;
905
+
906
+ border-radius: var(--rs-radius-xs);
907
+
908
+ border: 1px solid color-mix(in srgb, var(--rs-primary) 24%, transparent);
909
+
910
+ background: var(--rs-primary-container);
911
+
912
+ color: var(--rs-on-primary-container);
913
+
914
+ font-size: var(--rs-font-size-xs);
915
+
916
+ line-height: var(--rs-line-height-tight);
917
+
918
+ }
919
+
920
+ .rs-select__tag-label {
921
+
922
+ overflow: hidden;
923
+
924
+ text-overflow: ellipsis;
925
+
926
+ white-space: nowrap;
927
+
928
+ }
929
+
930
+ .rs-select__tag-remove {
931
+
932
+ display: inline-flex;
933
+
934
+ align-items: center;
935
+
936
+ justify-content: center;
937
+
938
+ width: 1rem;
939
+
940
+ height: 1rem;
941
+
942
+ padding: 0;
943
+
944
+ border: none;
945
+
946
+ border-radius: var(--rs-radius-xs);
947
+
948
+ background: transparent;
949
+
950
+ color: var(--rs-muted);
951
+
952
+ cursor: pointer;
953
+
954
+ transition:
955
+
956
+ color var(--rs-transition-fast),
957
+
958
+ background var(--rs-transition-fast);
959
+
960
+ }
961
+
962
+ .rs-select__tag-remove:hover {
963
+
964
+ color: var(--rs-on-primary-container);
965
+
966
+ background: color-mix(in srgb, var(--rs-primary) 16%, transparent);
967
+
968
+ }
969
+
970
+ .rs-select__actions {
971
+
972
+ display: inline-flex;
973
+
974
+ align-items: center;
975
+
976
+ gap: var(--rs-space-xs);
977
+
978
+ flex-shrink: 0;
979
+
980
+ }
981
+
982
+ .rs-select__clear {
983
+
984
+ display: inline-flex;
985
+
986
+ align-items: center;
987
+
988
+ justify-content: center;
989
+
990
+ width: 1.25rem;
991
+
992
+ height: 1.25rem;
993
+
994
+ padding: 0;
995
+
996
+ border: none;
997
+
998
+ border-radius: var(--rs-radius-xs);
999
+
1000
+ background: transparent;
1001
+
1002
+ color: var(--rs-muted);
1003
+
1004
+ cursor: pointer;
1005
+
1006
+ transition:
1007
+
1008
+ color var(--rs-transition-fast),
1009
+
1010
+ background var(--rs-transition-fast);
1011
+
1012
+ }
1013
+
1014
+ .rs-select__clear:hover {
1015
+
1016
+ color: var(--rs-text);
1017
+
1018
+ background: var(--rs-item-hover);
1019
+
1020
+ }
1021
+
1022
+ .rs-select__icon {
1023
+
1024
+ color: var(--rs-muted);
1025
+
1026
+ flex-shrink: 0;
1027
+
1028
+ transition: transform var(--rs-transition-fast);
1029
+
1030
+ }
1031
+
1032
+ .rs-select__trigger[data-state='open'] .rs-select__icon {
1033
+
1034
+ transform: rotate(180deg);
1035
+
1036
+ }
1037
+
1038
+ .rs-select__item--create .rs-select__item-label {
1039
+ color: var(--rs-primary);
1040
+ font-weight: 500;
1041
+ }
1042
+
1043
+ </style>
1044
+
1045
+