@treeui/vue 0.17.0 → 0.19.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 (120) hide show
  1. package/dist/components/TAppShell.cjs.map +1 -1
  2. package/dist/components/TAppShell.js.map +1 -1
  3. package/dist/components/TAppShell.vue.d.ts +24 -2
  4. package/dist/components/TAppShell.vue.d.ts.map +1 -1
  5. package/dist/components/TAppShell.vue_vue_type_script_setup_true_lang.cjs +1 -1
  6. package/dist/components/TAppShell.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  7. package/dist/components/TAppShell.vue_vue_type_script_setup_true_lang.js +96 -121
  8. package/dist/components/TAppShell.vue_vue_type_script_setup_true_lang.js.map +1 -1
  9. package/dist/components/TButton.cjs.map +1 -1
  10. package/dist/components/TButton.js.map +1 -1
  11. package/dist/components/TButton.vue.d.ts +21 -0
  12. package/dist/components/TButton.vue.d.ts.map +1 -1
  13. package/dist/components/TButton.vue_vue_type_script_setup_true_lang.cjs +1 -1
  14. package/dist/components/TButton.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  15. package/dist/components/TButton.vue_vue_type_script_setup_true_lang.js +47 -31
  16. package/dist/components/TButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
  17. package/dist/components/TDatePicker.cjs.map +1 -1
  18. package/dist/components/TDatePicker.js.map +1 -1
  19. package/dist/components/TDatePicker.vue_vue_type_script_setup_true_lang.cjs +1 -1
  20. package/dist/components/TDatePicker.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  21. package/dist/components/TDatePicker.vue_vue_type_script_setup_true_lang.js +1 -1
  22. package/dist/components/TDatePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  23. package/dist/components/TDateTimePicker.cjs.map +1 -1
  24. package/dist/components/TDateTimePicker.js.map +1 -1
  25. package/dist/components/TDateTimePicker.vue_vue_type_script_setup_true_lang.cjs +1 -1
  26. package/dist/components/TDateTimePicker.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  27. package/dist/components/TDateTimePicker.vue_vue_type_script_setup_true_lang.js +1 -1
  28. package/dist/components/TDateTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  29. package/dist/components/TDrawer.cjs.map +1 -1
  30. package/dist/components/TDrawer.js.map +1 -1
  31. package/dist/components/TDrawer.vue_vue_type_script_setup_true_lang.cjs +1 -1
  32. package/dist/components/TDrawer.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  33. package/dist/components/TDrawer.vue_vue_type_script_setup_true_lang.js +1 -1
  34. package/dist/components/TDrawer.vue_vue_type_script_setup_true_lang.js.map +1 -1
  35. package/dist/components/TIcon.cjs.map +1 -1
  36. package/dist/components/TIcon.js.map +1 -1
  37. package/dist/components/TIcon.vue.d.ts.map +1 -1
  38. package/dist/components/TIcon.vue_vue_type_script_setup_true_lang.cjs +1 -1
  39. package/dist/components/TIcon.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  40. package/dist/components/TIcon.vue_vue_type_script_setup_true_lang.js +18 -17
  41. package/dist/components/TIcon.vue_vue_type_script_setup_true_lang.js.map +1 -1
  42. package/dist/components/TLanguageSelect.vue.d.ts +1 -1
  43. package/dist/components/TLanguageSelect.vue.d.ts.map +1 -1
  44. package/dist/components/TModal.cjs.map +1 -1
  45. package/dist/components/TModal.js.map +1 -1
  46. package/dist/components/TModal.vue_vue_type_script_setup_true_lang.cjs +1 -1
  47. package/dist/components/TModal.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  48. package/dist/components/TModal.vue_vue_type_script_setup_true_lang.js +1 -1
  49. package/dist/components/TModal.vue_vue_type_script_setup_true_lang.js.map +1 -1
  50. package/dist/components/TMultiSelect.cjs.map +1 -1
  51. package/dist/components/TMultiSelect.js.map +1 -1
  52. package/dist/components/TMultiSelect.vue_vue_type_script_setup_true_lang.cjs +1 -1
  53. package/dist/components/TMultiSelect.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  54. package/dist/components/TMultiSelect.vue_vue_type_script_setup_true_lang.js +1 -1
  55. package/dist/components/TMultiSelect.vue_vue_type_script_setup_true_lang.js.map +1 -1
  56. package/dist/components/TNavMenu.cjs.map +1 -1
  57. package/dist/components/TNavMenu.js.map +1 -1
  58. package/dist/components/TNavMenu.vue.d.ts +7 -2
  59. package/dist/components/TNavMenu.vue.d.ts.map +1 -1
  60. package/dist/components/TNavMenu.vue_vue_type_script_setup_true_lang.cjs +1 -1
  61. package/dist/components/TNavMenu.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  62. package/dist/components/TNavMenu.vue_vue_type_script_setup_true_lang.js +50 -49
  63. package/dist/components/TNavMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  64. package/dist/components/TPricingCard.vue.d.ts +1 -1
  65. package/dist/components/TPricingCard.vue.d.ts.map +1 -1
  66. package/dist/components/TSidebar.cjs.map +1 -1
  67. package/dist/components/TSidebar.js.map +1 -1
  68. package/dist/components/TSidebar.vue_vue_type_script_setup_true_lang.cjs +1 -1
  69. package/dist/components/TSidebar.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  70. package/dist/components/TSidebar.vue_vue_type_script_setup_true_lang.js +1 -1
  71. package/dist/components/TSidebar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  72. package/dist/components/TSteps.cjs.map +1 -1
  73. package/dist/components/TSteps.js.map +1 -1
  74. package/dist/components/TSteps.vue_vue_type_script_setup_true_lang.cjs +1 -1
  75. package/dist/components/TSteps.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  76. package/dist/components/TSteps.vue_vue_type_script_setup_true_lang.js +1 -1
  77. package/dist/components/TSteps.vue_vue_type_script_setup_true_lang.js.map +1 -1
  78. package/dist/components/TTable.cjs.map +1 -1
  79. package/dist/components/TTable.js.map +1 -1
  80. package/dist/components/TTable.vue.d.ts +8 -2
  81. package/dist/components/TTable.vue.d.ts.map +1 -1
  82. package/dist/components/TTable.vue_vue_type_script_setup_true_lang.cjs +1 -1
  83. package/dist/components/TTable.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  84. package/dist/components/TTable.vue_vue_type_script_setup_true_lang.js +71 -80
  85. package/dist/components/TTable.vue_vue_type_script_setup_true_lang.js.map +1 -1
  86. package/dist/components/TTag.cjs.map +1 -1
  87. package/dist/components/TTag.js.map +1 -1
  88. package/dist/components/TTag.vue.d.ts.map +1 -1
  89. package/dist/components/TTag.vue_vue_type_script_setup_true_lang.cjs +1 -1
  90. package/dist/components/TTag.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  91. package/dist/components/TTag.vue_vue_type_script_setup_true_lang.js +22 -29
  92. package/dist/components/TTag.vue_vue_type_script_setup_true_lang.js.map +1 -1
  93. package/dist/components/TText.cjs.map +1 -1
  94. package/dist/components/TText.js.map +1 -1
  95. package/dist/components/TText.vue.d.ts +7 -0
  96. package/dist/components/TText.vue.d.ts.map +1 -1
  97. package/dist/components/TText.vue_vue_type_script_setup_true_lang.cjs +1 -1
  98. package/dist/components/TText.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  99. package/dist/components/TText.vue_vue_type_script_setup_true_lang.js +8 -1
  100. package/dist/components/TText.vue_vue_type_script_setup_true_lang.js.map +1 -1
  101. package/dist/components/TToast.cjs.map +1 -1
  102. package/dist/components/TToast.js.map +1 -1
  103. package/dist/components/TToast.vue.d.ts.map +1 -1
  104. package/dist/components/TToast.vue_vue_type_script_setup_true_lang.cjs +1 -1
  105. package/dist/components/TToast.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  106. package/dist/components/TToast.vue_vue_type_script_setup_true_lang.js +6 -6
  107. package/dist/components/TToast.vue_vue_type_script_setup_true_lang.js.map +1 -1
  108. package/dist/components/TTreeViewItem.cjs.map +1 -1
  109. package/dist/components/TTreeViewItem.js.map +1 -1
  110. package/dist/components/TTreeViewItem.vue_vue_type_script_setup_true_lang.cjs +1 -1
  111. package/dist/components/TTreeViewItem.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  112. package/dist/components/TTreeViewItem.vue_vue_type_script_setup_true_lang.js +1 -1
  113. package/dist/components/TTreeViewItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  114. package/dist/components/index.d.ts +2 -1
  115. package/dist/components/index.d.ts.map +1 -1
  116. package/dist/components/index.js +77 -0
  117. package/dist/index.cjs +1 -1
  118. package/dist/index.js +10 -9
  119. package/dist/style.css +1 -1
  120. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"TMultiSelect.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../src/components/TMultiSelect.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { getTreeIcon } from '@treeui/icons';\nimport { createId, getNextEnabledIndex, isEscapeKey } from '@treeui/utils';\nimport { computed, nextTick, onBeforeUnmount, ref, toRef, useAttrs, watch } from 'vue';\nimport { useControllableOpen } from '../composables/useControllableOpen';\nimport type { TSize } from '../types/contracts';\nimport TSpinner from './TSpinner.vue';\n\ndefineOptions({\n inheritAttrs: false,\n});\n\nexport interface TMultiSelectOption {\n label: string;\n value: string;\n description?: string;\n keywords?: string[];\n disabled?: boolean;\n}\n\nconst ChevronDownIcon = getTreeIcon('chevron-down');\nconst CheckIcon = getTreeIcon('check');\nconst XIcon = getTreeIcon('x');\n\nconst props = withDefaults(\n defineProps<{\n modelValue?: string[];\n options?: TMultiSelectOption[];\n open?: boolean;\n defaultOpen?: boolean;\n size?: TSize;\n disabled?: boolean;\n loading?: boolean;\n invalid?: boolean;\n placeholder?: string;\n emptyText?: string;\n maxVisibleTags?: number;\n }>(),\n {\n modelValue: () => [],\n options: () => [],\n open: undefined,\n defaultOpen: false,\n size: 'md',\n disabled: false,\n loading: false,\n invalid: false,\n placeholder: '',\n emptyText: 'No results found.',\n maxVisibleTags: 2,\n },\n);\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string[]];\n 'update:open': [value: boolean];\n 'open-change': [value: boolean];\n 'input-change': [value: string];\n}>();\n\ndefineSlots<{\n prefix?: () => unknown;\n tag?: (props: { option: TMultiSelectOption; remove: () => void }) => unknown;\n option?: (props: {\n option: TMultiSelectOption;\n selected: boolean;\n active: boolean;\n }) => unknown;\n empty?: (props: { query: string }) => unknown;\n}>();\n\nconst attrs = useAttrs();\nconst listboxId = createId('t-multi-select');\nconst rootRef = ref<HTMLElement | null>(null);\nconst inputRef = ref<HTMLInputElement | null>(null);\nconst query = ref('');\nconst activeIndex = ref(-1);\n\nconst inputAttrs = computed(() => {\n const { class: _class, style: _style, autocomplete, ...rest } = attrs;\n return {\n autocomplete: typeof autocomplete === 'string' ? autocomplete : 'off',\n ...rest,\n };\n});\n\nconst { value: isOpen, setValue } = useControllableOpen(\n toRef(props, 'open'),\n props.defaultOpen,\n (value) => {\n emit('update:open', value);\n emit('open-change', value);\n },\n);\n\nconst rootClasses = computed(() => [\n 't-multi-select',\n `t-multi-select--${props.size}`,\n {\n 'is-disabled': props.disabled,\n 'is-invalid': props.invalid,\n 'is-loading': props.loading,\n 'is-open': isOpen.value,\n },\n attrs.class,\n]);\n\nconst rootStyle = computed(() => attrs.style);\n\nconst normalize = (value: string) => value.trim().toLowerCase();\n\nconst filteredOptions = computed(() => {\n const normalizedQuery = normalize(query.value);\n\n if (!normalizedQuery) {\n return props.options;\n }\n\n return props.options.filter((option) => {\n const fields = [option.label, option.description ?? '', ...(option.keywords ?? [])];\n return fields.some((field) => field.toLowerCase().includes(normalizedQuery));\n });\n});\n\nconst selectedOptions = computed(() =>\n props.options.filter((option) => props.modelValue.includes(option.value)),\n);\n\nconst visibleTags = computed(() => selectedOptions.value.slice(0, props.maxVisibleTags));\nconst hiddenTagCount = computed(() =>\n Math.max(selectedOptions.value.length - visibleTags.value.length, 0),\n);\n\nconst enabledFilteredOptions = computed(() => filteredOptions.value.filter((option) => !option.disabled));\n\nconst activeOption = computed(() =>\n activeIndex.value >= 0 ? filteredOptions.value[activeIndex.value] : undefined,\n);\n\nconst activeDescendant = computed(() =>\n isOpen.value && activeOption.value\n ? `${listboxId}-option-${activeOption.value.value}`\n : undefined,\n);\n\nconst inputPlaceholder = computed(() =>\n props.modelValue.length === 0 && !query.value ? props.placeholder : '',\n);\n\nconst isSelected = (value: string) => props.modelValue.includes(value);\n\nconst setInitialActiveIndex = (fallback: 'first' | 'last' = 'first') => {\n const selectedIndex = filteredOptions.value.findIndex(\n (option) => isSelected(option.value) && !option.disabled,\n );\n\n if (selectedIndex >= 0) {\n activeIndex.value = selectedIndex;\n return;\n }\n\n if (enabledFilteredOptions.value.length === 0) {\n activeIndex.value = -1;\n return;\n }\n\n const targetOption =\n fallback === 'last'\n ? enabledFilteredOptions.value[enabledFilteredOptions.value.length - 1]\n : enabledFilteredOptions.value[0];\n\n activeIndex.value = filteredOptions.value.findIndex(\n (option) => option.value === targetOption.value,\n );\n};\n\nconst openListbox = (fallback: 'first' | 'last' = 'first') => {\n if (props.disabled) {\n return;\n }\n\n setValue(true);\n setInitialActiveIndex(fallback);\n};\n\nconst closeListbox = () => {\n setValue(false);\n activeIndex.value = -1;\n};\n\nconst focusInput = () => {\n nextTick(() => inputRef.value?.focus());\n};\n\nconst updateValues = (nextValues: string[]) => {\n emit('update:modelValue', nextValues);\n};\n\nconst toggleOption = (option: TMultiSelectOption) => {\n if (props.disabled || option.disabled) {\n return;\n }\n\n const nextValues = isSelected(option.value)\n ? props.modelValue.filter((value) => value !== option.value)\n : [...props.modelValue, option.value];\n\n updateValues(nextValues);\n query.value = '';\n setInitialActiveIndex('first');\n focusInput();\n};\n\nconst removeValue = (value: string) => {\n if (props.disabled) {\n return;\n }\n\n updateValues(props.modelValue.filter((item) => item !== value));\n focusInput();\n};\n\nconst moveActive = (direction: 1 | -1) => {\n if (filteredOptions.value.length === 0) {\n activeIndex.value = -1;\n return;\n }\n\n const nextIndex = getNextEnabledIndex(activeIndex.value, filteredOptions.value, direction);\n activeIndex.value = nextIndex;\n};\n\nconst onInput = (event: Event) => {\n query.value = (event.target as HTMLInputElement).value;\n emit('input-change', query.value);\n\n if (!props.disabled && !isOpen.value) {\n openListbox('first');\n return;\n }\n\n setInitialActiveIndex('first');\n};\n\nconst onInputFocus = () => {\n openListbox('first');\n};\n\nconst onInputKeydown = (event: KeyboardEvent) => {\n if (props.disabled) {\n return;\n }\n\n if (event.key === 'Tab') {\n closeListbox();\n return;\n }\n\n if (isEscapeKey(event)) {\n event.preventDefault();\n closeListbox();\n return;\n }\n\n if (event.key === 'Backspace' && !query.value && props.modelValue.length > 0) {\n event.preventDefault();\n removeValue(props.modelValue[props.modelValue.length - 1]);\n return;\n }\n\n if (event.key === 'ArrowDown') {\n event.preventDefault();\n\n if (!isOpen.value) {\n openListbox('first');\n return;\n }\n\n moveActive(1);\n return;\n }\n\n if (event.key === 'ArrowUp') {\n event.preventDefault();\n\n if (!isOpen.value) {\n openListbox('last');\n return;\n }\n\n moveActive(-1);\n return;\n }\n\n if (event.key === 'Home' && isOpen.value) {\n event.preventDefault();\n setInitialActiveIndex('first');\n return;\n }\n\n if (event.key === 'End' && isOpen.value) {\n event.preventDefault();\n setInitialActiveIndex('last');\n return;\n }\n\n if (event.key === 'Enter' && activeOption.value) {\n event.preventDefault();\n toggleOption(activeOption.value);\n }\n};\n\nconst onDocumentPointerDown = (event: PointerEvent) => {\n if (!isOpen.value) {\n return;\n }\n\n const target = event.target;\n\n if (!(target instanceof Node)) {\n return;\n }\n\n if (rootRef.value?.contains(target)) {\n return;\n }\n\n closeListbox();\n};\n\nwatch(\n isOpen,\n (value) => {\n if (value) {\n document.addEventListener('pointerdown', onDocumentPointerDown);\n } else {\n document.removeEventListener('pointerdown', onDocumentPointerDown);\n }\n },\n);\n\nonBeforeUnmount(() => {\n document.removeEventListener('pointerdown', onDocumentPointerDown);\n});\n</script>\n\n<template>\n <div\n ref=\"rootRef\"\n :class=\"rootClasses\"\n :style=\"rootStyle\"\n >\n <div\n class=\"t-multi-select__control\"\n @click=\"focusInput\"\n >\n <span\n v-if=\"$slots.prefix\"\n class=\"t-multi-select__slot\"\n >\n <slot name=\"prefix\" />\n </span>\n\n <div class=\"t-multi-select__values\">\n <span\n v-for=\"option in visibleTags\"\n :key=\"option.value\"\n class=\"t-multi-select__tag\"\n >\n <slot\n name=\"tag\"\n :option=\"option\"\n :remove=\"() => removeValue(option.value)\"\n >\n <span class=\"t-multi-select__tag-label\">{{ option.label }}</span>\n <button\n type=\"button\"\n class=\"t-multi-select__tag-remove\"\n :disabled=\"disabled\"\n aria-label=\"Remove selection\"\n @mousedown.prevent\n @click.stop=\"removeValue(option.value)\"\n >\n <XIcon :size=\"12\" />\n </button>\n </slot>\n </span>\n\n <span\n v-if=\"hiddenTagCount > 0\"\n class=\"t-multi-select__overflow\"\n >\n +{{ hiddenTagCount }}\n </span>\n\n <input\n ref=\"inputRef\"\n v-bind=\"inputAttrs\"\n :value=\"query\"\n type=\"text\"\n class=\"t-multi-select__input\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n :aria-expanded=\"isOpen\"\n :aria-controls=\"isOpen ? listboxId : undefined\"\n :aria-activedescendant=\"activeDescendant\"\n :aria-invalid=\"invalid || undefined\"\n :aria-busy=\"loading || undefined\"\n :disabled=\"disabled\"\n :placeholder=\"inputPlaceholder\"\n @focus=\"onInputFocus\"\n @input=\"onInput\"\n @keydown=\"onInputKeydown\"\n >\n </div>\n\n <TSpinner\n v-if=\"loading\"\n class=\"t-multi-select__indicator\"\n size=\"sm\"\n />\n <ChevronDownIcon\n v-else\n class=\"t-multi-select__indicator\"\n :size=\"16\"\n />\n </div>\n\n <Transition name=\"t-fade\">\n <ul\n v-if=\"isOpen\"\n :id=\"listboxId\"\n class=\"t-multi-select__listbox\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n >\n <li\n v-if=\"filteredOptions.length === 0\"\n class=\"t-multi-select__empty\"\n >\n <slot\n name=\"empty\"\n :query=\"query\"\n >\n {{ emptyText }}\n </slot>\n </li>\n\n <template v-else>\n <li\n v-for=\"(option, index) in filteredOptions\"\n :key=\"option.value\"\n >\n <button\n :id=\"`${listboxId}-option-${option.value}`\"\n type=\"button\"\n class=\"t-multi-select__option\"\n :class=\"{\n 'is-active': index === activeIndex,\n 'is-selected': isSelected(option.value),\n 'is-disabled': option.disabled,\n }\"\n role=\"option\"\n :aria-selected=\"isSelected(option.value)\"\n :aria-disabled=\"option.disabled || undefined\"\n :disabled=\"option.disabled\"\n @mousedown.prevent\n @click=\"toggleOption(option)\"\n >\n <slot\n name=\"option\"\n :option=\"option\"\n :selected=\"isSelected(option.value)\"\n :active=\"index === activeIndex\"\n >\n <span class=\"t-multi-select__option-copy\">\n <span class=\"t-multi-select__option-label\">{{ option.label }}</span>\n <span\n v-if=\"option.description\"\n class=\"t-multi-select__option-description\"\n >\n {{ option.description }}\n </span>\n </span>\n\n <span\n class=\"t-multi-select__check\"\n aria-hidden=\"true\"\n >\n <CheckIcon\n v-if=\"isSelected(option.value)\"\n :size=\"16\"\n />\n </span>\n </slot>\n </button>\n </li>\n </template>\n </ul>\n </Transition>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"TMultiSelect.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../src/components/TMultiSelect.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { getTreeIcon } from '@treeui/icons';\nimport { createId, getNextEnabledIndex, isEscapeKey } from '@treeui/utils';\nimport { computed, nextTick, onBeforeUnmount, ref, toRef, useAttrs, watch } from 'vue';\nimport { useControllableOpen } from '../composables/useControllableOpen';\nimport type { TSize } from '../types/contracts';\nimport TSpinner from './TSpinner.vue';\n\ndefineOptions({\n inheritAttrs: false,\n});\n\nexport interface TMultiSelectOption {\n label: string;\n value: string;\n description?: string;\n keywords?: string[];\n disabled?: boolean;\n}\n\nconst ChevronDownIcon = computed(() => getTreeIcon('chevron-down'));\nconst CheckIcon = computed(() => getTreeIcon('check'));\nconst XIcon = computed(() => getTreeIcon('x'));\n\nconst props = withDefaults(\n defineProps<{\n modelValue?: string[];\n options?: TMultiSelectOption[];\n open?: boolean;\n defaultOpen?: boolean;\n size?: TSize;\n disabled?: boolean;\n loading?: boolean;\n invalid?: boolean;\n placeholder?: string;\n emptyText?: string;\n maxVisibleTags?: number;\n }>(),\n {\n modelValue: () => [],\n options: () => [],\n open: undefined,\n defaultOpen: false,\n size: 'md',\n disabled: false,\n loading: false,\n invalid: false,\n placeholder: '',\n emptyText: 'No results found.',\n maxVisibleTags: 2,\n },\n);\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string[]];\n 'update:open': [value: boolean];\n 'open-change': [value: boolean];\n 'input-change': [value: string];\n}>();\n\ndefineSlots<{\n prefix?: () => unknown;\n tag?: (props: { option: TMultiSelectOption; remove: () => void }) => unknown;\n option?: (props: {\n option: TMultiSelectOption;\n selected: boolean;\n active: boolean;\n }) => unknown;\n empty?: (props: { query: string }) => unknown;\n}>();\n\nconst attrs = useAttrs();\nconst listboxId = createId('t-multi-select');\nconst rootRef = ref<HTMLElement | null>(null);\nconst inputRef = ref<HTMLInputElement | null>(null);\nconst query = ref('');\nconst activeIndex = ref(-1);\n\nconst inputAttrs = computed(() => {\n const { class: _class, style: _style, autocomplete, ...rest } = attrs;\n return {\n autocomplete: typeof autocomplete === 'string' ? autocomplete : 'off',\n ...rest,\n };\n});\n\nconst { value: isOpen, setValue } = useControllableOpen(\n toRef(props, 'open'),\n props.defaultOpen,\n (value) => {\n emit('update:open', value);\n emit('open-change', value);\n },\n);\n\nconst rootClasses = computed(() => [\n 't-multi-select',\n `t-multi-select--${props.size}`,\n {\n 'is-disabled': props.disabled,\n 'is-invalid': props.invalid,\n 'is-loading': props.loading,\n 'is-open': isOpen.value,\n },\n attrs.class,\n]);\n\nconst rootStyle = computed(() => attrs.style);\n\nconst normalize = (value: string) => value.trim().toLowerCase();\n\nconst filteredOptions = computed(() => {\n const normalizedQuery = normalize(query.value);\n\n if (!normalizedQuery) {\n return props.options;\n }\n\n return props.options.filter((option) => {\n const fields = [option.label, option.description ?? '', ...(option.keywords ?? [])];\n return fields.some((field) => field.toLowerCase().includes(normalizedQuery));\n });\n});\n\nconst selectedOptions = computed(() =>\n props.options.filter((option) => props.modelValue.includes(option.value)),\n);\n\nconst visibleTags = computed(() => selectedOptions.value.slice(0, props.maxVisibleTags));\nconst hiddenTagCount = computed(() =>\n Math.max(selectedOptions.value.length - visibleTags.value.length, 0),\n);\n\nconst enabledFilteredOptions = computed(() => filteredOptions.value.filter((option) => !option.disabled));\n\nconst activeOption = computed(() =>\n activeIndex.value >= 0 ? filteredOptions.value[activeIndex.value] : undefined,\n);\n\nconst activeDescendant = computed(() =>\n isOpen.value && activeOption.value\n ? `${listboxId}-option-${activeOption.value.value}`\n : undefined,\n);\n\nconst inputPlaceholder = computed(() =>\n props.modelValue.length === 0 && !query.value ? props.placeholder : '',\n);\n\nconst isSelected = (value: string) => props.modelValue.includes(value);\n\nconst setInitialActiveIndex = (fallback: 'first' | 'last' = 'first') => {\n const selectedIndex = filteredOptions.value.findIndex(\n (option) => isSelected(option.value) && !option.disabled,\n );\n\n if (selectedIndex >= 0) {\n activeIndex.value = selectedIndex;\n return;\n }\n\n if (enabledFilteredOptions.value.length === 0) {\n activeIndex.value = -1;\n return;\n }\n\n const targetOption =\n fallback === 'last'\n ? enabledFilteredOptions.value[enabledFilteredOptions.value.length - 1]\n : enabledFilteredOptions.value[0];\n\n activeIndex.value = filteredOptions.value.findIndex(\n (option) => option.value === targetOption.value,\n );\n};\n\nconst openListbox = (fallback: 'first' | 'last' = 'first') => {\n if (props.disabled) {\n return;\n }\n\n setValue(true);\n setInitialActiveIndex(fallback);\n};\n\nconst closeListbox = () => {\n setValue(false);\n activeIndex.value = -1;\n};\n\nconst focusInput = () => {\n nextTick(() => inputRef.value?.focus());\n};\n\nconst updateValues = (nextValues: string[]) => {\n emit('update:modelValue', nextValues);\n};\n\nconst toggleOption = (option: TMultiSelectOption) => {\n if (props.disabled || option.disabled) {\n return;\n }\n\n const nextValues = isSelected(option.value)\n ? props.modelValue.filter((value) => value !== option.value)\n : [...props.modelValue, option.value];\n\n updateValues(nextValues);\n query.value = '';\n setInitialActiveIndex('first');\n focusInput();\n};\n\nconst removeValue = (value: string) => {\n if (props.disabled) {\n return;\n }\n\n updateValues(props.modelValue.filter((item) => item !== value));\n focusInput();\n};\n\nconst moveActive = (direction: 1 | -1) => {\n if (filteredOptions.value.length === 0) {\n activeIndex.value = -1;\n return;\n }\n\n const nextIndex = getNextEnabledIndex(activeIndex.value, filteredOptions.value, direction);\n activeIndex.value = nextIndex;\n};\n\nconst onInput = (event: Event) => {\n query.value = (event.target as HTMLInputElement).value;\n emit('input-change', query.value);\n\n if (!props.disabled && !isOpen.value) {\n openListbox('first');\n return;\n }\n\n setInitialActiveIndex('first');\n};\n\nconst onInputFocus = () => {\n openListbox('first');\n};\n\nconst onInputKeydown = (event: KeyboardEvent) => {\n if (props.disabled) {\n return;\n }\n\n if (event.key === 'Tab') {\n closeListbox();\n return;\n }\n\n if (isEscapeKey(event)) {\n event.preventDefault();\n closeListbox();\n return;\n }\n\n if (event.key === 'Backspace' && !query.value && props.modelValue.length > 0) {\n event.preventDefault();\n removeValue(props.modelValue[props.modelValue.length - 1]);\n return;\n }\n\n if (event.key === 'ArrowDown') {\n event.preventDefault();\n\n if (!isOpen.value) {\n openListbox('first');\n return;\n }\n\n moveActive(1);\n return;\n }\n\n if (event.key === 'ArrowUp') {\n event.preventDefault();\n\n if (!isOpen.value) {\n openListbox('last');\n return;\n }\n\n moveActive(-1);\n return;\n }\n\n if (event.key === 'Home' && isOpen.value) {\n event.preventDefault();\n setInitialActiveIndex('first');\n return;\n }\n\n if (event.key === 'End' && isOpen.value) {\n event.preventDefault();\n setInitialActiveIndex('last');\n return;\n }\n\n if (event.key === 'Enter' && activeOption.value) {\n event.preventDefault();\n toggleOption(activeOption.value);\n }\n};\n\nconst onDocumentPointerDown = (event: PointerEvent) => {\n if (!isOpen.value) {\n return;\n }\n\n const target = event.target;\n\n if (!(target instanceof Node)) {\n return;\n }\n\n if (rootRef.value?.contains(target)) {\n return;\n }\n\n closeListbox();\n};\n\nwatch(\n isOpen,\n (value) => {\n if (value) {\n document.addEventListener('pointerdown', onDocumentPointerDown);\n } else {\n document.removeEventListener('pointerdown', onDocumentPointerDown);\n }\n },\n);\n\nonBeforeUnmount(() => {\n document.removeEventListener('pointerdown', onDocumentPointerDown);\n});\n</script>\n\n<template>\n <div\n ref=\"rootRef\"\n :class=\"rootClasses\"\n :style=\"rootStyle\"\n >\n <div\n class=\"t-multi-select__control\"\n @click=\"focusInput\"\n >\n <span\n v-if=\"$slots.prefix\"\n class=\"t-multi-select__slot\"\n >\n <slot name=\"prefix\" />\n </span>\n\n <div class=\"t-multi-select__values\">\n <span\n v-for=\"option in visibleTags\"\n :key=\"option.value\"\n class=\"t-multi-select__tag\"\n >\n <slot\n name=\"tag\"\n :option=\"option\"\n :remove=\"() => removeValue(option.value)\"\n >\n <span class=\"t-multi-select__tag-label\">{{ option.label }}</span>\n <button\n type=\"button\"\n class=\"t-multi-select__tag-remove\"\n :disabled=\"disabled\"\n aria-label=\"Remove selection\"\n @mousedown.prevent\n @click.stop=\"removeValue(option.value)\"\n >\n <XIcon :size=\"12\" />\n </button>\n </slot>\n </span>\n\n <span\n v-if=\"hiddenTagCount > 0\"\n class=\"t-multi-select__overflow\"\n >\n +{{ hiddenTagCount }}\n </span>\n\n <input\n ref=\"inputRef\"\n v-bind=\"inputAttrs\"\n :value=\"query\"\n type=\"text\"\n class=\"t-multi-select__input\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n :aria-expanded=\"isOpen\"\n :aria-controls=\"isOpen ? listboxId : undefined\"\n :aria-activedescendant=\"activeDescendant\"\n :aria-invalid=\"invalid || undefined\"\n :aria-busy=\"loading || undefined\"\n :disabled=\"disabled\"\n :placeholder=\"inputPlaceholder\"\n @focus=\"onInputFocus\"\n @input=\"onInput\"\n @keydown=\"onInputKeydown\"\n >\n </div>\n\n <TSpinner\n v-if=\"loading\"\n class=\"t-multi-select__indicator\"\n size=\"sm\"\n />\n <ChevronDownIcon\n v-else\n class=\"t-multi-select__indicator\"\n :size=\"16\"\n />\n </div>\n\n <Transition name=\"t-fade\">\n <ul\n v-if=\"isOpen\"\n :id=\"listboxId\"\n class=\"t-multi-select__listbox\"\n role=\"listbox\"\n aria-multiselectable=\"true\"\n >\n <li\n v-if=\"filteredOptions.length === 0\"\n class=\"t-multi-select__empty\"\n >\n <slot\n name=\"empty\"\n :query=\"query\"\n >\n {{ emptyText }}\n </slot>\n </li>\n\n <template v-else>\n <li\n v-for=\"(option, index) in filteredOptions\"\n :key=\"option.value\"\n >\n <button\n :id=\"`${listboxId}-option-${option.value}`\"\n type=\"button\"\n class=\"t-multi-select__option\"\n :class=\"{\n 'is-active': index === activeIndex,\n 'is-selected': isSelected(option.value),\n 'is-disabled': option.disabled,\n }\"\n role=\"option\"\n :aria-selected=\"isSelected(option.value)\"\n :aria-disabled=\"option.disabled || undefined\"\n :disabled=\"option.disabled\"\n @mousedown.prevent\n @click=\"toggleOption(option)\"\n >\n <slot\n name=\"option\"\n :option=\"option\"\n :selected=\"isSelected(option.value)\"\n :active=\"index === activeIndex\"\n >\n <span class=\"t-multi-select__option-copy\">\n <span class=\"t-multi-select__option-label\">{{ option.label }}</span>\n <span\n v-if=\"option.description\"\n class=\"t-multi-select__option-description\"\n >\n {{ option.description }}\n </span>\n </span>\n\n <span\n class=\"t-multi-select__check\"\n aria-hidden=\"true\"\n >\n <CheckIcon\n v-if=\"isSelected(option.value)\"\n :size=\"16\"\n />\n </span>\n </slot>\n </button>\n </li>\n </template>\n </ul>\n </Transition>\n </div>\n</template>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"TNavMenu.cjs","names":[],"sources":["../../src/components/TNavMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { getNextEnabledIndex, isActivationKey } from '@treeui/utils';\nimport { computed, getCurrentInstance, inject, nextTick, ref, useAttrs, watch, type Component, type ComponentPublicInstance } from 'vue';\nimport type { TSize } from '../types/contracts';\nimport { treeSidebarInjectionKey } from './sidebar';\n\ndefineOptions({\n inheritAttrs: false,\n});\n\nexport interface TNavMenuItem {\n label: string;\n value: string;\n shortLabel?: string;\n description?: string;\n icon?: Component;\n to?: string | Record<string, unknown>;\n badge?: string | number;\n disabled?: boolean;\n /**\n * When the item renders as a router link, controls whether an inclusive\n * (prefix) route match also marks it active. Defaults to the menu-level\n * `exact` prop. Set to `false` for section roots that should stay active on\n * nested routes.\n */\n exact?: boolean;\n}\n\nconst props = withDefaults(\n defineProps<{\n modelValue?: string;\n defaultValue?: string;\n items?: TNavMenuItem[];\n size?: TSize;\n collapsed?: boolean;\n disabled?: boolean;\n exact?: boolean;\n }>(),\n {\n modelValue: undefined,\n defaultValue: '',\n items: () => [],\n size: 'md',\n collapsed: undefined,\n disabled: false,\n exact: true,\n },\n);\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string];\n select: [value: string, item: TNavMenuItem];\n}>();\n\ndefineSlots<{\n item?: (props: {\n item: TNavMenuItem;\n selected: boolean;\n focused: boolean;\n collapsed: boolean;\n }) => unknown;\n empty?: () => unknown;\n}>();\n\nconst attrs = useAttrs();\nconst sidebar = inject(treeSidebarInjectionKey, null);\nconst internalValue = ref(props.defaultValue);\nconst itemRefs = ref<Map<string, HTMLElement>>(new Map());\nconst instance = getCurrentInstance();\n\nconst selectedValue = computed(() => props.modelValue ?? internalValue.value);\nconst resolvedCollapsed = computed(() => props.collapsed ?? sidebar?.collapsed.value ?? false);\n\nconst routerLinkComponent = computed<Component | null>(() => (\n (instance?.appContext.components.RouterLink as Component | undefined) ?? null\n));\n\nconst itemTag = (item: TNavMenuItem) => {\n if (item.to && routerLinkComponent.value) {\n return routerLinkComponent.value;\n }\n return 'button';\n};\n\nconst isRouterLinkItem = (item: TNavMenuItem) =>\n Boolean(item.to) && routerLinkComponent.value !== null;\n\n// Keep the active highlight deterministic when items render as router links.\n// Vue Router's default `router-link-active` uses inclusive (prefix) matching,\n// so several links can appear active at once. We map the router's active\n// classes onto TreeUI's own `is-selected`:\n// - Controlled menus (explicit `modelValue`) own selection, so the router adds\n// no class of its own — only the bound `is-selected` shows.\n// - Uncontrolled menus let the current route drive selection, using exact\n// matching by default (opt out per item, or menu-wide, via `exact`).\nconst routerActiveClass = (item: TNavMenuItem) => {\n if (!isRouterLinkItem(item)) {\n return undefined;\n }\n\n if (props.modelValue !== undefined) {\n return '';\n }\n\n return (item.exact ?? props.exact) ? '' : 'is-selected';\n};\n\nconst routerExactActiveClass = (item: TNavMenuItem) => {\n if (!isRouterLinkItem(item)) {\n return undefined;\n }\n\n return props.modelValue !== undefined ? '' : 'is-selected';\n};\n\nconst getInitialFocusedIndex = () => {\n const selectedIndex = props.items.findIndex((item) =>\n item.value === selectedValue.value && !item.disabled,\n );\n\n if (selectedIndex >= 0) {\n return selectedIndex;\n }\n\n return props.items.findIndex((item) => !item.disabled);\n};\n\nconst focusedIndex = ref(getInitialFocusedIndex());\n\nconst rootClasses = computed(() => [\n 't-nav-menu',\n `t-nav-menu--${props.size}`,\n {\n 'is-collapsed': resolvedCollapsed.value,\n 'is-disabled': props.disabled,\n },\n attrs.class,\n]);\n\nconst rootStyle = computed(() => attrs.style);\n\nconst rootAttrs = computed(() => {\n const { class: _class, style: _style, ...rest } = attrs;\n return rest;\n});\n\nconst isSelected = (value: string) => selectedValue.value === value;\n\nconst setItemRef = (element: Element | ComponentPublicInstance | null, value: string) => {\n const resolvedElement =\n element instanceof HTMLElement\n ? element\n : element && '$el' in element && element.$el instanceof HTMLElement\n ? element.$el\n : null;\n\n if (resolvedElement) {\n itemRefs.value.set(value, resolvedElement);\n } else {\n itemRefs.value.delete(value);\n }\n};\n\nconst focusItem = (index: number) => {\n const item = props.items[index];\n\n if (!item) {\n return;\n }\n\n focusedIndex.value = index;\n nextTick(() => itemRefs.value.get(item.value)?.focus());\n};\n\nconst selectItem = (item: TNavMenuItem) => {\n if (props.disabled || item.disabled) {\n return;\n }\n\n if (props.modelValue === undefined) {\n internalValue.value = item.value;\n }\n\n emit('update:modelValue', item.value);\n emit('select', item.value, item);\n};\n\nconst onItemKeydown = (event: KeyboardEvent, index: number) => {\n if (props.disabled) {\n return;\n }\n\n if (event.key === 'Home') {\n event.preventDefault();\n const firstIndex = props.items.findIndex((item) => !item.disabled);\n\n if (firstIndex >= 0) {\n focusItem(firstIndex);\n }\n\n return;\n }\n\n if (event.key === 'End') {\n event.preventDefault();\n const lastIndex = [...props.items].reverse().findIndex((item) => !item.disabled);\n\n if (lastIndex >= 0) {\n focusItem(props.items.length - 1 - lastIndex);\n }\n\n return;\n }\n\n if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {\n event.preventDefault();\n const nextIndex = getNextEnabledIndex(index, props.items, 1);\n\n if (nextIndex >= 0) {\n focusItem(nextIndex);\n }\n\n return;\n }\n\n if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {\n event.preventDefault();\n const previousIndex = getNextEnabledIndex(index, props.items, -1);\n\n if (previousIndex >= 0) {\n focusItem(previousIndex);\n }\n\n return;\n }\n\n if (isActivationKey(event)) {\n event.preventDefault();\n selectItem(props.items[index]);\n }\n};\n\nconst getTabIndex = (index: number) => (focusedIndex.value === index ? 0 : -1);\n\nwatch(\n () => [props.items, props.modelValue] as const,\n () => {\n const nextIndex = getInitialFocusedIndex();\n focusedIndex.value = nextIndex >= 0 ? nextIndex : -1;\n },\n { deep: true },\n);\n</script>\n\n<template>\n <nav\n v-bind=\"rootAttrs\"\n :class=\"rootClasses\"\n :style=\"rootStyle\"\n >\n <ul\n v-if=\"items.length > 0\"\n class=\"t-nav-menu__list\"\n >\n <li\n v-for=\"(item, index) in items\"\n :key=\"item.value\"\n class=\"t-nav-menu__entry\"\n >\n <component\n :is=\"itemTag(item)\"\n :ref=\"(element: Element | ComponentPublicInstance | null) => setItemRef(element, item.value)\"\n :type=\"item.to ? undefined : 'button'\"\n :to=\"item.to || undefined\"\n class=\"t-nav-menu__item\"\n :class=\"{\n 'is-selected': isSelected(item.value),\n 'is-disabled': disabled || item.disabled,\n }\"\n :active-class=\"routerActiveClass(item)\"\n :exact-active-class=\"routerExactActiveClass(item)\"\n :disabled=\"(!item.to && (disabled || item.disabled)) || undefined\"\n :aria-disabled=\"(item.to && (disabled || item.disabled)) || undefined\"\n :aria-current=\"isSelected(item.value) ? 'page' : undefined\"\n :tabindex=\"disabled || item.disabled ? -1 : getTabIndex(index)\"\n :title=\"resolvedCollapsed ? item.label : undefined\"\n @click=\"selectItem(item)\"\n @keydown=\"onItemKeydown($event, index)\"\n @focus=\"focusedIndex = index\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n :selected=\"isSelected(item.value)\"\n :focused=\"focusedIndex === index\"\n :collapsed=\"resolvedCollapsed\"\n >\n <component\n :is=\"item.icon\"\n v-if=\"item.icon\"\n class=\"t-nav-menu__icon\"\n aria-hidden=\"true\"\n />\n <span\n v-else\n class=\"t-nav-menu__marker\"\n aria-hidden=\"true\"\n >\n {{ (item.shortLabel ?? item.label.charAt(0)).toUpperCase() }}\n </span>\n\n <span class=\"t-nav-menu__copy\">\n <span class=\"t-nav-menu__label\">{{ item.label }}</span>\n <span\n v-if=\"item.description\"\n class=\"t-nav-menu__description\"\n >\n {{ item.description }}\n </span>\n </span>\n\n <span\n v-if=\"item.badge !== undefined && item.badge !== null\"\n class=\"t-nav-menu__badge\"\n >\n {{ item.badge }}\n </span>\n </slot>\n </component>\n </li>\n </ul>\n\n <div\n v-else\n class=\"t-nav-menu__empty\"\n >\n <slot name=\"empty\">\n No navigation items.\n </slot>\n </div>\n </nav>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"TNavMenu.cjs","names":[],"sources":["../../src/components/TNavMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { getNextEnabledIndex, isActivationKey } from '@treeui/utils';\nimport { resolveTreeIcon, type TIconInput } from '@treeui/icons';\nimport { computed, getCurrentInstance, inject, nextTick, ref, useAttrs, watch, type Component, type ComponentPublicInstance } from 'vue';\nimport type { TSize } from '../types/contracts';\nimport { treeSidebarInjectionKey } from './sidebar';\n\ndefineOptions({\n inheritAttrs: false,\n});\n\nexport interface TNavMenuItem {\n label: string;\n value: string;\n shortLabel?: string;\n description?: string;\n /**\n * A registered icon name (`'cpu'`), or any component. A name is the simpler\n * form: it needs no import and no `markRaw`, since a string is not made\n * reactive when the item lands in a `ref`.\n */\n icon?: TIconInput;\n to?: string | Record<string, unknown>;\n badge?: string | number;\n disabled?: boolean;\n /**\n * When the item renders as a router link, controls whether an inclusive\n * (prefix) route match also marks it active. Defaults to the menu-level\n * `exact` prop. Set to `false` for section roots that should stay active on\n * nested routes.\n */\n exact?: boolean;\n}\n\nconst props = withDefaults(\n defineProps<{\n modelValue?: string;\n defaultValue?: string;\n items?: TNavMenuItem[];\n size?: TSize;\n collapsed?: boolean;\n disabled?: boolean;\n exact?: boolean;\n }>(),\n {\n modelValue: undefined,\n defaultValue: '',\n items: () => [],\n size: 'md',\n collapsed: undefined,\n disabled: false,\n exact: true,\n },\n);\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string];\n select: [value: string, item: TNavMenuItem];\n}>();\n\ndefineSlots<{\n item?: (props: {\n item: TNavMenuItem;\n selected: boolean;\n focused: boolean;\n collapsed: boolean;\n }) => unknown;\n empty?: () => unknown;\n}>();\n\n/**\n * A registry lookup, so it stays cheap enough to call from the template. An\n * unresolved name returns `undefined` and the item falls back to its letter\n * marker rather than rendering a hole.\n */\nconst iconFor = (item: TNavMenuItem) => resolveTreeIcon(item.icon);\n\nconst attrs = useAttrs();\nconst sidebar = inject(treeSidebarInjectionKey, null);\nconst internalValue = ref(props.defaultValue);\nconst itemRefs = ref<Map<string, HTMLElement>>(new Map());\nconst instance = getCurrentInstance();\n\nconst selectedValue = computed(() => props.modelValue ?? internalValue.value);\nconst resolvedCollapsed = computed(() => props.collapsed ?? sidebar?.collapsed.value ?? false);\n\nconst routerLinkComponent = computed<Component | null>(() => (\n (instance?.appContext.components.RouterLink as Component | undefined) ?? null\n));\n\nconst itemTag = (item: TNavMenuItem) => {\n if (item.to && routerLinkComponent.value) {\n return routerLinkComponent.value;\n }\n return 'button';\n};\n\nconst isRouterLinkItem = (item: TNavMenuItem) =>\n Boolean(item.to) && routerLinkComponent.value !== null;\n\n// Keep the active highlight deterministic when items render as router links.\n// Vue Router's default `router-link-active` uses inclusive (prefix) matching,\n// so several links can appear active at once. We map the router's active\n// classes onto TreeUI's own `is-selected`:\n// - Controlled menus (explicit `modelValue`) own selection, so the router adds\n// no class of its own — only the bound `is-selected` shows.\n// - Uncontrolled menus let the current route drive selection, using exact\n// matching by default (opt out per item, or menu-wide, via `exact`).\nconst routerActiveClass = (item: TNavMenuItem) => {\n if (!isRouterLinkItem(item)) {\n return undefined;\n }\n\n if (props.modelValue !== undefined) {\n return '';\n }\n\n return (item.exact ?? props.exact) ? '' : 'is-selected';\n};\n\nconst routerExactActiveClass = (item: TNavMenuItem) => {\n if (!isRouterLinkItem(item)) {\n return undefined;\n }\n\n return props.modelValue !== undefined ? '' : 'is-selected';\n};\n\nconst getInitialFocusedIndex = () => {\n const selectedIndex = props.items.findIndex((item) =>\n item.value === selectedValue.value && !item.disabled,\n );\n\n if (selectedIndex >= 0) {\n return selectedIndex;\n }\n\n return props.items.findIndex((item) => !item.disabled);\n};\n\nconst focusedIndex = ref(getInitialFocusedIndex());\n\nconst rootClasses = computed(() => [\n 't-nav-menu',\n `t-nav-menu--${props.size}`,\n {\n 'is-collapsed': resolvedCollapsed.value,\n 'is-disabled': props.disabled,\n },\n attrs.class,\n]);\n\nconst rootStyle = computed(() => attrs.style);\n\nconst rootAttrs = computed(() => {\n const { class: _class, style: _style, ...rest } = attrs;\n return rest;\n});\n\nconst isSelected = (value: string) => selectedValue.value === value;\n\nconst setItemRef = (element: Element | ComponentPublicInstance | null, value: string) => {\n const resolvedElement =\n element instanceof HTMLElement\n ? element\n : element && '$el' in element && element.$el instanceof HTMLElement\n ? element.$el\n : null;\n\n if (resolvedElement) {\n itemRefs.value.set(value, resolvedElement);\n } else {\n itemRefs.value.delete(value);\n }\n};\n\nconst focusItem = (index: number) => {\n const item = props.items[index];\n\n if (!item) {\n return;\n }\n\n focusedIndex.value = index;\n nextTick(() => itemRefs.value.get(item.value)?.focus());\n};\n\nconst selectItem = (item: TNavMenuItem) => {\n if (props.disabled || item.disabled) {\n return;\n }\n\n if (props.modelValue === undefined) {\n internalValue.value = item.value;\n }\n\n emit('update:modelValue', item.value);\n emit('select', item.value, item);\n};\n\nconst onItemKeydown = (event: KeyboardEvent, index: number) => {\n if (props.disabled) {\n return;\n }\n\n if (event.key === 'Home') {\n event.preventDefault();\n const firstIndex = props.items.findIndex((item) => !item.disabled);\n\n if (firstIndex >= 0) {\n focusItem(firstIndex);\n }\n\n return;\n }\n\n if (event.key === 'End') {\n event.preventDefault();\n const lastIndex = [...props.items].reverse().findIndex((item) => !item.disabled);\n\n if (lastIndex >= 0) {\n focusItem(props.items.length - 1 - lastIndex);\n }\n\n return;\n }\n\n if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {\n event.preventDefault();\n const nextIndex = getNextEnabledIndex(index, props.items, 1);\n\n if (nextIndex >= 0) {\n focusItem(nextIndex);\n }\n\n return;\n }\n\n if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {\n event.preventDefault();\n const previousIndex = getNextEnabledIndex(index, props.items, -1);\n\n if (previousIndex >= 0) {\n focusItem(previousIndex);\n }\n\n return;\n }\n\n if (isActivationKey(event)) {\n event.preventDefault();\n selectItem(props.items[index]);\n }\n};\n\nconst getTabIndex = (index: number) => (focusedIndex.value === index ? 0 : -1);\n\nwatch(\n () => [props.items, props.modelValue] as const,\n () => {\n const nextIndex = getInitialFocusedIndex();\n focusedIndex.value = nextIndex >= 0 ? nextIndex : -1;\n },\n { deep: true },\n);\n</script>\n\n<template>\n <nav\n v-bind=\"rootAttrs\"\n :class=\"rootClasses\"\n :style=\"rootStyle\"\n >\n <ul\n v-if=\"items.length > 0\"\n class=\"t-nav-menu__list\"\n >\n <li\n v-for=\"(item, index) in items\"\n :key=\"item.value\"\n class=\"t-nav-menu__entry\"\n >\n <component\n :is=\"itemTag(item)\"\n :ref=\"(element: Element | ComponentPublicInstance | null) => setItemRef(element, item.value)\"\n :type=\"item.to ? undefined : 'button'\"\n :to=\"item.to || undefined\"\n class=\"t-nav-menu__item\"\n :class=\"{\n 'is-selected': isSelected(item.value),\n 'is-disabled': disabled || item.disabled,\n }\"\n :active-class=\"routerActiveClass(item)\"\n :exact-active-class=\"routerExactActiveClass(item)\"\n :disabled=\"(!item.to && (disabled || item.disabled)) || undefined\"\n :aria-disabled=\"(item.to && (disabled || item.disabled)) || undefined\"\n :aria-current=\"isSelected(item.value) ? 'page' : undefined\"\n :tabindex=\"disabled || item.disabled ? -1 : getTabIndex(index)\"\n :title=\"resolvedCollapsed ? item.label : undefined\"\n @click=\"selectItem(item)\"\n @keydown=\"onItemKeydown($event, index)\"\n @focus=\"focusedIndex = index\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n :selected=\"isSelected(item.value)\"\n :focused=\"focusedIndex === index\"\n :collapsed=\"resolvedCollapsed\"\n >\n <component\n :is=\"iconFor(item)\"\n v-if=\"iconFor(item)\"\n class=\"t-nav-menu__icon\"\n aria-hidden=\"true\"\n />\n <span\n v-else\n class=\"t-nav-menu__marker\"\n aria-hidden=\"true\"\n >\n {{ (item.shortLabel ?? item.label.charAt(0)).toUpperCase() }}\n </span>\n\n <span class=\"t-nav-menu__copy\">\n <span class=\"t-nav-menu__label\">{{ item.label }}</span>\n <span\n v-if=\"item.description\"\n class=\"t-nav-menu__description\"\n >\n {{ item.description }}\n </span>\n </span>\n\n <span\n v-if=\"item.badge !== undefined && item.badge !== null\"\n class=\"t-nav-menu__badge\"\n >\n {{ item.badge }}\n </span>\n </slot>\n </component>\n </li>\n </ul>\n\n <div\n v-else\n class=\"t-nav-menu__empty\"\n >\n <slot name=\"empty\">\n No navigation items.\n </slot>\n </div>\n </nav>\n</template>\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"TNavMenu.js","names":[],"sources":["../../src/components/TNavMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { getNextEnabledIndex, isActivationKey } from '@treeui/utils';\nimport { computed, getCurrentInstance, inject, nextTick, ref, useAttrs, watch, type Component, type ComponentPublicInstance } from 'vue';\nimport type { TSize } from '../types/contracts';\nimport { treeSidebarInjectionKey } from './sidebar';\n\ndefineOptions({\n inheritAttrs: false,\n});\n\nexport interface TNavMenuItem {\n label: string;\n value: string;\n shortLabel?: string;\n description?: string;\n icon?: Component;\n to?: string | Record<string, unknown>;\n badge?: string | number;\n disabled?: boolean;\n /**\n * When the item renders as a router link, controls whether an inclusive\n * (prefix) route match also marks it active. Defaults to the menu-level\n * `exact` prop. Set to `false` for section roots that should stay active on\n * nested routes.\n */\n exact?: boolean;\n}\n\nconst props = withDefaults(\n defineProps<{\n modelValue?: string;\n defaultValue?: string;\n items?: TNavMenuItem[];\n size?: TSize;\n collapsed?: boolean;\n disabled?: boolean;\n exact?: boolean;\n }>(),\n {\n modelValue: undefined,\n defaultValue: '',\n items: () => [],\n size: 'md',\n collapsed: undefined,\n disabled: false,\n exact: true,\n },\n);\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string];\n select: [value: string, item: TNavMenuItem];\n}>();\n\ndefineSlots<{\n item?: (props: {\n item: TNavMenuItem;\n selected: boolean;\n focused: boolean;\n collapsed: boolean;\n }) => unknown;\n empty?: () => unknown;\n}>();\n\nconst attrs = useAttrs();\nconst sidebar = inject(treeSidebarInjectionKey, null);\nconst internalValue = ref(props.defaultValue);\nconst itemRefs = ref<Map<string, HTMLElement>>(new Map());\nconst instance = getCurrentInstance();\n\nconst selectedValue = computed(() => props.modelValue ?? internalValue.value);\nconst resolvedCollapsed = computed(() => props.collapsed ?? sidebar?.collapsed.value ?? false);\n\nconst routerLinkComponent = computed<Component | null>(() => (\n (instance?.appContext.components.RouterLink as Component | undefined) ?? null\n));\n\nconst itemTag = (item: TNavMenuItem) => {\n if (item.to && routerLinkComponent.value) {\n return routerLinkComponent.value;\n }\n return 'button';\n};\n\nconst isRouterLinkItem = (item: TNavMenuItem) =>\n Boolean(item.to) && routerLinkComponent.value !== null;\n\n// Keep the active highlight deterministic when items render as router links.\n// Vue Router's default `router-link-active` uses inclusive (prefix) matching,\n// so several links can appear active at once. We map the router's active\n// classes onto TreeUI's own `is-selected`:\n// - Controlled menus (explicit `modelValue`) own selection, so the router adds\n// no class of its own — only the bound `is-selected` shows.\n// - Uncontrolled menus let the current route drive selection, using exact\n// matching by default (opt out per item, or menu-wide, via `exact`).\nconst routerActiveClass = (item: TNavMenuItem) => {\n if (!isRouterLinkItem(item)) {\n return undefined;\n }\n\n if (props.modelValue !== undefined) {\n return '';\n }\n\n return (item.exact ?? props.exact) ? '' : 'is-selected';\n};\n\nconst routerExactActiveClass = (item: TNavMenuItem) => {\n if (!isRouterLinkItem(item)) {\n return undefined;\n }\n\n return props.modelValue !== undefined ? '' : 'is-selected';\n};\n\nconst getInitialFocusedIndex = () => {\n const selectedIndex = props.items.findIndex((item) =>\n item.value === selectedValue.value && !item.disabled,\n );\n\n if (selectedIndex >= 0) {\n return selectedIndex;\n }\n\n return props.items.findIndex((item) => !item.disabled);\n};\n\nconst focusedIndex = ref(getInitialFocusedIndex());\n\nconst rootClasses = computed(() => [\n 't-nav-menu',\n `t-nav-menu--${props.size}`,\n {\n 'is-collapsed': resolvedCollapsed.value,\n 'is-disabled': props.disabled,\n },\n attrs.class,\n]);\n\nconst rootStyle = computed(() => attrs.style);\n\nconst rootAttrs = computed(() => {\n const { class: _class, style: _style, ...rest } = attrs;\n return rest;\n});\n\nconst isSelected = (value: string) => selectedValue.value === value;\n\nconst setItemRef = (element: Element | ComponentPublicInstance | null, value: string) => {\n const resolvedElement =\n element instanceof HTMLElement\n ? element\n : element && '$el' in element && element.$el instanceof HTMLElement\n ? element.$el\n : null;\n\n if (resolvedElement) {\n itemRefs.value.set(value, resolvedElement);\n } else {\n itemRefs.value.delete(value);\n }\n};\n\nconst focusItem = (index: number) => {\n const item = props.items[index];\n\n if (!item) {\n return;\n }\n\n focusedIndex.value = index;\n nextTick(() => itemRefs.value.get(item.value)?.focus());\n};\n\nconst selectItem = (item: TNavMenuItem) => {\n if (props.disabled || item.disabled) {\n return;\n }\n\n if (props.modelValue === undefined) {\n internalValue.value = item.value;\n }\n\n emit('update:modelValue', item.value);\n emit('select', item.value, item);\n};\n\nconst onItemKeydown = (event: KeyboardEvent, index: number) => {\n if (props.disabled) {\n return;\n }\n\n if (event.key === 'Home') {\n event.preventDefault();\n const firstIndex = props.items.findIndex((item) => !item.disabled);\n\n if (firstIndex >= 0) {\n focusItem(firstIndex);\n }\n\n return;\n }\n\n if (event.key === 'End') {\n event.preventDefault();\n const lastIndex = [...props.items].reverse().findIndex((item) => !item.disabled);\n\n if (lastIndex >= 0) {\n focusItem(props.items.length - 1 - lastIndex);\n }\n\n return;\n }\n\n if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {\n event.preventDefault();\n const nextIndex = getNextEnabledIndex(index, props.items, 1);\n\n if (nextIndex >= 0) {\n focusItem(nextIndex);\n }\n\n return;\n }\n\n if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {\n event.preventDefault();\n const previousIndex = getNextEnabledIndex(index, props.items, -1);\n\n if (previousIndex >= 0) {\n focusItem(previousIndex);\n }\n\n return;\n }\n\n if (isActivationKey(event)) {\n event.preventDefault();\n selectItem(props.items[index]);\n }\n};\n\nconst getTabIndex = (index: number) => (focusedIndex.value === index ? 0 : -1);\n\nwatch(\n () => [props.items, props.modelValue] as const,\n () => {\n const nextIndex = getInitialFocusedIndex();\n focusedIndex.value = nextIndex >= 0 ? nextIndex : -1;\n },\n { deep: true },\n);\n</script>\n\n<template>\n <nav\n v-bind=\"rootAttrs\"\n :class=\"rootClasses\"\n :style=\"rootStyle\"\n >\n <ul\n v-if=\"items.length > 0\"\n class=\"t-nav-menu__list\"\n >\n <li\n v-for=\"(item, index) in items\"\n :key=\"item.value\"\n class=\"t-nav-menu__entry\"\n >\n <component\n :is=\"itemTag(item)\"\n :ref=\"(element: Element | ComponentPublicInstance | null) => setItemRef(element, item.value)\"\n :type=\"item.to ? undefined : 'button'\"\n :to=\"item.to || undefined\"\n class=\"t-nav-menu__item\"\n :class=\"{\n 'is-selected': isSelected(item.value),\n 'is-disabled': disabled || item.disabled,\n }\"\n :active-class=\"routerActiveClass(item)\"\n :exact-active-class=\"routerExactActiveClass(item)\"\n :disabled=\"(!item.to && (disabled || item.disabled)) || undefined\"\n :aria-disabled=\"(item.to && (disabled || item.disabled)) || undefined\"\n :aria-current=\"isSelected(item.value) ? 'page' : undefined\"\n :tabindex=\"disabled || item.disabled ? -1 : getTabIndex(index)\"\n :title=\"resolvedCollapsed ? item.label : undefined\"\n @click=\"selectItem(item)\"\n @keydown=\"onItemKeydown($event, index)\"\n @focus=\"focusedIndex = index\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n :selected=\"isSelected(item.value)\"\n :focused=\"focusedIndex === index\"\n :collapsed=\"resolvedCollapsed\"\n >\n <component\n :is=\"item.icon\"\n v-if=\"item.icon\"\n class=\"t-nav-menu__icon\"\n aria-hidden=\"true\"\n />\n <span\n v-else\n class=\"t-nav-menu__marker\"\n aria-hidden=\"true\"\n >\n {{ (item.shortLabel ?? item.label.charAt(0)).toUpperCase() }}\n </span>\n\n <span class=\"t-nav-menu__copy\">\n <span class=\"t-nav-menu__label\">{{ item.label }}</span>\n <span\n v-if=\"item.description\"\n class=\"t-nav-menu__description\"\n >\n {{ item.description }}\n </span>\n </span>\n\n <span\n v-if=\"item.badge !== undefined && item.badge !== null\"\n class=\"t-nav-menu__badge\"\n >\n {{ item.badge }}\n </span>\n </slot>\n </component>\n </li>\n </ul>\n\n <div\n v-else\n class=\"t-nav-menu__empty\"\n >\n <slot name=\"empty\">\n No navigation items.\n </slot>\n </div>\n </nav>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"TNavMenu.js","names":[],"sources":["../../src/components/TNavMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { getNextEnabledIndex, isActivationKey } from '@treeui/utils';\nimport { resolveTreeIcon, type TIconInput } from '@treeui/icons';\nimport { computed, getCurrentInstance, inject, nextTick, ref, useAttrs, watch, type Component, type ComponentPublicInstance } from 'vue';\nimport type { TSize } from '../types/contracts';\nimport { treeSidebarInjectionKey } from './sidebar';\n\ndefineOptions({\n inheritAttrs: false,\n});\n\nexport interface TNavMenuItem {\n label: string;\n value: string;\n shortLabel?: string;\n description?: string;\n /**\n * A registered icon name (`'cpu'`), or any component. A name is the simpler\n * form: it needs no import and no `markRaw`, since a string is not made\n * reactive when the item lands in a `ref`.\n */\n icon?: TIconInput;\n to?: string | Record<string, unknown>;\n badge?: string | number;\n disabled?: boolean;\n /**\n * When the item renders as a router link, controls whether an inclusive\n * (prefix) route match also marks it active. Defaults to the menu-level\n * `exact` prop. Set to `false` for section roots that should stay active on\n * nested routes.\n */\n exact?: boolean;\n}\n\nconst props = withDefaults(\n defineProps<{\n modelValue?: string;\n defaultValue?: string;\n items?: TNavMenuItem[];\n size?: TSize;\n collapsed?: boolean;\n disabled?: boolean;\n exact?: boolean;\n }>(),\n {\n modelValue: undefined,\n defaultValue: '',\n items: () => [],\n size: 'md',\n collapsed: undefined,\n disabled: false,\n exact: true,\n },\n);\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string];\n select: [value: string, item: TNavMenuItem];\n}>();\n\ndefineSlots<{\n item?: (props: {\n item: TNavMenuItem;\n selected: boolean;\n focused: boolean;\n collapsed: boolean;\n }) => unknown;\n empty?: () => unknown;\n}>();\n\n/**\n * A registry lookup, so it stays cheap enough to call from the template. An\n * unresolved name returns `undefined` and the item falls back to its letter\n * marker rather than rendering a hole.\n */\nconst iconFor = (item: TNavMenuItem) => resolveTreeIcon(item.icon);\n\nconst attrs = useAttrs();\nconst sidebar = inject(treeSidebarInjectionKey, null);\nconst internalValue = ref(props.defaultValue);\nconst itemRefs = ref<Map<string, HTMLElement>>(new Map());\nconst instance = getCurrentInstance();\n\nconst selectedValue = computed(() => props.modelValue ?? internalValue.value);\nconst resolvedCollapsed = computed(() => props.collapsed ?? sidebar?.collapsed.value ?? false);\n\nconst routerLinkComponent = computed<Component | null>(() => (\n (instance?.appContext.components.RouterLink as Component | undefined) ?? null\n));\n\nconst itemTag = (item: TNavMenuItem) => {\n if (item.to && routerLinkComponent.value) {\n return routerLinkComponent.value;\n }\n return 'button';\n};\n\nconst isRouterLinkItem = (item: TNavMenuItem) =>\n Boolean(item.to) && routerLinkComponent.value !== null;\n\n// Keep the active highlight deterministic when items render as router links.\n// Vue Router's default `router-link-active` uses inclusive (prefix) matching,\n// so several links can appear active at once. We map the router's active\n// classes onto TreeUI's own `is-selected`:\n// - Controlled menus (explicit `modelValue`) own selection, so the router adds\n// no class of its own — only the bound `is-selected` shows.\n// - Uncontrolled menus let the current route drive selection, using exact\n// matching by default (opt out per item, or menu-wide, via `exact`).\nconst routerActiveClass = (item: TNavMenuItem) => {\n if (!isRouterLinkItem(item)) {\n return undefined;\n }\n\n if (props.modelValue !== undefined) {\n return '';\n }\n\n return (item.exact ?? props.exact) ? '' : 'is-selected';\n};\n\nconst routerExactActiveClass = (item: TNavMenuItem) => {\n if (!isRouterLinkItem(item)) {\n return undefined;\n }\n\n return props.modelValue !== undefined ? '' : 'is-selected';\n};\n\nconst getInitialFocusedIndex = () => {\n const selectedIndex = props.items.findIndex((item) =>\n item.value === selectedValue.value && !item.disabled,\n );\n\n if (selectedIndex >= 0) {\n return selectedIndex;\n }\n\n return props.items.findIndex((item) => !item.disabled);\n};\n\nconst focusedIndex = ref(getInitialFocusedIndex());\n\nconst rootClasses = computed(() => [\n 't-nav-menu',\n `t-nav-menu--${props.size}`,\n {\n 'is-collapsed': resolvedCollapsed.value,\n 'is-disabled': props.disabled,\n },\n attrs.class,\n]);\n\nconst rootStyle = computed(() => attrs.style);\n\nconst rootAttrs = computed(() => {\n const { class: _class, style: _style, ...rest } = attrs;\n return rest;\n});\n\nconst isSelected = (value: string) => selectedValue.value === value;\n\nconst setItemRef = (element: Element | ComponentPublicInstance | null, value: string) => {\n const resolvedElement =\n element instanceof HTMLElement\n ? element\n : element && '$el' in element && element.$el instanceof HTMLElement\n ? element.$el\n : null;\n\n if (resolvedElement) {\n itemRefs.value.set(value, resolvedElement);\n } else {\n itemRefs.value.delete(value);\n }\n};\n\nconst focusItem = (index: number) => {\n const item = props.items[index];\n\n if (!item) {\n return;\n }\n\n focusedIndex.value = index;\n nextTick(() => itemRefs.value.get(item.value)?.focus());\n};\n\nconst selectItem = (item: TNavMenuItem) => {\n if (props.disabled || item.disabled) {\n return;\n }\n\n if (props.modelValue === undefined) {\n internalValue.value = item.value;\n }\n\n emit('update:modelValue', item.value);\n emit('select', item.value, item);\n};\n\nconst onItemKeydown = (event: KeyboardEvent, index: number) => {\n if (props.disabled) {\n return;\n }\n\n if (event.key === 'Home') {\n event.preventDefault();\n const firstIndex = props.items.findIndex((item) => !item.disabled);\n\n if (firstIndex >= 0) {\n focusItem(firstIndex);\n }\n\n return;\n }\n\n if (event.key === 'End') {\n event.preventDefault();\n const lastIndex = [...props.items].reverse().findIndex((item) => !item.disabled);\n\n if (lastIndex >= 0) {\n focusItem(props.items.length - 1 - lastIndex);\n }\n\n return;\n }\n\n if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {\n event.preventDefault();\n const nextIndex = getNextEnabledIndex(index, props.items, 1);\n\n if (nextIndex >= 0) {\n focusItem(nextIndex);\n }\n\n return;\n }\n\n if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {\n event.preventDefault();\n const previousIndex = getNextEnabledIndex(index, props.items, -1);\n\n if (previousIndex >= 0) {\n focusItem(previousIndex);\n }\n\n return;\n }\n\n if (isActivationKey(event)) {\n event.preventDefault();\n selectItem(props.items[index]);\n }\n};\n\nconst getTabIndex = (index: number) => (focusedIndex.value === index ? 0 : -1);\n\nwatch(\n () => [props.items, props.modelValue] as const,\n () => {\n const nextIndex = getInitialFocusedIndex();\n focusedIndex.value = nextIndex >= 0 ? nextIndex : -1;\n },\n { deep: true },\n);\n</script>\n\n<template>\n <nav\n v-bind=\"rootAttrs\"\n :class=\"rootClasses\"\n :style=\"rootStyle\"\n >\n <ul\n v-if=\"items.length > 0\"\n class=\"t-nav-menu__list\"\n >\n <li\n v-for=\"(item, index) in items\"\n :key=\"item.value\"\n class=\"t-nav-menu__entry\"\n >\n <component\n :is=\"itemTag(item)\"\n :ref=\"(element: Element | ComponentPublicInstance | null) => setItemRef(element, item.value)\"\n :type=\"item.to ? undefined : 'button'\"\n :to=\"item.to || undefined\"\n class=\"t-nav-menu__item\"\n :class=\"{\n 'is-selected': isSelected(item.value),\n 'is-disabled': disabled || item.disabled,\n }\"\n :active-class=\"routerActiveClass(item)\"\n :exact-active-class=\"routerExactActiveClass(item)\"\n :disabled=\"(!item.to && (disabled || item.disabled)) || undefined\"\n :aria-disabled=\"(item.to && (disabled || item.disabled)) || undefined\"\n :aria-current=\"isSelected(item.value) ? 'page' : undefined\"\n :tabindex=\"disabled || item.disabled ? -1 : getTabIndex(index)\"\n :title=\"resolvedCollapsed ? item.label : undefined\"\n @click=\"selectItem(item)\"\n @keydown=\"onItemKeydown($event, index)\"\n @focus=\"focusedIndex = index\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n :selected=\"isSelected(item.value)\"\n :focused=\"focusedIndex === index\"\n :collapsed=\"resolvedCollapsed\"\n >\n <component\n :is=\"iconFor(item)\"\n v-if=\"iconFor(item)\"\n class=\"t-nav-menu__icon\"\n aria-hidden=\"true\"\n />\n <span\n v-else\n class=\"t-nav-menu__marker\"\n aria-hidden=\"true\"\n >\n {{ (item.shortLabel ?? item.label.charAt(0)).toUpperCase() }}\n </span>\n\n <span class=\"t-nav-menu__copy\">\n <span class=\"t-nav-menu__label\">{{ item.label }}</span>\n <span\n v-if=\"item.description\"\n class=\"t-nav-menu__description\"\n >\n {{ item.description }}\n </span>\n </span>\n\n <span\n v-if=\"item.badge !== undefined && item.badge !== null\"\n class=\"t-nav-menu__badge\"\n >\n {{ item.badge }}\n </span>\n </slot>\n </component>\n </li>\n </ul>\n\n <div\n v-else\n class=\"t-nav-menu__empty\"\n >\n <slot name=\"empty\">\n No navigation items.\n </slot>\n </div>\n </nav>\n</template>\n"],"mappings":""}
