@bitrix24/b24ui-nuxt 0.1.6 → 0.2.1

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 (38) hide show
  1. package/.nuxt/b24ui/alert.ts +18 -8
  2. package/.nuxt/b24ui/badge.ts +1 -1
  3. package/.nuxt/b24ui/content/description-list.ts +17 -7
  4. package/.nuxt/b24ui/index.ts +2 -0
  5. package/.nuxt/b24ui/input-menu.ts +591 -0
  6. package/.nuxt/b24ui/input.ts +5 -5
  7. package/.nuxt/b24ui/select-menu.ts +517 -0
  8. package/.nuxt/b24ui/select.ts +8 -8
  9. package/.nuxt/b24ui/toast.ts +18 -8
  10. package/README.md +8 -8
  11. package/dist/meta.cjs +13003 -3006
  12. package/dist/meta.d.cts +13003 -3006
  13. package/dist/meta.d.mts +13003 -3006
  14. package/dist/meta.d.ts +13003 -3006
  15. package/dist/meta.mjs +13003 -3006
  16. package/dist/module.cjs +1 -1
  17. package/dist/module.json +1 -1
  18. package/dist/module.mjs +1 -1
  19. package/dist/runtime/components/Alert.vue +13 -10
  20. package/dist/runtime/components/App.vue +2 -3
  21. package/dist/runtime/components/Button.vue +1 -3
  22. package/dist/runtime/components/InputMenu.vue +507 -0
  23. package/dist/runtime/components/SelectMenu.vue +465 -0
  24. package/dist/runtime/components/Toast.vue +26 -14
  25. package/dist/runtime/components/Toaster.vue +2 -2
  26. package/dist/runtime/components/content/DescriptionList.vue +9 -7
  27. package/dist/runtime/composables/useToast.d.ts +5 -4
  28. package/dist/runtime/composables/useToast.js +2 -2
  29. package/dist/runtime/types/index.d.ts +3 -0
  30. package/dist/runtime/types/index.js +3 -0
  31. package/dist/runtime/types/utils.d.ts +5 -0
  32. package/dist/shared/{b24ui-nuxt.n3bAiAAD.mjs → b24ui-nuxt.CrjojW8t.mjs} +332 -36
  33. package/dist/shared/{b24ui-nuxt.BAQG__ma.cjs → b24ui-nuxt.D5cXbZSx.cjs} +331 -35
  34. package/dist/unplugin.cjs +1 -1
  35. package/dist/unplugin.mjs +1 -1
  36. package/dist/vite.cjs +1 -1
  37. package/dist/vite.mjs +1 -1
  38. package/package.json +23 -2
package/dist/module.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  const defu = require('defu');
4
4
  const kit = require('@nuxt/kit');
5
- const templates = require('./shared/b24ui-nuxt.BAQG__ma.cjs');
5
+ const templates = require('./shared/b24ui-nuxt.D5cXbZSx.cjs');
6
6
  require('node:url');
7
7
  require('scule');
8
8
 
package/dist/module.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "nuxt": ">=3.13.1"
6
6
  },
7
7
  "docs": "https://bitrix24.github.io/b24ui/guide/installation-nuxt-app.html",
