@bitrix24/b24ui-nuxt 0.5.3 → 0.5.5

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 (50) hide show
  1. package/.nuxt/b24ui/button.ts +10 -10
  2. package/.nuxt/b24ui/container.ts +1 -1
  3. package/.nuxt/b24ui/navigation-menu.ts +2 -2
  4. package/.nuxt/b24ui/switch.ts +1 -1
  5. package/.nuxt/b24ui/tabs.ts +1 -1
  6. package/dist/meta.cjs +47928 -45201
  7. package/dist/meta.d.cts +47928 -45201
  8. package/dist/meta.d.mts +47928 -45201
  9. package/dist/meta.d.ts +47928 -45201
  10. package/dist/meta.mjs +47928 -45201
  11. package/dist/module.cjs +1 -1
  12. package/dist/module.json +1 -1
  13. package/dist/module.mjs +1 -1
  14. package/dist/runtime/components/Alert.vue +1 -1
  15. package/dist/runtime/components/App.vue +1 -1
  16. package/dist/runtime/components/Button.vue +1 -1
  17. package/dist/runtime/components/Calendar.vue +40 -12
  18. package/dist/runtime/components/DescriptionList.vue +99 -85
  19. package/dist/runtime/components/DropdownMenu.vue +23 -12
  20. package/dist/runtime/components/DropdownMenuContent.vue +22 -15
  21. package/dist/runtime/components/Form.vue +1 -0
  22. package/dist/runtime/components/FormField.vue +1 -0
  23. package/dist/runtime/components/InputMenu.vue +91 -53
  24. package/dist/runtime/components/Link.vue +3 -0
  25. package/dist/runtime/components/LinkBase.vue +1 -0
  26. package/dist/runtime/components/Modal.vue +1 -1
  27. package/dist/runtime/components/NavigationMenu.vue +49 -25
  28. package/dist/runtime/components/RadioGroup.vue +23 -12
  29. package/dist/runtime/components/Select.vue +74 -47
  30. package/dist/runtime/components/SelectMenu.vue +95 -56
  31. package/dist/runtime/components/Slideover.vue +1 -1
  32. package/dist/runtime/components/Tabs.vue +6 -5
  33. package/dist/runtime/components/Toast.vue +1 -1
  34. package/dist/runtime/composables/defineLocale.js +1 -0
  35. package/dist/runtime/composables/useFormField.js +1 -1
  36. package/dist/runtime/types/form.d.ts +1 -0
  37. package/dist/runtime/types/utils.d.ts +28 -7
  38. package/dist/runtime/utils/index.d.ts +1 -0
  39. package/dist/runtime/utils/index.js +3 -0
  40. package/dist/runtime/utils/link.d.ts +2 -25
  41. package/dist/runtime/utils/link.js +31 -1
  42. package/dist/runtime/vue/components/Link.vue +3 -0
  43. package/dist/runtime/vue/stubs.d.ts +2 -2
  44. package/dist/shared/{b24ui-nuxt.Bh_5o1_9.cjs → b24ui-nuxt.BGKKwlPY.cjs} +17 -15
  45. package/dist/shared/{b24ui-nuxt.BIuy4yic.mjs → b24ui-nuxt.DWwKgFlo.mjs} +17 -15
  46. package/dist/unplugin.cjs +1 -1
  47. package/dist/unplugin.mjs +1 -1
  48. package/dist/vite.cjs +1 -1
  49. package/dist/vite.mjs +1 -1
  50. package/package.json +32 -17
@@ -1,14 +1,23 @@
1
1
  <script lang="ts">
2
2
  import type { InputHTMLAttributes } from 'vue'
3
3
  import type { VariantProps } from 'tailwind-variants'
4
- import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxContentEmits, ComboboxArrowProps, AcceptableValue } from 'reka-ui'
4
+ import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxContentEmits, ComboboxArrowProps } from 'reka-ui'
5
5
  import type { AppConfig } from '@nuxt/schema'
6
6
  import _appConfig from '#build/app.config'
7
7
  import theme from '#build/b24ui/input-menu'
8
8
  import type { UseComponentIconsProps } from '../composables/useComponentIcons'
9
9
  import { tv } from '../utils/tv'
10
10
  import type { AvatarProps, ChipProps, InputProps, IconComponent } from '../types'
11
- import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey, EmitsToProps } from '../types/utils'
11
+ import type {
12
+ AcceptableValue,
13
+ ArrayOrNested,
14
+ GetItemKeys,
15
+ GetModelValue,
16
+ GetModelValueEmits,
17
+ NestedItem,
18
+ PartialString,
19
+ EmitsToProps
20
+ } from '../types/utils'
12
21
 
13
22
  const appConfigInputMenu = _appConfig as AppConfig & { b24ui: { inputMenu: Partial<typeof theme> } }
14
23
 