@@ -1,11 +1,16 @@
1
- import { Component } from 'vue';
1
+ import { TIconInput } from '@treeui/icons';
2
2
  import { TSize } from '../types/contracts';
3
3
  export interface TNavMenuItem {
4
4
  label: string;
5
5
  value: string;
6
6
  shortLabel?: string;
7
7
  description?: string;
8
- icon?: Component;
8
+ /**
9
+ * A registered icon name (`'cpu'`), or any component. A name is the simpler
10
+ * form: it needs no import and no `markRaw`, since a string is not made
11
+ * reactive when the item lands in a `ref`.
12
+ */
13
+ icon?: TIconInput;
9
14
  to?: string | Record<string, unknown>;
10
15
  badge?: string | number;
11
16
  disabled?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"TNavMenu.vue.d.ts","sourceRoot":"","sources":["../../src/components/TNavMenu.vue"],"names":[],"mappings":"AAyVA,OAAO,EAAwE,KAAK,SAAS,EAAgC,MAAM,KAAK,CAAC;AACzI,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAOhD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,KAAK,WAAW,GAAG;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE,KAAK,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAsOJ,iBAAS,cAAc;WAkJT,OAAO,IAA6B;;eAnWzC,CAAC,KAAK,EAAE;YACb,IAAI,EAAE,YAAY,CAAC;YACnB,QAAQ,EAAE,OAAO,CAAC;YAClB,OAAO,EAAE,OAAO,CAAC;YACjB,SAAS,EAAE,OAAO,CAAC;SACpB,KAAK,OAAO;gBACL,MAAM,OAAO;;eANd,CAAC,KAAK,EAAE;YACb,IAAI,EAAE,YAAY,CAAC;YACnB,QAAQ,EAAE,OAAO,CAAC;YAClB,OAAO,EAAE,OAAO,CAAC;YACjB,SAAS,EAAE,OAAO,CAAC;SACpB,KAAK,OAAO;gBACL,MAAM,OAAO;;;;EAkWtB;AAuBD,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AAC9D,QAAA,MAAM,eAAe;;;;;;;gBA5ZJ,MAAM;kBACJ,MAAM;cAIV,OAAO;UAFX,KAAK;WADJ,YAAY,EAAE;WAId,OAAO;eAFH,OAAO;6EAgarB,CAAC;wBACkB,uBAAuB,CAAC,OAAO,eAAe,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAAnG,wBAAoG;AAapG,KAAK,uBAAuB,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IACxC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KAEV,CAAA;CACD,CAAC"}