8
- "version": "0.1.6",
8
+ "version": "0.2.1",
9
9
  "builder": {
10
10
  "@nuxt/module-builder": "0.8.4",
11
11
  "unbuild": "2.0.0"
package/dist/module.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { defu } from 'defu';
2
2
  import { defineNuxtModule, createResolver, addVitePlugin, addPlugin, addComponentsDir, addImportsDir, hasNuxtModule, installModule } from '@nuxt/kit';
3
- import { d as defaultOptions, a as getDefaultUiConfig, b as addTemplates } from './shared/b24ui-nuxt.n3bAiAAD.mjs';
3
+ import { d as defaultOptions, a as getDefaultUiConfig, b as addTemplates } from './shared/b24ui-nuxt.CrjojW8t.mjs';
4
4
  import 'node:url';
5
5
  import 'scule';
6
6
 
@@ -23,11 +23,12 @@ export interface AlertProps {
23
23
  icon?: IconComponent
24
24
  avatar?: AvatarProps
25
25
  color?: AlertVariants['color']
26
+ orientation?: AlertVariants['orientation']
26
27
  size?: AlertVariants['size']
27
28
  /**
28
29
  * Display a list of actions:
29
- * - under the title and description if multiline
30
- * - next to the close button if not multiline
30
+ * - under the title and description when orientation is `vertical`
31
+ * - next to the close button when orientation is `horizontal`
31
32
  * `{ size: 'xs' }`{lang="ts-type"}
32
33
  */
33
34
  actions?: ButtonProps[]
@@ -68,22 +69,24 @@ import icons from '../dictionary/icons'
68
69
  import B24Avatar from './Avatar.vue'
69
70
  import B24Button from './Button.vue'
70
71
 
71
- const props = defineProps<AlertProps>()
72
+ const props = withDefaults(defineProps<AlertProps>(), {
73
+ orientation: 'vertical'
74
+ })
72
75
  const emits = defineEmits<AlertEmits>()
73
76
  const slots = defineSlots<AlertSlots>()
74
77
 
75
78
  const { t } = useLocale()
76
79
 
77
- const multiline = computed(() => !!props.title && !!props.description)
78
-
79
80
  const b24ui = computed(() => alert({
80
81
  color: props.color,
81
- size: props.size
82
+ size: props.size,
83
+ orientation: props.orientation,
84
+ title: !!props.title || !!slots.title
82
85
  }))
83
86
  </script>
84
87
 
85
88
  <template>
86
- <Primitive :as="as" :class="b24ui.root({ class: [props.class, props.b24ui?.root], multiline })">
89
+ <Primitive :as="as" :data-orientation="orientation" :class="b24ui.root({ class: [props.class, props.b24ui?.root] })">
87
90
  <slot name="leading">
88
91
  <Component
89
92
  :is="icon"
@@ -105,15 +108,15 @@ const b24ui = computed(() => alert({
105
108
  </slot>
106
109
  </div>
107
110
 
108
- <div v-if="multiline && actions?.length" :class="b24ui.actions({ class: props.b24ui?.actions, multiline: true })">
111
+ <div v-if="orientation === 'vertical' && actions?.length" :class="b24ui.actions({ class: props.b24ui?.actions })">
109
112
  <slot name="actions">
110
113
  <B24Button v-for="(action, index) in actions" :key="index" size="xs" v-bind="action" />
111
114
  </slot>
112
115
  </div>
113
116
  </div>
114
117
 
115
- <div v-if="(!multiline && actions?.length) || close" :class="b24ui.actions({ class: props.b24ui?.actions, multiline: false })">
116
- <template v-if="!multiline">
118
+ <div v-if="(orientation === 'horizontal' && actions?.length) || close" :class="b24ui.actions({ class: props.b24ui?.actions, orientation: 'horizontal' })">
119
+ <template v-if="orientation === 'horizontal' && actions?.length">
117
120
  <slot name="actions">
118
121
  <B24Button v-for="(action, index) in actions" :key="index" size="xs" v-bind="action" />
119
122
  </slot>
@@ -44,9 +44,8 @@ provide(localeContextInjectionKey, locale)
44
44
  <slot />
45
45
  </B24Toaster>
46
46
  <slot v-else />
47
+ <!-- B24ModalProvider / -->
48
+ <!-- B24SlideoverProvider / -->
47
49
  </TooltipProvider>
48
-
49
- <!-- B24ModalProvider / -->
50
- <!-- B24SlideoverProvider / -->
51
50
  </ConfigProvider>
52
51
  </template>
@@ -130,10 +130,8 @@ const b24ui = computed(() => button({
130
130
  @click="onClickWrapper"
131
131
  >
132
132
  <div
133
+ v-if="isLoading"
133
134
  class="h-full w-full absolute inset-0 flex flex-row flex-nowrap items-center justify-center"
134
- :class="[
135
- isLoading ? 'visible' : 'invisible'
136
- ]"
137
135
  >
138
136
  <LoaderWaitIcon v-if="useWait" class="size-2xl" aria-hidden="true" />
139
137
  <LoaderClockIcon v-else-if="useClock" class="size-2xl" aria-hidden="true" />
@@ -0,0 +1,507 @@
1
+ <script lang="ts">
2
+ import type { InputHTMLAttributes } from 'vue'
3
+ import type { VariantProps } from 'tailwind-variants'
4
+ import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxArrowProps, AcceptableValue } from 'reka-ui'
5
+ import type { AppConfig } from '@nuxt/schema'
6
+ import _appConfig from '#build/app.config'
7
+ import theme from '#build/b24ui/input-menu'
8
+ import type { UseComponentIconsProps } from '../composables/useComponentIcons'
9
+ import { tv } from '../utils/tv'
10
+ import type { AvatarProps, ChipProps, InputProps, IconComponent } from '../types'
11
+ import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
12
+
13
+ const appConfigInputMenu = _appConfig as AppConfig & { b24ui: { inputMenu: Partial<typeof theme> } }
14
+
15
+ const inputMenu = tv({ extend: tv(theme), ...(appConfigInputMenu.b24ui?.inputMenu || {}) })
16
+
17
+ export interface InputMenuItem {
18
+ label?: string
19
+ icon?: IconComponent
20
+ avatar?: AvatarProps
21
+ chip?: ChipProps
22
+ /**
23
+ * The item type.
24
+ * @defaultValue 'item'
25
+ */
26
+ type?: 'label' | 'separator' | 'item'
27
+ disabled?: boolean
28
+ onSelect?(e?: Event): void
29
+ }
30
+
31
+ type InputMenuVariants = VariantProps<typeof inputMenu>
32
+
33
+ export interface InputMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<InputMenuItem | AcceptableValue | boolean> = MaybeArrayOfArray<InputMenuItem | AcceptableValue | boolean>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false> extends Pick<ComboboxRootProps<T>, 'open' | 'defaultOpen' | 'disabled' | 'name' | 'resetSearchTermOnBlur' | 'highlightOnHover'>, UseComponentIconsProps {
34
+ /**
35
+ * The element or component this component should render as.
36
+ * @defaultValue 'div'
37
+ */
38
+ as?: any
39
+ id?: string
40
+ type?: InputHTMLAttributes['type']
41
+ /** The placeholder text when the input is empty. */
42
+ placeholder?: string
43
+ color?: InputMenuVariants['color']
44
+ size?: InputMenuVariants['size']
45
+ /** Removes padding from input. */
46
+ noPadding?: boolean
47
+ /** removes all borders (rings). */
48
+ noBorder?: boolean
49
+ /** removes all borders (rings) except the bottom one. */
50
+ underline?: boolean
51
+ /** Rounds the corners of the button. */
52
+ rounded?: boolean
53
+ required?: boolean
54
+ autofocus?: boolean
55
+ autofocusDelay?: number
56
+ /**
57
+ * The icon displayed to open the menu.
58
+ * @defaultValue icons.chevronDown = `ChevronDownIcon`
59
+ */
60
+ trailingIcon?: IconComponent
61
+ /**
62
+ * The icon displayed when an item is selected.
63
+ * @defaultValue icons.check = `CheckIcon`
64
+ */
65
+ selectedIcon?: IconComponent
66
+ /**
67
+ * The icon displayed to delete a tag.
68
+ * Works only when `multiple` is `true`.
69
+ * @defaultValue icons.close = `Cross30Icon`
70
+ */
71
+ deleteIcon?: IconComponent
72
+ /**
73
+ * The content of the menu.
74
+ * @defaultValue { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }
75
+ */
76
+ content?: Omit<ComboboxContentProps, 'as' | 'asChild' | 'forceMount'>
77
+ /**
78
+ * Display an arrow alongside the menu.
79
+ * @defaultValue false
80
+ */
81
+ arrow?: boolean | Omit<ComboboxArrowProps, 'as' | 'asChild'>
82
+ /**
83
+ * Render the menu in a portal.
84
+ * @defaultValue true
85
+ */
86
+ portal?: boolean
87
+ /**
88
+ * When `items` is an array of objects, select the field to use as the value instead of the object itself.
89
+ * @defaultValue undefined
90
+ */
91
+ valueKey?: V
92
+ /**
93
+ * When `items` is an array of objects, select the field to use as the label.
94
+ * @defaultValue 'label'
95
+ */
96
+ labelKey?: V
97
+ items?: I
98
+ /** The value of the InputMenu when initially rendered. Use when you do not need to control the state of the InputMenu. */
99
+ defaultValue?: SelectModelValue<T, V, M>
100
+ /** The controlled value of the InputMenu. Can be binded-with with `v-model`. */
101
+ modelValue?: SelectModelValue<T, V, M>
102
+ /** Whether multiple options can be selected or not. */
103
+ multiple?: M & boolean
104
+ tag?: string
105
+ tagColor?: InputMenuVariants['tagColor']
106
+ /** Highlight the ring color like a focus state. */
107
+ highlight?: boolean
108
+ /**
109
+ * Determines if custom user input that does not exist in options can be added.
110
+ * @defaultValue false
111
+ */
112
+ createItem?: boolean | 'always' | { position?: 'top' | 'bottom', when?: 'empty' | 'always' }
113
+ /**
114
+ * Fields to filter items by.
115
+ * @defaultValue [labelKey]
116
+ */
117
+ filterFields?: string[]
118
+ /**
119
+ * When `true`, disable the default filters, useful for custom filtering (useAsyncData, useFetch, etc.).
120
+ * @defaultValue false
121
+ */
122
+ ignoreFilter?: boolean
123
+ class?: any
124
+ b24ui?: PartialString<typeof inputMenu.slots>
125
+ }
126
+
127
+ export type InputMenuEmits<T, V, M extends boolean> = Omit<ComboboxRootEmits<T>, 'update:modelValue'> & {
128
+ change: [payload: Event]
129
+ blur: [payload: FocusEvent]
130
+ focus: [payload: FocusEvent]
131
+ create: [item: string]
132
+ } & SelectModelValueEmits<T, V, M>
133
+
134
+ type SlotProps<T> = (props: { item: T, index: number }) => any
135
+
136
+ export interface InputMenuSlots<T, M extends boolean> {
137
+ 'leading'(props: { modelValue?: M extends true ? T[] : T, open: boolean, b24ui: any }): any
138
+ 'trailing'(props: { modelValue?: M extends true ? T[] : T, open: boolean, b24ui: any }): any
139
+ 'empty'(props: { searchTerm?: string }): any
140
+ 'item': SlotProps<T>
141
+ 'item-leading': SlotProps<T>
142
+ 'item-label': SlotProps<T>
143
+ 'item-trailing': SlotProps<T>
144
+ 'tags-item-text': SlotProps<T>
145
+ 'tags-item-delete': SlotProps<T>
146
+ 'create-item-label'(props: { item: string }): any
147
+ }
148
+ </script>
149
+
150
+ <script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<InputMenuItem | AcceptableValue | boolean> = MaybeArrayOfArray<InputMenuItem | AcceptableValue | boolean>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false">
151
+ import { computed, ref, toRef, onMounted, toRaw } from 'vue'
152
+ import { ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxViewport, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, TagsInputRoot, TagsInputItem, TagsInputItemText, TagsInputItemDelete, TagsInputInput, useForwardPropsEmits, useFilter } from 'reka-ui'
153
+ import { defu } from 'defu'
154
+ import { isEqual } from 'ohash'
155
+ import { reactivePick, createReusableTemplate } from '@vueuse/core'
156
+ import { useButtonGroup } from '../composables/useButtonGroup'
157
+ import { useComponentIcons } from '../composables/useComponentIcons'
158
+ import { useFormField } from '../composables/useFormField'
159
+ import { useLocale } from '../composables/useLocale'
160
+ import { get, compare } from '../utils'
161
+ import icons from '../dictionary/icons'
162
+ import B24Avatar from './Avatar.vue'
163
+ import B24Chip from './Chip.vue'
164
+
165
+ defineOptions({ inheritAttrs: false })
166
+
167
+ const props = withDefaults(defineProps<InputMenuProps<T, I, V, M>>(), {
168
+ type: 'text',
169
+ autofocusDelay: 0,
170
+ portal: true,
171
+ labelKey: 'label' as never
172
+ })
173
+ const emits = defineEmits<InputMenuEmits<T, V, M>>()
174
+ const slots = defineSlots<InputMenuSlots<T, M>>()
175
+
176
+ const searchTerm = defineModel<string>('searchTerm', { default: '' })
177
+
178
+ const { t } = useLocale()
179
+ const { contains } = useFilter({ sensitivity: 'base' })
180
+
181
+ const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'open', 'defaultOpen', 'multiple', 'resetSearchTermOnBlur', 'highlightOnHover', 'ignoreFilter'), emits)
182
+ const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }) as ComboboxContentProps)
183
+ const arrowProps = toRef(() => props.arrow as ComboboxArrowProps)
184
+
185
+ const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, size: formGroupSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField<InputProps>(props)
186
+ const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
187
+ const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, { trailingIcon: icons.chevronDown })))
188
+
189
+ const inputSize = computed(() => buttonGroupSize.value || formGroupSize.value)
190
+
191
+ const isTag = computed(() => {
192
+ return props.tag
193
+ })
194
+
195
+ const [DefineCreateItemTemplate, ReuseCreateItemTemplate] = createReusableTemplate()
196
+
197
+ const b24ui = computed(() => inputMenu({
198
+ color: color.value,
199
+ size: inputSize?.value,
200
+ loading: props.loading,
201
+ tagColor: props.tagColor,
202
+ highlight: highlight.value,
203
+ rounded: Boolean(props.rounded),
204
+ noPadding: Boolean(props.noPadding),
205
+ noBorder: Boolean(props.noBorder),
206
+ underline: Boolean(props.underline),
207
+ leading: Boolean(isLeading.value || !!props.avatar || !!slots.leading),
208
+ trailing: Boolean(isTrailing.value || !!slots.trailing),
209
+ multiple: props.multiple,
210
+ buttonGroup: orientation.value
211
+ }))
212
+
213
+ function displayValue(value: T): string {
214
+ if (!props.valueKey) {
215
+ return value && (typeof value === 'object' ? get(value, props.labelKey as string) : value)
216
+ }
217
+
218
+ const item = items.value.find(item => compare(typeof item === 'object' ? get(item as Record<string, any>, props.valueKey as string) : item, value))
219
+ return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item)
220
+ }
221
+
222
+ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0]) ? props.items : [props.items]) as InputMenuItem[][] : [])
223
+ // eslint-disable-next-line vue/no-dupe-keys
224
+ const items = computed(() => groups.value.flatMap(group => group) as T[])
225
+
226
+ const filteredGroups = computed(() => {
227
+ if (props.ignoreFilter || !searchTerm.value) {
228
+ return groups.value
229
+ }
230
+
231
+ const fields = Array.isArray(props.filterFields) ? props.filterFields : [props.labelKey] as string[]
232
+
233
+ return groups.value.map(items => items.filter((item) => {
234
+ if (typeof item !== 'object') {
235
+ return contains(item, searchTerm.value)
236
+ }
237
+
238
+ if (item.type && ['label', 'separator'].includes(item.type)) {
239
+ return true
240
+ }
241
+
242
+ return fields.some(field => contains(get(item, field), searchTerm.value))
243
+ })).filter(group => group.filter(item => !item.type || !['label', 'separator'].includes(item.type)).length > 0)
244
+ })
245
+ const filteredItems = computed(() => filteredGroups.value.flatMap(group => group) as T[])
246
+
247
+ const createItem = computed(() => {
248
+ if (!props.createItem || !searchTerm.value) {
249
+ return false
250
+ }
251
+
252
+ const newItem = props.valueKey ? { [props.valueKey]: searchTerm.value } as T : searchTerm.value
253
+
254
+ if ((typeof props.createItem === 'object' && props.createItem.when === 'always') || props.createItem === 'always') {
255
+ return !filteredItems.value.find(item => compare(item, newItem, props.valueKey))
256
+ }
257
+
258
+ return !filteredItems.value.length
259
+ })
260
+ const createItemPosition = computed(() => typeof props.createItem === 'object' ? props.createItem.position : 'bottom')
261
+
262
+ const inputRef = ref<InstanceType<typeof ComboboxInput> | null>(null)
263
+
264
+ function autoFocus() {
265
+ if (props.autofocus) {
266
+ inputRef.value?.$el?.focus()
267
+ }
268
+ }
269
+
270
+ onMounted(() => {
271
+ setTimeout(() => {
272
+ autoFocus()
273
+ }, props.autofocusDelay)
274
+ })
275
+
276
+ function onUpdate(value: any) {
277
+ if (toRaw(props.modelValue) === value) {
278
+ return
279
+ }
280
+ // @ts-expect-error - 'target' does not exist in type 'EventInit'
281
+ const event = new Event('change', { target: { value } })
282
+ emits('change', event)
283
+ emitFormChange()
284
+ emitFormInput()
285
+ }
286
+
287
+ function onBlur(event: FocusEvent) {
288
+ emits('blur', event)
289
+ emitFormBlur()
290
+ }
291
+
292
+ function onFocus(event: FocusEvent) {
293
+ emits('focus', event)
294
+ emitFormFocus()
295
+ }
296
+
297
+ function onUpdateOpen(value: boolean) {
298
+ if (!value) {
299
+ const event = new FocusEvent('blur')
300
+ emits('blur', event)
301
+ emitFormBlur()
302
+ } else {
303
+ const event = new FocusEvent('focus')
304
+ emits('focus', event)
305
+ }
306
+ }
307
+
308
+ function onRemoveTag(event: any) {
309
+ if (props.multiple) {
310
+ const modelValue = props.modelValue as SelectModelValue<T, V, true>
311
+ const filteredValue = modelValue.filter(value => !isEqual(value, event))
312
+ emits('update:modelValue', filteredValue as SelectModelValue<T, V, M>)
313
+ }
314
+ }
315
+
316
+ defineExpose({
317
+ inputRef
318
+ })
319
+ </script>
320
+
321
+ <!-- eslint-disable vue/no-template-shadow -->
322
+ <template>
323
+ <DefineCreateItemTemplate>
324
+ <ComboboxGroup :class="b24ui.group({ addNew: true, class: props.b24ui?.group })">
325
+ <ComboboxItem
326
+ :class="b24ui.item({ addNew: true, class: props.b24ui?.item })"
327
+ :value="searchTerm"
328
+ @select.prevent="emits('create', searchTerm)"
329
+ >
330
+ <span :class="b24ui.itemLabel({ addNew: true, class: props.b24ui?.itemLabel })">
331
+ <slot name="create-item-label" :item="searchTerm">
332
+ <Component
333
+ :is="icons.plus"
334
+ :class="b24ui.itemLeadingIcon({ addNew: true, class: props.b24ui?.itemLeadingIcon })"
335
+ />
336
+ {{ t('inputMenu.create', { label: searchTerm }) }}
337
+ </slot>
338
+ </span>
339
+ </ComboboxItem>
340
+ </ComboboxGroup>
341
+ </DefineCreateItemTemplate>
342
+
343
+ <ComboboxRoot
344
+ :id="id"
345
+ v-slot="{ modelValue, open }"
346
+ v-bind="rootProps"
347
+ :name="name"
348
+ :disabled="disabled"
349
+ :class="b24ui.root({ class: [props.class, props.b24ui?.root] })"
350
+ :as-child="!!multiple"
351
+ ignore-filter
352
+ @update:model-value="onUpdate"
353
+ @update:open="onUpdateOpen"
354
+ @keydown.enter="$event.preventDefault()"
355
+ >
356
+ <div v-if="isTag" :class="b24ui.tag({ class: props.b24ui?.tag })">
357
+ {{ props.tag }}
358
+ </div>
359
+ <ComboboxAnchor :as-child="!multiple" :class="b24ui.base({ class: props.b24ui?.base })">
360
+ <TagsInputRoot
361
+ v-if="multiple"
362
+ v-slot="{ modelValue: tags }"
363
+ :model-value="(modelValue as string[])"
364
+ :disabled="disabled"
365
+ delimiter=""
366
+ as-child
367
+ @blur="onBlur"
368
+ @focus="onFocus"
369
+ @remove-tag="onRemoveTag"
370
+ >
371
+ <TagsInputItem v-for="(item, index) in tags" :key="index" :value="(item as string)" :class="b24ui.tagsItem({ class: props.b24ui?.tagsItem })">
372
+ <TagsInputItemText :class="b24ui.tagsItemText({ class: props.b24ui?.tagsItemText })">
373
+ <slot name="tags-item-text" :item="(item as T)" :index="index">
374
+ {{ displayValue(item as T) }}
375
+ </slot>
376
+ </TagsInputItemText>
377
+
378
+ <TagsInputItemDelete :class="b24ui.tagsItemDelete({ class: props.b24ui?.tagsItemDelete })" :disabled="disabled">
379
+ <slot name="tags-item-delete" :item="(item as T)" :index="index">
380
+ <Component
381
+ :is="deleteIcon || icons.close"
382
+ :class="b24ui.tagsItemDeleteIcon({ class: props.b24ui?.tagsItemDeleteIcon })"
383
+ />
384
+ </slot>
385
+ </TagsInputItemDelete>
386
+ </TagsInputItem>
387
+
388
+ <ComboboxInput v-model="searchTerm" :display-value="displayValue" as-child>
389
+ <TagsInputInput
390
+ ref="inputRef"
391
+ v-bind="{ ...$attrs, ...ariaAttrs }"
392
+ :placeholder="placeholder"
393
+ :required="required"
394
+ :class="b24ui.tagsInput({ class: props.b24ui?.tagsInput })"
395
+ @keydown.enter.prevent
396
+ />
397
+ </ComboboxInput>
398
+ </TagsInputRoot>
399
+
400
+ <ComboboxInput
401
+ v-else
402
+ ref="inputRef"
403
+ v-model="searchTerm"
404
+ :display-value="displayValue"
405
+ v-bind="{ ...$attrs, ...ariaAttrs }"
406
+ :type="type"
407
+ :placeholder="placeholder"
408
+ :required="required"
409
+ @blur="onBlur"
410
+ @focus="onFocus"
411
+ />
412
+
413
+ <span v-if="isLeading || !!avatar || !!slots.leading" :class="b24ui.leading({ class: props.b24ui?.leading })">
414
+ <slot name="leading" :model-value="(modelValue as M extends true ? T[] : T)" :open="open" :b24ui="b24ui">
415
+ <Component
416
+ :is="leadingIconName"
417
+ v-if="isLeading && leadingIconName"
418
+ :class="b24ui.leadingIcon({ class: props.b24ui?.leadingIcon })"
419
+ />
420
+ <B24Avatar v-else-if="!!avatar" :size="((props.b24ui?.itemLeadingAvatarSize || b24ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="avatar" :class="b24ui.itemLeadingAvatar({ class: props.b24ui?.itemLeadingAvatar })" />
421
+ </slot>
422
+ </span>
423
+
424
+ <ComboboxTrigger v-if="isTrailing || !!slots.trailing" :class="b24ui.trailing({ class: props.b24ui?.trailing })">
425
+ <slot name="trailing" :model-value="(modelValue as M extends true ? T[] : T)" :open="open" :b24ui="b24ui">
426
+ <Component
427
+ :is="trailingIconName"
428
+ v-if="trailingIconName"
429
+ :class="b24ui.trailingIcon({ class: props.b24ui?.trailingIcon })"
430
+ />
431
+ </slot>
432
+ </ComboboxTrigger>
433
+ </ComboboxAnchor>
434
+
435
+ <ComboboxPortal :disabled="!portal">
436
+ <ComboboxContent :class="b24ui.content({ class: props.b24ui?.content })" v-bind="contentProps">
437
+ <ComboboxEmpty :class="b24ui.empty({ class: props.b24ui?.empty })">
438
+ <slot name="empty" :search-term="searchTerm">
439
+ {{ searchTerm ? t('inputMenu.noMatch', { searchTerm }) : t('inputMenu.noData') }}
440
+ </slot>
441
+ </ComboboxEmpty>
442
+
443
+ <ComboboxViewport :class="b24ui.viewport({ class: props.b24ui?.viewport })">
444
+ <ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'top'" />
445
+
446
+ <ComboboxGroup v-for="(group, groupIndex) in filteredGroups" :key="`group-${groupIndex}`" :class="b24ui.group({ class: props.b24ui?.group })">
447
+ <template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
448
+ <ComboboxLabel v-if="item?.type === 'label'" :class="b24ui.label({ class: props.b24ui?.label })">
449
+ {{ get(item, props.labelKey as string) }}
450
+ </ComboboxLabel>
451
+
452
+ <ComboboxSeparator v-else-if="item?.type === 'separator'" :class="b24ui.separator({ class: props.b24ui?.separator })" />
453
+
454
+ <ComboboxItem
455
+ v-else
456
+ :class="b24ui.item({ class: props.b24ui?.item })"
457
+ :disabled="item.disabled"
458
+ :value="valueKey && typeof item === 'object' ? get(item, props.valueKey as string) : item"
459
+ @select="item.onSelect"
460
+ >
461
+ <slot name="item" :item="(item as T)" :index="index">
462
+ <slot name="item-leading" :item="(item as T)" :index="index">
463
+ <Component
464
+ :is="item.icon"
465
+ v-if="item.icon"
466
+ :class="b24ui.itemLeadingIcon({ class: props.b24ui?.itemLeadingIcon })"
467
+ />
468
+ <B24Avatar v-else-if="item.avatar" :size="((props.b24ui?.itemLeadingAvatarSize || b24ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="b24ui.itemLeadingAvatar({ class: props.b24ui?.itemLeadingAvatar })" />
469
+ <B24Chip
470
+ v-else-if="item.chip"
471
+ :size="((props.b24ui?.itemLeadingChipSize || b24ui.itemLeadingChipSize()) as ChipProps['size'])"
472
+ inset
473
+ standalone
474
+ v-bind="item.chip"
475
+ :class="b24ui.itemLeadingChip({ class: props.b24ui?.itemLeadingChip })"
476
+ />
477
+ </slot>
478
+
479
+ <span :class="b24ui.itemLabel({ class: props.b24ui?.itemLabel })">
480
+ <slot name="item-label" :item="(item as T)" :index="index">
481
+ {{ typeof item === 'object' ? get(item, props.labelKey as string) : item }}
482
+ </slot>
483
+ </span>
484
+
485
+ <span :class="b24ui.itemTrailing({ class: props.b24ui?.itemTrailing })">
486
+ <slot name="item-trailing" :item="(item as T)" :index="index" />
487
+
488
+ <ComboboxItemIndicator as-child>
489
+ <Component
490
+ :is="selectedIcon || icons.check"
491
+ :class="b24ui.itemTrailingIcon({ class: props.b24ui?.itemTrailingIcon })"
492
+ />
493
+ </ComboboxItemIndicator>
494
+ </span>
495
+ </slot>
496
+ </ComboboxItem>
497
+ </template>
498
+ </ComboboxGroup>
499
+
500
+ <ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'bottom'" />
501
+ </ComboboxViewport>
502
+
503
+ <ComboboxArrow v-if="!!arrow" v-bind="arrowProps" :class="b24ui.arrow({ class: props.b24ui?.arrow })" />
504
+ </ComboboxContent>
505
+ </ComboboxPortal>
506
+ </ComboboxRoot>
507
+ </template>