@treeui/vue 0.15.0 → 0.17.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 (78) hide show
  1. package/dist/components/TAccordion.cjs.map +1 -1
  2. package/dist/components/TAccordion.js.map +1 -1
  3. package/dist/components/TAccordion.vue_vue_type_script_setup_true_lang.cjs +1 -1
  4. package/dist/components/TAccordion.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  5. package/dist/components/TAccordion.vue_vue_type_script_setup_true_lang.js +3 -3
  6. package/dist/components/TAccordion.vue_vue_type_script_setup_true_lang.js.map +1 -1
  7. package/dist/components/TAccordionItem.cjs.map +1 -1
  8. package/dist/components/TAccordionItem.js.map +1 -1
  9. package/dist/components/TAccordionItem.vue_vue_type_script_setup_true_lang.cjs +1 -1
  10. package/dist/components/TAccordionItem.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  11. package/dist/components/TAccordionItem.vue_vue_type_script_setup_true_lang.js +1 -1
  12. package/dist/components/TAccordionItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  13. package/dist/components/TCard.cjs.map +1 -1
  14. package/dist/components/TCard.js.map +1 -1
  15. package/dist/components/TCard.vue_vue_type_script_setup_true_lang.cjs +1 -1
  16. package/dist/components/TCard.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  17. package/dist/components/TCard.vue_vue_type_script_setup_true_lang.js +2 -2
  18. package/dist/components/TCard.vue_vue_type_script_setup_true_lang.js.map +1 -1
  19. package/dist/components/TDropdown.cjs.map +1 -1
  20. package/dist/components/TDropdown.js.map +1 -1
  21. package/dist/components/TDropdown.vue.d.ts +6 -2
  22. package/dist/components/TDropdown.vue.d.ts.map +1 -1
  23. package/dist/components/TDropdown.vue_vue_type_script_setup_true_lang.cjs +1 -1
  24. package/dist/components/TDropdown.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  25. package/dist/components/TDropdown.vue_vue_type_script_setup_true_lang.js +31 -24
  26. package/dist/components/TDropdown.vue_vue_type_script_setup_true_lang.js.map +1 -1
  27. package/dist/components/TFlag.cjs +2 -0
  28. package/dist/components/TFlag.cjs.map +1 -0
  29. package/dist/components/TFlag.js +7 -0
  30. package/dist/components/TFlag.js.map +1 -0
  31. package/dist/components/TFlag.vue.d.ts +20 -0
  32. package/dist/components/TFlag.vue.d.ts.map +1 -0
  33. package/dist/components/TFlag.vue_vue_type_script_setup_true_lang.cjs +2 -0
  34. package/dist/components/TFlag.vue_vue_type_script_setup_true_lang.cjs.map +1 -0
  35. package/dist/components/TFlag.vue_vue_type_script_setup_true_lang.js +58 -0
  36. package/dist/components/TFlag.vue_vue_type_script_setup_true_lang.js.map +1 -0
  37. package/dist/components/TIcon.vue.d.ts +1 -1
  38. package/dist/components/TLanguageSelect.cjs +2 -0
  39. package/dist/components/TLanguageSelect.cjs.map +1 -0
  40. package/dist/components/TLanguageSelect.js +7 -0
  41. package/dist/components/TLanguageSelect.js.map +1 -0
  42. package/dist/components/TLanguageSelect.vue.d.ts +103 -0
  43. package/dist/components/TLanguageSelect.vue.d.ts.map +1 -0
  44. package/dist/components/TLanguageSelect.vue_vue_type_script_setup_true_lang.cjs +2 -0
  45. package/dist/components/TLanguageSelect.vue_vue_type_script_setup_true_lang.cjs.map +1 -0
  46. package/dist/components/TLanguageSelect.vue_vue_type_script_setup_true_lang.js +299 -0
  47. package/dist/components/TLanguageSelect.vue_vue_type_script_setup_true_lang.js.map +1 -0
  48. package/dist/components/TMarkdownEditor.cjs.map +1 -1
  49. package/dist/components/TMarkdownEditor.js.map +1 -1
  50. package/dist/components/TMarkdownEditor.vue.d.ts +2 -0
  51. package/dist/components/TMarkdownEditor.vue.d.ts.map +1 -1
  52. package/dist/components/TMarkdownEditor.vue_vue_type_script_setup_true_lang.cjs +9 -9
  53. package/dist/components/TMarkdownEditor.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  54. package/dist/components/TMarkdownEditor.vue_vue_type_script_setup_true_lang.js +117 -96
  55. package/dist/components/TMarkdownEditor.vue_vue_type_script_setup_true_lang.js.map +1 -1
  56. package/dist/components/TPopover.cjs.map +1 -1
  57. package/dist/components/TPopover.js.map +1 -1
  58. package/dist/components/TPopover.vue.d.ts +4 -2
  59. package/dist/components/TPopover.vue.d.ts.map +1 -1
  60. package/dist/components/TPopover.vue_vue_type_script_setup_true_lang.cjs +1 -1
  61. package/dist/components/TPopover.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  62. package/dist/components/TPopover.vue_vue_type_script_setup_true_lang.js +10 -5
  63. package/dist/components/TPopover.vue_vue_type_script_setup_true_lang.js.map +1 -1
  64. package/dist/components/accordion.cjs.map +1 -1
  65. package/dist/components/accordion.d.ts +4 -4
  66. package/dist/components/accordion.d.ts.map +1 -1
  67. package/dist/components/accordion.js.map +1 -1
  68. package/dist/components/index.d.ts +13 -1
  69. package/dist/components/index.d.ts.map +1 -1
  70. package/dist/index.cjs +1 -1
  71. package/dist/index.js +80 -78
  72. package/dist/plugin.cjs +1 -1
  73. package/dist/plugin.cjs.map +1 -1
  74. package/dist/plugin.d.ts.map +1 -1
  75. package/dist/plugin.js +146 -142
  76. package/dist/plugin.js.map +1 -1
  77. package/dist/style.css +1 -1
  78. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"TDropdown.vue_vue_type_script_setup_true_lang.cjs","names":[],"sources":["../../src/components/TDropdown.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { createId, isActivationKey, 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';\n\ndefineOptions({\n inheritAttrs: false,\n});\n\nexport interface TDropdownItem {\n label: string;\n value: string;\n disabled?: boolean;\n}\n\nexport type TDropdownAlign = 'start' | 'end';\n\nconst props = withDefaults(\n defineProps<{\n items?: TDropdownItem[];\n open?: boolean;\n defaultOpen?: boolean;\n disabled?: boolean;\n size?: TSize;\n label?: string;\n align?: TDropdownAlign;\n }>(),\n {\n items: () => [],\n open: undefined,\n defaultOpen: false,\n disabled: false,\n size: 'md',\n label: '',\n align: 'start',\n },\n);\n\nconst emit = defineEmits<{\n select: [value: string];\n 'update:open': [value: boolean];\n 'open-change': [value: boolean];\n}>();\n\ndefineSlots<{\n trigger(props: { isOpen: boolean }): unknown;\n item(props: { item: TDropdownItem; index: number }): unknown;\n}>();\n\nconst attrs = useAttrs();\nconst menuId = createId('t-dropdown');\nconst rootRef = ref<HTMLElement | null>(null);\nconst triggerRef = ref<HTMLButtonElement | null>(null);\nconst itemRefs = ref<Map<string, HTMLElement>>(new Map());\nconst focusedIndex = ref(-1);\n\nconst triggerAttrs = computed(() => {\n const { class: _class, style: _style, ...rest } = attrs;\n return rest;\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-dropdown',\n `t-dropdown--${props.size}`,\n {\n 't-dropdown--align-end': props.align === 'end',\n 'is-disabled': props.disabled,\n 'is-open': isOpen.value,\n },\n attrs.class,\n]);\n\nconst rootStyle = computed(() => attrs.style);\n\nconst enabledItems = computed(() => props.items.filter((i) => !i.disabled));\n\nconst openMenu = () => {\n if (props.disabled) return;\n focusedIndex.value = enabledItems.value.length > 0\n ? props.items.indexOf(enabledItems.value[0])\n : 0;\n setValue(true);\n nextTick(() => focusItem(focusedIndex.value));\n};\n\nconst closeMenu = (restoreFocus = false) => {\n setValue(false);\n focusedIndex.value = -1;\n if (restoreFocus) {\n nextTick(() => triggerRef.value?.focus());\n }\n};\n\nconst toggleMenu = () => {\n if (isOpen.value) {\n closeMenu();\n } else {\n openMenu();\n }\n};\n\nconst selectItem = (item: TDropdownItem) => {\n if (item.disabled) return;\n emit('select', item.value);\n closeMenu(true);\n};\n\nconst focusItem = (index: number) => {\n const item = props.items[index];\n if (!item) return;\n const el = itemRefs.value.get(item.value);\n el?.focus();\n};\n\nconst setItemRef = (el: Element | null, value: string) => {\n if (el instanceof HTMLElement) {\n itemRefs.value.set(value, el);\n } else {\n itemRefs.value.delete(value);\n }\n};\n\nconst moveFocus = (direction: 1 | -1) => {\n let nextIndex = focusedIndex.value + direction;\n while (nextIndex >= 0 && nextIndex < props.items.length) {\n if (!props.items[nextIndex].disabled) {\n focusedIndex.value = nextIndex;\n focusItem(nextIndex);\n return;\n }\n nextIndex += direction;\n }\n};\n\nconst onTriggerKeydown = (event: KeyboardEvent) => {\n if (props.disabled) return;\n\n if (isEscapeKey(event) && isOpen.value) {\n event.preventDefault();\n closeMenu();\n return;\n }\n\n if (isActivationKey(event) || event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault();\n openMenu();\n }\n};\n\nconst onItemKeydown = (event: KeyboardEvent, item: TDropdownItem) => {\n if (isEscapeKey(event)) {\n event.preventDefault();\n closeMenu(true);\n return;\n }\n\n if (isActivationKey(event)) {\n event.preventDefault();\n selectItem(item);\n return;\n }\n\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n moveFocus(1);\n break;\n case 'ArrowUp':\n event.preventDefault();\n moveFocus(-1);\n break;\n case 'Home':\n event.preventDefault();\n focusedIndex.value = enabledItems.value.length > 0\n ? props.items.indexOf(enabledItems.value[0])\n : 0;\n focusItem(focusedIndex.value);\n break;\n case 'End':\n event.preventDefault();\n focusedIndex.value = enabledItems.value.length > 0\n ? props.items.indexOf(enabledItems.value[enabledItems.value.length - 1])\n : props.items.length - 1;\n focusItem(focusedIndex.value);\n break;\n default:\n break;\n }\n};\n\nconst onDocumentPointerDown = (event: PointerEvent) => {\n if (!isOpen.value) return;\n const target = event.target;\n if (!(target instanceof Node)) return;\n if (rootRef.value?.contains(target)) return;\n closeMenu();\n};\n\nwatch(isOpen, (value) => {\n if (value) {\n document.addEventListener('pointerdown', onDocumentPointerDown);\n } else {\n document.removeEventListener('pointerdown', onDocumentPointerDown);\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 :data-state=\"isOpen ? 'open' : 'closed'\"\n >\n <span class=\"t-dropdown__trigger-wrapper\">\n <slot\n name=\"trigger\"\n :is-open=\"isOpen\"\n >\n <button\n ref=\"triggerRef\"\n v-bind=\"triggerAttrs\"\n type=\"button\"\n class=\"t-dropdown__trigger\"\n :disabled=\"disabled\"\n :aria-controls=\"isOpen ? menuId : undefined\"\n aria-haspopup=\"menu\"\n :aria-expanded=\"isOpen\"\n @click=\"toggleMenu\"\n @keydown=\"onTriggerKeydown\"\n >\n <span class=\"t-dropdown__label\">{{ label }}</span>\n <svg\n class=\"t-dropdown__chevron\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <polyline points=\"4 6 8 10 12 6\" />\n </svg>\n </button>\n </slot>\n </span>\n <transition name=\"t-fade\">\n <ul\n v-if=\"isOpen && !disabled\"\n :id=\"menuId\"\n role=\"menu\"\n class=\"t-dropdown__menu\"\n :aria-label=\"label || undefined\"\n >\n <li\n v-for=\"(item, index) in items\"\n :key=\"item.value\"\n :ref=\"(el) => setItemRef(el as Element | null, item.value)\"\n role=\"menuitem\"\n class=\"t-dropdown__item\"\n :class=\"{\n 'is-disabled': item.disabled,\n 'is-focused': index === focusedIndex,\n }\"\n :aria-disabled=\"item.disabled || undefined\"\n :tabindex=\"item.disabled ? -1 : 0\"\n @click=\"selectItem(item)\"\n @keydown=\"onItemKeydown($event, item)\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n :index=\"index\"\n >\n {{ item.label }}\n </slot>\n </li>\n </ul>\n </transition>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"TDropdown.vue_vue_type_script_setup_true_lang.cjs","names":[],"sources":["../../src/components/TDropdown.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { createId, focusFirst, isActivationKey, 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';\n\ndefineOptions({\n inheritAttrs: false,\n});\n\nexport interface TDropdownItem {\n label: string;\n value: string;\n disabled?: boolean;\n}\n\nexport type TDropdownAlign = 'start' | 'end';\n\nconst props = withDefaults(\n defineProps<{\n items?: TDropdownItem[];\n open?: boolean;\n defaultOpen?: boolean;\n disabled?: boolean;\n size?: TSize;\n label?: string;\n align?: TDropdownAlign;\n id?: string;\n }>(),\n {\n items: () => [],\n open: undefined,\n defaultOpen: false,\n disabled: false,\n size: 'md',\n label: '',\n align: 'start',\n id: undefined,\n },\n);\n\nconst emit = defineEmits<{\n select: [value: string];\n 'update:open': [value: boolean];\n 'open-change': [value: boolean];\n}>();\n\ndefineSlots<{\n trigger(props: { isOpen: boolean; menuId: string }): unknown;\n item(props: { item: TDropdownItem; index: number }): unknown;\n}>();\n\nconst attrs = useAttrs();\nconst menuId = props.id ?? createId('t-dropdown');\nconst rootRef = ref<HTMLElement | null>(null);\nconst triggerRef = ref<HTMLElement | null>(null);\nconst itemRefs = ref<Map<string, HTMLElement>>(new Map());\nconst focusedIndex = ref(-1);\n\nconst triggerAttrs = computed(() => {\n const { class: _class, style: _style, ...rest } = attrs;\n return rest;\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-dropdown',\n `t-dropdown--${props.size}`,\n {\n 't-dropdown--align-end': props.align === 'end',\n 'is-disabled': props.disabled,\n 'is-open': isOpen.value,\n },\n attrs.class,\n]);\n\nconst rootStyle = computed(() => attrs.style);\n\nconst enabledItems = computed(() => props.items.filter((i) => !i.disabled));\n\nconst openMenu = () => {\n if (props.disabled) return;\n focusedIndex.value = enabledItems.value.length > 0\n ? props.items.indexOf(enabledItems.value[0])\n : 0;\n setValue(true);\n nextTick(() => focusItem(focusedIndex.value));\n};\n\nconst closeMenu = (restoreFocus = false) => {\n setValue(false);\n focusedIndex.value = -1;\n if (restoreFocus) {\n nextTick(() => {\n if (triggerRef.value) focusFirst(triggerRef.value);\n });\n }\n};\n\nconst toggleMenu = () => {\n if (props.disabled) return;\n if (isOpen.value) {\n closeMenu();\n } else {\n openMenu();\n }\n};\n\nconst selectItem = (item: TDropdownItem) => {\n if (item.disabled) return;\n emit('select', item.value);\n closeMenu(true);\n};\n\nconst focusItem = (index: number) => {\n const item = props.items[index];\n if (!item) return;\n const el = itemRefs.value.get(item.value);\n el?.focus();\n};\n\nconst setItemRef = (el: Element | null, value: string) => {\n if (el instanceof HTMLElement) {\n itemRefs.value.set(value, el);\n } else {\n itemRefs.value.delete(value);\n }\n};\n\nconst moveFocus = (direction: 1 | -1) => {\n let nextIndex = focusedIndex.value + direction;\n while (nextIndex >= 0 && nextIndex < props.items.length) {\n if (!props.items[nextIndex].disabled) {\n focusedIndex.value = nextIndex;\n focusItem(nextIndex);\n return;\n }\n nextIndex += direction;\n }\n};\n\nconst onTriggerKeydown = (event: KeyboardEvent) => {\n if (props.disabled) return;\n\n if (isEscapeKey(event) && isOpen.value) {\n event.preventDefault();\n closeMenu();\n return;\n }\n\n if (isActivationKey(event) || event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault();\n openMenu();\n }\n};\n\nconst onItemKeydown = (event: KeyboardEvent, item: TDropdownItem) => {\n if (isEscapeKey(event)) {\n event.preventDefault();\n closeMenu(true);\n return;\n }\n\n if (isActivationKey(event)) {\n event.preventDefault();\n selectItem(item);\n return;\n }\n\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n moveFocus(1);\n break;\n case 'ArrowUp':\n event.preventDefault();\n moveFocus(-1);\n break;\n case 'Home':\n event.preventDefault();\n focusedIndex.value = enabledItems.value.length > 0\n ? props.items.indexOf(enabledItems.value[0])\n : 0;\n focusItem(focusedIndex.value);\n break;\n case 'End':\n event.preventDefault();\n focusedIndex.value = enabledItems.value.length > 0\n ? props.items.indexOf(enabledItems.value[enabledItems.value.length - 1])\n : props.items.length - 1;\n focusItem(focusedIndex.value);\n break;\n default:\n break;\n }\n};\n\nconst onDocumentPointerDown = (event: PointerEvent) => {\n if (!isOpen.value) return;\n const target = event.target;\n if (!(target instanceof Node)) return;\n if (rootRef.value?.contains(target)) return;\n closeMenu();\n};\n\nwatch(isOpen, (value) => {\n if (value) {\n document.addEventListener('pointerdown', onDocumentPointerDown);\n } else {\n document.removeEventListener('pointerdown', onDocumentPointerDown);\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 :data-state=\"isOpen ? 'open' : 'closed'\"\n >\n <span\n ref=\"triggerRef\"\n class=\"t-dropdown__trigger-wrapper\"\n @click=\"toggleMenu\"\n @keydown=\"onTriggerKeydown\"\n >\n <slot\n name=\"trigger\"\n :is-open=\"isOpen\"\n :menu-id=\"menuId\"\n >\n <button\n v-bind=\"triggerAttrs\"\n type=\"button\"\n class=\"t-dropdown__trigger\"\n :disabled=\"disabled\"\n :aria-controls=\"isOpen ? menuId : undefined\"\n aria-haspopup=\"menu\"\n :aria-expanded=\"isOpen\"\n >\n <span class=\"t-dropdown__label\">{{ label }}</span>\n <svg\n class=\"t-dropdown__chevron\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <polyline points=\"4 6 8 10 12 6\" />\n </svg>\n </button>\n </slot>\n </span>\n <transition name=\"t-fade\">\n <ul\n v-if=\"isOpen && !disabled\"\n :id=\"menuId\"\n role=\"menu\"\n class=\"t-dropdown__menu\"\n :aria-label=\"label || undefined\"\n >\n <li\n v-for=\"(item, index) in items\"\n :key=\"item.value\"\n :ref=\"(el) => setItemRef(el as Element | null, item.value)\"\n role=\"menuitem\"\n class=\"t-dropdown__item\"\n :class=\"{\n 'is-disabled': item.disabled,\n 'is-focused': index === focusedIndex,\n }\"\n :aria-disabled=\"item.disabled || undefined\"\n :tabindex=\"item.disabled ? -1 : 0\"\n @click=\"selectItem(item)\"\n @keydown=\"onItemKeydown($event, item)\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n :index=\"index\"\n >\n {{ item.label }}\n </slot>\n </li>\n </ul>\n </transition>\n </div>\n</template>\n"],"mappings":"gnBAkBA,IAAM,EAAQ,EAuBR,EAAO,EAWP,GAAA,EAAA,EAAA,SAAA,CAAiB,EACjB,EAAS,EAAM,KAAA,EAAA,EAAA,SAAA,CAAe,YAAY,EAC1C,GAAA,EAAA,EAAA,IAAA,CAAkC,IAAI,EACtC,GAAA,EAAA,EAAA,IAAA,CAAqC,IAAI,EACzC,GAAA,EAAA,EAAA,IAAA,CAAyC,IAAI,GAAK,EAClD,GAAA,EAAA,EAAA,IAAA,CAAmB,EAAE,EAErB,GAAA,EAAA,EAAA,SAAA,KAA8B,CAClC,GAAM,CAAE,MAAO,EAAQ,MAAO,EAAQ,GAAG,GAAS,EAClD,OAAO,CACT,CAAC,EAEK,CAAE,MAAO,EAAQ,YAAa,EAAA,qBAAA,EAAA,EAAA,MAAA,CAC5B,EAAO,MAAM,EACnB,EAAM,YACL,GAAU,CACT,EAAK,cAAe,CAAK,EACzB,EAAK,cAAe,CAAK,CAC3B,CACF,EAEM,GAAA,EAAA,EAAA,SAAA,KAA6B,CACjC,aACA,eAAe,EAAM,OACrB,CACE,wBAAyB,EAAM,QAAU,MACzC,cAAe,EAAM,SACrB,UAAW,EAAO,KACpB,EACA,EAAM,KACR,CAAC,EAEK,GAAA,EAAA,EAAA,SAAA,KAA2B,EAAM,KAAK,EAEtC,GAAA,EAAA,EAAA,SAAA,KAA8B,EAAM,MAAM,OAAQ,GAAM,CAAC,EAAE,QAAQ,CAAC,EAEpE,MAAiB,CACjB,EAAM,WACV,EAAa,MAAQ,EAAa,MAAM,OAAS,EAC7C,EAAM,MAAM,QAAQ,EAAa,MAAM,EAAE,EACzC,EACJ,EAAS,EAAI,GACb,EAAA,EAAA,SAAA,KAAe,EAAU,EAAa,KAAK,CAAC,EAC9C,EAEM,GAAa,EAAe,KAAU,CAC1C,EAAS,EAAK,EACd,EAAa,MAAQ,GACjB,IACF,EAAA,EAAA,SAAA,KAAe,CACT,EAAW,QAAO,EAAA,EAAA,WAAA,CAAW,EAAW,KAAK,CACnD,CAAC,CAEL,EAEM,MAAmB,CACnB,EAAM,WACN,EAAO,MACT,EAAU,EAEV,EAAS,EAEb,EAEM,EAAc,GAAwB,CACtC,EAAK,WACT,EAAK,SAAU,EAAK,KAAK,EACzB,EAAU,EAAI,EAChB,EAEM,EAAa,GAAkB,CACnC,IAAM,EAAO,EAAM,MAAM,GACpB,GAEL,EADoB,MAAM,IAAI,EAAK,KACnC,CAAA,EAAI,MAAM,CACZ,EAEM,GAAc,EAAoB,IAAkB,CACpD,aAAc,YAChB,EAAS,MAAM,IAAI,EAAO,CAAE,EAE5B,EAAS,MAAM,OAAO,CAAK,CAE/B,EAEM,EAAa,GAAsB,CACvC,IAAI,EAAY,EAAa,MAAQ,EACrC,KAAO,GAAa,GAAK,EAAY,EAAM,MAAM,QAAQ,CACvD,GAAI,CAAC,EAAM,MAAM,EAAU,CAAC,SAAU,CACpC,EAAa,MAAQ,EACrB,EAAU,CAAS,EACnB,MACF,CACA,GAAa,CACf,CACF,EAEM,EAAoB,GAAyB,CAC7C,MAAM,SAEV,KAAA,EAAA,EAAA,YAAA,CAAgB,CAAK,GAAK,EAAO,MAAO,CACtC,EAAM,eAAe,EACrB,EAAU,EACV,MACF,GAEA,EAAA,EAAA,gBAAA,CAAoB,CAAK,GAAK,EAAM,MAAQ,aAAe,EAAM,MAAQ,aACvE,EAAM,eAAe,EACrB,EAAS,EAJX,CAMF,EAEM,GAAiB,EAAsB,IAAwB,CACnE,IAAA,EAAA,EAAA,YAAA,CAAgB,CAAK,EAAG,CACtB,EAAM,eAAe,EACrB,EAAU,EAAI,EACd,MACF,CAEA,IAAA,EAAA,EAAA,gBAAA,CAAoB,CAAK,EAAG,CAC1B,EAAM,eAAe,EACrB,EAAW,CAAI,EACf,MACF,CAEA,OAAQ,EAAM,IAAd,CACE,IAAK,YACH,EAAM,eAAe,EACrB,EAAU,CAAC,EACX,MACF,IAAK,UACH,EAAM,eAAe,EACrB,EAAU,EAAE,EACZ,MACF,IAAK,OACH,EAAM,eAAe,EACrB,EAAa,MAAQ,EAAa,MAAM,OAAS,EAC7C,EAAM,MAAM,QAAQ,EAAa,MAAM,EAAE,EACzC,EACJ,EAAU,EAAa,KAAK,EAC5B,MACF,IAAK,MACH,EAAM,eAAe,EACrB,EAAa,MAAQ,EAAa,MAAM,OAAS,EAC7C,EAAM,MAAM,QAAQ,EAAa,MAAM,EAAa,MAAM,OAAS,EAAE,EACrE,EAAM,MAAM,OAAS,EACzB,EAAU,EAAa,KAAK,EAC5B,MACF,QACE,KACJ,CACF,EAEM,EAAyB,GAAwB,CACrD,GAAI,CAAC,EAAO,MAAO,OACnB,IAAM,EAAS,EAAM,OACf,aAAkB,OACpB,EAAQ,OAAO,SAAS,CAAM,GAClC,EAAU,EACZ,SAEA,EAAA,EAAA,MAAA,CAAM,EAAS,GAAU,CACnB,EACF,SAAS,iBAAiB,cAAe,CAAqB,EAE9D,SAAS,oBAAoB,cAAe,CAAqB,CAErE,CAAC,GAED,EAAA,EAAA,gBAAA,KAAsB,CACpB,SAAS,oBAAoB,cAAe,CAAqB,CACnE,CAAC,qDA+EO,MAAA,SA1EA,UAAJ,IAAI,EACH,OAAA,EAAA,EAAA,eAAA,CAAO,EAAA,KAAW,EAClB,OAAA,EAAA,EAAA,eAAA,CAAO,EAAA,KAAS,EAChB,cAAA,EAAA,EAAA,MAAA,CAAY,CAAA,EAAM,OAAA,oCAqCZ,OAAA,SAlCD,aAAJ,IAAI,EACJ,MAAM,8BACL,QAAO,EACP,UAAS,qBA8BH,EAAA,OAAA,UAAA,CA1BJ,QAAA,EAAA,EAAA,MAAA,CAAS,CAAA,EACT,QAAA,EAAA,EAAA,MAAA,CAAS,CAAA,OAyBL,EAAA,EAAA,EAAA,mBAAA,CADI,UAAA,EAAA,EAAA,WAAA,CArBC,EAqBD,MArBa,CACpB,KAAK,SACL,MAAM,sBACL,SAAU,EAAA,SACV,iBAAA,EAAA,EAAA,MAAA,CAAe,CAAA,GAAA,EAAA,EAAA,MAAA,CAAS,CAAA,EAAS,IAAA,GAClC,gBAAc,OACb,iBAAA,EAAA,EAAA,MAAA,CAAe,CAAA,8BAEkC,OAAlD,GAAA,EAAA,EAAA,gBAAA,CAAmC,EAAA,KAAK,EAAA,CAAA,EAAA,AAAA,EAAA,MAAA,EAAA,EAAA,mBAAA,CAYlC,MAAA,CAVJ,MAAM,sBACN,QAAQ,YACR,KAAK,OACL,OAAO,eACP,eAAa,IACb,iBAAe,QACf,kBAAgB,QAChB,cAAY,kCAEuB,WAAA,CAAzB,OAAO,eAAe,CAAA,CAAA,EAAA,EAAA,CAAA,EAAA,GAAA,CAAA,CAAA,CAAA,CAAA,EAAA,GAAA,GAAA,EAAA,EAAA,YAAA,CAqC3B,EAAA,WAAA,CAhCD,KAAK,QAAQ,EAAA,2BA+BlB,EAAA,EAAA,EAAA,MAAA,CA7BG,CAAA,GAAM,CAAK,EAAA,WAAA,EAAA,EAAA,UAAA,CAAA,GAAA,EAAA,EAAA,mBAAA,CA6Bd,KAAA,OA5BF,IAAA,EAAA,EAAA,MAAA,CAAI,CAAA,EACL,KAAK,OACL,MAAM,mBACL,aAAY,EAAA,OAAS,IAAA,mDAwBjB,EAAA,SAAA,MAAA,EAAA,EAAA,WAAA,CArBqB,EAAA,OAAhB,EAAM,gDAqBX,KAAA,CApBF,IAAK,EAAK,iBACV,IAAM,GAAO,EAAW,EAAsB,EAAK,KAAK,EACzD,KAAK,WACL,OAAA,EAAA,EAAA,eAAA,CAAK,CAAC,mBAAkB,eACa,EAAK,sBAAoC,IAAU,EAAA,SAIvF,gBAAe,EAAK,UAAY,IAAA,GAChC,SAAU,EAAK,SAAQ,GAAA,EACvB,QAAK,GAAE,EAAW,CAAI,EACtB,UAAO,GAAE,EAAc,EAAQ,CAAI,qBAQ7B,EAAA,OAAA,OAAA,CAJE,OACC,aAGH,EAAA,EAAA,EAAA,gBAAA,EAAA,EAAA,EAAA,gBAAA,CADF,EAAK,KAAK,EAAA,CAAA,CAAA,CAAA,CAAA,EAAA,GAAA,CAAA"}
@@ -1,8 +1,8 @@
1
1
  import { useControllableOpen as e } from "../composables/useControllableOpen.js";
2
- import { Fragment as t, Transition as n, computed as r, createCommentVNode as i, createElementBlock as a, createElementVNode as o, createTextVNode as s, createVNode as c, defineComponent as l, mergeProps as ee, nextTick as u, normalizeClass as d, normalizeStyle as f, onBeforeUnmount as p, openBlock as m, ref as h, renderList as g, renderSlot as _, toDisplayString as v, toRef as te, unref as y, useAttrs as b, watch as x, withCtx as S } from "vue";
3
- import { createId as C, isActivationKey as w, isEscapeKey as T } from "@treeui/utils";
2
+ import { Fragment as t, Transition as n, computed as r, createCommentVNode as i, createElementBlock as a, createElementVNode as o, createTextVNode as s, createVNode as c, defineComponent as l, mergeProps as ee, nextTick as u, normalizeClass as d, normalizeStyle as f, onBeforeUnmount as p, openBlock as m, ref as h, renderList as g, renderSlot as _, toDisplayString as v, toRef as y, unref as b, useAttrs as te, watch as x, withCtx as S } from "vue";
3
+ import { createId as C, focusFirst as w, isActivationKey as T, isEscapeKey as E } from "@treeui/utils";
4
4
  //#region src/components/TDropdown.vue?vue&type=script&setup=true&lang.ts
5
- var E = ["data-state"], D = { class: "t-dropdown__trigger-wrapper" }, O = [
5
+ var D = ["data-state"], O = [
6
6
  "disabled",
7
7
  "aria-controls",
8
8
  "aria-expanded"
@@ -30,7 +30,8 @@ var E = ["data-state"], D = { class: "t-dropdown__trigger-wrapper" }, O = [
30
30
  },
31
31
  size: { default: "md" },
32
32
  label: { default: "" },
33
- align: { default: "start" }
33
+ align: { default: "start" },
34
+ id: { default: void 0 }
34
35
  },
35
36
  emits: [
36
37
  "select",
@@ -38,10 +39,10 @@ var E = ["data-state"], D = { class: "t-dropdown__trigger-wrapper" }, O = [
38
39
  "open-change"
39
40
  ],
40
41
  setup(l, { emit: M }) {
41
- let N = l, P = M, F = b(), I = C("t-dropdown"), L = h(null), R = h(null), z = h(/* @__PURE__ */ new Map()), B = h(-1), V = r(() => {
42
+ let N = l, P = M, F = te(), I = N.id ?? C("t-dropdown"), L = h(null), R = h(null), z = h(/* @__PURE__ */ new Map()), B = h(-1), V = r(() => {
42
43
  let { class: e, style: t, ...n } = F;
43
44
  return n;
44
- }), { value: H, setValue: U } = e(te(N, "open"), N.defaultOpen, (e) => {
45
+ }), { value: H, setValue: U } = e(y(N, "open"), N.defaultOpen, (e) => {
45
46
  P("update:open", e), P("open-change", e);
46
47
  }), W = r(() => [
47
48
  "t-dropdown",
@@ -55,9 +56,11 @@ var E = ["data-state"], D = { class: "t-dropdown__trigger-wrapper" }, O = [
55
56
  ]), G = r(() => F.style), K = r(() => N.items.filter((e) => !e.disabled)), q = () => {
56
57
  N.disabled || (B.value = K.value.length > 0 ? N.items.indexOf(K.value[0]) : 0, U(!0), u(() => Z(B.value)));
57
58
  }, J = (e = !1) => {
58
- U(!1), B.value = -1, e && u(() => R.value?.focus());
59
+ U(!1), B.value = -1, e && u(() => {
60
+ R.value && w(R.value);
61
+ });
59
62
  }, Y = () => {
60
- H.value ? J() : q();
63
+ N.disabled || (H.value ? J() : q());
61
64
  }, X = (e) => {
62
65
  e.disabled || (P("select", e.value), J(!0));
63
66
  }, Z = (e) => {
@@ -76,18 +79,18 @@ var E = ["data-state"], D = { class: "t-dropdown__trigger-wrapper" }, O = [
76
79
  }
77
80
  }, re = (e) => {
78
81
  if (!N.disabled) {
79
- if (T(e) && H.value) {
82
+ if (E(e) && H.value) {
80
83
  e.preventDefault(), J();
81
84
  return;
82
85
  }
83
- (w(e) || e.key === "ArrowDown" || e.key === "ArrowUp") && (e.preventDefault(), q());
86
+ (T(e) || e.key === "ArrowDown" || e.key === "ArrowUp") && (e.preventDefault(), q());
84
87
  }
85
88
  }, ie = (e, t) => {
86
- if (T(e)) {
89
+ if (E(e)) {
87
90
  e.preventDefault(), J(!0);
88
91
  return;
89
92
  }
90
- if (w(e)) {
93
+ if (T(e)) {
91
94
  e.preventDefault(), X(t);
92
95
  return;
93
96
  }
@@ -120,19 +123,23 @@ var E = ["data-state"], D = { class: "t-dropdown__trigger-wrapper" }, O = [
120
123
  ref: L,
121
124
  class: d(W.value),
122
125
  style: f(G.value),
123
- "data-state": y(H) ? "open" : "closed"
124
- }, [o("span", D, [_(e.$slots, "trigger", { isOpen: y(H) }, () => [o("button", ee({
126
+ "data-state": b(H) ? "open" : "closed"
127
+ }, [o("span", {
125
128
  ref_key: "triggerRef",
126
- ref: R
127
- }, V.value, {
129
+ ref: R,
130
+ class: "t-dropdown__trigger-wrapper",
131
+ onClick: Y,
132
+ onKeydown: re
133
+ }, [_(e.$slots, "trigger", {
134
+ isOpen: b(H),
135
+ menuId: b(I)
136
+ }, () => [o("button", ee(V.value, {
128
137
  type: "button",
129
138
  class: "t-dropdown__trigger",
130
139
  disabled: l.disabled,
131
- "aria-controls": y(H) ? y(I) : void 0,
140
+ "aria-controls": b(H) ? b(I) : void 0,
132
141
  "aria-haspopup": "menu",
133
- "aria-expanded": y(H),
134
- onClick: Y,
135
- onKeydown: re
142
+ "aria-expanded": b(H)
136
143
  }), [o("span", k, v(l.label), 1), r[0] ||= o("svg", {
137
144
  class: "t-dropdown__chevron",
138
145
  viewBox: "0 0 16 16",
@@ -142,10 +149,10 @@ var E = ["data-state"], D = { class: "t-dropdown__trigger-wrapper" }, O = [
142
149
  "stroke-linecap": "round",
143
150
  "stroke-linejoin": "round",
144
151
  "aria-hidden": "true"
145
- }, [o("polyline", { points: "4 6 8 10 12 6" })], -1)], 16, O)])]), c(n, { name: "t-fade" }, {
146
- default: S(() => [y(H) && !l.disabled ? (m(), a("ul", {
152
+ }, [o("polyline", { points: "4 6 8 10 12 6" })], -1)], 16, O)])], 544), c(n, { name: "t-fade" }, {
153
+ default: S(() => [b(H) && !l.disabled ? (m(), a("ul", {
147
154
  key: 0,
148
- id: y(I),
155
+ id: b(I),
149
156
  role: "menu",
150
157
  class: "t-dropdown__menu",
151
158
  "aria-label": l.label || void 0
@@ -167,7 +174,7 @@ var E = ["data-state"], D = { class: "t-dropdown__trigger-wrapper" }, O = [
167
174
  index: n
168
175
  }, () => [s(v(t.label), 1)])], 42, j))), 128))], 8, A)) : i("", !0)]),
169
176
  _: 3
170
- })], 14, E));
177
+ })], 14, D));
171
178
  }
172
179
  });
173
180
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"TDropdown.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/TDropdown.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { createId, isActivationKey, 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';\n\ndefineOptions({\n inheritAttrs: false,\n});\n\nexport interface TDropdownItem {\n label: string;\n value: string;\n disabled?: boolean;\n}\n\nexport type TDropdownAlign = 'start' | 'end';\n\nconst props = withDefaults(\n defineProps<{\n items?: TDropdownItem[];\n open?: boolean;\n defaultOpen?: boolean;\n disabled?: boolean;\n size?: TSize;\n label?: string;\n align?: TDropdownAlign;\n }>(),\n {\n items: () => [],\n open: undefined,\n defaultOpen: false,\n disabled: false,\n size: 'md',\n label: '',\n align: 'start',\n },\n);\n\nconst emit = defineEmits<{\n select: [value: string];\n 'update:open': [value: boolean];\n 'open-change': [value: boolean];\n}>();\n\ndefineSlots<{\n trigger(props: { isOpen: boolean }): unknown;\n item(props: { item: TDropdownItem; index: number }): unknown;\n}>();\n\nconst attrs = useAttrs();\nconst menuId = createId('t-dropdown');\nconst rootRef = ref<HTMLElement | null>(null);\nconst triggerRef = ref<HTMLButtonElement | null>(null);\nconst itemRefs = ref<Map<string, HTMLElement>>(new Map());\nconst focusedIndex = ref(-1);\n\nconst triggerAttrs = computed(() => {\n const { class: _class, style: _style, ...rest } = attrs;\n return rest;\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-dropdown',\n `t-dropdown--${props.size}`,\n {\n 't-dropdown--align-end': props.align === 'end',\n 'is-disabled': props.disabled,\n 'is-open': isOpen.value,\n },\n attrs.class,\n]);\n\nconst rootStyle = computed(() => attrs.style);\n\nconst enabledItems = computed(() => props.items.filter((i) => !i.disabled));\n\nconst openMenu = () => {\n if (props.disabled) return;\n focusedIndex.value = enabledItems.value.length > 0\n ? props.items.indexOf(enabledItems.value[0])\n : 0;\n setValue(true);\n nextTick(() => focusItem(focusedIndex.value));\n};\n\nconst closeMenu = (restoreFocus = false) => {\n setValue(false);\n focusedIndex.value = -1;\n if (restoreFocus) {\n nextTick(() => triggerRef.value?.focus());\n }\n};\n\nconst toggleMenu = () => {\n if (isOpen.value) {\n closeMenu();\n } else {\n openMenu();\n }\n};\n\nconst selectItem = (item: TDropdownItem) => {\n if (item.disabled) return;\n emit('select', item.value);\n closeMenu(true);\n};\n\nconst focusItem = (index: number) => {\n const item = props.items[index];\n if (!item) return;\n const el = itemRefs.value.get(item.value);\n el?.focus();\n};\n\nconst setItemRef = (el: Element | null, value: string) => {\n if (el instanceof HTMLElement) {\n itemRefs.value.set(value, el);\n } else {\n itemRefs.value.delete(value);\n }\n};\n\nconst moveFocus = (direction: 1 | -1) => {\n let nextIndex = focusedIndex.value + direction;\n while (nextIndex >= 0 && nextIndex < props.items.length) {\n if (!props.items[nextIndex].disabled) {\n focusedIndex.value = nextIndex;\n focusItem(nextIndex);\n return;\n }\n nextIndex += direction;\n }\n};\n\nconst onTriggerKeydown = (event: KeyboardEvent) => {\n if (props.disabled) return;\n\n if (isEscapeKey(event) && isOpen.value) {\n event.preventDefault();\n closeMenu();\n return;\n }\n\n if (isActivationKey(event) || event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault();\n openMenu();\n }\n};\n\nconst onItemKeydown = (event: KeyboardEvent, item: TDropdownItem) => {\n if (isEscapeKey(event)) {\n event.preventDefault();\n closeMenu(true);\n return;\n }\n\n if (isActivationKey(event)) {\n event.preventDefault();\n selectItem(item);\n return;\n }\n\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n moveFocus(1);\n break;\n case 'ArrowUp':\n event.preventDefault();\n moveFocus(-1);\n break;\n case 'Home':\n event.preventDefault();\n focusedIndex.value = enabledItems.value.length > 0\n ? props.items.indexOf(enabledItems.value[0])\n : 0;\n focusItem(focusedIndex.value);\n break;\n case 'End':\n event.preventDefault();\n focusedIndex.value = enabledItems.value.length > 0\n ? props.items.indexOf(enabledItems.value[enabledItems.value.length - 1])\n : props.items.length - 1;\n focusItem(focusedIndex.value);\n break;\n default:\n break;\n }\n};\n\nconst onDocumentPointerDown = (event: PointerEvent) => {\n if (!isOpen.value) return;\n const target = event.target;\n if (!(target instanceof Node)) return;\n if (rootRef.value?.contains(target)) return;\n closeMenu();\n};\n\nwatch(isOpen, (value) => {\n if (value) {\n document.addEventListener('pointerdown', onDocumentPointerDown);\n } else {\n document.removeEventListener('pointerdown', onDocumentPointerDown);\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 :data-state=\"isOpen ? 'open' : 'closed'\"\n >\n <span class=\"t-dropdown__trigger-wrapper\">\n <slot\n name=\"trigger\"\n :is-open=\"isOpen\"\n >\n <button\n ref=\"triggerRef\"\n v-bind=\"triggerAttrs\"\n type=\"button\"\n class=\"t-dropdown__trigger\"\n :disabled=\"disabled\"\n :aria-controls=\"isOpen ? menuId : undefined\"\n aria-haspopup=\"menu\"\n :aria-expanded=\"isOpen\"\n @click=\"toggleMenu\"\n @keydown=\"onTriggerKeydown\"\n >\n <span class=\"t-dropdown__label\">{{ label }}</span>\n <svg\n class=\"t-dropdown__chevron\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <polyline points=\"4 6 8 10 12 6\" />\n </svg>\n </button>\n </slot>\n </span>\n <transition name=\"t-fade\">\n <ul\n v-if=\"isOpen && !disabled\"\n :id=\"menuId\"\n role=\"menu\"\n class=\"t-dropdown__menu\"\n :aria-label=\"label || undefined\"\n >\n <li\n v-for=\"(item, index) in items\"\n :key=\"item.value\"\n :ref=\"(el) => setItemRef(el as Element | null, item.value)\"\n role=\"menuitem\"\n class=\"t-dropdown__item\"\n :class=\"{\n 'is-disabled': item.disabled,\n 'is-focused': index === focusedIndex,\n }\"\n :aria-disabled=\"item.disabled || undefined\"\n :tabindex=\"item.disabled ? -1 : 0\"\n @click=\"selectItem(item)\"\n @keydown=\"onItemKeydown($event, item)\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n :index=\"index\"\n >\n {{ item.label }}\n </slot>\n </li>\n </ul>\n </transition>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"TDropdown.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/TDropdown.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { createId, focusFirst, isActivationKey, 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';\n\ndefineOptions({\n inheritAttrs: false,\n});\n\nexport interface TDropdownItem {\n label: string;\n value: string;\n disabled?: boolean;\n}\n\nexport type TDropdownAlign = 'start' | 'end';\n\nconst props = withDefaults(\n defineProps<{\n items?: TDropdownItem[];\n open?: boolean;\n defaultOpen?: boolean;\n disabled?: boolean;\n size?: TSize;\n label?: string;\n align?: TDropdownAlign;\n id?: string;\n }>(),\n {\n items: () => [],\n open: undefined,\n defaultOpen: false,\n disabled: false,\n size: 'md',\n label: '',\n align: 'start',\n id: undefined,\n },\n);\n\nconst emit = defineEmits<{\n select: [value: string];\n 'update:open': [value: boolean];\n 'open-change': [value: boolean];\n}>();\n\ndefineSlots<{\n trigger(props: { isOpen: boolean; menuId: string }): unknown;\n item(props: { item: TDropdownItem; index: number }): unknown;\n}>();\n\nconst attrs = useAttrs();\nconst menuId = props.id ?? createId('t-dropdown');\nconst rootRef = ref<HTMLElement | null>(null);\nconst triggerRef = ref<HTMLElement | null>(null);\nconst itemRefs = ref<Map<string, HTMLElement>>(new Map());\nconst focusedIndex = ref(-1);\n\nconst triggerAttrs = computed(() => {\n const { class: _class, style: _style, ...rest } = attrs;\n return rest;\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-dropdown',\n `t-dropdown--${props.size}`,\n {\n 't-dropdown--align-end': props.align === 'end',\n 'is-disabled': props.disabled,\n 'is-open': isOpen.value,\n },\n attrs.class,\n]);\n\nconst rootStyle = computed(() => attrs.style);\n\nconst enabledItems = computed(() => props.items.filter((i) => !i.disabled));\n\nconst openMenu = () => {\n if (props.disabled) return;\n focusedIndex.value = enabledItems.value.length > 0\n ? props.items.indexOf(enabledItems.value[0])\n : 0;\n setValue(true);\n nextTick(() => focusItem(focusedIndex.value));\n};\n\nconst closeMenu = (restoreFocus = false) => {\n setValue(false);\n focusedIndex.value = -1;\n if (restoreFocus) {\n nextTick(() => {\n if (triggerRef.value) focusFirst(triggerRef.value);\n });\n }\n};\n\nconst toggleMenu = () => {\n if (props.disabled) return;\n if (isOpen.value) {\n closeMenu();\n } else {\n openMenu();\n }\n};\n\nconst selectItem = (item: TDropdownItem) => {\n if (item.disabled) return;\n emit('select', item.value);\n closeMenu(true);\n};\n\nconst focusItem = (index: number) => {\n const item = props.items[index];\n if (!item) return;\n const el = itemRefs.value.get(item.value);\n el?.focus();\n};\n\nconst setItemRef = (el: Element | null, value: string) => {\n if (el instanceof HTMLElement) {\n itemRefs.value.set(value, el);\n } else {\n itemRefs.value.delete(value);\n }\n};\n\nconst moveFocus = (direction: 1 | -1) => {\n let nextIndex = focusedIndex.value + direction;\n while (nextIndex >= 0 && nextIndex < props.items.length) {\n if (!props.items[nextIndex].disabled) {\n focusedIndex.value = nextIndex;\n focusItem(nextIndex);\n return;\n }\n nextIndex += direction;\n }\n};\n\nconst onTriggerKeydown = (event: KeyboardEvent) => {\n if (props.disabled) return;\n\n if (isEscapeKey(event) && isOpen.value) {\n event.preventDefault();\n closeMenu();\n return;\n }\n\n if (isActivationKey(event) || event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault();\n openMenu();\n }\n};\n\nconst onItemKeydown = (event: KeyboardEvent, item: TDropdownItem) => {\n if (isEscapeKey(event)) {\n event.preventDefault();\n closeMenu(true);\n return;\n }\n\n if (isActivationKey(event)) {\n event.preventDefault();\n selectItem(item);\n return;\n }\n\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n moveFocus(1);\n break;\n case 'ArrowUp':\n event.preventDefault();\n moveFocus(-1);\n break;\n case 'Home':\n event.preventDefault();\n focusedIndex.value = enabledItems.value.length > 0\n ? props.items.indexOf(enabledItems.value[0])\n : 0;\n focusItem(focusedIndex.value);\n break;\n case 'End':\n event.preventDefault();\n focusedIndex.value = enabledItems.value.length > 0\n ? props.items.indexOf(enabledItems.value[enabledItems.value.length - 1])\n : props.items.length - 1;\n focusItem(focusedIndex.value);\n break;\n default:\n break;\n }\n};\n\nconst onDocumentPointerDown = (event: PointerEvent) => {\n if (!isOpen.value) return;\n const target = event.target;\n if (!(target instanceof Node)) return;\n if (rootRef.value?.contains(target)) return;\n closeMenu();\n};\n\nwatch(isOpen, (value) => {\n if (value) {\n document.addEventListener('pointerdown', onDocumentPointerDown);\n } else {\n document.removeEventListener('pointerdown', onDocumentPointerDown);\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 :data-state=\"isOpen ? 'open' : 'closed'\"\n >\n <span\n ref=\"triggerRef\"\n class=\"t-dropdown__trigger-wrapper\"\n @click=\"toggleMenu\"\n @keydown=\"onTriggerKeydown\"\n >\n <slot\n name=\"trigger\"\n :is-open=\"isOpen\"\n :menu-id=\"menuId\"\n >\n <button\n v-bind=\"triggerAttrs\"\n type=\"button\"\n class=\"t-dropdown__trigger\"\n :disabled=\"disabled\"\n :aria-controls=\"isOpen ? menuId : undefined\"\n aria-haspopup=\"menu\"\n :aria-expanded=\"isOpen\"\n >\n <span class=\"t-dropdown__label\">{{ label }}</span>\n <svg\n class=\"t-dropdown__chevron\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <polyline points=\"4 6 8 10 12 6\" />\n </svg>\n </button>\n </slot>\n </span>\n <transition name=\"t-fade\">\n <ul\n v-if=\"isOpen && !disabled\"\n :id=\"menuId\"\n role=\"menu\"\n class=\"t-dropdown__menu\"\n :aria-label=\"label || undefined\"\n >\n <li\n v-for=\"(item, index) in items\"\n :key=\"item.value\"\n :ref=\"(el) => setItemRef(el as Element | null, item.value)\"\n role=\"menuitem\"\n class=\"t-dropdown__item\"\n :class=\"{\n 'is-disabled': item.disabled,\n 'is-focused': index === focusedIndex,\n }\"\n :aria-disabled=\"item.disabled || undefined\"\n :tabindex=\"item.disabled ? -1 : 0\"\n @click=\"selectItem(item)\"\n @keydown=\"onItemKeydown($event, item)\"\n >\n <slot\n name=\"item\"\n :item=\"item\"\n :index=\"index\"\n >\n {{ item.label }}\n </slot>\n </li>\n </ul>\n </transition>\n </div>\n</template>\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ var e=require("./TFlag.vue_vue_type_script_setup_true_lang.cjs").default;exports.default=e;
2
+ //# sourceMappingURL=TFlag.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TFlag.cjs","names":[],"sources":["../../src/components/TFlag.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue';\nimport type { TSize } from '../types/contracts';\n\n/**\n * Flags are requested from the CDN's fixed-height endpoints, which serve the\n * flat artwork at each flag's true proportions — Nepal stays a pennant and\n * Switzerland stays square. The CDN's 4:3 endpoints would normalise every ratio,\n * but only by switching to the waving artwork, which reads as decoration next to\n * flat UI icons. A common height is what actually aligns a column of flags; the\n * varying widths are absorbed by the fixed box in CSS.\n *\n * Each step's retina asset is at least twice its box height so 2x screens never\n * upscale.\n */\nconst FLAG_ASSETS: Record<TSize, { base: string; retina: string }> = {\n sm: { base: 'h20', retina: 'h40' },\n md: { base: 'h20', retina: 'h40' },\n lg: { base: 'h24', retina: 'h60' },\n};\n\nconst props = withDefaults(\n defineProps<{\n /** ISO 3166-1 alpha-2 country code. Case-insensitive. */\n code: string;\n /** Origin serving the flag assets. Point at a mirror to self-host. */\n baseUrl?: string;\n size?: TSize;\n /** Set to expose the flag to assistive tech; omit to keep it decorative. */\n label?: string;\n /** Text shown when the image cannot load. Defaults to the uppercased code. */\n fallbackText?: string;\n }>(),\n {\n baseUrl: 'https://flagcdn.com',\n size: 'md',\n label: undefined,\n fallbackText: undefined,\n },\n);\n\nconst classes = computed(() => [\n 't-flag',\n `t-flag--${props.size}`,\n]);\n\nconst normalizedCode = computed(() => props.code.trim().toLowerCase());\n\n// A JS consumer can hand us a size outside the union; fall back rather than\n// throwing on an undefined lookup.\nconst assets = computed(() => FLAG_ASSETS[props.size] ?? FLAG_ASSETS.md);\n\n// Tolerate a baseUrl written with a trailing slash so a self-hosted mirror does\n// not silently produce a double-slashed path.\nconst origin = computed(() => props.baseUrl.replace(/\\/+$/, ''));\n\nconst src = computed(\n () => `${origin.value}/${assets.value.base}/${normalizedCode.value}.png`,\n);\n\nconst srcset = computed(\n () => `${origin.value}/${assets.value.retina}/${normalizedCode.value}.png 2x`,\n);\n\nconst resolvedFallback = computed(\n () => props.fallbackText ?? normalizedCode.value.toUpperCase(),\n);\n\nconst failed = ref(false);\n\n// A new source deserves a fresh attempt — otherwise one bad code would leave the\n// component stuck on the fallback for every later flag.\nwatch(src, () => {\n failed.value = false;\n});\n\nconst onError = () => {\n failed.value = true;\n};\n</script>\n\n<template>\n <span\n :class=\"classes\"\n :role=\"label ? 'img' : undefined\"\n :aria-label=\"label || undefined\"\n :aria-hidden=\"label ? undefined : 'true'\"\n >\n <img\n v-if=\"!failed\"\n :src=\"src\"\n :srcset=\"srcset\"\n alt=\"\"\n class=\"t-flag__image\"\n @error=\"onError\"\n >\n <!--\n Flags come from a third-party origin, so the request can fail offline or\n behind a strict img-src policy. Showing the country code keeps the control\n legible instead of collapsing it to an empty box.\n -->\n <span\n v-else\n class=\"t-flag__fallback\"\n >\n {{ resolvedFallback }}\n </span>\n </span>\n</template>\n"],"mappings":""}
@@ -0,0 +1,7 @@
1
+ import e from "./TFlag.vue_vue_type_script_setup_true_lang.js";
2
+ //#region src/components/TFlag.vue
3
+ var t = e;
4
+ //#endregion
5
+ export { t as default };
6
+
7
+ //# sourceMappingURL=TFlag.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TFlag.js","names":[],"sources":["../../src/components/TFlag.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue';\nimport type { TSize } from '../types/contracts';\n\n/**\n * Flags are requested from the CDN's fixed-height endpoints, which serve the\n * flat artwork at each flag's true proportions — Nepal stays a pennant and\n * Switzerland stays square. The CDN's 4:3 endpoints would normalise every ratio,\n * but only by switching to the waving artwork, which reads as decoration next to\n * flat UI icons. A common height is what actually aligns a column of flags; the\n * varying widths are absorbed by the fixed box in CSS.\n *\n * Each step's retina asset is at least twice its box height so 2x screens never\n * upscale.\n */\nconst FLAG_ASSETS: Record<TSize, { base: string; retina: string }> = {\n sm: { base: 'h20', retina: 'h40' },\n md: { base: 'h20', retina: 'h40' },\n lg: { base: 'h24', retina: 'h60' },\n};\n\nconst props = withDefaults(\n defineProps<{\n /** ISO 3166-1 alpha-2 country code. Case-insensitive. */\n code: string;\n /** Origin serving the flag assets. Point at a mirror to self-host. */\n baseUrl?: string;\n size?: TSize;\n /** Set to expose the flag to assistive tech; omit to keep it decorative. */\n label?: string;\n /** Text shown when the image cannot load. Defaults to the uppercased code. */\n fallbackText?: string;\n }>(),\n {\n baseUrl: 'https://flagcdn.com',\n size: 'md',\n label: undefined,\n fallbackText: undefined,\n },\n);\n\nconst classes = computed(() => [\n 't-flag',\n `t-flag--${props.size}`,\n]);\n\nconst normalizedCode = computed(() => props.code.trim().toLowerCase());\n\n// A JS consumer can hand us a size outside the union; fall back rather than\n// throwing on an undefined lookup.\nconst assets = computed(() => FLAG_ASSETS[props.size] ?? FLAG_ASSETS.md);\n\n// Tolerate a baseUrl written with a trailing slash so a self-hosted mirror does\n// not silently produce a double-slashed path.\nconst origin = computed(() => props.baseUrl.replace(/\\/+$/, ''));\n\nconst src = computed(\n () => `${origin.value}/${assets.value.base}/${normalizedCode.value}.png`,\n);\n\nconst srcset = computed(\n () => `${origin.value}/${assets.value.retina}/${normalizedCode.value}.png 2x`,\n);\n\nconst resolvedFallback = computed(\n () => props.fallbackText ?? normalizedCode.value.toUpperCase(),\n);\n\nconst failed = ref(false);\n\n// A new source deserves a fresh attempt — otherwise one bad code would leave the\n// component stuck on the fallback for every later flag.\nwatch(src, () => {\n failed.value = false;\n});\n\nconst onError = () => {\n failed.value = true;\n};\n</script>\n\n<template>\n <span\n :class=\"classes\"\n :role=\"label ? 'img' : undefined\"\n :aria-label=\"label || undefined\"\n :aria-hidden=\"label ? undefined : 'true'\"\n >\n <img\n v-if=\"!failed\"\n :src=\"src\"\n :srcset=\"srcset\"\n alt=\"\"\n class=\"t-flag__image\"\n @error=\"onError\"\n >\n <!--\n Flags come from a third-party origin, so the request can fail offline or\n behind a strict img-src policy. Showing the country code keeps the control\n legible instead of collapsing it to an empty box.\n -->\n <span\n v-else\n class=\"t-flag__fallback\"\n >\n {{ resolvedFallback }}\n </span>\n </span>\n</template>\n"],"mappings":""}
@@ -0,0 +1,20 @@
1
+ import { TSize } from '../types/contracts';
2
+ type __VLS_Props = {
3
+ /** ISO 3166-1 alpha-2 country code. Case-insensitive. */
4
+ code: string;
5
+ /** Origin serving the flag assets. Point at a mirror to self-host. */
6
+ baseUrl?: string;
7
+ size?: TSize;
8
+ /** Set to expose the flag to assistive tech; omit to keep it decorative. */
9
+ label?: string;
10
+ /** Text shown when the image cannot load. Defaults to the uppercased code. */
11
+ fallbackText?: string;
12
+ };
13
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
14
+ size: TSize;
15
+ label: string;
16
+ baseUrl: string;
17
+ fallbackText: string;
18
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLSpanElement>;
19
+ export default _default;
20
+ //# sourceMappingURL=TFlag.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TFlag.vue.d.ts","sourceRoot":"","sources":["../../src/components/TFlag.vue"],"names":[],"mappings":"AAgHA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAmBhD,KAAK,WAAW,GAAG;IACf,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,KAAK,CAAC;IACb,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;;UALO,KAAK;WAEJ,MAAM;aAHJ,MAAM;kBAKD,MAAM;;AAuHzB,wBAQG"}
@@ -0,0 +1,2 @@
1
+ let e=require("vue");var t=[`role`,`aria-label`,`aria-hidden`],n=[`src`,`srcset`],r={key:1,class:`t-flag__fallback`},i=(0,e.defineComponent)({__name:`TFlag`,props:{code:{},baseUrl:{default:`https://flagcdn.com`},size:{default:`md`},label:{default:void 0},fallbackText:{default:void 0}},setup(i){let a={sm:{base:`h20`,retina:`h40`},md:{base:`h20`,retina:`h40`},lg:{base:`h24`,retina:`h60`}},o=i,s=(0,e.computed)(()=>[`t-flag`,`t-flag--${o.size}`]),c=(0,e.computed)(()=>o.code.trim().toLowerCase()),l=(0,e.computed)(()=>a[o.size]??a.md),u=(0,e.computed)(()=>o.baseUrl.replace(/\/+$/,``)),d=(0,e.computed)(()=>`${u.value}/${l.value.base}/${c.value}.png`),f=(0,e.computed)(()=>`${u.value}/${l.value.retina}/${c.value}.png 2x`),p=(0,e.computed)(()=>o.fallbackText??c.value.toUpperCase()),m=(0,e.ref)(!1);(0,e.watch)(d,()=>{m.value=!1});let h=()=>{m.value=!0};return(a,o)=>((0,e.openBlock)(),(0,e.createElementBlock)(`span`,{class:(0,e.normalizeClass)(s.value),role:i.label?`img`:void 0,"aria-label":i.label||void 0,"aria-hidden":i.label?void 0:`true`},[m.value?((0,e.openBlock)(),(0,e.createElementBlock)(`span`,r,(0,e.toDisplayString)(p.value),1)):((0,e.openBlock)(),(0,e.createElementBlock)(`img`,{key:0,src:d.value,srcset:f.value,alt:``,class:`t-flag__image`,onError:h},null,40,n))],10,t))}});exports.default=i;
2
+ //# sourceMappingURL=TFlag.vue_vue_type_script_setup_true_lang.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TFlag.vue_vue_type_script_setup_true_lang.cjs","names":[],"sources":["../../src/components/TFlag.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue';\nimport type { TSize } from '../types/contracts';\n\n/**\n * Flags are requested from the CDN's fixed-height endpoints, which serve the\n * flat artwork at each flag's true proportions — Nepal stays a pennant and\n * Switzerland stays square. The CDN's 4:3 endpoints would normalise every ratio,\n * but only by switching to the waving artwork, which reads as decoration next to\n * flat UI icons. A common height is what actually aligns a column of flags; the\n * varying widths are absorbed by the fixed box in CSS.\n *\n * Each step's retina asset is at least twice its box height so 2x screens never\n * upscale.\n */\nconst FLAG_ASSETS: Record<TSize, { base: string; retina: string }> = {\n sm: { base: 'h20', retina: 'h40' },\n md: { base: 'h20', retina: 'h40' },\n lg: { base: 'h24', retina: 'h60' },\n};\n\nconst props = withDefaults(\n defineProps<{\n /** ISO 3166-1 alpha-2 country code. Case-insensitive. */\n code: string;\n /** Origin serving the flag assets. Point at a mirror to self-host. */\n baseUrl?: string;\n size?: TSize;\n /** Set to expose the flag to assistive tech; omit to keep it decorative. */\n label?: string;\n /** Text shown when the image cannot load. Defaults to the uppercased code. */\n fallbackText?: string;\n }>(),\n {\n baseUrl: 'https://flagcdn.com',\n size: 'md',\n label: undefined,\n fallbackText: undefined,\n },\n);\n\nconst classes = computed(() => [\n 't-flag',\n `t-flag--${props.size}`,\n]);\n\nconst normalizedCode = computed(() => props.code.trim().toLowerCase());\n\n// A JS consumer can hand us a size outside the union; fall back rather than\n// throwing on an undefined lookup.\nconst assets = computed(() => FLAG_ASSETS[props.size] ?? FLAG_ASSETS.md);\n\n// Tolerate a baseUrl written with a trailing slash so a self-hosted mirror does\n// not silently produce a double-slashed path.\nconst origin = computed(() => props.baseUrl.replace(/\\/+$/, ''));\n\nconst src = computed(\n () => `${origin.value}/${assets.value.base}/${normalizedCode.value}.png`,\n);\n\nconst srcset = computed(\n () => `${origin.value}/${assets.value.retina}/${normalizedCode.value}.png 2x`,\n);\n\nconst resolvedFallback = computed(\n () => props.fallbackText ?? normalizedCode.value.toUpperCase(),\n);\n\nconst failed = ref(false);\n\n// A new source deserves a fresh attempt — otherwise one bad code would leave the\n// component stuck on the fallback for every later flag.\nwatch(src, () => {\n failed.value = false;\n});\n\nconst onError = () => {\n failed.value = true;\n};\n</script>\n\n<template>\n <span\n :class=\"classes\"\n :role=\"label ? 'img' : undefined\"\n :aria-label=\"label || undefined\"\n :aria-hidden=\"label ? undefined : 'true'\"\n >\n <img\n v-if=\"!failed\"\n :src=\"src\"\n :srcset=\"srcset\"\n alt=\"\"\n class=\"t-flag__image\"\n @error=\"onError\"\n >\n <!--\n Flags come from a third-party origin, so the request can fail offline or\n behind a strict img-src policy. Showing the country code keeps the control\n legible instead of collapsing it to an empty box.\n -->\n <span\n v-else\n class=\"t-flag__fallback\"\n >\n {{ resolvedFallback }}\n </span>\n </span>\n</template>\n"],"mappings":"uSAeA,IAAM,EAA+D,CACnE,GAAI,CAAE,KAAM,MAAO,OAAQ,KAAM,EACjC,GAAI,CAAE,KAAM,MAAO,OAAQ,KAAM,EACjC,GAAI,CAAE,KAAM,MAAO,OAAQ,KAAM,CACnC,EAEM,EAAQ,EAoBR,GAAA,EAAA,EAAA,SAAA,KAAyB,CAC7B,SACA,WAAW,EAAM,MACnB,CAAC,EAEK,GAAA,EAAA,EAAA,SAAA,KAAgC,EAAM,KAAK,KAAK,CAAC,CAAC,YAAY,CAAC,EAI/D,GAAA,EAAA,EAAA,SAAA,KAAwB,EAAY,EAAM,OAAS,EAAY,EAAE,EAIjE,GAAA,EAAA,EAAA,SAAA,KAAwB,EAAM,QAAQ,QAAQ,OAAQ,EAAE,CAAC,EAEzD,GAAA,EAAA,EAAA,SAAA,KACE,GAAG,EAAO,MAAM,GAAG,EAAO,MAAM,KAAK,GAAG,EAAe,MAAM,KACrE,EAEM,GAAA,EAAA,EAAA,SAAA,KACE,GAAG,EAAO,MAAM,GAAG,EAAO,MAAM,OAAO,GAAG,EAAe,MAAM,QACvE,EAEM,GAAA,EAAA,EAAA,SAAA,KACE,EAAM,cAAgB,EAAe,MAAM,YAAY,CAC/D,EAEM,GAAA,EAAA,EAAA,IAAA,CAAa,EAAK,GAIxB,EAAA,EAAA,MAAA,CAAM,MAAW,CACf,EAAO,MAAQ,EACjB,CAAC,EAED,IAAM,MAAgB,CACpB,EAAO,MAAQ,EACjB,2DA6BS,OAAA,CAxBJ,OAAA,EAAA,EAAA,eAAA,CAAO,EAAA,KAAO,EACd,KAAM,EAAA,MAAK,MAAW,IAAA,GACtB,aAAY,EAAA,OAAS,IAAA,GACrB,cAAa,EAAA,MAAQ,IAAA,GAAS,SAGtB,EAAA,kDAiBF,OALP,GAAA,EAAA,EAAA,gBAAA,CAIK,EAAA,KAAgB,EAAA,CAAA,KAhBZ,EAAA,EAAA,UAAA,CAAA,GAAA,EAAA,EAAA,mBAAA,CAMR,MAAA,OALE,IAAK,EAAA,MACL,OAAQ,EAAA,MACT,IAAI,GACJ,MAAM,gBACE,sBAWW,EAAA,GAAA,CAAA"}
@@ -0,0 +1,58 @@
1
+ import { computed as e, createElementBlock as t, defineComponent as n, normalizeClass as r, openBlock as i, ref as a, toDisplayString as o, watch as s } from "vue";
2
+ //#region src/components/TFlag.vue?vue&type=script&setup=true&lang.ts
3
+ var c = [
4
+ "role",
5
+ "aria-label",
6
+ "aria-hidden"
7
+ ], l = ["src", "srcset"], u = {
8
+ key: 1,
9
+ class: "t-flag__fallback"
10
+ }, d = /*@__PURE__*/ n({
11
+ __name: "TFlag",
12
+ props: {
13
+ code: {},
14
+ baseUrl: { default: "https://flagcdn.com" },
15
+ size: { default: "md" },
16
+ label: { default: void 0 },
17
+ fallbackText: { default: void 0 }
18
+ },
19
+ setup(n) {
20
+ let d = {
21
+ sm: {
22
+ base: "h20",
23
+ retina: "h40"
24
+ },
25
+ md: {
26
+ base: "h20",
27
+ retina: "h40"
28
+ },
29
+ lg: {
30
+ base: "h24",
31
+ retina: "h60"
32
+ }
33
+ }, f = n, p = e(() => ["t-flag", `t-flag--${f.size}`]), m = e(() => f.code.trim().toLowerCase()), h = e(() => d[f.size] ?? d.md), g = e(() => f.baseUrl.replace(/\/+$/, "")), _ = e(() => `${g.value}/${h.value.base}/${m.value}.png`), v = e(() => `${g.value}/${h.value.retina}/${m.value}.png 2x`), y = e(() => f.fallbackText ?? m.value.toUpperCase()), b = a(!1);
34
+ s(_, () => {
35
+ b.value = !1;
36
+ });
37
+ let x = () => {
38
+ b.value = !0;
39
+ };
40
+ return (e, a) => (i(), t("span", {
41
+ class: r(p.value),
42
+ role: n.label ? "img" : void 0,
43
+ "aria-label": n.label || void 0,
44
+ "aria-hidden": n.label ? void 0 : "true"
45
+ }, [b.value ? (i(), t("span", u, o(y.value), 1)) : (i(), t("img", {
46
+ key: 0,
47
+ src: _.value,
48
+ srcset: v.value,
49
+ alt: "",
50
+ class: "t-flag__image",
51
+ onError: x
52
+ }, null, 40, l))], 10, c));
53
+ }
54
+ });
55
+ //#endregion
56
+ export { d as default };
57
+
58
+ //# sourceMappingURL=TFlag.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TFlag.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/TFlag.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue';\nimport type { TSize } from '../types/contracts';\n\n/**\n * Flags are requested from the CDN's fixed-height endpoints, which serve the\n * flat artwork at each flag's true proportions — Nepal stays a pennant and\n * Switzerland stays square. The CDN's 4:3 endpoints would normalise every ratio,\n * but only by switching to the waving artwork, which reads as decoration next to\n * flat UI icons. A common height is what actually aligns a column of flags; the\n * varying widths are absorbed by the fixed box in CSS.\n *\n * Each step's retina asset is at least twice its box height so 2x screens never\n * upscale.\n */\nconst FLAG_ASSETS: Record<TSize, { base: string; retina: string }> = {\n sm: { base: 'h20', retina: 'h40' },\n md: { base: 'h20', retina: 'h40' },\n lg: { base: 'h24', retina: 'h60' },\n};\n\nconst props = withDefaults(\n defineProps<{\n /** ISO 3166-1 alpha-2 country code. Case-insensitive. */\n code: string;\n /** Origin serving the flag assets. Point at a mirror to self-host. */\n baseUrl?: string;\n size?: TSize;\n /** Set to expose the flag to assistive tech; omit to keep it decorative. */\n label?: string;\n /** Text shown when the image cannot load. Defaults to the uppercased code. */\n fallbackText?: string;\n }>(),\n {\n baseUrl: 'https://flagcdn.com',\n size: 'md',\n label: undefined,\n fallbackText: undefined,\n },\n);\n\nconst classes = computed(() => [\n 't-flag',\n `t-flag--${props.size}`,\n]);\n\nconst normalizedCode = computed(() => props.code.trim().toLowerCase());\n\n// A JS consumer can hand us a size outside the union; fall back rather than\n// throwing on an undefined lookup.\nconst assets = computed(() => FLAG_ASSETS[props.size] ?? FLAG_ASSETS.md);\n\n// Tolerate a baseUrl written with a trailing slash so a self-hosted mirror does\n// not silently produce a double-slashed path.\nconst origin = computed(() => props.baseUrl.replace(/\\/+$/, ''));\n\nconst src = computed(\n () => `${origin.value}/${assets.value.base}/${normalizedCode.value}.png`,\n);\n\nconst srcset = computed(\n () => `${origin.value}/${assets.value.retina}/${normalizedCode.value}.png 2x`,\n);\n\nconst resolvedFallback = computed(\n () => props.fallbackText ?? normalizedCode.value.toUpperCase(),\n);\n\nconst failed = ref(false);\n\n// A new source deserves a fresh attempt — otherwise one bad code would leave the\n// component stuck on the fallback for every later flag.\nwatch(src, () => {\n failed.value = false;\n});\n\nconst onError = () => {\n failed.value = true;\n};\n</script>\n\n<template>\n <span\n :class=\"classes\"\n :role=\"label ? 'img' : undefined\"\n :aria-label=\"label || undefined\"\n :aria-hidden=\"label ? undefined : 'true'\"\n >\n <img\n v-if=\"!failed\"\n :src=\"src\"\n :srcset=\"srcset\"\n alt=\"\"\n class=\"t-flag__image\"\n @error=\"onError\"\n >\n <!--\n Flags come from a third-party origin, so the request can fail offline or\n behind a strict img-src policy. Showing the country code keeps the control\n legible instead of collapsing it to an empty box.\n -->\n <span\n v-else\n class=\"t-flag__fallback\"\n >\n {{ resolvedFallback }}\n </span>\n </span>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;EAeA,IAAM,IAA+D;GACnE,IAAI;IAAE,MAAM;IAAO,QAAQ;GAAM;GACjC,IAAI;IAAE,MAAM;IAAO,QAAQ;GAAM;GACjC,IAAI;IAAE,MAAM;IAAO,QAAQ;GAAM;EACnC,GAEM,IAAQ,GAoBR,IAAU,QAAe,CAC7B,UACA,WAAW,EAAM,MACnB,CAAC,GAEK,IAAiB,QAAe,EAAM,KAAK,KAAK,CAAC,CAAC,YAAY,CAAC,GAI/D,IAAS,QAAe,EAAY,EAAM,SAAS,EAAY,EAAE,GAIjE,IAAS,QAAe,EAAM,QAAQ,QAAQ,QAAQ,EAAE,CAAC,GAEzD,IAAM,QACJ,GAAG,EAAO,MAAM,GAAG,EAAO,MAAM,KAAK,GAAG,EAAe,MAAM,KACrE,GAEM,IAAS,QACP,GAAG,EAAO,MAAM,GAAG,EAAO,MAAM,OAAO,GAAG,EAAe,MAAM,QACvE,GAEM,IAAmB,QACjB,EAAM,gBAAgB,EAAe,MAAM,YAAY,CAC/D,GAEM,IAAS,EAAI,EAAK;EAIxB,EAAM,SAAW;GACf,EAAO,QAAQ;EACjB,CAAC;EAED,IAAM,UAAgB;GACpB,EAAO,QAAQ;EACjB;yBAIE,EAyBO,QAAA;GAxBJ,OAAK,EAAE,EAAA,KAAO;GACd,MAAM,EAAA,QAAK,QAAW,KAAA;GACtB,cAAY,EAAA,SAAS,KAAA;GACrB,eAAa,EAAA,QAAQ,KAAA,IAAS;MAGtB,EAAA,cAYT,EAKO,QALP,GAKO,EADF,EAAA,KAAgB,GAAA,CAAA,MAhBZ,EAAA,GADT,EAOC,OAAA;;GALE,KAAK,EAAA;GACL,QAAQ,EAAA;GACT,KAAI;GACJ,OAAM;GACE;kBAWW,GAAA,IAAA,CAAA"}
@@ -1,4 +1,4 @@
1
- import { TIconName } from '../../../icons/src/index.ts';
1
+ import { TIconName } from '@treeui/icons';
2
2
  type __VLS_Props = {
3
3
  name: TIconName;
4
4
  size?: number | string;
@@ -0,0 +1,2 @@
1
+ var e=require("./TLanguageSelect.vue_vue_type_script_setup_true_lang.cjs").default;exports.default=e;
2
+ //# sourceMappingURL=TLanguageSelect.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TLanguageSelect.cjs","names":[],"sources":["../../src/components/TLanguageSelect.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { createId, isActivationKey, isEscapeKey } from '@treeui/utils';\nimport { computed, nextTick, onBeforeUnmount, ref, toRef, useAttrs, watch } from 'vue';\nimport { useControllableOpen } from '../composables/useControllableOpen';\nimport type { TFieldWidth, TSize } from '../types/contracts';\nimport TFlag from './TFlag.vue';\nimport TIcon from './TIcon.vue';\nimport TSpinner from './TSpinner.vue';\n\ndefineOptions({\n inheritAttrs: false,\n});\n\n/**\n * `field` is a form control: it reads as the value of a labelled question, so the\n * flag leads and the trigger fills its container.\n *\n * `switcher` is a page-level control, typically in a navbar, where nothing nearby\n * says what it changes. The translate icon states the purpose up front and the\n * current flag closes the row, so the whole thing reads as \"page language, now\n * set to this\" without an external label.\n */\nexport type TLanguageSelectVariant = 'field' | 'switcher';\n\nexport interface TLanguageOption {\n label: string;\n /** Opaque application key — typically a BCP-47 tag. Never parsed or validated. */\n value: string;\n /** ISO 3166-1 alpha-2 code for the flag. Omit to render the option without one. */\n code?: string;\n /** Secondary line, e.g. the language's endonym. */\n description?: string;\n disabled?: boolean;\n}\n\nconst props = withDefaults(\n defineProps<{\n modelValue?: string;\n options?: TLanguageOption[];\n open?: boolean;\n defaultOpen?: boolean;\n size?: TSize;\n variant?: TLanguageSelectVariant;\n /** Hide the language name, leaving icon + flag. `switcher` only. */\n iconOnly?: boolean;\n /** Inline-size cap. Fluid (`full`) by default. */\n width?: TFieldWidth;\n disabled?: boolean;\n loading?: boolean;\n invalid?: boolean;\n placeholder?: string;\n /** Forwarded to every nested TFlag. Set once here to self-host flag assets. */\n flagBaseUrl?: string;\n emptyText?: string;\n }>(),\n {\n modelValue: '',\n options: () => [],\n open: undefined,\n defaultOpen: false,\n size: 'md',\n variant: 'field',\n iconOnly: false,\n width: 'full',\n disabled: false,\n loading: false,\n invalid: false,\n placeholder: '',\n flagBaseUrl: undefined,\n emptyText: 'No languages available.',\n },\n);\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string];\n 'update:open': [value: boolean];\n 'open-change': [value: boolean];\n}>();\n\ndefineSlots<{\n prefix?: () => unknown;\n option?: (props: { option: TLanguageOption; selected: boolean; focused: boolean }) => unknown;\n empty?: () => unknown;\n}>();\n\nconst attrs = useAttrs();\nconst listboxId = createId('t-language-select');\nconst rootRef = ref<HTMLElement | null>(null);\nconst triggerRef = ref<HTMLButtonElement | null>(null);\nconst listboxRef = ref<HTMLElement | null>(null);\nconst optionRefs = ref<Map<string, HTMLElement>>(new Map());\nconst focusedIndex = ref(-1);\nconst dropUp = ref(false);\n\nconst triggerAttrs = computed(() => {\n const { class: _class, style: _style, ...rest } = attrs;\n return rest;\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 isSwitcher = computed(() => props.variant === 'switcher');\n\nconst rootClasses = computed(() => [\n 't-language-select',\n `t-language-select--${props.size}`,\n `t-language-select--${props.variant}`,\n // A switcher sizes to its content, so the field width scale does not apply.\n !isSwitcher.value && props.width !== 'full' ? `t-field-width--${props.width}` : null,\n {\n 't-language-select--icon-only': isSwitcher.value && props.iconOnly,\n 't-language-select--drop-up': dropUp.value,\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\n// Nearest ancestor that can clip the listbox (scroll container or hidden overflow).\nconst findClippingAncestor = (element: HTMLElement): HTMLElement | null => {\n let parent = element.parentElement;\n while (parent && parent !== document.body) {\n const { overflowY } = window.getComputedStyle(parent);\n if (overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'hidden') {\n return parent;\n }\n parent = parent.parentElement;\n }\n return null;\n};\n\n// Flip the listbox above the trigger when its rendered position overflows the\n// nearest clipping ancestor (e.g. a modal body) or the viewport, and there is\n// more room above the trigger than below it. Measuring the rendered rect keeps\n// the decision in sync with the CSS offset (--tree-space-2) automatically.\nconst updateDropDirection = () => {\n const trigger = triggerRef.value;\n const listbox = listboxRef.value;\n if (!trigger || !listbox) {\n dropUp.value = false;\n return;\n }\n const triggerRect = trigger.getBoundingClientRect();\n const listboxRect = listbox.getBoundingClientRect();\n const boundary = findClippingAncestor(trigger)?.getBoundingClientRect();\n const bottomLimit = Math.min(boundary?.bottom ?? Infinity, window.innerHeight);\n const topLimit = Math.max(boundary?.top ?? 0, 0);\n const spaceBelow = bottomLimit - triggerRect.bottom;\n const spaceAbove = triggerRect.top - topLimit;\n dropUp.value = listboxRect.bottom > bottomLimit && spaceAbove > spaceBelow;\n};\n\nconst rootStyle = computed(() => attrs.style);\n\nconst selectedOption = computed(() =>\n props.options.find((o) => o.value === props.modelValue),\n);\n\nconst selectedLabel = computed(() => selectedOption.value?.label ?? '');\n\n// iconOnly leans on the flag to carry the current value. With no flag to show it\n// would leave the trigger stating nothing at all, so the name comes back.\nconst showValue = computed(\n () => !(isSwitcher.value && props.iconOnly) || !selectedOption.value?.code,\n);\n\nconst enabledOptions = computed(() =>\n props.options.filter((o) => !o.disabled),\n);\n\n// Runs for every open, however it was triggered. The listbox can also be opened\n// through `open` / `defaultOpen` without going near openDropdown(), so this lives\n// on the isOpen watcher rather than inside the click handler.\nconst onOpened = () => {\n const selectedIdx = props.options.findIndex((o) => o.value === props.modelValue);\n focusedIndex.value = selectedIdx >= 0 ? selectedIdx : 0;\n // Render downward first so the measurement always starts from the default position.\n dropUp.value = false;\n nextTick(() => {\n updateDropDirection();\n focusOption(focusedIndex.value);\n });\n};\n\nconst openDropdown = () => {\n if (props.disabled) return;\n setValue(true);\n};\n\nconst closeDropdown = (restoreFocus = false) => {\n setValue(false);\n focusedIndex.value = -1;\n if (restoreFocus) {\n nextTick(() => triggerRef.value?.focus());\n }\n};\n\nconst toggleDropdown = () => {\n if (isOpen.value) {\n closeDropdown();\n } else {\n openDropdown();\n }\n};\n\nconst selectOption = (opt: TLanguageOption) => {\n if (opt.disabled) return;\n emit('update:modelValue', opt.value);\n closeDropdown(true);\n};\n\nconst focusOption = (index: number) => {\n const opt = props.options[index];\n if (!opt) return;\n const el = optionRefs.value.get(opt.value);\n el?.focus();\n};\n\nconst setOptionRef = (el: Element | null, value: string) => {\n if (el instanceof HTMLElement) {\n optionRefs.value.set(value, el);\n } else {\n optionRefs.value.delete(value);\n }\n};\n\nconst moveFocus = (direction: 1 | -1) => {\n let nextIndex = focusedIndex.value + direction;\n while (nextIndex >= 0 && nextIndex < props.options.length) {\n if (!props.options[nextIndex].disabled) {\n focusedIndex.value = nextIndex;\n focusOption(nextIndex);\n return;\n }\n nextIndex += direction;\n }\n};\n\nconst onTriggerKeydown = (event: KeyboardEvent) => {\n if (props.disabled) return;\n\n if (isEscapeKey(event) && isOpen.value) {\n event.preventDefault();\n closeDropdown();\n return;\n }\n\n if (isActivationKey(event) || event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault();\n openDropdown();\n }\n};\n\nconst onOptionKeydown = (event: KeyboardEvent, opt: TLanguageOption, _index: number) => {\n if (isEscapeKey(event)) {\n event.preventDefault();\n closeDropdown(true);\n return;\n }\n\n if (isActivationKey(event)) {\n event.preventDefault();\n selectOption(opt);\n return;\n }\n\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n moveFocus(1);\n break;\n case 'ArrowUp':\n event.preventDefault();\n moveFocus(-1);\n break;\n case 'Home':\n event.preventDefault();\n focusedIndex.value = enabledOptions.value.length > 0\n ? props.options.indexOf(enabledOptions.value[0])\n : 0;\n focusOption(focusedIndex.value);\n break;\n case 'End':\n event.preventDefault();\n focusedIndex.value = enabledOptions.value.length > 0\n ? props.options.indexOf(enabledOptions.value[enabledOptions.value.length - 1])\n : props.options.length - 1;\n focusOption(focusedIndex.value);\n break;\n default:\n break;\n }\n};\n\nconst onDocumentPointerDown = (event: PointerEvent) => {\n if (!isOpen.value) return;\n const target = event.target;\n if (!(target instanceof Node)) return;\n if (rootRef.value?.contains(target)) return;\n closeDropdown();\n};\n\nconst hasSelection = computed(() => props.modelValue !== '');\n\n// immediate, so a listbox that is already open on mount (`defaultOpen`, or a\n// controlled `open` that starts true) still dismisses on outside click and still\n// measures its drop direction.\nwatch(isOpen, (value) => {\n if (value) {\n document.addEventListener('pointerdown', onDocumentPointerDown);\n onOpened();\n } else {\n document.removeEventListener('pointerdown', onDocumentPointerDown);\n }\n}, { immediate: true });\n\n// Options can shrink underneath an open listbox. Leaving focusedIndex past the\n// end would strand moveFocus(), which only ever walks outward from it.\nwatch(\n () => props.options.length,\n (length) => {\n if (isOpen.value && focusedIndex.value >= length) {\n focusedIndex.value = length - 1;\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 :data-state=\"isOpen ? 'open' : 'closed'\"\n >\n <button\n ref=\"triggerRef\"\n v-bind=\"triggerAttrs\"\n type=\"button\"\n class=\"t-language-select__trigger\"\n :disabled=\"disabled\"\n :aria-controls=\"isOpen ? listboxId : undefined\"\n aria-haspopup=\"listbox\"\n :aria-expanded=\"isOpen\"\n :aria-invalid=\"invalid || undefined\"\n :aria-busy=\"loading || undefined\"\n @click=\"toggleDropdown\"\n @keydown=\"onTriggerKeydown\"\n >\n <span\n v-if=\"$slots.prefix\"\n class=\"t-language-select__slot t-language-select__slot--prefix\"\n >\n <slot name=\"prefix\" />\n </span>\n <!--\n The switcher leads with the translate icon so the control announces what\n it changes; the field leads with the flag, since a form label already\n does that job.\n -->\n <!-- Sized in CSS from the icon token scale, not TIcon's pixel `size` prop. -->\n <TIcon\n v-if=\"isSwitcher\"\n name=\"languages\"\n class=\"t-language-select__translate\"\n />\n <TFlag\n v-else-if=\"selectedOption?.code\"\n :code=\"selectedOption.code\"\n :base-url=\"flagBaseUrl\"\n :size=\"size\"\n />\n <span\n v-if=\"showValue\"\n class=\"t-language-select__value\"\n :data-placeholder=\"!hasSelection ? true : undefined\"\n >\n {{ selectedLabel || placeholder }}\n </span>\n <TFlag\n v-if=\"isSwitcher && selectedOption?.code\"\n :code=\"selectedOption.code\"\n :base-url=\"flagBaseUrl\"\n :size=\"size\"\n />\n <TSpinner\n v-if=\"loading\"\n size=\"sm\"\n label=\"Loading\"\n />\n <svg\n v-else\n class=\"t-language-select__chevron\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <polyline points=\"4 6 8 10 12 6\" />\n </svg>\n </button>\n <transition name=\"t-fade\">\n <ul\n v-if=\"isOpen && !disabled\"\n :id=\"listboxId\"\n ref=\"listboxRef\"\n role=\"listbox\"\n class=\"t-language-select__listbox\"\n :aria-label=\"typeof triggerAttrs['aria-label'] === 'string' ? triggerAttrs['aria-label'] : undefined\"\n >\n <li\n v-if=\"options.length === 0\"\n class=\"t-language-select__empty\"\n role=\"presentation\"\n >\n <slot name=\"empty\">\n {{ emptyText }}\n </slot>\n </li>\n <li\n v-for=\"(opt, index) in options\"\n :key=\"opt.value\"\n :ref=\"(el) => setOptionRef(el as Element | null, opt.value)\"\n role=\"option\"\n class=\"t-language-select__option\"\n :class=\"{\n 'is-selected': opt.value === modelValue,\n 'is-disabled': opt.disabled,\n 'is-focused': index === focusedIndex,\n }\"\n :aria-selected=\"opt.value === modelValue\"\n :aria-disabled=\"opt.disabled || undefined\"\n :tabindex=\"opt.disabled ? -1 : 0\"\n @click=\"selectOption(opt)\"\n @keydown=\"onOptionKeydown($event, opt, index)\"\n >\n <slot\n name=\"option\"\n :option=\"opt\"\n :selected=\"opt.value === modelValue\"\n :focused=\"index === focusedIndex\"\n >\n <TFlag\n v-if=\"opt.code\"\n :code=\"opt.code\"\n :base-url=\"flagBaseUrl\"\n :size=\"size\"\n />\n <span class=\"t-language-select__option-text\">\n <span class=\"t-language-select__option-label\">{{ opt.label }}</span>\n <span\n v-if=\"opt.description\"\n class=\"t-language-select__description\"\n >\n {{ opt.description }}\n </span>\n </span>\n </slot>\n <svg\n v-if=\"opt.value === modelValue\"\n class=\"t-language-select__check\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <polyline points=\"3.5 8.5 6.5 11.5 12.5 4.5\" />\n </svg>\n </li>\n </ul>\n </transition>\n </div>\n</template>\n"],"mappings":""}
@@ -0,0 +1,7 @@
1
+ import e from "./TLanguageSelect.vue_vue_type_script_setup_true_lang.js";
2
+ //#region src/components/TLanguageSelect.vue
3
+ var t = e;
4
+ //#endregion
5
+ export { t as default };
6
+
7
+ //# sourceMappingURL=TLanguageSelect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TLanguageSelect.js","names":[],"sources":["../../src/components/TLanguageSelect.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { createId, isActivationKey, isEscapeKey } from '@treeui/utils';\nimport { computed, nextTick, onBeforeUnmount, ref, toRef, useAttrs, watch } from 'vue';\nimport { useControllableOpen } from '../composables/useControllableOpen';\nimport type { TFieldWidth, TSize } from '../types/contracts';\nimport TFlag from './TFlag.vue';\nimport TIcon from './TIcon.vue';\nimport TSpinner from './TSpinner.vue';\n\ndefineOptions({\n inheritAttrs: false,\n});\n\n/**\n * `field` is a form control: it reads as the value of a labelled question, so the\n * flag leads and the trigger fills its container.\n *\n * `switcher` is a page-level control, typically in a navbar, where nothing nearby\n * says what it changes. The translate icon states the purpose up front and the\n * current flag closes the row, so the whole thing reads as \"page language, now\n * set to this\" without an external label.\n */\nexport type TLanguageSelectVariant = 'field' | 'switcher';\n\nexport interface TLanguageOption {\n label: string;\n /** Opaque application key — typically a BCP-47 tag. Never parsed or validated. */\n value: string;\n /** ISO 3166-1 alpha-2 code for the flag. Omit to render the option without one. */\n code?: string;\n /** Secondary line, e.g. the language's endonym. */\n description?: string;\n disabled?: boolean;\n}\n\nconst props = withDefaults(\n defineProps<{\n modelValue?: string;\n options?: TLanguageOption[];\n open?: boolean;\n defaultOpen?: boolean;\n size?: TSize;\n variant?: TLanguageSelectVariant;\n /** Hide the language name, leaving icon + flag. `switcher` only. */\n iconOnly?: boolean;\n /** Inline-size cap. Fluid (`full`) by default. */\n width?: TFieldWidth;\n disabled?: boolean;\n loading?: boolean;\n invalid?: boolean;\n placeholder?: string;\n /** Forwarded to every nested TFlag. Set once here to self-host flag assets. */\n flagBaseUrl?: string;\n emptyText?: string;\n }>(),\n {\n modelValue: '',\n options: () => [],\n open: undefined,\n defaultOpen: false,\n size: 'md',\n variant: 'field',\n iconOnly: false,\n width: 'full',\n disabled: false,\n loading: false,\n invalid: false,\n placeholder: '',\n flagBaseUrl: undefined,\n emptyText: 'No languages available.',\n },\n);\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string];\n 'update:open': [value: boolean];\n 'open-change': [value: boolean];\n}>();\n\ndefineSlots<{\n prefix?: () => unknown;\n option?: (props: { option: TLanguageOption; selected: boolean; focused: boolean }) => unknown;\n empty?: () => unknown;\n}>();\n\nconst attrs = useAttrs();\nconst listboxId = createId('t-language-select');\nconst rootRef = ref<HTMLElement | null>(null);\nconst triggerRef = ref<HTMLButtonElement | null>(null);\nconst listboxRef = ref<HTMLElement | null>(null);\nconst optionRefs = ref<Map<string, HTMLElement>>(new Map());\nconst focusedIndex = ref(-1);\nconst dropUp = ref(false);\n\nconst triggerAttrs = computed(() => {\n const { class: _class, style: _style, ...rest } = attrs;\n return rest;\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 isSwitcher = computed(() => props.variant === 'switcher');\n\nconst rootClasses = computed(() => [\n 't-language-select',\n `t-language-select--${props.size}`,\n `t-language-select--${props.variant}`,\n // A switcher sizes to its content, so the field width scale does not apply.\n !isSwitcher.value && props.width !== 'full' ? `t-field-width--${props.width}` : null,\n {\n 't-language-select--icon-only': isSwitcher.value && props.iconOnly,\n 't-language-select--drop-up': dropUp.value,\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\n// Nearest ancestor that can clip the listbox (scroll container or hidden overflow).\nconst findClippingAncestor = (element: HTMLElement): HTMLElement | null => {\n let parent = element.parentElement;\n while (parent && parent !== document.body) {\n const { overflowY } = window.getComputedStyle(parent);\n if (overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'hidden') {\n return parent;\n }\n parent = parent.parentElement;\n }\n return null;\n};\n\n// Flip the listbox above the trigger when its rendered position overflows the\n// nearest clipping ancestor (e.g. a modal body) or the viewport, and there is\n// more room above the trigger than below it. Measuring the rendered rect keeps\n// the decision in sync with the CSS offset (--tree-space-2) automatically.\nconst updateDropDirection = () => {\n const trigger = triggerRef.value;\n const listbox = listboxRef.value;\n if (!trigger || !listbox) {\n dropUp.value = false;\n return;\n }\n const triggerRect = trigger.getBoundingClientRect();\n const listboxRect = listbox.getBoundingClientRect();\n const boundary = findClippingAncestor(trigger)?.getBoundingClientRect();\n const bottomLimit = Math.min(boundary?.bottom ?? Infinity, window.innerHeight);\n const topLimit = Math.max(boundary?.top ?? 0, 0);\n const spaceBelow = bottomLimit - triggerRect.bottom;\n const spaceAbove = triggerRect.top - topLimit;\n dropUp.value = listboxRect.bottom > bottomLimit && spaceAbove > spaceBelow;\n};\n\nconst rootStyle = computed(() => attrs.style);\n\nconst selectedOption = computed(() =>\n props.options.find((o) => o.value === props.modelValue),\n);\n\nconst selectedLabel = computed(() => selectedOption.value?.label ?? '');\n\n// iconOnly leans on the flag to carry the current value. With no flag to show it\n// would leave the trigger stating nothing at all, so the name comes back.\nconst showValue = computed(\n () => !(isSwitcher.value && props.iconOnly) || !selectedOption.value?.code,\n);\n\nconst enabledOptions = computed(() =>\n props.options.filter((o) => !o.disabled),\n);\n\n// Runs for every open, however it was triggered. The listbox can also be opened\n// through `open` / `defaultOpen` without going near openDropdown(), so this lives\n// on the isOpen watcher rather than inside the click handler.\nconst onOpened = () => {\n const selectedIdx = props.options.findIndex((o) => o.value === props.modelValue);\n focusedIndex.value = selectedIdx >= 0 ? selectedIdx : 0;\n // Render downward first so the measurement always starts from the default position.\n dropUp.value = false;\n nextTick(() => {\n updateDropDirection();\n focusOption(focusedIndex.value);\n });\n};\n\nconst openDropdown = () => {\n if (props.disabled) return;\n setValue(true);\n};\n\nconst closeDropdown = (restoreFocus = false) => {\n setValue(false);\n focusedIndex.value = -1;\n if (restoreFocus) {\n nextTick(() => triggerRef.value?.focus());\n }\n};\n\nconst toggleDropdown = () => {\n if (isOpen.value) {\n closeDropdown();\n } else {\n openDropdown();\n }\n};\n\nconst selectOption = (opt: TLanguageOption) => {\n if (opt.disabled) return;\n emit('update:modelValue', opt.value);\n closeDropdown(true);\n};\n\nconst focusOption = (index: number) => {\n const opt = props.options[index];\n if (!opt) return;\n const el = optionRefs.value.get(opt.value);\n el?.focus();\n};\n\nconst setOptionRef = (el: Element | null, value: string) => {\n if (el instanceof HTMLElement) {\n optionRefs.value.set(value, el);\n } else {\n optionRefs.value.delete(value);\n }\n};\n\nconst moveFocus = (direction: 1 | -1) => {\n let nextIndex = focusedIndex.value + direction;\n while (nextIndex >= 0 && nextIndex < props.options.length) {\n if (!props.options[nextIndex].disabled) {\n focusedIndex.value = nextIndex;\n focusOption(nextIndex);\n return;\n }\n nextIndex += direction;\n }\n};\n\nconst onTriggerKeydown = (event: KeyboardEvent) => {\n if (props.disabled) return;\n\n if (isEscapeKey(event) && isOpen.value) {\n event.preventDefault();\n closeDropdown();\n return;\n }\n\n if (isActivationKey(event) || event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault();\n openDropdown();\n }\n};\n\nconst onOptionKeydown = (event: KeyboardEvent, opt: TLanguageOption, _index: number) => {\n if (isEscapeKey(event)) {\n event.preventDefault();\n closeDropdown(true);\n return;\n }\n\n if (isActivationKey(event)) {\n event.preventDefault();\n selectOption(opt);\n return;\n }\n\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault();\n moveFocus(1);\n break;\n case 'ArrowUp':\n event.preventDefault();\n moveFocus(-1);\n break;\n case 'Home':\n event.preventDefault();\n focusedIndex.value = enabledOptions.value.length > 0\n ? props.options.indexOf(enabledOptions.value[0])\n : 0;\n focusOption(focusedIndex.value);\n break;\n case 'End':\n event.preventDefault();\n focusedIndex.value = enabledOptions.value.length > 0\n ? props.options.indexOf(enabledOptions.value[enabledOptions.value.length - 1])\n : props.options.length - 1;\n focusOption(focusedIndex.value);\n break;\n default:\n break;\n }\n};\n\nconst onDocumentPointerDown = (event: PointerEvent) => {\n if (!isOpen.value) return;\n const target = event.target;\n if (!(target instanceof Node)) return;\n if (rootRef.value?.contains(target)) return;\n closeDropdown();\n};\n\nconst hasSelection = computed(() => props.modelValue !== '');\n\n// immediate, so a listbox that is already open on mount (`defaultOpen`, or a\n// controlled `open` that starts true) still dismisses on outside click and still\n// measures its drop direction.\nwatch(isOpen, (value) => {\n if (value) {\n document.addEventListener('pointerdown', onDocumentPointerDown);\n onOpened();\n } else {\n document.removeEventListener('pointerdown', onDocumentPointerDown);\n }\n}, { immediate: true });\n\n// Options can shrink underneath an open listbox. Leaving focusedIndex past the\n// end would strand moveFocus(), which only ever walks outward from it.\nwatch(\n () => props.options.length,\n (length) => {\n if (isOpen.value && focusedIndex.value >= length) {\n focusedIndex.value = length - 1;\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 :data-state=\"isOpen ? 'open' : 'closed'\"\n >\n <button\n ref=\"triggerRef\"\n v-bind=\"triggerAttrs\"\n type=\"button\"\n class=\"t-language-select__trigger\"\n :disabled=\"disabled\"\n :aria-controls=\"isOpen ? listboxId : undefined\"\n aria-haspopup=\"listbox\"\n :aria-expanded=\"isOpen\"\n :aria-invalid=\"invalid || undefined\"\n :aria-busy=\"loading || undefined\"\n @click=\"toggleDropdown\"\n @keydown=\"onTriggerKeydown\"\n >\n <span\n v-if=\"$slots.prefix\"\n class=\"t-language-select__slot t-language-select__slot--prefix\"\n >\n <slot name=\"prefix\" />\n </span>\n <!--\n The switcher leads with the translate icon so the control announces what\n it changes; the field leads with the flag, since a form label already\n does that job.\n -->\n <!-- Sized in CSS from the icon token scale, not TIcon's pixel `size` prop. -->\n <TIcon\n v-if=\"isSwitcher\"\n name=\"languages\"\n class=\"t-language-select__translate\"\n />\n <TFlag\n v-else-if=\"selectedOption?.code\"\n :code=\"selectedOption.code\"\n :base-url=\"flagBaseUrl\"\n :size=\"size\"\n />\n <span\n v-if=\"showValue\"\n class=\"t-language-select__value\"\n :data-placeholder=\"!hasSelection ? true : undefined\"\n >\n {{ selectedLabel || placeholder }}\n </span>\n <TFlag\n v-if=\"isSwitcher && selectedOption?.code\"\n :code=\"selectedOption.code\"\n :base-url=\"flagBaseUrl\"\n :size=\"size\"\n />\n <TSpinner\n v-if=\"loading\"\n size=\"sm\"\n label=\"Loading\"\n />\n <svg\n v-else\n class=\"t-language-select__chevron\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <polyline points=\"4 6 8 10 12 6\" />\n </svg>\n </button>\n <transition name=\"t-fade\">\n <ul\n v-if=\"isOpen && !disabled\"\n :id=\"listboxId\"\n ref=\"listboxRef\"\n role=\"listbox\"\n class=\"t-language-select__listbox\"\n :aria-label=\"typeof triggerAttrs['aria-label'] === 'string' ? triggerAttrs['aria-label'] : undefined\"\n >\n <li\n v-if=\"options.length === 0\"\n class=\"t-language-select__empty\"\n role=\"presentation\"\n >\n <slot name=\"empty\">\n {{ emptyText }}\n </slot>\n </li>\n <li\n v-for=\"(opt, index) in options\"\n :key=\"opt.value\"\n :ref=\"(el) => setOptionRef(el as Element | null, opt.value)\"\n role=\"option\"\n class=\"t-language-select__option\"\n :class=\"{\n 'is-selected': opt.value === modelValue,\n 'is-disabled': opt.disabled,\n 'is-focused': index === focusedIndex,\n }\"\n :aria-selected=\"opt.value === modelValue\"\n :aria-disabled=\"opt.disabled || undefined\"\n :tabindex=\"opt.disabled ? -1 : 0\"\n @click=\"selectOption(opt)\"\n @keydown=\"onOptionKeydown($event, opt, index)\"\n >\n <slot\n name=\"option\"\n :option=\"opt\"\n :selected=\"opt.value === modelValue\"\n :focused=\"index === focusedIndex\"\n >\n <TFlag\n v-if=\"opt.code\"\n :code=\"opt.code\"\n :base-url=\"flagBaseUrl\"\n :size=\"size\"\n />\n <span class=\"t-language-select__option-text\">\n <span class=\"t-language-select__option-label\">{{ opt.label }}</span>\n <span\n v-if=\"opt.description\"\n class=\"t-language-select__description\"\n >\n {{ opt.description }}\n </span>\n </span>\n </slot>\n <svg\n v-if=\"opt.value === modelValue\"\n class=\"t-language-select__check\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <polyline points=\"3.5 8.5 6.5 11.5 12.5 4.5\" />\n </svg>\n </li>\n </ul>\n </transition>\n </div>\n</template>\n"],"mappings":""}
@@ -0,0 +1,103 @@
1
+ import { TFieldWidth, TSize } from '../types/contracts';
2
+ /**
3
+ * `field` is a form control: it reads as the value of a labelled question, so the
4
+ * flag leads and the trigger fills its container.
5
+ *
6
+ * `switcher` is a page-level control, typically in a navbar, where nothing nearby
7
+ * says what it changes. The translate icon states the purpose up front and the
8
+ * current flag closes the row, so the whole thing reads as "page language, now
9
+ * set to this" without an external label.
10
+ */
11
+ export type TLanguageSelectVariant = 'field' | 'switcher';
12
+ export interface TLanguageOption {
13
+ label: string;
14
+ /** Opaque application key — typically a BCP-47 tag. Never parsed or validated. */
15
+ value: string;
16
+ /** ISO 3166-1 alpha-2 code for the flag. Omit to render the option without one. */
17
+ code?: string;
18
+ /** Secondary line, e.g. the language's endonym. */
19
+ description?: string;
20
+ disabled?: boolean;
21
+ }
22
+ type __VLS_Props = {
23
+ modelValue?: string;
24
+ options?: TLanguageOption[];
25
+ open?: boolean;
26
+ defaultOpen?: boolean;
27
+ size?: TSize;
28
+ variant?: TLanguageSelectVariant;
29
+ /** Hide the language name, leaving icon + flag. `switcher` only. */
30
+ iconOnly?: boolean;
31
+ /** Inline-size cap. Fluid (`full`) by default. */
32
+ width?: TFieldWidth;
33
+ disabled?: boolean;
34
+ loading?: boolean;
35
+ invalid?: boolean;
36
+ placeholder?: string;
37
+ /** Forwarded to every nested TFlag. Set once here to self-host flag assets. */
38
+ flagBaseUrl?: string;
39
+ emptyText?: string;
40
+ };
41
+ declare function __VLS_template(): {
42
+ attrs: Partial<{}>;
43
+ slots: Readonly<{
44
+ prefix?: () => unknown;
45
+ option?: (props: {
46
+ option: TLanguageOption;
47
+ selected: boolean;
48
+ focused: boolean;
49
+ }) => unknown;
50
+ empty?: () => unknown;
51
+ }> & {
52
+ prefix?: () => unknown;
53
+ option?: (props: {
54
+ option: TLanguageOption;
55
+ selected: boolean;
56
+ focused: boolean;
57
+ }) => unknown;
58
+ empty?: () => unknown;
59
+ };
60
+ refs: {
61
+ rootRef: HTMLDivElement;
62
+ triggerRef: HTMLButtonElement;
63
+ listboxRef: HTMLUListElement;
64
+ };
65
+ rootEl: any;
66
+ };
67
+ type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
68
+ declare const __VLS_component: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
69
+ "update:modelValue": (value: string) => any;
70
+ "update:open": (value: boolean) => any;
71
+ "open-change": (value: boolean) => any;
72
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
73
+ "onUpdate:modelValue"?: ((value: string) => any) | undefined;
74
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
75
+ "onOpen-change"?: ((value: boolean) => any) | undefined;
76
+ }>, {
77
+ modelValue: string;
78
+ disabled: boolean;
79
+ size: TSize;
80
+ width: TFieldWidth;
81
+ variant: TLanguageSelectVariant;
82
+ loading: boolean;
83
+ invalid: boolean;
84
+ options: TLanguageOption[];
85
+ open: boolean;
86
+ defaultOpen: boolean;
87
+ placeholder: string;
88
+ emptyText: string;
89
+ iconOnly: boolean;
90
+ flagBaseUrl: string;
91
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
92
+ rootRef: HTMLDivElement;
93
+ triggerRef: HTMLButtonElement;
94
+ listboxRef: HTMLUListElement;
95
+ }, any>;
96
+ declare const _default: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
97
+ export default _default;
98
+ type __VLS_WithTemplateSlots<T, S> = T & {
99
+ new (): {
100
+ $slots: S;
101
+ };
102
+ };
103
+ //# sourceMappingURL=TLanguageSelect.vue.d.ts.map