1
+ {"version":3,"file":"TNavMenu.vue.d.ts","sourceRoot":"","sources":["../../src/components/TNavMenu.vue"],"names":[],"mappings":"AAsWA,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAEjE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAOhD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,KAAK,WAAW,GAAG;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE,KAAK,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AA6OJ,iBAAS,cAAc;WAkJT,OAAO,IAA6B;;eA1WzC,CAAC,KAAK,EAAE;YACb,IAAI,EAAE,YAAY,CAAC;YACnB,QAAQ,EAAE,OAAO,CAAC;YAClB,OAAO,EAAE,OAAO,CAAC;YACjB,SAAS,EAAE,OAAO,CAAC;SACpB,KAAK,OAAO;gBACL,MAAM,OAAO;;eANd,CAAC,KAAK,EAAE;YACb,IAAI,EAAE,YAAY,CAAC;YACnB,QAAQ,EAAE,OAAO,CAAC;YAClB,OAAO,EAAE,OAAO,CAAC;YACjB,SAAS,EAAE,OAAO,CAAC;SACpB,KAAK,OAAO;gBACL,MAAM,OAAO;;;;EAyWtB;AAwBD,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AAC9D,QAAA,MAAM,eAAe;;;;;;;gBApaJ,MAAM;kBACJ,MAAM;cAIV,OAAO;UAFX,KAAK;WADJ,YAAY,EAAE;WAId,OAAO;eAFH,OAAO;6EAwarB,CAAC;wBACkB,uBAAuB,CAAC,OAAO,eAAe,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAAnG,wBAAoG;AAapG,KAAK,uBAAuB,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IACxC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KAEV,CAAA;CACD,CAAC"}
@@ -1,2 +1,2 @@
1
- const e=require("./sidebar.cjs");let t=require("vue"),n=require("@treeui/utils");var r={key:0,class:`t-nav-menu__list`},i={key:1,class:`t-nav-menu__marker`,"aria-hidden":`true`},a={class:`t-nav-menu__copy`},o={class:`t-nav-menu__label`},s={key:0,class:`t-nav-menu__description`},c={key:2,class:`t-nav-menu__badge`},l={key:1,class:`t-nav-menu__empty`},u=(0,t.defineComponent)({inheritAttrs:!1,__name:`TNavMenu`,props:{modelValue:{default:void 0},defaultValue:{default:``},items:{default:()=>[]},size:{default:`md`},collapsed:{type:Boolean,default:void 0},disabled:{type:Boolean,default:!1},exact:{type:Boolean,default:!0}},emits:[`update:modelValue`,`select`],setup(u,{emit:d}){let f=u,p=d,m=(0,t.useAttrs)(),h=(0,t.inject)(e.treeSidebarInjectionKey,null),g=(0,t.ref)(f.defaultValue),_=(0,t.ref)(new Map),v=(0,t.getCurrentInstance)(),y=(0,t.computed)(()=>f.modelValue??g.value),b=(0,t.computed)(()=>f.collapsed??h?.collapsed.value??!1),x=(0,t.computed)(()=>v?.appContext.components.RouterLink??null),S=e=>e.to&&x.value?x.value:`button`,C=e=>!!e.to&&x.value!==null,w=e=>{if(C(e))return f.modelValue===void 0?e.exact??f.exact?``:`is-selected`:``},T=e=>{if(C(e))return f.modelValue===void 0?`is-selected`:``},E=()=>{let e=f.items.findIndex(e=>e.value===y.value&&!e.disabled);return e>=0?e:f.items.findIndex(e=>!e.disabled)},D=(0,t.ref)(E()),O=(0,t.computed)(()=>[`t-nav-menu`,`t-nav-menu--${f.size}`,{"is-collapsed":b.value,"is-disabled":f.disabled},m.class]),k=(0,t.computed)(()=>m.style),A=(0,t.computed)(()=>{let{class:e,style:t,...n}=m;return n}),j=e=>y.value===e,M=(e,t)=>{let n=e instanceof HTMLElement?e:e&&`$el`in e&&e.$el instanceof HTMLElement?e.$el:null;n?_.value.set(t,n):_.value.delete(t)},N=e=>{let n=f.items[e];n&&(D.value=e,(0,t.nextTick)(()=>_.value.get(n.value)?.focus()))},P=e=>{f.disabled||e.disabled||(f.modelValue===void 0&&(g.value=e.value),p(`update:modelValue`,e.value),p(`select`,e.value,e))},F=(e,t)=>{if(!f.disabled){if(e.key===`Home`){e.preventDefault();let t=f.items.findIndex(e=>!e.disabled);t>=0&&N(t);return}if(e.key===`End`){e.preventDefault();let t=[...f.items].reverse().findIndex(e=>!e.disabled);t>=0&&N(f.items.length-1-t);return}if(e.key===`ArrowDown`||e.key===`ArrowRight`){e.preventDefault();let r=(0,n.getNextEnabledIndex)(t,f.items,1);r>=0&&N(r);return}if(e.key===`ArrowUp`||e.key===`ArrowLeft`){e.preventDefault();let r=(0,n.getNextEnabledIndex)(t,f.items,-1);r>=0&&N(r);return}(0,n.isActivationKey)(e)&&(e.preventDefault(),P(f.items[t]))}},I=e=>D.value===e?0:-1;return(0,t.watch)(()=>[f.items,f.modelValue],()=>{let e=E();D.value=e>=0?e:-1},{deep:!0}),(e,n)=>((0,t.openBlock)(),(0,t.createElementBlock)(`nav`,(0,t.mergeProps)(A.value,{class:O.value,style:k.value}),[u.items.length>0?((0,t.openBlock)(),(0,t.createElementBlock)(`ul`,r,[((0,t.openBlock)(!0),(0,t.createElementBlock)(t.Fragment,null,(0,t.renderList)(u.items,(n,r)=>((0,t.openBlock)(),(0,t.createElementBlock)(`li`,{key:n.value,class:`t-nav-menu__entry`},[((0,t.openBlock)(),(0,t.createBlock)((0,t.resolveDynamicComponent)(S(n)),{ref_for:!0,ref:e=>M(e,n.value),type:n.to?void 0:`button`,to:n.to||void 0,class:(0,t.normalizeClass)([`t-nav-menu__item`,{"is-selected":j(n.value),"is-disabled":u.disabled||n.disabled}]),"active-class":w(n),"exact-active-class":T(n),disabled:!n.to&&(u.disabled||n.disabled)||void 0,"aria-disabled":n.to&&(u.disabled||n.disabled)||void 0,"aria-current":j(n.value)?`page`:void 0,tabindex:u.disabled||n.disabled?-1:I(r),title:b.value?n.label:void 0,onClick:e=>P(n),onKeydown:e=>F(e,r),onFocus:e=>D.value=r},{default:(0,t.withCtx)(()=>[(0,t.renderSlot)(e.$slots,`item`,{item:n,selected:j(n.value),focused:D.value===r,collapsed:b.value},()=>[n.icon?((0,t.openBlock)(),(0,t.createBlock)((0,t.resolveDynamicComponent)(n.icon),{key:0,class:`t-nav-menu__icon`,"aria-hidden":`true`})):((0,t.openBlock)(),(0,t.createElementBlock)(`span`,i,(0,t.toDisplayString)((n.shortLabel??n.label.charAt(0)).toUpperCase()),1)),(0,t.createElementVNode)(`span`,a,[(0,t.createElementVNode)(`span`,o,(0,t.toDisplayString)(n.label),1),n.description?((0,t.openBlock)(),(0,t.createElementBlock)(`span`,s,(0,t.toDisplayString)(n.description),1)):(0,t.createCommentVNode)(``,!0)]),n.badge!==void 0&&n.badge!==null?((0,t.openBlock)(),(0,t.createElementBlock)(`span`,c,(0,t.toDisplayString)(n.badge),1)):(0,t.createCommentVNode)(``,!0)])]),_:2},1064,[`type`,`to`,`class`,`active-class`,`exact-active-class`,`disabled`,`aria-disabled`,`aria-current`,`tabindex`,`title`,`onClick`,`onKeydown`,`onFocus`]))]))),128))])):((0,t.openBlock)(),(0,t.createElementBlock)(`div`,l,[(0,t.renderSlot)(e.$slots,`empty`,{},()=>[n[0]||=(0,t.createTextVNode)(` No navigation items. `,-1)])]))],16))}});exports.default=u;
1
+ const e=require("./sidebar.cjs");let t=require("vue"),n=require("@treeui/utils"),r=require("@treeui/icons");var i={key:0,class:`t-nav-menu__list`},a={key:1,class:`t-nav-menu__marker`,"aria-hidden":`true`},o={class:`t-nav-menu__copy`},s={class:`t-nav-menu__label`},c={key:0,class:`t-nav-menu__description`},l={key:2,class:`t-nav-menu__badge`},u={key:1,class:`t-nav-menu__empty`},d=(0,t.defineComponent)({inheritAttrs:!1,__name:`TNavMenu`,props:{modelValue:{default:void 0},defaultValue:{default:``},items:{default:()=>[]},size:{default:`md`},collapsed:{type:Boolean,default:void 0},disabled:{type:Boolean,default:!1},exact:{type:Boolean,default:!0}},emits:[`update:modelValue`,`select`],setup(d,{emit:f}){let p=d,m=f,h=e=>(0,r.resolveTreeIcon)(e.icon),g=(0,t.useAttrs)(),_=(0,t.inject)(e.treeSidebarInjectionKey,null),v=(0,t.ref)(p.defaultValue),y=(0,t.ref)(new Map),b=(0,t.getCurrentInstance)(),x=(0,t.computed)(()=>p.modelValue??v.value),S=(0,t.computed)(()=>p.collapsed??_?.collapsed.value??!1),C=(0,t.computed)(()=>b?.appContext.components.RouterLink??null),w=e=>e.to&&C.value?C.value:`button`,T=e=>!!e.to&&C.value!==null,E=e=>{if(T(e))return p.modelValue===void 0?e.exact??p.exact?``:`is-selected`:``},D=e=>{if(T(e))return p.modelValue===void 0?`is-selected`:``},O=()=>{let e=p.items.findIndex(e=>e.value===x.value&&!e.disabled);return e>=0?e:p.items.findIndex(e=>!e.disabled)},k=(0,t.ref)(O()),A=(0,t.computed)(()=>[`t-nav-menu`,`t-nav-menu--${p.size}`,{"is-collapsed":S.value,"is-disabled":p.disabled},g.class]),j=(0,t.computed)(()=>g.style),M=(0,t.computed)(()=>{let{class:e,style:t,...n}=g;return n}),N=e=>x.value===e,P=(e,t)=>{let n=e instanceof HTMLElement?e:e&&`$el`in e&&e.$el instanceof HTMLElement?e.$el:null;n?y.value.set(t,n):y.value.delete(t)},F=e=>{let n=p.items[e];n&&(k.value=e,(0,t.nextTick)(()=>y.value.get(n.value)?.focus()))},I=e=>{p.disabled||e.disabled||(p.modelValue===void 0&&(v.value=e.value),m(`update:modelValue`,e.value),m(`select`,e.value,e))},L=(e,t)=>{if(!p.disabled){if(e.key===`Home`){e.preventDefault();let t=p.items.findIndex(e=>!e.disabled);t>=0&&F(t);return}if(e.key===`End`){e.preventDefault();let t=[...p.items].reverse().findIndex(e=>!e.disabled);t>=0&&F(p.items.length-1-t);return}if(e.key===`ArrowDown`||e.key===`ArrowRight`){e.preventDefault();let r=(0,n.getNextEnabledIndex)(t,p.items,1);r>=0&&F(r);return}if(e.key===`ArrowUp`||e.key===`ArrowLeft`){e.preventDefault();let r=(0,n.getNextEnabledIndex)(t,p.items,-1);r>=0&&F(r);return}(0,n.isActivationKey)(e)&&(e.preventDefault(),I(p.items[t]))}},R=e=>k.value===e?0:-1;return(0,t.watch)(()=>[p.items,p.modelValue],()=>{let e=O();k.value=e>=0?e:-1},{deep:!0}),(e,n)=>((0,t.openBlock)(),(0,t.createElementBlock)(`nav`,(0,t.mergeProps)(M.value,{class:A.value,style:j.value}),[d.items.length>0?((0,t.openBlock)(),(0,t.createElementBlock)(`ul`,i,[((0,t.openBlock)(!0),(0,t.createElementBlock)(t.Fragment,null,(0,t.renderList)(d.items,(n,r)=>((0,t.openBlock)(),(0,t.createElementBlock)(`li`,{key:n.value,class:`t-nav-menu__entry`},[((0,t.openBlock)(),(0,t.createBlock)((0,t.resolveDynamicComponent)(w(n)),{ref_for:!0,ref:e=>P(e,n.value),type:n.to?void 0:`button`,to:n.to||void 0,class:(0,t.normalizeClass)([`t-nav-menu__item`,{"is-selected":N(n.value),"is-disabled":d.disabled||n.disabled}]),"active-class":E(n),"exact-active-class":D(n),disabled:!n.to&&(d.disabled||n.disabled)||void 0,"aria-disabled":n.to&&(d.disabled||n.disabled)||void 0,"aria-current":N(n.value)?`page`:void 0,tabindex:d.disabled||n.disabled?-1:R(r),title:S.value?n.label:void 0,onClick:e=>I(n),onKeydown:e=>L(e,r),onFocus:e=>k.value=r},{default:(0,t.withCtx)(()=>[(0,t.renderSlot)(e.$slots,`item`,{item:n,selected:N(n.value),focused:k.value===r,collapsed:S.value},()=>[h(n)?((0,t.openBlock)(),(0,t.createBlock)((0,t.resolveDynamicComponent)(h(n)),{key:0,class:`t-nav-menu__icon`,"aria-hidden":`true`})):((0,t.openBlock)(),(0,t.createElementBlock)(`span`,a,(0,t.toDisplayString)((n.shortLabel??n.label.charAt(0)).toUpperCase()),1)),(0,t.createElementVNode)(`span`,o,[(0,t.createElementVNode)(`span`,s,(0,t.toDisplayString)(n.label),1),n.description?((0,t.openBlock)(),(0,t.createElementBlock)(`span`,c,(0,t.toDisplayString)(n.description),1)):(0,t.createCommentVNode)(``,!0)]),n.badge!==void 0&&n.badge!==null?((0,t.openBlock)(),(0,t.createElementBlock)(`span`,l,(0,t.toDisplayString)(n.badge),1)):(0,t.createCommentVNode)(``,!0)])]),_:2},1064,[`type`,`to`,`class`,`active-class`,`exact-active-class`,`disabled`,`aria-disabled`,`aria-current`,`tabindex`,`title`,`onClick`,`onKeydown`,`onFocus`]))]))),128))])):((0,t.openBlock)(),(0,t.createElementBlock)(`div`,u,[(0,t.renderSlot)(e.$slots,`empty`,{},()=>[n[0]||=(0,t.createTextVNode)(` No navigation items. `,-1)])]))],16))}});exports.default=d;
2
2
  //# sourceMappingURL=TNavMenu.vue_vue_type_script_setup_true_lang.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"TNavMenu.vue_vue_type_script_setup_true_lang.cjs","names":[],"sources":["../../src/components/TNavMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { getNextEnabledIndex, isActivationKey } from '@treeui/utils';\nimport { computed, getCurrentInstance, inject, nextTick, ref, useAttrs, watch, type Component, type ComponentPublicInstance } from 'vue';\nimport type { TSize } from '../types/contracts';\nimport { treeSidebarInjectionKey } from './sidebar';\n\ndefineOptions({\n inheritAttrs: false,\n});\n\nexport interface TNavMenuItem {\n label: string;\n value: string;\n shortLabel?: string;\n description?: string;\n icon?: Component;\n to?: string | Record<string, unknown>;\n badge?: string | number;\n disabled?: boolean;\n /**\n * When the item renders as a router link, controls whether an inclusive\n * (prefix) route match also marks it active. Defaults to the menu-level\n * `exact` prop. Set to `false` for section roots that should stay active on\n * nested routes.\n */\n exact?: boolean;\n}\n\nconst props = withDefaults(\n defineProps<{\n modelValue?: string;\n defaultValue?: string;\n items?: TNavMenuItem[];\n size?: TSize;\n collapsed?: boolean;\n disabled?: boolean;\n exact?: boolean;\n }>(),\n {\n modelValue: undefined,\n defaultValue: '',\n items: () => [],\n size: 'md',\n collapsed: undefined,\n disabled: false,\n exact: true,\n },\n);\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string];\n select: [value: string, item: TNavMenuItem];\n}>();\n\ndefineSlots<{\n item?: (props: {\n item: TNavMenuItem;\n selected: boolean;\n focused: boolean;\n collapsed: boolean;\n }) => unknown;\n empty?: () => unknown;\n}>();\n\nconst attrs = useAttrs();\nconst sidebar = inject(treeSidebarInjectionKey, null);\nconst internalValue = ref(props.defaultValue);\nconst itemRefs = ref<Map<string, HTMLElement>>(new Map());\nconst instance = getCurrentInstance();\n\nconst selectedValue = computed(() => props.modelValue ?? internalValue.value);\nconst resolvedCollapsed = computed(() => props.collapsed ?? sidebar?.collapsed.value ?? false);\n\nconst routerLinkComponent = computed<Component | null>(() => (\n (instance?.appContext.components.RouterLink as Component | undefined) ?? null\n));\n\nconst itemTag = (item: TNavMenuItem) => {\n if (item.to && routerLinkComponent.value) {\n return routerLinkComponent.value;\n }\n return 'button';\n};\n\nconst isRouterLinkItem = (item: TNavMenuItem) =>\n Boolean(item.to) && routerLinkComponent.value !== null;\n\n// Keep the active highlight deterministic when items render as router links.\n// Vue Router's default `router-link-active` uses inclusive (prefix) matching,\n// so several links can appear active at once. We map the router's active\n// classes onto TreeUI's own `is-selected`:\n// - Controlled menus (explicit `modelValue`) own selection, so the router adds\n// no class of its own — only the bound `is-selected` shows.\n// - Uncontrolled menus let the current route drive selection, using exact\n// matching by default (opt out per item, or menu-wide, via `exact`).\nconst routerActiveClass = (item: TNavMenuItem) => {\n if (!isRouterLinkItem(item)) {\n return undefined;\n }\n\n if (props.modelValue !== undefined) {\n return '';\n }\n\n return (item.exact ?? props.exact) ? '' : 'is-selected';\n};\n\nconst routerExactActiveClass = (item: TNavMenuItem) => {\n if (!isRouterLinkItem(item)) {\n return undefined;\n }\n\n return props.modelValue !== undefined ? '' : 'is-selected';\n};\n\nconst getInitialFocusedIndex = () => {\n const selectedIndex = props.items.findIndex((item) =>\n item.value === selectedValue.value && !item.disabled,\n );\n\n if (selectedIndex >= 0) {\n return selectedIndex;\n }\n\n return props.items.findIndex((item) => !item.disabled);\n};\n\nconst focusedIndex = ref(getInitialFocusedIndex());\n\nconst rootClasses = computed(() => [\n 't-nav-menu',\n `t-nav-menu--${props.size}`,\n {\n 'is-collapsed': resolvedCollapsed.value,\n 'is-disabled': props.disabled,\n },\n attrs.class,\n]);\n\nconst rootStyle = computed(() => attrs.style);\n\nconst rootAttrs = computed(() => {\n const { class: _class, style: _style, ...rest } = attrs;\n return rest;\n});\n\nconst isSelected = (value: string) => selectedValue.value === value;\n\nconst setItemRef = (element: Element | ComponentPublicInstance | null, value: string) => {\n const resolvedElement =\n element instanceof HTMLElement\n ? element\n : element && '$el' in element && element.$el instanceof HTMLElement\n ? element.$el\n : null;\n\n if (resolvedElement) {\n itemRefs.value.set(value, resolvedElement);\n } else {\n itemRefs.value.delete(value);\n }\n};\n\nconst focusItem = (index: number) => {\n const item = props.items[index];\n\n if (!item) {\n return;\n }\n\n focusedIndex.value = index;\n nextTick(() => itemRefs.value.get(item.value)?.focus());\n};\n\nconst selectItem = (item: TNavMenuItem) => {\n if (props.disabled || item.disabled) {\n return;\n }\n\n if (props.modelValue === undefined) {\n internalValue.value = item.value;\n }\n\n emit('update:modelValue', item.value);\n emit('select', item.value, item);\n};\n\nconst onItemKeydown = (event: KeyboardEvent, index: number) => {\n if (props.disabled) {\n return;\n }\n\n if (event.key === 'Home') {\n event.preventDefault();\n const firstIndex = props.items.findIndex((item) => !item.disabled);\n\n if (firstIndex >= 0) {\n focusItem(firstIndex);\n }\n\n return;\n }\n\n if (event.key === 'End') {\n event.preventDefault();\n const lastIndex = [...props.items].reverse().findIndex((item) => !item.disabled);\n\n if (lastIndex >= 0) {\n focusItem(props.items.length - 1 - lastIndex);\n }\n\n return;\n }\n\n if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {\n event.preventDefault();\n const nextIndex = getNextEnabledIndex(index, props.items, 1);\n\n if (nextIndex >= 0) {\n focusItem(nextIndex);\n }\n\n return;\n }\n\n if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {\n event.preventDefault();\n const previousIndex = getNextEnabledIndex(index, props.items, -1);\n\n if (previousIndex >= 0) {\n focusItem(previousIndex);\n }\n\n return;\n }\n\n if (isActivationKey(event)) {\n event.preventDefault();\n selectItem(props.items[index]);\n }\n};\n\nconst getTabIndex = (index: number) => (focusedIndex.value === index ? 0 : -1);\n\nwatch(\n () => [props.items, props.modelValue] as const,\n () => {\n const nextIndex = getInitialFocusedIndex();\n focusedIndex.value = nextIndex >= 0 ? nextIndex : -1;\n },\n { deep: true },\n);\n</script>\n\n<template>\n <nav\n v-bind=\"rootAttrs\"\n :class=\"rootClasses\"\n :style=\"rootStyle\"\n >\n <ul\n v-if=\"items.length > 0\"\n class=\"t-nav-menu__list\"\n >\n <li\n v-for=\"(item, index) in items\"\n :key=\"item.value\"\n class=\"t-nav-menu__entry\"\n >\n <component\n :is=\"itemTag(item)\"\n :ref=\"(element: Element | ComponentPublicInstance | null) => setItemRef(element, item.value)\"\n :type=\"item.to ? undefined : 'button'\"\n :to=\"item.to || undefined\"\n class=\"t-nav-menu__item\"\n :class=\"{\n 'is-selected': isSelected(item.value),\n 'is-disabled': disabled || item.disabled,\n }\"\n :active-class=\"routerActiveClass(item)\"\n :exact-active-class=\"routerExactActiveClass(item)\"\n :disabled=\"(!item.to && (disabled || item.disabled)) || undefined\"\n :aria-disabled=\"(item.to && (disabled || item.disabled)) || undefined\"\n :aria-current=\"isSelected(item.value) ? 'page' : undefined\"\n :tabindex=\"disabled || item.disabled ? -1 : getTabIndex(index)\"\n :title=\"resolvedCollapsed ? item.label : undefined\"\n @click=\"selectItem(item)\"\n @keydown=\"onItemKeydown($event, index)\"\n @focus=\"focusedIndex = index\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n :selected=\"isSelected(item.value)\"\n :focused=\"focusedIndex === index\"\n :collapsed=\"resolvedCollapsed\"\n >\n <component\n :is=\"item.icon\"\n v-if=\"item.icon\"\n class=\"t-nav-menu__icon\"\n aria-hidden=\"true\"\n />\n <span\n v-else\n class=\"t-nav-menu__marker\"\n aria-hidden=\"true\"\n >\n {{ (item.shortLabel ?? item.label.charAt(0)).toUpperCase() }}\n </span>\n\n <span class=\"t-nav-menu__copy\">\n <span class=\"t-nav-menu__label\">{{ item.label }}</span>\n <span\n v-if=\"item.description\"\n class=\"t-nav-menu__description\"\n >\n {{ item.description }}\n </span>\n </span>\n\n <span\n v-if=\"item.badge !== undefined && item.badge !== null\"\n class=\"t-nav-menu__badge\"\n >\n {{ item.badge }}\n </span>\n </slot>\n </component>\n </li>\n </ul>\n\n <div\n v-else\n class=\"t-nav-menu__empty\"\n >\n <slot name=\"empty\">\n No navigation items.\n </slot>\n </div>\n </nav>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"TNavMenu.vue_vue_type_script_setup_true_lang.cjs","names":[],"sources":["../../src/components/TNavMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { getNextEnabledIndex, isActivationKey } from '@treeui/utils';\nimport { resolveTreeIcon, type TIconInput } from '@treeui/icons';\nimport { computed, getCurrentInstance, inject, nextTick, ref, useAttrs, watch, type Component, type ComponentPublicInstance } from 'vue';\nimport type { TSize } from '../types/contracts';\nimport { treeSidebarInjectionKey } from './sidebar';\n\ndefineOptions({\n inheritAttrs: false,\n});\n\nexport interface TNavMenuItem {\n label: string;\n value: string;\n shortLabel?: string;\n description?: string;\n /**\n * A registered icon name (`'cpu'`), or any component. A name is the simpler\n * form: it needs no import and no `markRaw`, since a string is not made\n * reactive when the item lands in a `ref`.\n */\n icon?: TIconInput;\n to?: string | Record<string, unknown>;\n badge?: string | number;\n disabled?: boolean;\n /**\n * When the item renders as a router link, controls whether an inclusive\n * (prefix) route match also marks it active. Defaults to the menu-level\n * `exact` prop. Set to `false` for section roots that should stay active on\n * nested routes.\n */\n exact?: boolean;\n}\n\nconst props = withDefaults(\n defineProps<{\n modelValue?: string;\n defaultValue?: string;\n items?: TNavMenuItem[];\n size?: TSize;\n collapsed?: boolean;\n disabled?: boolean;\n exact?: boolean;\n }>(),\n {\n modelValue: undefined,\n defaultValue: '',\n items: () => [],\n size: 'md',\n collapsed: undefined,\n disabled: false,\n exact: true,\n },\n);\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string];\n select: [value: string, item: TNavMenuItem];\n}>();\n\ndefineSlots<{\n item?: (props: {\n item: TNavMenuItem;\n selected: boolean;\n focused: boolean;\n collapsed: boolean;\n }) => unknown;\n empty?: () => unknown;\n}>();\n\n/**\n * A registry lookup, so it stays cheap enough to call from the template. An\n * unresolved name returns `undefined` and the item falls back to its letter\n * marker rather than rendering a hole.\n */\nconst iconFor = (item: TNavMenuItem) => resolveTreeIcon(item.icon);\n\nconst attrs = useAttrs();\nconst sidebar = inject(treeSidebarInjectionKey, null);\nconst internalValue = ref(props.defaultValue);\nconst itemRefs = ref<Map<string, HTMLElement>>(new Map());\nconst instance = getCurrentInstance();\n\nconst selectedValue = computed(() => props.modelValue ?? internalValue.value);\nconst resolvedCollapsed = computed(() => props.collapsed ?? sidebar?.collapsed.value ?? false);\n\nconst routerLinkComponent = computed<Component | null>(() => (\n (instance?.appContext.components.RouterLink as Component | undefined) ?? null\n));\n\nconst itemTag = (item: TNavMenuItem) => {\n if (item.to && routerLinkComponent.value) {\n return routerLinkComponent.value;\n }\n return 'button';\n};\n\nconst isRouterLinkItem = (item: TNavMenuItem) =>\n Boolean(item.to) && routerLinkComponent.value !== null;\n\n// Keep the active highlight deterministic when items render as router links.\n// Vue Router's default `router-link-active` uses inclusive (prefix) matching,\n// so several links can appear active at once. We map the router's active\n// classes onto TreeUI's own `is-selected`:\n// - Controlled menus (explicit `modelValue`) own selection, so the router adds\n// no class of its own — only the bound `is-selected` shows.\n// - Uncontrolled menus let the current route drive selection, using exact\n// matching by default (opt out per item, or menu-wide, via `exact`).\nconst routerActiveClass = (item: TNavMenuItem) => {\n if (!isRouterLinkItem(item)) {\n return undefined;\n }\n\n if (props.modelValue !== undefined) {\n return '';\n }\n\n return (item.exact ?? props.exact) ? '' : 'is-selected';\n};\n\nconst routerExactActiveClass = (item: TNavMenuItem) => {\n if (!isRouterLinkItem(item)) {\n return undefined;\n }\n\n return props.modelValue !== undefined ? '' : 'is-selected';\n};\n\nconst getInitialFocusedIndex = () => {\n const selectedIndex = props.items.findIndex((item) =>\n item.value === selectedValue.value && !item.disabled,\n );\n\n if (selectedIndex >= 0) {\n return selectedIndex;\n }\n\n return props.items.findIndex((item) => !item.disabled);\n};\n\nconst focusedIndex = ref(getInitialFocusedIndex());\n\nconst rootClasses = computed(() => [\n 't-nav-menu',\n `t-nav-menu--${props.size}`,\n {\n 'is-collapsed': resolvedCollapsed.value,\n 'is-disabled': props.disabled,\n },\n attrs.class,\n]);\n\nconst rootStyle = computed(() => attrs.style);\n\nconst rootAttrs = computed(() => {\n const { class: _class, style: _style, ...rest } = attrs;\n return rest;\n});\n\nconst isSelected = (value: string) => selectedValue.value === value;\n\nconst setItemRef = (element: Element | ComponentPublicInstance | null, value: string) => {\n const resolvedElement =\n element instanceof HTMLElement\n ? element\n : element && '$el' in element && element.$el instanceof HTMLElement\n ? element.$el\n : null;\n\n if (resolvedElement) {\n itemRefs.value.set(value, resolvedElement);\n } else {\n itemRefs.value.delete(value);\n }\n};\n\nconst focusItem = (index: number) => {\n const item = props.items[index];\n\n if (!item) {\n return;\n }\n\n focusedIndex.value = index;\n nextTick(() => itemRefs.value.get(item.value)?.focus());\n};\n\nconst selectItem = (item: TNavMenuItem) => {\n if (props.disabled || item.disabled) {\n return;\n }\n\n if (props.modelValue === undefined) {\n internalValue.value = item.value;\n }\n\n emit('update:modelValue', item.value);\n emit('select', item.value, item);\n};\n\nconst onItemKeydown = (event: KeyboardEvent, index: number) => {\n if (props.disabled) {\n return;\n }\n\n if (event.key === 'Home') {\n event.preventDefault();\n const firstIndex = props.items.findIndex((item) => !item.disabled);\n\n if (firstIndex >= 0) {\n focusItem(firstIndex);\n }\n\n return;\n }\n\n if (event.key === 'End') {\n event.preventDefault();\n const lastIndex = [...props.items].reverse().findIndex((item) => !item.disabled);\n\n if (lastIndex >= 0) {\n focusItem(props.items.length - 1 - lastIndex);\n }\n\n return;\n }\n\n if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {\n event.preventDefault();\n const nextIndex = getNextEnabledIndex(index, props.items, 1);\n\n if (nextIndex >= 0) {\n focusItem(nextIndex);\n }\n\n return;\n }\n\n if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {\n event.preventDefault();\n const previousIndex = getNextEnabledIndex(index, props.items, -1);\n\n if (previousIndex >= 0) {\n focusItem(previousIndex);\n }\n\n return;\n }\n\n if (isActivationKey(event)) {\n event.preventDefault();\n selectItem(props.items[index]);\n }\n};\n\nconst getTabIndex = (index: number) => (focusedIndex.value === index ? 0 : -1);\n\nwatch(\n () => [props.items, props.modelValue] as const,\n () => {\n const nextIndex = getInitialFocusedIndex();\n focusedIndex.value = nextIndex >= 0 ? nextIndex : -1;\n },\n { deep: true },\n);\n</script>\n\n<template>\n <nav\n v-bind=\"rootAttrs\"\n :class=\"rootClasses\"\n :style=\"rootStyle\"\n >\n <ul\n v-if=\"items.length > 0\"\n class=\"t-nav-menu__list\"\n >\n <li\n v-for=\"(item, index) in items\"\n :key=\"item.value\"\n class=\"t-nav-menu__entry\"\n >\n <component\n :is=\"itemTag(item)\"\n :ref=\"(element: Element | ComponentPublicInstance | null) => setItemRef(element, item.value)\"\n :type=\"item.to ? undefined : 'button'\"\n :to=\"item.to || undefined\"\n class=\"t-nav-menu__item\"\n :class=\"{\n 'is-selected': isSelected(item.value),\n 'is-disabled': disabled || item.disabled,\n }\"\n :active-class=\"routerActiveClass(item)\"\n :exact-active-class=\"routerExactActiveClass(item)\"\n :disabled=\"(!item.to && (disabled || item.disabled)) || undefined\"\n :aria-disabled=\"(item.to && (disabled || item.disabled)) || undefined\"\n :aria-current=\"isSelected(item.value) ? 'page' : undefined\"\n :tabindex=\"disabled || item.disabled ? -1 : getTabIndex(index)\"\n :title=\"resolvedCollapsed ? item.label : undefined\"\n @click=\"selectItem(item)\"\n @keydown=\"onItemKeydown($event, index)\"\n @focus=\"focusedIndex = index\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n :selected=\"isSelected(item.value)\"\n :focused=\"focusedIndex === index\"\n :collapsed=\"resolvedCollapsed\"\n >\n <component\n :is=\"iconFor(item)\"\n v-if=\"iconFor(item)\"\n class=\"t-nav-menu__icon\"\n aria-hidden=\"true\"\n />\n <span\n v-else\n class=\"t-nav-menu__marker\"\n aria-hidden=\"true\"\n >\n {{ (item.shortLabel ?? item.label.charAt(0)).toUpperCase() }}\n </span>\n\n <span class=\"t-nav-menu__copy\">\n <span class=\"t-nav-menu__label\">{{ item.label }}</span>\n <span\n v-if=\"item.description\"\n class=\"t-nav-menu__description\"\n >\n {{ item.description }}\n </span>\n </span>\n\n <span\n v-if=\"item.badge !== undefined && item.badge !== null\"\n class=\"t-nav-menu__badge\"\n >\n {{ item.badge }}\n </span>\n </slot>\n </component>\n </li>\n </ul>\n\n <div\n v-else\n class=\"t-nav-menu__empty\"\n >\n <slot name=\"empty\">\n No navigation items.\n </slot>\n </div>\n </nav>\n</template>\n"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { treeSidebarInjectionKey as e } from "./sidebar.js";