@@ -16,7 +25,7 @@ const inputMenu = tv({ extend: tv(theme), ...(appConfigInputMenu.b24ui?.inputMen
16
25
 
17
26
  type InputMenuVariants = VariantProps<typeof inputMenu>
18
27
 
19
- export interface InputMenuItem {
28
+ interface _InputMenuItem {
20
29
  label?: string
21
30
  /**
22
31
  * Display an icon on the left side.
@@ -31,11 +40,14 @@ export interface InputMenuItem {
31
40
  * @defaultValue 'item'
32
41
  */
33
42
  type?: 'label' | 'separator' | 'item'
43
+ value?: string | number
34
44
  disabled?: boolean
35
45
  onSelect?(e?: Event): void
46
+ [key: string]: any
36
47
  }
48
+ export type InputMenuItem = _InputMenuItem | AcceptableValue | boolean
37
49
 
38
- 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 {
50
+ export interface InputMenuProps<T extends ArrayOrNested<InputMenuItem> = ArrayOrNested<InputMenuItem>, VK extends GetItemKeys<T> | undefined = undefined, M extends boolean = false> extends Pick<ComboboxRootProps<T>, 'open' | 'defaultOpen' | 'disabled' | 'name' | 'resetSearchTermOnBlur' | 'highlightOnHover'>, UseComponentIconsProps {
39
51
  /**
40
52
  * The element or component this component should render as.
41
53
  * @defaultValue 'div'
@@ -128,21 +140,21 @@ export interface InputMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends Ma
128
140
  * When `items` is an array of objects, select the field to use as the value instead of the object itself.
129
141
  * @defaultValue undefined
130
142
  */
131
- valueKey?: V
143
+ valueKey?: VK
132
144
  /**
133
145
  * When `items` is an array of objects, select the field to use as the label.
134
146
  * @defaultValue 'label'
135
147
  */
136
- labelKey?: V
137
- items?: I
148
+ labelKey?: keyof NestedItem<T>
149
+ items?: T
138
150
  /**
139
151
  * The value of the InputMenu when initially rendered. Use when you do not need to control the state of the InputMenu
140
152
  */
141
- defaultValue?: SelectModelValue<T, V, M>
153
+ defaultValue?: GetModelValue<T, VK, M>
142
154
  /**
143
155
  * The controlled value of the InputMenu. Can be binded-with with `v-model`
144
156
  */
145
- modelValue?: SelectModelValue<T, V, M>
157
+ modelValue?: GetModelValue<T, VK, M>
146
158
  /**
147
159
  * Whether multiple options can be selected or not
148
160
  * @defaultValue false
@@ -177,18 +189,28 @@ export interface InputMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends Ma
177
189
  b24ui?: PartialString<typeof inputMenu.slots>
178
190
  }
179
191
 
180
- export type InputMenuEmits<T, V, M extends boolean> = Omit<ComboboxRootEmits<T>, 'update:modelValue'> & {
192
+ export type InputMenuEmits<A extends ArrayOrNested<InputMenuItem>, VK extends GetItemKeys<A> | undefined, M extends boolean> = Pick<ComboboxRootEmits, 'update:open'> & {
181
193
  change: [payload: Event]
182
194
  blur: [payload: FocusEvent]
183
195
  focus: [payload: FocusEvent]
184
196
  create: [item: string]
185
- } & SelectModelValueEmits<T, V, M>
186
-
187
- type SlotProps<T> = (props: { item: T, index: number }) => any
188
-
189
- export interface InputMenuSlots<T, M extends boolean> {
190
- 'leading'(props: { modelValue?: M extends true ? T[] : T, open: boolean, b24ui: any }): any
191
- 'trailing'(props: { modelValue?: M extends true ? T[] : T, open: boolean, b24ui: any }): any
197
+ /** Event handler when highlighted element changes. */
198
+ highlight: [payload: {
199
+ ref: HTMLElement
200
+ value: GetModelValue<A, VK, M>
201
+ } | undefined]
202
+ } & GetModelValueEmits<A, VK, M>
203
+
204
+ type SlotProps<T extends InputMenuItem> = (props: { item: T, index: number }) => any
205
+
206
+ export interface InputMenuSlots<
207
+ A extends ArrayOrNested<InputMenuItem> = ArrayOrNested<InputMenuItem>,
208
+ VK extends GetItemKeys<A> | undefined = undefined,
209
+ M extends boolean = false,
210
+ T extends NestedItem<A> = NestedItem<A>
211
+ > {
212
+ 'leading'(props: { modelValue?: GetModelValue<A, VK, M>, open: boolean, b24ui: ReturnType<typeof inputMenu> }): any
213
+ 'trailing'(props: { modelValue?: GetModelValue<A, VK, M>, open: boolean, b24ui: ReturnType<typeof inputMenu> }): any
192
214
  'empty'(props: { searchTerm?: string }): any
193
215
  'item': SlotProps<T>
194
216
  'item-leading': SlotProps<T>
@@ -200,7 +222,7 @@ export interface InputMenuSlots<T, M extends boolean> {
200
222
  }
201
223
  </script>
202
224
 
203
- <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">
225
+ <script setup lang="ts" generic="T extends ArrayOrNested<InputMenuItem>, VK extends GetItemKeys<T> | undefined = undefined, M extends boolean = false">
204
226
  import { computed, ref, toRef, onMounted, toRaw } from 'vue'
205
227
  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'
206
228
  import { defu } from 'defu'
@@ -210,21 +232,21 @@ import { useButtonGroup } from '../composables/useButtonGroup'
210
232
  import { useComponentIcons } from '../composables/useComponentIcons'
211
233
  import { useFormField } from '../composables/useFormField'
212
234
  import { useLocale } from '../composables/useLocale'
213
- import { get, compare } from '../utils'
235
+ import { compare, get, isArrayOfArray } from '../utils'
214
236
  import icons from '../dictionary/icons'
215
237
  import B24Avatar from './Avatar.vue'
216
238
  import B24Chip from './Chip.vue'
217
239
 
218
240
  defineOptions({ inheritAttrs: false })
219
241
 
220
- const props = withDefaults(defineProps<InputMenuProps<T, I, V, M>>(), {
242
+ const props = withDefaults(defineProps<InputMenuProps<T, VK, M>>(), {
221
243
  type: 'text',
222
244
  autofocusDelay: 0,
223
245
  portal: true,
224
246
  labelKey: 'label' as never
225
247
  })
226
- const emits = defineEmits<InputMenuEmits<T, V, M>>()
227
- const slots = defineSlots<InputMenuSlots<T, M>>()
248
+ const emits = defineEmits<InputMenuEmits<T, VK, M>>()
249
+ const slots = defineSlots<InputMenuSlots<T, VK, M>>()
228
250
 
229
251
  const searchTerm = defineModel<string>('searchTerm', { default: '' })
230
252
 
@@ -272,9 +294,15 @@ function displayValue(value: T): string {
272
294
  return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item)
273
295
  }
274
296
 
275
- const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0]) ? props.items : [props.items]) as InputMenuItem[][] : [])
297
+ const groups = computed<InputMenuItem[][]>(() =>
298
+ props.items?.length
299
+ ? isArrayOfArray(props.items)
300
+ ? props.items
301
+ : [props.items]
302
+ : []
303
+ )
276
304
  // eslint-disable-next-line vue/no-dupe-keys
277
- const items = computed(() => groups.value.flatMap(group => group) as T[])
305
+ const items = computed(() => groups.value.flatMap(group => group))
278
306
 
279
307
  const filteredGroups = computed(() => {
280
308
  if (props.ignoreFilter || !searchTerm.value) {
@@ -283,9 +311,9 @@ const filteredGroups = computed(() => {
283
311
 
284
312
  const fields = Array.isArray(props.filterFields) ? props.filterFields : [props.labelKey] as string[]
285
313
 
286
- return groups.value.map(items => items.filter((item) => {
287
- if (typeof item !== 'object') {
288
- return contains(item, searchTerm.value)
314
+ return groups.value.map(group => group.filter((item) => {
315
+ if (typeof item !== 'object' || item === null) {
316
+ return contains(String(item), searchTerm.value)
289
317
  }
290
318
 
291
319
  if (item.type && ['label', 'separator'].includes(item.type)) {
@@ -293,19 +321,25 @@ const filteredGroups = computed(() => {
293
321
  }
294
322
 
295
323
  return fields.some(field => contains(get(item, field), searchTerm.value))
296
- })).filter(group => group.filter(item => !item.type || !['label', 'separator'].includes(item.type)).length > 0)
324
+ })).filter(group => group.filter(item =>
325
+ /**
326
+ * @memo fix not obj
327
+ * @see selectMenu
328
+ */
329
+ typeof item !== 'object' || (isInputItem(item) && (!item.type || !['label', 'separator'].includes(item.type)))
330
+ ).length > 0)
297
331
  })
298
- const filteredItems = computed(() => filteredGroups.value.flatMap(group => group) as T[])
332
+ const filteredItems = computed(() => filteredGroups.value.flatMap(group => group))
299
333
 
300
334
  const createItem = computed(() => {
301
335
  if (!props.createItem || !searchTerm.value) {
302
336
  return false
303
337
  }
304
338
 
305
- const newItem = props.valueKey ? { [props.valueKey]: searchTerm.value } as T : searchTerm.value
339
+ const newItem = props.valueKey ? { [props.valueKey]: searchTerm.value } as NestedItem<T> : searchTerm.value
306
340
 
307
341
  if ((typeof props.createItem === 'object' && props.createItem.when === 'always') || props.createItem === 'always') {
308
- return !filteredItems.value.find(item => compare(item, newItem, props.valueKey))
342
+ return !filteredItems.value.find(item => compare(item, newItem, String(props.valueKey)))
309
343
  }
310
344
 
311
345
  return !filteredItems.value.length
@@ -360,12 +394,16 @@ function onUpdateOpen(value: boolean) {
360
394
 
361
395
  function onRemoveTag(event: any) {
362
396
  if (props.multiple) {
363
- const modelValue = props.modelValue as SelectModelValue<T, V, true>
397
+ const modelValue = props.modelValue as GetModelValue<T, VK, true>
364
398
  const filteredValue = modelValue.filter(value => !isEqual(value, event))
365
- emits('update:modelValue', filteredValue as SelectModelValue<T, V, M>)
399
+ emits('update:modelValue', filteredValue as GetModelValue<T, VK, M>)
366
400
  }
367
401
  }
368
402
 
403
+ function isInputItem(item: InputMenuItem): item is _InputMenuItem {
404
+ return typeof item === 'object' && item !== null
405
+ }
406
+
369
407
  defineExpose({
370
408
  inputRef
371
409
  })
@@ -424,13 +462,13 @@ defineExpose({
424
462
  >
425
463
  <TagsInputItem v-for="(item, index) in tags" :key="index" :value="(item as string)" :class="b24ui.tagsItem({ class: props.b24ui?.tagsItem })">
426
464
  <TagsInputItemText :class="b24ui.tagsItemText({ class: props.b24ui?.tagsItemText })">
427
- <slot name="tags-item-text" :item="(item as T)" :index="index">
465
+ <slot name="tags-item-text" :item="(item as NestedItem<T>)" :index="index">
428
466
  {{ displayValue(item as T) }}
429
467
  </slot>
430
468
  </TagsInputItemText>
431
469
 
432
470
  <TagsInputItemDelete :class="b24ui.tagsItemDelete({ class: props.b24ui?.tagsItemDelete })" :disabled="disabled">
433
- <slot name="tags-item-delete" :item="(item as T)" :index="index">
471
+ <slot name="tags-item-delete" :item="(item as NestedItem<T>)" :index="index">
434
472
  <Component
435
473
  :is="deleteIcon || icons.close"
436
474
  :class="b24ui.tagsItemDeleteIcon({ class: props.b24ui?.tagsItemDeleteIcon })"
@@ -464,7 +502,7 @@ defineExpose({
464
502
  />
465
503
 
466
504
  <span v-if="isLeading || !!avatar || !!slots.leading" :class="b24ui.leading({ class: props.b24ui?.leading })">
467
- <slot name="leading" :model-value="(modelValue as M extends true ? T[] : T)" :open="open" :b24ui="b24ui">
505
+ <slot name="leading" :model-value="(modelValue as GetModelValue<T, VK, M>)" :open="open" :b24ui="b24ui">
468
506
  <Component
469
507
  :is="leadingIconName"
470
508
  v-if="isLeading && leadingIconName"
@@ -475,7 +513,7 @@ defineExpose({
475
513
  </span>
476
514
 
477
515
  <ComboboxTrigger v-if="isTrailing || !!slots.trailing" :class="b24ui.trailing({ class: props.b24ui?.trailing })">
478
- <slot name="trailing" :model-value="(modelValue as M extends true ? T[] : T)" :open="open" :b24ui="b24ui">
516
+ <slot name="trailing" :model-value="(modelValue as GetModelValue<T, VK, M>)" :open="open" :b24ui="b24ui">
479
517
  <Component
480
518
  :is="trailingIconName"
481
519
  v-if="trailingIconName"
@@ -498,29 +536,29 @@ defineExpose({
498
536
 
499
537
  <ComboboxGroup v-for="(group, groupIndex) in filteredGroups" :key="`group-${groupIndex}`" :class="b24ui.group({ class: props.b24ui?.group })">
500
538
  <template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
501
- <ComboboxLabel v-if="item?.type === 'label'" :class="b24ui.label({ class: props.b24ui?.label })">
539
+ <ComboboxLabel v-if="isInputItem(item) && item.type === 'label'" :class="b24ui.label({ class: props.b24ui?.label })">
502
540
  {{ get(item, props.labelKey as string) }}
503
541
  </ComboboxLabel>
504
542
 
505
- <ComboboxSeparator v-else-if="item?.type === 'separator'" :class="b24ui.separator({ class: props.b24ui?.separator })" />
543
+ <ComboboxSeparator v-else-if="isInputItem(item) && item.type === 'separator'" :class="b24ui.separator({ class: props.b24ui?.separator })" />
506
544
 
507
545
  <ComboboxItem
508
546
  v-else
509
- :class="b24ui.item({ class: props.b24ui?.item, colorItem: item?.color })"
510
- :disabled="item.disabled"
511
- :value="valueKey && typeof item === 'object' ? get(item, props.valueKey as string) : item"
512
- @select="item.onSelect"
547
+ :class="b24ui.item({ class: props.b24ui?.item, colorItem: isInputItem(item) ? item?.color : undefined })"
548
+ :disabled="isInputItem(item) && item.disabled"
549
+ :value="props.valueKey && isInputItem(item) ? get(item, String(props.valueKey)) : item"
550
+ @select="isInputItem(item) && item.onSelect"
513
551
  >
514
- <slot name="item" :item="(item as T)" :index="index">
515
- <slot name="item-leading" :item="(item as T)" :index="index">
552
+ <slot name="item" :item="(item as NestedItem<T>)" :index="index">
553
+ <slot name="item-leading" :item="(item as NestedItem<T>)" :index="index">
516
554
  <Component
517
555
  :is="item.icon"
518
- v-if="item.icon"
556
+ v-if="isInputItem(item) && item.icon"
519
557
  :class="b24ui.itemLeadingIcon({ class: props.b24ui?.itemLeadingIcon, colorItem: item?.color })"
520
558
  />
521
- <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, colorItem: item?.color })" />
559
+ <B24Avatar v-else-if="isInputItem(item) && item.avatar" :size="((props.b24ui?.itemLeadingAvatarSize || b24ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="item.avatar" :class="b24ui.itemLeadingAvatar({ class: props.b24ui?.itemLeadingAvatar, colorItem: item?.color })" />
522
560
  <B24Chip
523
- v-else-if="item.chip"
561
+ v-else-if="isInputItem(item) && item.chip"
524
562
  :size="((props.b24ui?.itemLeadingChipSize || b24ui.itemLeadingChipSize()) as ChipProps['size'])"
525
563
  inset
526
564
  standalone
@@ -530,18 +568,18 @@ defineExpose({
530
568
  </slot>
531
569
 
532
570
  <span :class="b24ui.itemLabel({ class: props.b24ui?.itemLabel })">
533
- <slot name="item-label" :item="(item as T)" :index="index">
534
- {{ typeof item === 'object' ? get(item, props.labelKey as string) : item }}
571
+ <slot name="item-label" :item="(item as NestedItem<T>)" :index="index">
572
+ {{ isInputItem(item) ? get(item, props.labelKey as string) : item }}
535
573
  </slot>
536
574
  </span>
537
575
 
538
- <span :class="b24ui.itemTrailing({ class: props.b24ui?.itemTrailing, colorItem: item?.color })">
539
- <slot name="item-trailing" :item="(item as T)" :index="index" />
576
+ <span :class="b24ui.itemTrailing({ class: props.b24ui?.itemTrailing, colorItem: isInputItem(item) ? item?.color : undefined })">
577
+ <slot name="item-trailing" :item="(item as NestedItem<T>)" :index="index" />
540
578
 
541
579
  <ComboboxItemIndicator as-child>
542
580
  <Component
543
581
  :is="selectedIcon || icons.check"
544
- :class="b24ui.itemTrailingIcon({ class: props.b24ui?.itemTrailingIcon, colorItem: item?.color })"
582
+ :class="b24ui.itemTrailingIcon({ class: props.b24ui?.itemTrailingIcon, colorItem: isInputItem(item) ? item?.color : undefined })"
545
583
  />
546
584
  </ComboboxItemIndicator>
547
585
  </span>
@@ -104,6 +104,7 @@ defineOptions({ inheritAttrs: false })
104
104
  const props = withDefaults(defineProps<LinkProps>(), {
105
105
  as: 'button',
106
106
  type: 'button',
107
+ ariaCurrentValue: 'page',
107
108
  active: undefined,
108
109
  isAction: false,
109
110
  activeClass: '',
@@ -190,6 +191,7 @@ function resolveLinkClass({ route, isActive, isExactActive }: any) {
190
191
  <slot
191
192
  v-bind="{
192
193
  ...$attrs,
194
+ ...(exact && isExactActive ? { 'aria-current': props.ariaCurrentValue } : {}),
193
195
  as,
194
196
  type,
195
197
  disabled,
@@ -206,6 +208,7 @@ function resolveLinkClass({ route, isActive, isExactActive }: any) {
206
208
  v-else
207
209
  v-bind="{
208
210
  ...$attrs,
211
+ ...(exact && isExactActive ? { 'aria-current': props.ariaCurrentValue } : {}),
209
212
  as,
210
213
  type,
211
214
  disabled,
@@ -10,6 +10,7 @@ export interface LinkBaseProps {
10
10
  navigate?: (e: MouseEvent) => void
11
11
  target?: LinkProps['target']
12
12
  rel?: LinkProps['rel']
13
+ active?: boolean
13
14
  isExternal?: boolean
14
15
  }
15
16
  </script>
@@ -82,7 +82,7 @@ export interface ModalSlots {
82
82
  header(props?: {}): any
83
83
  title(props?: {}): any
84
84
  description(props?: {}): any
85
- close(props: { b24ui: any }): any
85
+ close(props: { b24ui: ReturnType<typeof modal> }): any
86
86
  body(props?: {}): any
87
87
  footer(props?: {}): any
88
88
  }
@@ -7,15 +7,23 @@ import _appConfig from '#build/app.config'
7
7
  import theme from '#build/b24ui/navigation-menu'
8
8
  import { tv } from '../utils/tv'
9
9
  import type { AvatarProps, BadgeProps, LinkProps, IconComponent } from '../types'
10
- import type { DynamicSlots, MaybeArrayOfArray, MaybeArrayOfArrayItem, PartialString, EmitsToProps } from '../types/utils'
10
+ import type {
11
+ ArrayOrNested,
12
+ DynamicSlots,
13
+ MergeTypes,
14
+ NestedItem,
15
+ PartialString,
16
+ EmitsToProps
17
+ } from '../types/utils'
11
18
 
12
19
  const appConfigNavigationMenu = _appConfig as AppConfig & { b24ui: { navigationMenu: Partial<typeof theme> } }
13
20
 
14
21
  const navigationMenu = tv({ extend: tv(theme), ...(appConfigNavigationMenu.b24ui?.navigationMenu || {}) })
15
22
 
16
- export interface NavigationMenuChildItem extends Omit<NavigationMenuItem, 'children' | 'type'> {
23
+ export interface NavigationMenuChildItem extends Omit<NavigationMenuItem, 'type'> {
17
24
  /** Description is only used when `orientation` is `horizontal`. */
18
25
  description?: string
26
+ [key: string]: any
19
27
  }
20
28
 
21
29
  export interface NavigationMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'custom'>, Pick<CollapsibleRootProps, 'defaultOpen' | 'open'> {
@@ -48,11 +56,12 @@ export interface NavigationMenuItem extends Omit<LinkProps, 'type' | 'raw' | 'cu
48
56
  */
49
57
  viewportRtl?: boolean
50
58
  onSelect?(e: Event): void
59
+ [key: string]: any
51
60
  }
52
61
 
53
62
  type NavigationMenuVariants = VariantProps<typeof navigationMenu>
54
63
 
55
- export interface NavigationMenuProps<T> extends Pick<NavigationMenuRootProps, 'modelValue' | 'defaultValue' | 'delayDuration' | 'disableClickTrigger' | 'disableHoverTrigger' | 'skipDelayDuration' | 'disablePointerLeaveClose' | 'unmountOnHide'> {
64
+ export interface NavigationMenuProps<T extends ArrayOrNested<NavigationMenuItem> = ArrayOrNested<NavigationMenuItem>> extends Pick<NavigationMenuRootProps, 'modelValue' | 'defaultValue' | 'delayDuration' | 'disableClickTrigger' | 'disableHoverTrigger' | 'skipDelayDuration' | 'disablePointerLeaveClose' | 'unmountOnHide'> {
56
65
  /**
57
66
  * The element or component this component should render as.
58
67
  * @defaultValue 'div'
@@ -116,30 +125,33 @@ export interface NavigationMenuProps<T> extends Pick<NavigationMenuRootProps, 'm
116
125
  * The key used to get the label from the item.
117
126
  * @defaultValue 'label'
118
127
  */
119
- labelKey?: string
128
+ labelKey?: keyof NestedItem<T>
120
129
  class?: any
121
130
  b24ui?: PartialString<typeof navigationMenu.slots>
122
131
  }
123
132
 
124
133
  export interface NavigationMenuEmits extends NavigationMenuRootEmits {}
125
134
 
126
- type SlotProps<T> = (props: { item: T, index: number, active?: boolean }) => any
135
+ type SlotProps<T extends NavigationMenuItem> = (props: { item: T, index: number, active?: boolean }) => any
127
136
 
128
- export type NavigationMenuSlots<T extends { slot?: string }> = {
137
+ export type NavigationMenuSlots<
138
+ A extends ArrayOrNested<NavigationMenuItem> = ArrayOrNested<NavigationMenuItem>,
139
+ T extends NestedItem<A> = NestedItem<A>
140
+ > = {
129
141
  'item': SlotProps<T>
130
142
  'item-leading': SlotProps<T>
131
143
  'item-label': SlotProps<T>
132
144
  'item-trailing': SlotProps<T>
133
145
  'item-content': SlotProps<T>
134
- } & DynamicSlots<T, SlotProps<T>>
146
+ } & DynamicSlots<MergeTypes<T>, 'leading' | 'label' | 'trailing' | 'content', { index: number, active?: boolean }>
135
147
 
136
148
  </script>
137
149
 
138
- <script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<NavigationMenuItem>">
150
+ <script setup lang="ts" generic="T extends ArrayOrNested<NavigationMenuItem>">
139
151
  import { computed, toRef } from 'vue'
140
152
  import { NavigationMenuRoot, NavigationMenuList, NavigationMenuItem, NavigationMenuTrigger, NavigationMenuContent, NavigationMenuLink, NavigationMenuIndicator, NavigationMenuViewport, useForwardPropsEmits } from 'reka-ui'
141
153
  import { createReusableTemplate } from '@vueuse/core'
142
- import { get } from '../utils'
154
+ import { get, isArrayOfArray } from '../utils'
143
155
  import { pickLinkProps } from '../utils/link'
144
156
  import icons from '../dictionary/icons'
145
157
  import B24LinkBase from './LinkBase.vue'
@@ -148,7 +160,7 @@ import B24Avatar from './Avatar.vue'
148
160
  import B24Badge from './Badge.vue'
149
161
  import B24Collapsible from './Collapsible.vue'
150
162
 
151
- const props = withDefaults(defineProps<NavigationMenuProps<I>>(), {
163
+ const props = withDefaults(defineProps<NavigationMenuProps<T>>(), {
152
164
  orientation: 'horizontal',
153
165
  contentOrientation: 'vertical',
154
166
  externalIcon: true,
@@ -174,8 +186,14 @@ const rootProps = useForwardPropsEmits(computed(() => ({
174
186
 
175
187
  const contentProps = toRef(() => props.content)
176
188
 
177
- const [DefineLinkTemplate, ReuseLinkTemplate] = createReusableTemplate<{ item: NavigationMenuItem, index: number, active?: boolean }>()
178
- const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: NavigationMenuItem, index: number, level?: number }>({
189
+ const [DefineLinkTemplate, ReuseLinkTemplate] = createReusableTemplate<
190
+ { item: NavigationMenuItem, index: number, active?: boolean },
191
+ NavigationMenuSlots<T>
192
+ >()
193
+ const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<
194
+ { item: NavigationMenuItem, index: number, level?: number },
195
+ NavigationMenuSlots<T>
196
+ >({
179
197
  props: {
180
198
  item: Object,
181
199
  index: Number,
@@ -193,14 +211,20 @@ const b24ui = computed(() => navigationMenu({
193
211
  highlightColor: props.highlightColor || props.color
194
212
  }))
195
213
 
196
- const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]) ? props.items : [props.items]) as T[][] : [])
214
+ const lists = computed<NavigationMenuItem[][]>(() =>
215
+ props.items?.length
216
+ ? isArrayOfArray(props.items)
217
+ ? props.items
218
+ : [props.items]
219
+ : []
220
+ )
197
221
  </script>
198
222
 
199
223
  <template>
200
224
  <DefineLinkTemplate v-slot="{ item, active, index }">
201
- <slot :name="item.slot || 'item'" :item="(item as T)" :index="index">
225
+ <slot :name="((item.slot || 'item') as keyof NavigationMenuSlots<T>)" :item="item" :index="index">
202
226
  <span :class="b24ui.linkLabelWrapper({ class: props.b24ui?.linkLabelWrapper, active })">
203
- <slot :name="item.slot ? `${item.slot}-leading` : 'item-leading'" :item="(item as T)" :active="active" :index="index">
227
+ <slot :name="((item.slot ? `${item.slot}-leading` : 'item-leading') as keyof NavigationMenuSlots<T>)" :item="item" :active="active" :index="index">
204
228
  <Component
205
229
  :is="item.icon"
206
230
  v-if="item.icon"
@@ -210,10 +234,10 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
210
234
  </slot>
211
235
 
212
236
  <span
213
- v-if="(!collapsed || orientation !== 'vertical') && (get(item, props.labelKey as string) || !!slots[item.slot ? `${item.slot}-label` : 'item-label'])"
237
+ v-if="(!collapsed || orientation !== 'vertical') && (get(item, props.labelKey as string) || !!slots[(item.slot ? `${item.slot}-label` : 'item-label') as keyof NavigationMenuSlots<T>])"
214
238
  :class="b24ui.linkLabel({ class: props.b24ui?.linkLabel, active })"
215
239
  >
216
- <slot :name="item.slot ? `${item.slot}-label` : 'item-label'" :item="(item as T)" :active="active" :index="index">
240
+ <slot :name="((item.slot ? `${item.slot}-label` : 'item-label') as keyof NavigationMenuSlots<T>)" :item="item" :active="active" :index="index">
217
241
  {{ get(item, props.labelKey as string) }}
218
242
  </slot>
219
243
 
@@ -224,11 +248,11 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
224
248
  />
225
249
  </span>
226
250
  </span>
227
- <span v-if="(!collapsed || orientation !== 'vertical') && (item.badge || (orientation === 'horizontal' && (item.children?.length || !!slots[item.slot ? `${item.slot}-content` : 'item-content'])) || (orientation === 'vertical' && item.children?.length) || item.trailingIcon || !!slots[item.slot ? `${item.slot}-trailing` : 'item-trailing'])" :class="b24ui.linkTrailing({ class: props.b24ui?.linkTrailing })">
228
- <slot :name="item.slot ? `${item.slot}-trailing` : 'item-trailing'" :item="(item as T)" :active="active" :index="index">
251
+ <span v-if="(!collapsed || orientation !== 'vertical') && (item.badge || (orientation === 'horizontal' && (item.children?.length || !!slots[(item.slot ? `${item.slot}-content` : 'item-content') as keyof NavigationMenuSlots<T>])) || (orientation === 'vertical' && item.children?.length) || item.trailingIcon || !!slots[(item.slot ? `${item.slot}-trailing` : 'item-trailing') as keyof NavigationMenuSlots<T>])" :class="b24ui.linkTrailing({ class: props.b24ui?.linkTrailing })">
252
+ <slot :name="((item.slot ? `${item.slot}-trailing` : 'item-trailing') as keyof NavigationMenuSlots<T>)" :item="item" :active="active" :index="index">
229
253
  <Component
230
254
  :is="item.trailingIcon || trailingIcon || icons.chevronDown"
231
- v-if="(orientation === 'horizontal' && (item.children?.length || !!slots[item.slot ? `${item.slot}-content` : 'item-content'])) || (orientation === 'vertical' && item.children?.length)"
255
+ v-if="(orientation === 'horizontal' && (item.children?.length || !!slots[(item.slot ? `${item.slot}-content` : 'item-content') as keyof NavigationMenuSlots<T>])) || (orientation === 'vertical' && item.children?.length)"
232
256
  :class="b24ui.linkTrailingIcon({ class: props.b24ui?.linkTrailingIcon, active })"
233
257
  />
234
258
  <Component
@@ -261,28 +285,28 @@ const lists = computed(() => props.items?.length ? (Array.isArray(props.items[0]
261
285
  :open="item.open"
262
286
  >
263
287
  <div v-if="orientation === 'vertical' && item.type === 'label'" :class="b24ui.label({ class: props.b24ui?.label })">
264
- <ReuseLinkTemplate :item="(item as T)" :index="index" />
288
+ <ReuseLinkTemplate :item="item" :index="index" />
265
289
  </div>
266
290
  <B24Link v-else-if="item.type !== 'label'" v-slot="{ active, ...slotProps }" v-bind="(orientation === 'vertical' && item.children?.length && !collapsed) ? {} : pickLinkProps(item as Omit<NavigationMenuItem, 'type'>)" custom>
267
291
  <component
268
- :is="(orientation === 'horizontal' && (item.children?.length || !!slots[item.slot ? `${item.slot}-content` : 'item-content'])) ? NavigationMenuTrigger : NavigationMenuLink"
292
+ :is="(orientation === 'horizontal' && (item.children?.length || !!slots[(item.slot ? `${item.slot}-content` : 'item-content') as keyof NavigationMenuSlots<T>])) ? NavigationMenuTrigger : NavigationMenuLink"
269
293
  as-child
270
294
  :active="active || item.active"
271
295
  :disabled="item.disabled"
272
296
  @select="item.onSelect"
273
297
  >
274
298
  <B24LinkBase v-bind="slotProps" :class="b24ui.link({ class: [props.b24ui?.link, item.class], active: active || item.active, disabled: !!item.disabled, level: orientation === 'horizontal' || level > 0 })">
275
- <ReuseLinkTemplate :item="(item as T)" :active="active || item.active" :index="index" />
299
+ <ReuseLinkTemplate :item="item" :active="active || item.active" :index="index" />
276
300
  </B24LinkBase>
277
301
  </component>
278
302
 
279
303
  <NavigationMenuContent
280
- v-if="orientation === 'horizontal' && (item.children?.length || !!slots[item.slot ? `${item.slot}-content` : 'item-content'])"
304
+ v-if="orientation === 'horizontal' && (item.children?.length || !!slots[(item.slot ? `${item.slot}-content` : 'item-content') as keyof NavigationMenuSlots<T>])"
281
305
  v-bind="contentProps"
282
306
  :data-viewport="item.viewportRtl ? 'rtl' : 'ltr'"
283
307
  :class="b24ui.content({ class: props.b24ui?.content })"
284
308
  >
285
- <slot :name="item.slot ? `${item.slot}-content` : 'item-content'" :item="(item as T)" :active="active" :index="index">
309
+ <slot :name="((item.slot ? `${item.slot}-content` : 'item-content') as keyof NavigationMenuSlots<T>)" :item="item" :active="active" :index="index">
286
310
  <ul :class="b24ui.childList({ class: props.b24ui?.childList })">
287
311
  <li v-for="(childItem, childIndex) in item.children" :key="childIndex" :class="b24ui.childItem({ class: props.b24ui?.childItem })">
288
312
  <B24Link v-slot="{ active: childActive, ...childSlotProps }" v-bind="pickLinkProps(childItem)" custom>