2
- import { Fragment as t, computed as n, createBlock as r, createCommentVNode as i, createElementBlock as a, createElementVNode as o, createTextVNode as s, defineComponent as c, getCurrentInstance as l, inject as u, mergeProps as d, nextTick as f, normalizeClass as p, openBlock as m, ref as h, renderList as g, renderSlot as _, resolveDynamicComponent as v, toDisplayString as y, useAttrs as b, watch as x, withCtx as S } from "vue";
3
- import { getNextEnabledIndex as C, isActivationKey as w } from "@treeui/utils";
2
+ import { Fragment as t, computed as n, createBlock as r, createCommentVNode as i, createElementBlock as a, createElementVNode as o, createTextVNode as s, defineComponent as c, getCurrentInstance as l, inject as u, mergeProps as d, nextTick as f, normalizeClass as p, openBlock as m, ref as h, renderList as ee, renderSlot as g, resolveDynamicComponent as _, toDisplayString as v, useAttrs as y, watch as b, withCtx as x } from "vue";
3
+ import { getNextEnabledIndex as S, isActivationKey as C } from "@treeui/utils";
4
+ import { resolveTreeIcon as w } from "@treeui/icons";
4
5
  //#region src/components/TNavMenu.vue?vue&type=script&setup=true&lang.ts
5
6
  var T = {
6
7
  key: 0,
@@ -9,7 +10,7 @@ var T = {
9
10
  key: 1,
10
11
  class: "t-nav-menu__marker",
11
12
  "aria-hidden": "true"
12
- }, D = { class: "t-nav-menu__copy" }, ee = { class: "t-nav-menu__label" }, te = {
13
+ }, D = { class: "t-nav-menu__copy" }, te = { class: "t-nav-menu__label" }, ne = {
13
14
  key: 0,
14
15
  class: "t-nav-menu__description"
15
16
  }, O = {
@@ -41,33 +42,33 @@ var T = {
41
42
  },
42
43
  emits: ["update:modelValue", "select"],
43
44
  setup(c, { emit: A }) {
44
- let j = c, M = A, N = b(), P = u(e, null), F = h(j.defaultValue), I = h(/* @__PURE__ */ new Map()), L = l(), R = n(() => j.modelValue ?? F.value), z = n(() => j.collapsed ?? P?.collapsed.value ?? !1), B = n(() => L?.appContext.components.RouterLink ?? null), V = (e) => e.to && B.value ? B.value : "button", H = (e) => !!e.to && B.value !== null, U = (e) => {
45
- if (H(e)) return j.modelValue === void 0 ? e.exact ?? j.exact ? "" : "is-selected" : "";
46
- }, W = (e) => {
47
- if (H(e)) return j.modelValue === void 0 ? "is-selected" : "";
48
- }, G = () => {
49
- let e = j.items.findIndex((e) => e.value === R.value && !e.disabled);
45
+ let j = c, M = A, N = (e) => w(e.icon), P = y(), F = u(e, null), I = h(j.defaultValue), L = h(/* @__PURE__ */ new Map()), R = l(), z = n(() => j.modelValue ?? I.value), B = n(() => j.collapsed ?? F?.collapsed.value ?? !1), V = n(() => R?.appContext.components.RouterLink ?? null), H = (e) => e.to && V.value ? V.value : "button", U = (e) => !!e.to && V.value !== null, W = (e) => {
46
+ if (U(e)) return j.modelValue === void 0 ? e.exact ?? j.exact ? "" : "is-selected" : "";
47
+ }, G = (e) => {
48
+ if (U(e)) return j.modelValue === void 0 ? "is-selected" : "";
49
+ }, K = () => {
50
+ let e = j.items.findIndex((e) => e.value === z.value && !e.disabled);
50
51
  return e >= 0 ? e : j.items.findIndex((e) => !e.disabled);
51
- }, K = h(G()), q = n(() => [
52
+ }, q = h(K()), J = n(() => [
52
53
  "t-nav-menu",
53
54
  `t-nav-menu--${j.size}`,
54
55
  {
55
- "is-collapsed": z.value,
56
+ "is-collapsed": B.value,
56
57
  "is-disabled": j.disabled
57
58
  },
58
- N.class
59
- ]), J = n(() => N.style), Y = n(() => {
60
- let { class: e, style: t, ...n } = N;
59
+ P.class
60
+ ]), Y = n(() => P.style), X = n(() => {
61
+ let { class: e, style: t, ...n } = P;
61
62
  return n;
62
- }), X = (e) => R.value === e, Z = (e, t) => {
63
+ }), Z = (e) => z.value === e, re = (e, t) => {
63
64
  let n = e instanceof HTMLElement ? e : e && "$el" in e && e.$el instanceof HTMLElement ? e.$el : null;
64
- n ? I.value.set(t, n) : I.value.delete(t);
65
+ n ? L.value.set(t, n) : L.value.delete(t);
65
66
  }, Q = (e) => {
66
67
  let t = j.items[e];
67
- t && (K.value = e, f(() => I.value.get(t.value)?.focus()));
68
+ t && (q.value = e, f(() => L.value.get(t.value)?.focus()));
68
69
  }, $ = (e) => {
69
- j.disabled || e.disabled || (j.modelValue === void 0 && (F.value = e.value), M("update:modelValue", e.value), M("select", e.value, e));
70
- }, ne = (e, t) => {
70
+ j.disabled || e.disabled || (j.modelValue === void 0 && (I.value = e.value), M("update:modelValue", e.value), M("select", e.value, e));
71
+ }, ie = (e, t) => {
71
72
  if (!j.disabled) {
72
73
  if (e.key === "Home") {
73
74
  e.preventDefault();
@@ -83,61 +84,61 @@ var T = {
83
84
  }
84
85
  if (e.key === "ArrowDown" || e.key === "ArrowRight") {
85
86
  e.preventDefault();
86
- let n = C(t, j.items, 1);
87
+ let n = S(t, j.items, 1);
87
88
  n >= 0 && Q(n);
88
89
  return;
89
90
  }
90
91
  if (e.key === "ArrowUp" || e.key === "ArrowLeft") {
91
92
  e.preventDefault();
92
- let n = C(t, j.items, -1);
93
+ let n = S(t, j.items, -1);
93
94
  n >= 0 && Q(n);
94
95
  return;
95
96
  }
96
- w(e) && (e.preventDefault(), $(j.items[t]));
97
+ C(e) && (e.preventDefault(), $(j.items[t]));
97
98
  }
98
- }, re = (e) => K.value === e ? 0 : -1;
99
- return x(() => [j.items, j.modelValue], () => {
100
- let e = G();
101
- K.value = e >= 0 ? e : -1;
102
- }, { deep: !0 }), (e, n) => (m(), a("nav", d(Y.value, {
103
- class: q.value,
104
- style: J.value
105
- }), [c.items.length > 0 ? (m(), a("ul", T, [(m(!0), a(t, null, g(c.items, (t, n) => (m(), a("li", {
99
+ }, ae = (e) => q.value === e ? 0 : -1;
100
+ return b(() => [j.items, j.modelValue], () => {
101
+ let e = K();
102
+ q.value = e >= 0 ? e : -1;
103
+ }, { deep: !0 }), (e, n) => (m(), a("nav", d(X.value, {
104
+ class: J.value,
105
+ style: Y.value
106
+ }), [c.items.length > 0 ? (m(), a("ul", T, [(m(!0), a(t, null, ee(c.items, (t, n) => (m(), a("li", {
106
107
  key: t.value,
107
108
  class: "t-nav-menu__entry"
108
- }, [(m(), r(v(V(t)), {
109
+ }, [(m(), r(_(H(t)), {
109
110
  ref_for: !0,
110
- ref: (e) => Z(e, t.value),
111
+ ref: (e) => re(e, t.value),
111
112
  type: t.to ? void 0 : "button",
112
113
  to: t.to || void 0,
113
114
  class: p(["t-nav-menu__item", {
114
- "is-selected": X(t.value),
115
+ "is-selected": Z(t.value),
115
116
  "is-disabled": c.disabled || t.disabled
116
117
  }]),
117
- "active-class": U(t),
118
- "exact-active-class": W(t),
118
+ "active-class": W(t),
119
+ "exact-active-class": G(t),
119
120
  disabled: !t.to && (c.disabled || t.disabled) || void 0,
120
121
  "aria-disabled": t.to && (c.disabled || t.disabled) || void 0,
121
- "aria-current": X(t.value) ? "page" : void 0,
122
- tabindex: c.disabled || t.disabled ? -1 : re(n),
123
- title: z.value ? t.label : void 0,
122
+ "aria-current": Z(t.value) ? "page" : void 0,
123
+ tabindex: c.disabled || t.disabled ? -1 : ae(n),
124
+ title: B.value ? t.label : void 0,
124
125
  onClick: (e) => $(t),
125
- onKeydown: (e) => ne(e, n),
126
- onFocus: (e) => K.value = n
126
+ onKeydown: (e) => ie(e, n),
127
+ onFocus: (e) => q.value = n
127
128
  }, {
128
- default: S(() => [_(e.$slots, "item", {
129
+ default: x(() => [g(e.$slots, "item", {
129
130
  item: t,
130
- selected: X(t.value),
131
- focused: K.value === n,
132
- collapsed: z.value
131
+ selected: Z(t.value),
132
+ focused: q.value === n,
133
+ collapsed: B.value
133
134
  }, () => [
134
- t.icon ? (m(), r(v(t.icon), {
135
+ N(t) ? (m(), r(_(N(t)), {
135
136
  key: 0,
136
137
  class: "t-nav-menu__icon",
137
138
  "aria-hidden": "true"
138
- })) : (m(), a("span", E, y((t.shortLabel ?? t.label.charAt(0)).toUpperCase()), 1)),
139
- o("span", D, [o("span", ee, y(t.label), 1), t.description ? (m(), a("span", te, y(t.description), 1)) : i("", !0)]),
140
- t.badge !== void 0 && t.badge !== null ? (m(), a("span", O, y(t.badge), 1)) : i("", !0)
139
+ })) : (m(), a("span", E, v((t.shortLabel ?? t.label.charAt(0)).toUpperCase()), 1)),
140
+ o("span", D, [o("span", te, v(t.label), 1), t.description ? (m(), a("span", ne, v(t.description), 1)) : i("", !0)]),
141
+ t.badge !== void 0 && t.badge !== null ? (m(), a("span", O, v(t.badge), 1)) : i("", !0)
141
142
  ])]),
142
143
  _: 2
143
144
  }, 1064, [
@@ -154,7 +155,7 @@ var T = {
154
155
  "onClick",
155
156
  "onKeydown",
156
157
  "onFocus"
157
- ]))]))), 128))])) : (m(), a("div", k, [_(e.$slots, "empty", {}, () => [n[0] ||= s(" No navigation items. ", -1)])]))], 16));
158
+ ]))]))), 128))])) : (m(), a("div", k, [g(e.$slots, "empty", {}, () => [n[0] ||= s(" No navigation items. ", -1)])]))], 16));
158
159
  }
159
160
  });
160
161
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"TNavMenu.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/TNavMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { getNextEnabledIndex, isActivationKey } from '@treeui/utils';\nimport { computed, getCurrentInstance, inject, nextTick, ref, useAttrs, watch, type Component, type ComponentPublicInstance } from 'vue';\nimport type { TSize } from '../types/contracts';\nimport { treeSidebarInjectionKey } from './sidebar';\n\ndefineOptions({\n inheritAttrs: false,\n});\n\nexport interface TNavMenuItem {\n label: string;\n value: string;\n shortLabel?: string;\n description?: string;\n icon?: Component;\n to?: string | Record<string, unknown>;\n badge?: string | number;\n disabled?: boolean;\n /**\n * When the item renders as a router link, controls whether an inclusive\n * (prefix) route match also marks it active. Defaults to the menu-level\n * `exact` prop. Set to `false` for section roots that should stay active on\n * nested routes.\n */\n exact?: boolean;\n}\n\nconst props = withDefaults(\n defineProps<{\n modelValue?: string;\n defaultValue?: string;\n items?: TNavMenuItem[];\n size?: TSize;\n collapsed?: boolean;\n disabled?: boolean;\n exact?: boolean;\n }>(),\n {\n modelValue: undefined,\n defaultValue: '',\n items: () => [],\n size: 'md',\n collapsed: undefined,\n disabled: false,\n exact: true,\n },\n);\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string];\n select: [value: string, item: TNavMenuItem];\n}>();\n\ndefineSlots<{\n item?: (props: {\n item: TNavMenuItem;\n selected: boolean;\n focused: boolean;\n collapsed: boolean;\n }) => unknown;\n empty?: () => unknown;\n}>();\n\nconst attrs = useAttrs();\nconst sidebar = inject(treeSidebarInjectionKey, null);\nconst internalValue = ref(props.defaultValue);\nconst itemRefs = ref<Map<string, HTMLElement>>(new Map());\nconst instance = getCurrentInstance();\n\nconst selectedValue = computed(() => props.modelValue ?? internalValue.value);\nconst resolvedCollapsed = computed(() => props.collapsed ?? sidebar?.collapsed.value ?? false);\n\nconst routerLinkComponent = computed<Component | null>(() => (\n (instance?.appContext.components.RouterLink as Component | undefined) ?? null\n));\n\nconst itemTag = (item: TNavMenuItem) => {\n if (item.to && routerLinkComponent.value) {\n return routerLinkComponent.value;\n }\n return 'button';\n};\n\nconst isRouterLinkItem = (item: TNavMenuItem) =>\n Boolean(item.to) && routerLinkComponent.value !== null;\n\n// Keep the active highlight deterministic when items render as router links.\n// Vue Router's default `router-link-active` uses inclusive (prefix) matching,\n// so several links can appear active at once. We map the router's active\n// classes onto TreeUI's own `is-selected`:\n// - Controlled menus (explicit `modelValue`) own selection, so the router adds\n// no class of its own — only the bound `is-selected` shows.\n// - Uncontrolled menus let the current route drive selection, using exact\n// matching by default (opt out per item, or menu-wide, via `exact`).\nconst routerActiveClass = (item: TNavMenuItem) => {\n if (!isRouterLinkItem(item)) {\n return undefined;\n }\n\n if (props.modelValue !== undefined) {\n return '';\n }\n\n return (item.exact ?? props.exact) ? '' : 'is-selected';\n};\n\nconst routerExactActiveClass = (item: TNavMenuItem) => {\n if (!isRouterLinkItem(item)) {\n return undefined;\n }\n\n return props.modelValue !== undefined ? '' : 'is-selected';\n};\n\nconst getInitialFocusedIndex = () => {\n const selectedIndex = props.items.findIndex((item) =>\n item.value === selectedValue.value && !item.disabled,\n );\n\n if (selectedIndex >= 0) {\n return selectedIndex;\n }\n\n return props.items.findIndex((item) => !item.disabled);\n};\n\nconst focusedIndex = ref(getInitialFocusedIndex());\n\nconst rootClasses = computed(() => [\n 't-nav-menu',\n `t-nav-menu--${props.size}`,\n {\n 'is-collapsed': resolvedCollapsed.value,\n 'is-disabled': props.disabled,\n },\n attrs.class,\n]);\n\nconst rootStyle = computed(() => attrs.style);\n\nconst rootAttrs = computed(() => {\n const { class: _class, style: _style, ...rest } = attrs;\n return rest;\n});\n\nconst isSelected = (value: string) => selectedValue.value === value;\n\nconst setItemRef = (element: Element | ComponentPublicInstance | null, value: string) => {\n const resolvedElement =\n element instanceof HTMLElement\n ? element\n : element && '$el' in element && element.$el instanceof HTMLElement\n ? element.$el\n : null;\n\n if (resolvedElement) {\n itemRefs.value.set(value, resolvedElement);\n } else {\n itemRefs.value.delete(value);\n }\n};\n\nconst focusItem = (index: number) => {\n const item = props.items[index];\n\n if (!item) {\n return;\n }\n\n focusedIndex.value = index;\n nextTick(() => itemRefs.value.get(item.value)?.focus());\n};\n\nconst selectItem = (item: TNavMenuItem) => {\n if (props.disabled || item.disabled) {\n return;\n }\n\n if (props.modelValue === undefined) {\n internalValue.value = item.value;\n }\n\n emit('update:modelValue', item.value);\n emit('select', item.value, item);\n};\n\nconst onItemKeydown = (event: KeyboardEvent, index: number) => {\n if (props.disabled) {\n return;\n }\n\n if (event.key === 'Home') {\n event.preventDefault();\n const firstIndex = props.items.findIndex((item) => !item.disabled);\n\n if (firstIndex >= 0) {\n focusItem(firstIndex);\n }\n\n return;\n }\n\n if (event.key === 'End') {\n event.preventDefault();\n const lastIndex = [...props.items].reverse().findIndex((item) => !item.disabled);\n\n if (lastIndex >= 0) {\n focusItem(props.items.length - 1 - lastIndex);\n }\n\n return;\n }\n\n if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {\n event.preventDefault();\n const nextIndex = getNextEnabledIndex(index, props.items, 1);\n\n if (nextIndex >= 0) {\n focusItem(nextIndex);\n }\n\n return;\n }\n\n if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {\n event.preventDefault();\n const previousIndex = getNextEnabledIndex(index, props.items, -1);\n\n if (previousIndex >= 0) {\n focusItem(previousIndex);\n }\n\n return;\n }\n\n if (isActivationKey(event)) {\n event.preventDefault();\n selectItem(props.items[index]);\n }\n};\n\nconst getTabIndex = (index: number) => (focusedIndex.value === index ? 0 : -1);\n\nwatch(\n () => [props.items, props.modelValue] as const,\n () => {\n const nextIndex = getInitialFocusedIndex();\n focusedIndex.value = nextIndex >= 0 ? nextIndex : -1;\n },\n { deep: true },\n);\n</script>\n\n<template>\n <nav\n v-bind=\"rootAttrs\"\n :class=\"rootClasses\"\n :style=\"rootStyle\"\n >\n <ul\n v-if=\"items.length > 0\"\n class=\"t-nav-menu__list\"\n >\n <li\n v-for=\"(item, index) in items\"\n :key=\"item.value\"\n class=\"t-nav-menu__entry\"\n >\n <component\n :is=\"itemTag(item)\"\n :ref=\"(element: Element | ComponentPublicInstance | null) => setItemRef(element, item.value)\"\n :type=\"item.to ? undefined : 'button'\"\n :to=\"item.to || undefined\"\n class=\"t-nav-menu__item\"\n :class=\"{\n 'is-selected': isSelected(item.value),\n 'is-disabled': disabled || item.disabled,\n }\"\n :active-class=\"routerActiveClass(item)\"\n :exact-active-class=\"routerExactActiveClass(item)\"\n :disabled=\"(!item.to && (disabled || item.disabled)) || undefined\"\n :aria-disabled=\"(item.to && (disabled || item.disabled)) || undefined\"\n :aria-current=\"isSelected(item.value) ? 'page' : undefined\"\n :tabindex=\"disabled || item.disabled ? -1 : getTabIndex(index)\"\n :title=\"resolvedCollapsed ? item.label : undefined\"\n @click=\"selectItem(item)\"\n @keydown=\"onItemKeydown($event, index)\"\n @focus=\"focusedIndex = index\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n :selected=\"isSelected(item.value)\"\n :focused=\"focusedIndex === index\"\n :collapsed=\"resolvedCollapsed\"\n >\n <component\n :is=\"item.icon\"\n v-if=\"item.icon\"\n class=\"t-nav-menu__icon\"\n aria-hidden=\"true\"\n />\n <span\n v-else\n class=\"t-nav-menu__marker\"\n aria-hidden=\"true\"\n >\n {{ (item.shortLabel ?? item.label.charAt(0)).toUpperCase() }}\n </span>\n\n <span class=\"t-nav-menu__copy\">\n <span class=\"t-nav-menu__label\">{{ item.label }}</span>\n <span\n v-if=\"item.description\"\n class=\"t-nav-menu__description\"\n >\n {{ item.description }}\n </span>\n </span>\n\n <span\n v-if=\"item.badge !== undefined && item.badge !== null\"\n class=\"t-nav-menu__badge\"\n >\n {{ item.badge }}\n </span>\n </slot>\n </component>\n </li>\n </ul>\n\n <div\n v-else\n class=\"t-nav-menu__empty\"\n >\n <slot name=\"empty\">\n No navigation items.\n </slot>\n </div>\n </nav>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"TNavMenu.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/TNavMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { getNextEnabledIndex, isActivationKey } from '@treeui/utils';\nimport { resolveTreeIcon, type TIconInput } from '@treeui/icons';\nimport { computed, getCurrentInstance, inject, nextTick, ref, useAttrs, watch, type Component, type ComponentPublicInstance } from 'vue';\nimport type { TSize } from '../types/contracts';\nimport { treeSidebarInjectionKey } from './sidebar';\n\ndefineOptions({\n inheritAttrs: false,\n});\n\nexport interface TNavMenuItem {\n label: string;\n value: string;\n shortLabel?: string;\n description?: string;\n /**\n * A registered icon name (`'cpu'`), or any component. A name is the simpler\n * form: it needs no import and no `markRaw`, since a string is not made\n * reactive when the item lands in a `ref`.\n */\n icon?: TIconInput;\n to?: string | Record<string, unknown>;\n badge?: string | number;\n disabled?: boolean;\n /**\n * When the item renders as a router link, controls whether an inclusive\n * (prefix) route match also marks it active. Defaults to the menu-level\n * `exact` prop. Set to `false` for section roots that should stay active on\n * nested routes.\n */\n exact?: boolean;\n}\n\nconst props = withDefaults(\n defineProps<{\n modelValue?: string;\n defaultValue?: string;\n items?: TNavMenuItem[];\n size?: TSize;\n collapsed?: boolean;\n disabled?: boolean;\n exact?: boolean;\n }>(),\n {\n modelValue: undefined,\n defaultValue: '',\n items: () => [],\n size: 'md',\n collapsed: undefined,\n disabled: false,\n exact: true,\n },\n);\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string];\n select: [value: string, item: TNavMenuItem];\n}>();\n\ndefineSlots<{\n item?: (props: {\n item: TNavMenuItem;\n selected: boolean;\n focused: boolean;\n collapsed: boolean;\n }) => unknown;\n empty?: () => unknown;\n}>();\n\n/**\n * A registry lookup, so it stays cheap enough to call from the template. An\n * unresolved name returns `undefined` and the item falls back to its letter\n * marker rather than rendering a hole.\n */\nconst iconFor = (item: TNavMenuItem) => resolveTreeIcon(item.icon);\n\nconst attrs = useAttrs();\nconst sidebar = inject(treeSidebarInjectionKey, null);\nconst internalValue = ref(props.defaultValue);\nconst itemRefs = ref<Map<string, HTMLElement>>(new Map());\nconst instance = getCurrentInstance();\n\nconst selectedValue = computed(() => props.modelValue ?? internalValue.value);\nconst resolvedCollapsed = computed(() => props.collapsed ?? sidebar?.collapsed.value ?? false);\n\nconst routerLinkComponent = computed<Component | null>(() => (\n (instance?.appContext.components.RouterLink as Component | undefined) ?? null\n));\n\nconst itemTag = (item: TNavMenuItem) => {\n if (item.to && routerLinkComponent.value) {\n return routerLinkComponent.value;\n }\n return 'button';\n};\n\nconst isRouterLinkItem = (item: TNavMenuItem) =>\n Boolean(item.to) && routerLinkComponent.value !== null;\n\n// Keep the active highlight deterministic when items render as router links.\n// Vue Router's default `router-link-active` uses inclusive (prefix) matching,\n// so several links can appear active at once. We map the router's active\n// classes onto TreeUI's own `is-selected`:\n// - Controlled menus (explicit `modelValue`) own selection, so the router adds\n// no class of its own — only the bound `is-selected` shows.\n// - Uncontrolled menus let the current route drive selection, using exact\n// matching by default (opt out per item, or menu-wide, via `exact`).\nconst routerActiveClass = (item: TNavMenuItem) => {\n if (!isRouterLinkItem(item)) {\n return undefined;\n }\n\n if (props.modelValue !== undefined) {\n return '';\n }\n\n return (item.exact ?? props.exact) ? '' : 'is-selected';\n};\n\nconst routerExactActiveClass = (item: TNavMenuItem) => {\n if (!isRouterLinkItem(item)) {\n return undefined;\n }\n\n return props.modelValue !== undefined ? '' : 'is-selected';\n};\n\nconst getInitialFocusedIndex = () => {\n const selectedIndex = props.items.findIndex((item) =>\n item.value === selectedValue.value && !item.disabled,\n );\n\n if (selectedIndex >= 0) {\n return selectedIndex;\n }\n\n return props.items.findIndex((item) => !item.disabled);\n};\n\nconst focusedIndex = ref(getInitialFocusedIndex());\n\nconst rootClasses = computed(() => [\n 't-nav-menu',\n `t-nav-menu--${props.size}`,\n {\n 'is-collapsed': resolvedCollapsed.value,\n 'is-disabled': props.disabled,\n },\n attrs.class,\n]);\n\nconst rootStyle = computed(() => attrs.style);\n\nconst rootAttrs = computed(() => {\n const { class: _class, style: _style, ...rest } = attrs;\n return rest;\n});\n\nconst isSelected = (value: string) => selectedValue.value === value;\n\nconst setItemRef = (element: Element | ComponentPublicInstance | null, value: string) => {\n const resolvedElement =\n element instanceof HTMLElement\n ? element\n : element && '$el' in element && element.$el instanceof HTMLElement\n ? element.$el\n : null;\n\n if (resolvedElement) {\n itemRefs.value.set(value, resolvedElement);\n } else {\n itemRefs.value.delete(value);\n }\n};\n\nconst focusItem = (index: number) => {\n const item = props.items[index];\n\n if (!item) {\n return;\n }\n\n focusedIndex.value = index;\n nextTick(() => itemRefs.value.get(item.value)?.focus());\n};\n\nconst selectItem = (item: TNavMenuItem) => {\n if (props.disabled || item.disabled) {\n return;\n }\n\n if (props.modelValue === undefined) {\n internalValue.value = item.value;\n }\n\n emit('update:modelValue', item.value);\n emit('select', item.value, item);\n};\n\nconst onItemKeydown = (event: KeyboardEvent, index: number) => {\n if (props.disabled) {\n return;\n }\n\n if (event.key === 'Home') {\n event.preventDefault();\n const firstIndex = props.items.findIndex((item) => !item.disabled);\n\n if (firstIndex >= 0) {\n focusItem(firstIndex);\n }\n\n return;\n }\n\n if (event.key === 'End') {\n event.preventDefault();\n const lastIndex = [...props.items].reverse().findIndex((item) => !item.disabled);\n\n if (lastIndex >= 0) {\n focusItem(props.items.length - 1 - lastIndex);\n }\n\n return;\n }\n\n if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {\n event.preventDefault();\n const nextIndex = getNextEnabledIndex(index, props.items, 1);\n\n if (nextIndex >= 0) {\n focusItem(nextIndex);\n }\n\n return;\n }\n\n if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {\n event.preventDefault();\n const previousIndex = getNextEnabledIndex(index, props.items, -1);\n\n if (previousIndex >= 0) {\n focusItem(previousIndex);\n }\n\n return;\n }\n\n if (isActivationKey(event)) {\n event.preventDefault();\n selectItem(props.items[index]);\n }\n};\n\nconst getTabIndex = (index: number) => (focusedIndex.value === index ? 0 : -1);\n\nwatch(\n () => [props.items, props.modelValue] as const,\n () => {\n const nextIndex = getInitialFocusedIndex();\n focusedIndex.value = nextIndex >= 0 ? nextIndex : -1;\n },\n { deep: true },\n);\n</script>\n\n<template>\n <nav\n v-bind=\"rootAttrs\"\n :class=\"rootClasses\"\n :style=\"rootStyle\"\n >\n <ul\n v-if=\"items.length > 0\"\n class=\"t-nav-menu__list\"\n >\n <li\n v-for=\"(item, index) in items\"\n :key=\"item.value\"\n class=\"t-nav-menu__entry\"\n >\n <component\n :is=\"itemTag(item)\"\n :ref=\"(element: Element | ComponentPublicInstance | null) => setItemRef(element, item.value)\"\n :type=\"item.to ? undefined : 'button'\"\n :to=\"item.to || undefined\"\n class=\"t-nav-menu__item\"\n :class=\"{\n 'is-selected': isSelected(item.value),\n 'is-disabled': disabled || item.disabled,\n }\"\n :active-class=\"routerActiveClass(item)\"\n :exact-active-class=\"routerExactActiveClass(item)\"\n :disabled=\"(!item.to && (disabled || item.disabled)) || undefined\"\n :aria-disabled=\"(item.to && (disabled || item.disabled)) || undefined\"\n :aria-current=\"isSelected(item.value) ? 'page' : undefined\"\n :tabindex=\"disabled || item.disabled ? -1 : getTabIndex(index)\"\n :title=\"resolvedCollapsed ? item.label : undefined\"\n @click=\"selectItem(item)\"\n @keydown=\"onItemKeydown($event, index)\"\n @focus=\"focusedIndex = index\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n :selected=\"isSelected(item.value)\"\n :focused=\"focusedIndex === index\"\n :collapsed=\"resolvedCollapsed\"\n >\n <component\n :is=\"iconFor(item)\"\n v-if=\"iconFor(item)\"\n class=\"t-nav-menu__icon\"\n aria-hidden=\"true\"\n />\n <span\n v-else\n class=\"t-nav-menu__marker\"\n aria-hidden=\"true\"\n >\n {{ (item.shortLabel ?? item.label.charAt(0)).toUpperCase() }}\n </span>\n\n <span class=\"t-nav-menu__copy\">\n <span class=\"t-nav-menu__label\">{{ item.label }}</span>\n <span\n v-if=\"item.description\"\n class=\"t-nav-menu__description\"\n >\n {{ item.description }}\n </span>\n </span>\n\n <span\n v-if=\"item.badge !== undefined && item.badge !== null\"\n class=\"t-nav-menu__badge\"\n >\n {{ item.badge }}\n </span>\n </slot>\n </component>\n </li>\n </ul>\n\n <div\n v-else\n class=\"t-nav-menu__empty\"\n >\n <slot name=\"empty\">\n No navigation items.\n </slot>\n </div>\n </nav>\n</template>\n"],"mappings":"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"}