@treeui/vue 0.16.0 → 0.18.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 (143) 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/TAppShell.cjs.map +1 -1
  14. package/dist/components/TAppShell.js.map +1 -1
  15. package/dist/components/TAppShell.vue_vue_type_script_setup_true_lang.cjs +1 -1
  16. package/dist/components/TAppShell.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  17. package/dist/components/TAppShell.vue_vue_type_script_setup_true_lang.js +1 -1
  18. package/dist/components/TAppShell.vue_vue_type_script_setup_true_lang.js.map +1 -1
  19. package/dist/components/TDatePicker.cjs.map +1 -1
  20. package/dist/components/TDatePicker.js.map +1 -1
  21. package/dist/components/TDatePicker.vue_vue_type_script_setup_true_lang.cjs +1 -1
  22. package/dist/components/TDatePicker.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  23. package/dist/components/TDatePicker.vue_vue_type_script_setup_true_lang.js +1 -1
  24. package/dist/components/TDatePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  25. package/dist/components/TDateTimePicker.cjs.map +1 -1
  26. package/dist/components/TDateTimePicker.js.map +1 -1
  27. package/dist/components/TDateTimePicker.vue_vue_type_script_setup_true_lang.cjs +1 -1
  28. package/dist/components/TDateTimePicker.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  29. package/dist/components/TDateTimePicker.vue_vue_type_script_setup_true_lang.js +1 -1
  30. package/dist/components/TDateTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
  31. package/dist/components/TDrawer.cjs.map +1 -1
  32. package/dist/components/TDrawer.js.map +1 -1
  33. package/dist/components/TDrawer.vue_vue_type_script_setup_true_lang.cjs +1 -1
  34. package/dist/components/TDrawer.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  35. package/dist/components/TDrawer.vue_vue_type_script_setup_true_lang.js +1 -1
  36. package/dist/components/TDrawer.vue_vue_type_script_setup_true_lang.js.map +1 -1
  37. package/dist/components/TDropdown.cjs.map +1 -1
  38. package/dist/components/TDropdown.js.map +1 -1
  39. package/dist/components/TDropdown.vue.d.ts +4 -0
  40. package/dist/components/TDropdown.vue.d.ts.map +1 -1
  41. package/dist/components/TDropdown.vue_vue_type_script_setup_true_lang.cjs +1 -1
  42. package/dist/components/TDropdown.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  43. package/dist/components/TDropdown.vue_vue_type_script_setup_true_lang.js +22 -18
  44. package/dist/components/TDropdown.vue_vue_type_script_setup_true_lang.js.map +1 -1
  45. package/dist/components/TFlag.cjs +2 -0
  46. package/dist/components/TFlag.cjs.map +1 -0
  47. package/dist/components/TFlag.js +7 -0
  48. package/dist/components/TFlag.js.map +1 -0
  49. package/dist/components/TFlag.vue.d.ts +20 -0
  50. package/dist/components/TFlag.vue.d.ts.map +1 -0
  51. package/dist/components/TFlag.vue_vue_type_script_setup_true_lang.cjs +2 -0
  52. package/dist/components/TFlag.vue_vue_type_script_setup_true_lang.cjs.map +1 -0
  53. package/dist/components/TFlag.vue_vue_type_script_setup_true_lang.js +58 -0
  54. package/dist/components/TFlag.vue_vue_type_script_setup_true_lang.js.map +1 -0
  55. package/dist/components/TIcon.cjs.map +1 -1
  56. package/dist/components/TIcon.js.map +1 -1
  57. package/dist/components/TIcon.vue.d.ts +1 -1
  58. package/dist/components/TIcon.vue.d.ts.map +1 -1
  59. package/dist/components/TIcon.vue_vue_type_script_setup_true_lang.cjs +1 -1
  60. package/dist/components/TIcon.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  61. package/dist/components/TIcon.vue_vue_type_script_setup_true_lang.js +18 -17
  62. package/dist/components/TIcon.vue_vue_type_script_setup_true_lang.js.map +1 -1
  63. package/dist/components/TLanguageSelect.cjs +2 -0
  64. package/dist/components/TLanguageSelect.cjs.map +1 -0
  65. package/dist/components/TLanguageSelect.js +7 -0
  66. package/dist/components/TLanguageSelect.js.map +1 -0
  67. package/dist/components/TLanguageSelect.vue.d.ts +103 -0
  68. package/dist/components/TLanguageSelect.vue.d.ts.map +1 -0
  69. package/dist/components/TLanguageSelect.vue_vue_type_script_setup_true_lang.cjs +2 -0
  70. package/dist/components/TLanguageSelect.vue_vue_type_script_setup_true_lang.cjs.map +1 -0
  71. package/dist/components/TLanguageSelect.vue_vue_type_script_setup_true_lang.js +299 -0
  72. package/dist/components/TLanguageSelect.vue_vue_type_script_setup_true_lang.js.map +1 -0
  73. package/dist/components/TModal.cjs.map +1 -1
  74. package/dist/components/TModal.js.map +1 -1
  75. package/dist/components/TModal.vue_vue_type_script_setup_true_lang.cjs +1 -1
  76. package/dist/components/TModal.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  77. package/dist/components/TModal.vue_vue_type_script_setup_true_lang.js +1 -1
  78. package/dist/components/TModal.vue_vue_type_script_setup_true_lang.js.map +1 -1
  79. package/dist/components/TMultiSelect.cjs.map +1 -1
  80. package/dist/components/TMultiSelect.js.map +1 -1
  81. package/dist/components/TMultiSelect.vue_vue_type_script_setup_true_lang.cjs +1 -1
  82. package/dist/components/TMultiSelect.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  83. package/dist/components/TMultiSelect.vue_vue_type_script_setup_true_lang.js +1 -1
  84. package/dist/components/TMultiSelect.vue_vue_type_script_setup_true_lang.js.map +1 -1
  85. package/dist/components/TNavMenu.cjs.map +1 -1
  86. package/dist/components/TNavMenu.js.map +1 -1
  87. package/dist/components/TNavMenu.vue.d.ts +7 -2
  88. package/dist/components/TNavMenu.vue.d.ts.map +1 -1
  89. package/dist/components/TNavMenu.vue_vue_type_script_setup_true_lang.cjs +1 -1
  90. package/dist/components/TNavMenu.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  91. package/dist/components/TNavMenu.vue_vue_type_script_setup_true_lang.js +50 -49
  92. package/dist/components/TNavMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
  93. package/dist/components/TPopover.cjs.map +1 -1
  94. package/dist/components/TPopover.js.map +1 -1
  95. package/dist/components/TPopover.vue.d.ts +4 -2
  96. package/dist/components/TPopover.vue.d.ts.map +1 -1
  97. package/dist/components/TPopover.vue_vue_type_script_setup_true_lang.cjs +1 -1
  98. package/dist/components/TPopover.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  99. package/dist/components/TPopover.vue_vue_type_script_setup_true_lang.js +10 -5
  100. package/dist/components/TPopover.vue_vue_type_script_setup_true_lang.js.map +1 -1
  101. package/dist/components/TPricingCard.vue.d.ts +1 -1
  102. package/dist/components/TPricingCard.vue.d.ts.map +1 -1
  103. package/dist/components/TSidebar.cjs.map +1 -1
  104. package/dist/components/TSidebar.js.map +1 -1
  105. package/dist/components/TSidebar.vue_vue_type_script_setup_true_lang.cjs +1 -1
  106. package/dist/components/TSidebar.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  107. package/dist/components/TSidebar.vue_vue_type_script_setup_true_lang.js +1 -1
  108. package/dist/components/TSidebar.vue_vue_type_script_setup_true_lang.js.map +1 -1
  109. package/dist/components/TSteps.cjs.map +1 -1
  110. package/dist/components/TSteps.js.map +1 -1
  111. package/dist/components/TSteps.vue_vue_type_script_setup_true_lang.cjs +1 -1
  112. package/dist/components/TSteps.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  113. package/dist/components/TSteps.vue_vue_type_script_setup_true_lang.js +1 -1
  114. package/dist/components/TSteps.vue_vue_type_script_setup_true_lang.js.map +1 -1
  115. package/dist/components/TToast.cjs.map +1 -1
  116. package/dist/components/TToast.js.map +1 -1
  117. package/dist/components/TToast.vue.d.ts.map +1 -1
  118. package/dist/components/TToast.vue_vue_type_script_setup_true_lang.cjs +1 -1
  119. package/dist/components/TToast.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  120. package/dist/components/TToast.vue_vue_type_script_setup_true_lang.js +6 -6
  121. package/dist/components/TToast.vue_vue_type_script_setup_true_lang.js.map +1 -1
  122. package/dist/components/TTreeViewItem.cjs.map +1 -1
  123. package/dist/components/TTreeViewItem.js.map +1 -1
  124. package/dist/components/TTreeViewItem.vue_vue_type_script_setup_true_lang.cjs +1 -1
  125. package/dist/components/TTreeViewItem.vue_vue_type_script_setup_true_lang.cjs.map +1 -1
  126. package/dist/components/TTreeViewItem.vue_vue_type_script_setup_true_lang.js +1 -1
  127. package/dist/components/TTreeViewItem.vue_vue_type_script_setup_true_lang.js.map +1 -1
  128. package/dist/components/accordion.cjs.map +1 -1
  129. package/dist/components/accordion.d.ts +4 -4
  130. package/dist/components/accordion.d.ts.map +1 -1
  131. package/dist/components/accordion.js.map +1 -1
  132. package/dist/components/index.d.ts +5 -1
  133. package/dist/components/index.d.ts.map +1 -1
  134. package/dist/components/index.js +77 -0
  135. package/dist/index.cjs +1 -1
  136. package/dist/index.js +81 -78
  137. package/dist/plugin.cjs +1 -1
  138. package/dist/plugin.cjs.map +1 -1
  139. package/dist/plugin.d.ts.map +1 -1
  140. package/dist/plugin.js +146 -142
  141. package/dist/plugin.js.map +1 -1
  142. package/dist/style.css +1 -1
  143. package/package.json +2 -2
@@ -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 +1 @@
1
- {"version":3,"file":"TIcon.cjs","names":[],"sources":["../../src/components/TIcon.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, type Component } from 'vue';\nimport { getTreeIcon, treeIconDefaults, type TIconName } from '@treeui/icons';\n\nconst props = withDefaults(\n defineProps<{\n name: TIconName;\n size?: number | string;\n strokeWidth?: number | string;\n absoluteStrokeWidth?: boolean;\n label?: string;\n }>(),\n {\n size: treeIconDefaults.size,\n strokeWidth: treeIconDefaults.strokeWidth,\n absoluteStrokeWidth: treeIconDefaults.absoluteStrokeWidth,\n label: undefined,\n },\n);\n\nconst iconComponent = computed(() => getTreeIcon(props.name) as Component);\n</script>\n\n<template>\n <component\n :is=\"iconComponent\"\n class=\"t-icon\"\n :size=\"size\"\n :stroke-width=\"strokeWidth\"\n :absolute-stroke-width=\"absoluteStrokeWidth\"\n :role=\"label ? 'img' : undefined\"\n :aria-label=\"label || undefined\"\n :aria-hidden=\"label ? undefined : 'true'\"\n />\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"TIcon.cjs","names":[],"sources":["../../src/components/TIcon.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue';\nimport { resolveTreeIcon, treeIconDefaults, type TIconName } from '@treeui/icons';\n\nconst props = withDefaults(\n defineProps<{\n name: TIconName;\n size?: number | string;\n strokeWidth?: number | string;\n absoluteStrokeWidth?: boolean;\n label?: string;\n }>(),\n {\n size: treeIconDefaults.size,\n strokeWidth: treeIconDefaults.strokeWidth,\n absoluteStrokeWidth: treeIconDefaults.absoluteStrokeWidth,\n label: undefined,\n },\n);\n\n// Resolved per render rather than once at setup, so an icon registered after\n// this component mounted still appears.\nconst iconComponent = computed(() => resolveTreeIcon(props.name));\n</script>\n\n<template>\n <component\n :is=\"iconComponent\"\n v-if=\"iconComponent\"\n class=\"t-icon\"\n :size=\"size\"\n :stroke-width=\"strokeWidth\"\n :absolute-stroke-width=\"absoluteStrokeWidth\"\n :role=\"label ? 'img' : undefined\"\n :aria-label=\"label || undefined\"\n :aria-hidden=\"label ? undefined : 'true'\"\n />\n</template>\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"TIcon.js","names":[],"sources":["../../src/components/TIcon.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, type Component } from 'vue';\nimport { getTreeIcon, treeIconDefaults, type TIconName } from '@treeui/icons';\n\nconst props = withDefaults(\n defineProps<{\n name: TIconName;\n size?: number | string;\n strokeWidth?: number | string;\n absoluteStrokeWidth?: boolean;\n label?: string;\n }>(),\n {\n size: treeIconDefaults.size,\n strokeWidth: treeIconDefaults.strokeWidth,\n absoluteStrokeWidth: treeIconDefaults.absoluteStrokeWidth,\n label: undefined,\n },\n);\n\nconst iconComponent = computed(() => getTreeIcon(props.name) as Component);\n</script>\n\n<template>\n <component\n :is=\"iconComponent\"\n class=\"t-icon\"\n :size=\"size\"\n :stroke-width=\"strokeWidth\"\n :absolute-stroke-width=\"absoluteStrokeWidth\"\n :role=\"label ? 'img' : undefined\"\n :aria-label=\"label || undefined\"\n :aria-hidden=\"label ? undefined : 'true'\"\n />\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"TIcon.js","names":[],"sources":["../../src/components/TIcon.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue';\nimport { resolveTreeIcon, treeIconDefaults, type TIconName } from '@treeui/icons';\n\nconst props = withDefaults(\n defineProps<{\n name: TIconName;\n size?: number | string;\n strokeWidth?: number | string;\n absoluteStrokeWidth?: boolean;\n label?: string;\n }>(),\n {\n size: treeIconDefaults.size,\n strokeWidth: treeIconDefaults.strokeWidth,\n absoluteStrokeWidth: treeIconDefaults.absoluteStrokeWidth,\n label: undefined,\n },\n);\n\n// Resolved per render rather than once at setup, so an icon registered after\n// this component mounted still appears.\nconst iconComponent = computed(() => resolveTreeIcon(props.name));\n</script>\n\n<template>\n <component\n :is=\"iconComponent\"\n v-if=\"iconComponent\"\n class=\"t-icon\"\n :size=\"size\"\n :stroke-width=\"strokeWidth\"\n :absolute-stroke-width=\"absoluteStrokeWidth\"\n :role=\"label ? 'img' : undefined\"\n :aria-label=\"label || undefined\"\n :aria-hidden=\"label ? undefined : 'true'\"\n />\n</template>\n"],"mappings":""}
@@ -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;
@@ -1 +1 @@
1
- {"version":3,"file":"TIcon.vue.d.ts","sourceRoot":"","sources":["../../src/components/TIcon.vue"],"names":[],"mappings":"AAsCA,OAAO,EAAiC,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAE9E,KAAK,WAAW,GAAG;IACf,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;;UAJO,MAAM,GAAG,MAAM;WAGd,MAAM;iBAFA,MAAM,GAAG,MAAM;yBACP,OAAO;;AA+EjC,wBAQG"}
1
+ {"version":3,"file":"TIcon.vue.d.ts","sourceRoot":"","sources":["../../src/components/TIcon.vue"],"names":[],"mappings":"AAyCA,OAAO,EAAqC,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAElF,KAAK,WAAW,GAAG;IACf,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;;UAJO,MAAM,GAAG,MAAM;WAGd,MAAM;iBAFA,MAAM,GAAG,MAAM;yBACP,OAAO;;AAiFjC,wBAOG"}
@@ -1,2 +1,2 @@
1
- let e=require("vue"),t=require("@treeui/icons");var n=(0,e.defineComponent)({__name:`TIcon`,props:{name:{},size:{default:t.treeIconDefaults.size},strokeWidth:{default:t.treeIconDefaults.strokeWidth},absoluteStrokeWidth:{type:Boolean,default:t.treeIconDefaults.absoluteStrokeWidth},label:{default:void 0}},setup(n){let r=n,i=(0,e.computed)(()=>(0,t.getTreeIcon)(r.name));return(t,r)=>((0,e.openBlock)(),(0,e.createBlock)((0,e.resolveDynamicComponent)(i.value),{class:`t-icon`,size:n.size,"stroke-width":n.strokeWidth,"absolute-stroke-width":n.absoluteStrokeWidth,role:n.label?`img`:void 0,"aria-label":n.label||void 0,"aria-hidden":n.label?void 0:`true`},null,8,[`size`,`stroke-width`,`absolute-stroke-width`,`role`,`aria-label`,`aria-hidden`]))}});exports.default=n;
1
+ let e=require("vue"),t=require("@treeui/icons");var n=(0,e.defineComponent)({__name:`TIcon`,props:{name:{},size:{default:t.treeIconDefaults.size},strokeWidth:{default:t.treeIconDefaults.strokeWidth},absoluteStrokeWidth:{type:Boolean,default:t.treeIconDefaults.absoluteStrokeWidth},label:{default:void 0}},setup(n){let r=n,i=(0,e.computed)(()=>(0,t.resolveTreeIcon)(r.name));return(t,r)=>i.value?((0,e.openBlock)(),(0,e.createBlock)((0,e.resolveDynamicComponent)(i.value),{key:0,class:`t-icon`,size:n.size,"stroke-width":n.strokeWidth,"absolute-stroke-width":n.absoluteStrokeWidth,role:n.label?`img`:void 0,"aria-label":n.label||void 0,"aria-hidden":n.label?void 0:`true`},null,8,[`size`,`stroke-width`,`absolute-stroke-width`,`role`,`aria-label`,`aria-hidden`])):(0,e.createCommentVNode)(``,!0)}});exports.default=n;
2
2
  //# sourceMappingURL=TIcon.vue_vue_type_script_setup_true_lang.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"TIcon.vue_vue_type_script_setup_true_lang.cjs","names":[],"sources":["../../src/components/TIcon.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, type Component } from 'vue';\nimport { getTreeIcon, treeIconDefaults, type TIconName } from '@treeui/icons';\n\nconst props = withDefaults(\n defineProps<{\n name: TIconName;\n size?: number | string;\n strokeWidth?: number | string;\n absoluteStrokeWidth?: boolean;\n label?: string;\n }>(),\n {\n size: treeIconDefaults.size,\n strokeWidth: treeIconDefaults.strokeWidth,\n absoluteStrokeWidth: treeIconDefaults.absoluteStrokeWidth,\n label: undefined,\n },\n);\n\nconst iconComponent = computed(() => getTreeIcon(props.name) as Component);\n</script>\n\n<template>\n <component\n :is=\"iconComponent\"\n class=\"t-icon\"\n :size=\"size\"\n :stroke-width=\"strokeWidth\"\n :absolute-stroke-width=\"absoluteStrokeWidth\"\n :role=\"label ? 'img' : undefined\"\n :aria-label=\"label || undefined\"\n :aria-hidden=\"label ? undefined : 'true'\"\n />\n</template>\n"],"mappings":"0TAIA,IAAM,EAAQ,EAgBR,GAAA,EAAA,EAAA,SAAA,MAAA,EAAA,EAAA,YAAA,CAA2C,EAAM,IAAI,CAAc,kFAKhE,EAAA,KAAa,EAAA,CAClB,MAAM,SACL,KAAM,EAAA,KACN,eAAc,EAAA,YACd,wBAAuB,EAAA,oBACvB,KAAM,EAAA,MAAK,MAAW,IAAA,GACtB,aAAY,EAAA,OAAS,IAAA,GACrB,cAAa,EAAA,MAAQ,IAAA,GAAS"}
1
+ {"version":3,"file":"TIcon.vue_vue_type_script_setup_true_lang.cjs","names":[],"sources":["../../src/components/TIcon.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue';\nimport { resolveTreeIcon, treeIconDefaults, type TIconName } from '@treeui/icons';\n\nconst props = withDefaults(\n defineProps<{\n name: TIconName;\n size?: number | string;\n strokeWidth?: number | string;\n absoluteStrokeWidth?: boolean;\n label?: string;\n }>(),\n {\n size: treeIconDefaults.size,\n strokeWidth: treeIconDefaults.strokeWidth,\n absoluteStrokeWidth: treeIconDefaults.absoluteStrokeWidth,\n label: undefined,\n },\n);\n\n// Resolved per render rather than once at setup, so an icon registered after\n// this component mounted still appears.\nconst iconComponent = computed(() => resolveTreeIcon(props.name));\n</script>\n\n<template>\n <component\n :is=\"iconComponent\"\n v-if=\"iconComponent\"\n class=\"t-icon\"\n :size=\"size\"\n :stroke-width=\"strokeWidth\"\n :absolute-stroke-width=\"absoluteStrokeWidth\"\n :role=\"label ? 'img' : undefined\"\n :aria-label=\"label || undefined\"\n :aria-hidden=\"label ? undefined : 'true'\"\n />\n</template>\n"],"mappings":"0TAIA,IAAM,EAAQ,EAkBR,GAAA,EAAA,EAAA,SAAA,MAAA,EAAA,EAAA,gBAAA,CAA+C,EAAM,IAAI,CAAC,eAMtD,EAAA,QAAA,EAAA,EAAA,UAAA,CAAA,GAAA,EAAA,EAAA,YAAA,EAAA,EAAA,EAAA,wBAAA,CADD,EAAA,KAAa,EAAA,OAElB,MAAM,SACL,KAAM,EAAA,KACN,eAAc,EAAA,YACd,wBAAuB,EAAA,oBACvB,KAAM,EAAA,MAAK,MAAW,IAAA,GACtB,aAAY,EAAA,OAAS,IAAA,GACrB,cAAa,EAAA,MAAQ,IAAA,GAAS"}
@@ -1,28 +1,29 @@
1
- import { computed as e, createBlock as t, defineComponent as n, openBlock as r, resolveDynamicComponent as i } from "vue";
2
- import { getTreeIcon as a, treeIconDefaults as o } from "@treeui/icons";
1
+ import { computed as e, createBlock as t, createCommentVNode as n, defineComponent as r, openBlock as i, resolveDynamicComponent as a } from "vue";
2
+ import { resolveTreeIcon as o, treeIconDefaults as s } from "@treeui/icons";
3
3
  //#region src/components/TIcon.vue?vue&type=script&setup=true&lang.ts
4
- var s = /*@__PURE__*/ n({
4
+ var c = /*@__PURE__*/ r({
5
5
  __name: "TIcon",
6
6
  props: {
7
7
  name: {},
8
- size: { default: o.size },
9
- strokeWidth: { default: o.strokeWidth },
8
+ size: { default: s.size },
9
+ strokeWidth: { default: s.strokeWidth },
10
10
  absoluteStrokeWidth: {
11
11
  type: Boolean,
12
- default: o.absoluteStrokeWidth
12
+ default: s.absoluteStrokeWidth
13
13
  },
14
14
  label: { default: void 0 }
15
15
  },
16
- setup(n) {
17
- let o = n, s = e(() => a(o.name));
18
- return (e, a) => (r(), t(i(s.value), {
16
+ setup(r) {
17
+ let s = r, c = e(() => o(s.name));
18
+ return (e, o) => c.value ? (i(), t(a(c.value), {
19
+ key: 0,
19
20
  class: "t-icon",
20
- size: n.size,
21
- "stroke-width": n.strokeWidth,
22
- "absolute-stroke-width": n.absoluteStrokeWidth,
23
- role: n.label ? "img" : void 0,
24
- "aria-label": n.label || void 0,
25
- "aria-hidden": n.label ? void 0 : "true"
21
+ size: r.size,
22
+ "stroke-width": r.strokeWidth,
23
+ "absolute-stroke-width": r.absoluteStrokeWidth,
24
+ role: r.label ? "img" : void 0,
25
+ "aria-label": r.label || void 0,
26
+ "aria-hidden": r.label ? void 0 : "true"
26
27
  }, null, 8, [
27
28
  "size",
28
29
  "stroke-width",
@@ -30,10 +31,10 @@ var s = /*@__PURE__*/ n({
30
31
  "role",
31
32
  "aria-label",
32
33
  "aria-hidden"
33
- ]));
34
+ ])) : n("", !0);
34
35
  }
35
36
  });
36
37
  //#endregion
37
- export { s as default };
38
+ export { c as default };
38
39
 
39
40
  //# sourceMappingURL=TIcon.vue_vue_type_script_setup_true_lang.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TIcon.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/TIcon.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, type Component } from 'vue';\nimport { getTreeIcon, treeIconDefaults, type TIconName } from '@treeui/icons';\n\nconst props = withDefaults(\n defineProps<{\n name: TIconName;\n size?: number | string;\n strokeWidth?: number | string;\n absoluteStrokeWidth?: boolean;\n label?: string;\n }>(),\n {\n size: treeIconDefaults.size,\n strokeWidth: treeIconDefaults.strokeWidth,\n absoluteStrokeWidth: treeIconDefaults.absoluteStrokeWidth,\n label: undefined,\n },\n);\n\nconst iconComponent = computed(() => getTreeIcon(props.name) as Component);\n</script>\n\n<template>\n <component\n :is=\"iconComponent\"\n class=\"t-icon\"\n :size=\"size\"\n :stroke-width=\"strokeWidth\"\n :absolute-stroke-width=\"absoluteStrokeWidth\"\n :role=\"label ? 'img' : undefined\"\n :aria-label=\"label || undefined\"\n :aria-hidden=\"label ? undefined : 'true'\"\n />\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;EAIA,IAAM,IAAQ,GAgBR,IAAgB,QAAe,EAAY,EAAM,IAAI,CAAc;yBAIvE,EASE,EARK,EAAA,KAAa,GAAA;GAClB,OAAM;GACL,MAAM,EAAA;GACN,gBAAc,EAAA;GACd,yBAAuB,EAAA;GACvB,MAAM,EAAA,QAAK,QAAW,KAAA;GACtB,cAAY,EAAA,SAAS,KAAA;GACrB,eAAa,EAAA,QAAQ,KAAA,IAAS"}
1
+ {"version":3,"file":"TIcon.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/TIcon.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue';\nimport { resolveTreeIcon, treeIconDefaults, type TIconName } from '@treeui/icons';\n\nconst props = withDefaults(\n defineProps<{\n name: TIconName;\n size?: number | string;\n strokeWidth?: number | string;\n absoluteStrokeWidth?: boolean;\n label?: string;\n }>(),\n {\n size: treeIconDefaults.size,\n strokeWidth: treeIconDefaults.strokeWidth,\n absoluteStrokeWidth: treeIconDefaults.absoluteStrokeWidth,\n label: undefined,\n },\n);\n\n// Resolved per render rather than once at setup, so an icon registered after\n// this component mounted still appears.\nconst iconComponent = computed(() => resolveTreeIcon(props.name));\n</script>\n\n<template>\n <component\n :is=\"iconComponent\"\n v-if=\"iconComponent\"\n class=\"t-icon\"\n :size=\"size\"\n :stroke-width=\"strokeWidth\"\n :absolute-stroke-width=\"absoluteStrokeWidth\"\n :role=\"label ? 'img' : undefined\"\n :aria-label=\"label || undefined\"\n :aria-hidden=\"label ? undefined : 'true'\"\n />\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;EAIA,IAAM,IAAQ,GAkBR,IAAgB,QAAe,EAAgB,EAAM,IAAI,CAAC;mBAMtD,EAAA,SAAA,EAAA,GAFR,EAUE,EATK,EAAA,KAAa,GAAA;;GAElB,OAAM;GACL,MAAM,EAAA;GACN,gBAAc,EAAA;GACd,yBAAuB,EAAA;GACvB,MAAM,EAAA,QAAK,QAAW,KAAA;GACtB,cAAY,EAAA,SAAS,KAAA;GACrB,eAAa,EAAA,QAAQ,KAAA,IAAS"}
@@ -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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TLanguageSelect.vue.d.ts","sourceRoot":"","sources":["../../src/components/TLanguageSelect.vue"],"names":[],"mappings":"AAifA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAS7D;;;;;;;;GAQG;AACH,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,UAAU,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,KAAK,WAAW,GAAG;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,KAAK,CAAC;IACb,OAAO,CAAC,EAAE,sBAAsB,CAAC;IACjC,oEAAoE;IACpE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kDAAkD;IAClD,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAmTJ,iBAAS,cAAc;WAyOT,OAAO,IAA6B;;iBA/fvC,MAAM,OAAO;iBACb,CAAC,KAAK,EAAE;YAAE,MAAM,EAAE,eAAe,CAAC;YAAC,QAAQ,EAAE,OAAO,CAAC;YAAC,OAAO,EAAE,OAAO,CAAA;SAAE,KAAK,OAAO;gBACrF,MAAM,OAAO;;iBAFZ,MAAM,OAAO;iBACb,CAAC,KAAK,EAAE;YAAE,MAAM,EAAE,eAAe,CAAC;YAAC,QAAQ,EAAE,OAAO,CAAC;YAAC,OAAO,EAAE,OAAO,CAAA;SAAE,KAAK,OAAO;gBACrF,MAAM,OAAO;;;;;;;;EAkgBtB;AAgCD,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AAC9D,QAAA,MAAM,eAAe;;;;;;;;;gBAnlBJ,MAAM;cAUR,OAAO;UANX,KAAK;WAKJ,WAAW;aAJT,sBAAsB;aAMtB,OAAO;aACP,OAAO;aAXP,eAAe,EAAE;UACpB,OAAO;iBACA,OAAO;iBAUP,MAAM;eAGR,MAAM;cATP,OAAO;iBAQJ,MAAM;;;;;OA6kBtB,CAAC;wBACkB,uBAAuB,CAAC,OAAO,eAAe,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAAnG,wBAAoG;AAapG,KAAK,uBAAuB,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IACxC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KAEV,CAAA;CACD,CAAC"}
@@ -0,0 +1,2 @@
1
+ const e=require("./TSpinner.cjs"),t=require("../composables/useControllableOpen.cjs"),n=require("./TFlag.cjs"),r=require("./TIcon.cjs");let i=require("vue"),a=require("@treeui/utils");var o=[`data-state`],s=[`disabled`,`aria-controls`,`aria-expanded`,`aria-invalid`,`aria-busy`],c={key:0,class:`t-language-select__slot t-language-select__slot--prefix`},l=[`data-placeholder`],u={key:6,class:`t-language-select__chevron`,viewBox:`0 0 16 16`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},d=[`id`,`aria-label`],f={key:0,class:`t-language-select__empty`,role:`presentation`},p=[`aria-selected`,`aria-disabled`,`tabindex`,`onClick`,`onKeydown`],m={class:`t-language-select__option-text`},h={class:`t-language-select__option-label`},g={key:0,class:`t-language-select__description`},_={key:0,class:`t-language-select__check`,viewBox:`0 0 16 16`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`,"stroke-linecap":`round`,"stroke-linejoin":`round`,"aria-hidden":`true`},v=(0,i.defineComponent)({inheritAttrs:!1,__name:`TLanguageSelect`,props:{modelValue:{default:``},options:{default:()=>[]},open:{type:Boolean,default:void 0},defaultOpen:{type:Boolean,default:!1},size:{default:`md`},variant:{default:`field`},iconOnly:{type:Boolean,default:!1},width:{default:`full`},disabled:{type:Boolean,default:!1},loading:{type:Boolean,default:!1},invalid:{type:Boolean,default:!1},placeholder:{default:``},flagBaseUrl:{default:void 0},emptyText:{default:`No languages available.`}},emits:[`update:modelValue`,`update:open`,`open-change`],setup(v,{emit:y}){let b=v,x=y,S=(0,i.useAttrs)(),C=(0,a.createId)(`t-language-select`),w=(0,i.ref)(null),T=(0,i.ref)(null),E=(0,i.ref)(null),D=(0,i.ref)(new Map),O=(0,i.ref)(-1),k=(0,i.ref)(!1),A=(0,i.computed)(()=>{let{class:e,style:t,...n}=S;return n}),{value:j,setValue:M}=t.useControllableOpen((0,i.toRef)(b,`open`),b.defaultOpen,e=>{x(`update:open`,e),x(`open-change`,e)}),N=(0,i.computed)(()=>b.variant===`switcher`),P=(0,i.computed)(()=>[`t-language-select`,`t-language-select--${b.size}`,`t-language-select--${b.variant}`,!N.value&&b.width!==`full`?`t-field-width--${b.width}`:null,{"t-language-select--icon-only":N.value&&b.iconOnly,"t-language-select--drop-up":k.value,"is-disabled":b.disabled,"is-invalid":b.invalid,"is-loading":b.loading,"is-open":j.value},S.class]),F=e=>{let t=e.parentElement;for(;t&&t!==document.body;){let{overflowY:e}=window.getComputedStyle(t);if(e===`auto`||e===`scroll`||e===`hidden`)return t;t=t.parentElement}return null},I=()=>{let e=T.value,t=E.value;if(!e||!t){k.value=!1;return}let n=e.getBoundingClientRect(),r=t.getBoundingClientRect(),i=F(e)?.getBoundingClientRect(),a=Math.min(i?.bottom??1/0,window.innerHeight),o=Math.max(i?.top??0,0),s=a-n.bottom,c=n.top-o;k.value=r.bottom>a&&c>s},L=(0,i.computed)(()=>S.style),R=(0,i.computed)(()=>b.options.find(e=>e.value===b.modelValue)),z=(0,i.computed)(()=>R.value?.label??``),B=(0,i.computed)(()=>!(N.value&&b.iconOnly)||!R.value?.code),V=(0,i.computed)(()=>b.options.filter(e=>!e.disabled)),H=()=>{let e=b.options.findIndex(e=>e.value===b.modelValue);O.value=e>=0?e:0,k.value=!1,(0,i.nextTick)(()=>{I(),q(O.value)})},U=()=>{b.disabled||M(!0)},W=(e=!1)=>{M(!1),O.value=-1,e&&(0,i.nextTick)(()=>T.value?.focus())},G=()=>{j.value?W():U()},K=e=>{e.disabled||(x(`update:modelValue`,e.value),W(!0))},q=e=>{let t=b.options[e];t&&D.value.get(t.value)?.focus()},J=(e,t)=>{e instanceof HTMLElement?D.value.set(t,e):D.value.delete(t)},Y=e=>{let t=O.value+e;for(;t>=0&&t<b.options.length;){if(!b.options[t].disabled){O.value=t,q(t);return}t+=e}},X=e=>{if(!b.disabled){if((0,a.isEscapeKey)(e)&&j.value){e.preventDefault(),W();return}((0,a.isActivationKey)(e)||e.key===`ArrowDown`||e.key===`ArrowUp`)&&(e.preventDefault(),U())}},Z=(e,t,n)=>{if((0,a.isEscapeKey)(e)){e.preventDefault(),W(!0);return}if((0,a.isActivationKey)(e)){e.preventDefault(),K(t);return}switch(e.key){case`ArrowDown`:e.preventDefault(),Y(1);break;case`ArrowUp`:e.preventDefault(),Y(-1);break;case`Home`:e.preventDefault(),O.value=V.value.length>0?b.options.indexOf(V.value[0]):0,q(O.value);break;case`End`:e.preventDefault(),O.value=V.value.length>0?b.options.indexOf(V.value[V.value.length-1]):b.options.length-1,q(O.value);break;default:break}},Q=e=>{if(!j.value)return;let t=e.target;t instanceof Node&&(w.value?.contains(t)||W())},$=(0,i.computed)(()=>b.modelValue!==``);return(0,i.watch)(j,e=>{e?(document.addEventListener(`pointerdown`,Q),H()):document.removeEventListener(`pointerdown`,Q)},{immediate:!0}),(0,i.watch)(()=>b.options.length,e=>{j.value&&O.value>=e&&(O.value=e-1)}),(0,i.onBeforeUnmount)(()=>{document.removeEventListener(`pointerdown`,Q)}),(t,a)=>((0,i.openBlock)(),(0,i.createElementBlock)(`div`,{ref_key:`rootRef`,ref:w,class:(0,i.normalizeClass)(P.value),style:(0,i.normalizeStyle)(L.value),"data-state":(0,i.unref)(j)?`open`:`closed`},[(0,i.createElementVNode)(`button`,(0,i.mergeProps)({ref_key:`triggerRef`,ref:T},A.value,{type:`button`,class:`t-language-select__trigger`,disabled:v.disabled,"aria-controls":(0,i.unref)(j)?(0,i.unref)(C):void 0,"aria-haspopup":`listbox`,"aria-expanded":(0,i.unref)(j),"aria-invalid":v.invalid||void 0,"aria-busy":v.loading||void 0,onClick:G,onKeydown:X}),[t.$slots.prefix?((0,i.openBlock)(),(0,i.createElementBlock)(`span`,c,[(0,i.renderSlot)(t.$slots,`prefix`)])):(0,i.createCommentVNode)(``,!0),N.value?((0,i.openBlock)(),(0,i.createBlock)(r.default,{key:1,name:`languages`,class:`t-language-select__translate`})):R.value?.code?((0,i.openBlock)(),(0,i.createBlock)(n.default,{key:2,code:R.value.code,"base-url":v.flagBaseUrl,size:v.size},null,8,[`code`,`base-url`,`size`])):(0,i.createCommentVNode)(``,!0),B.value?((0,i.openBlock)(),(0,i.createElementBlock)(`span`,{key:3,class:`t-language-select__value`,"data-placeholder":!$.value||void 0},(0,i.toDisplayString)(z.value||v.placeholder),9,l)):(0,i.createCommentVNode)(``,!0),N.value&&R.value?.code?((0,i.openBlock)(),(0,i.createBlock)(n.default,{key:4,code:R.value.code,"base-url":v.flagBaseUrl,size:v.size},null,8,[`code`,`base-url`,`size`])):(0,i.createCommentVNode)(``,!0),v.loading?((0,i.openBlock)(),(0,i.createBlock)(e.default,{key:5,size:`sm`,label:`Loading`})):((0,i.openBlock)(),(0,i.createElementBlock)(`svg`,u,[...a[0]||=[(0,i.createElementVNode)(`polyline`,{points:`4 6 8 10 12 6`},null,-1)]]))],16,s),(0,i.createVNode)(i.Transition,{name:`t-fade`},{default:(0,i.withCtx)(()=>[(0,i.unref)(j)&&!v.disabled?((0,i.openBlock)(),(0,i.createElementBlock)(`ul`,{key:0,id:(0,i.unref)(C),ref_key:`listboxRef`,ref:E,role:`listbox`,class:`t-language-select__listbox`,"aria-label":typeof A.value[`aria-label`]==`string`?A.value[`aria-label`]:void 0},[v.options.length===0?((0,i.openBlock)(),(0,i.createElementBlock)(`li`,f,[(0,i.renderSlot)(t.$slots,`empty`,{},()=>[(0,i.createTextVNode)((0,i.toDisplayString)(v.emptyText),1)])])):(0,i.createCommentVNode)(``,!0),((0,i.openBlock)(!0),(0,i.createElementBlock)(i.Fragment,null,(0,i.renderList)(v.options,(e,r)=>((0,i.openBlock)(),(0,i.createElementBlock)(`li`,{key:e.value,ref_for:!0,ref:t=>J(t,e.value),role:`option`,class:(0,i.normalizeClass)([`t-language-select__option`,{"is-selected":e.value===v.modelValue,"is-disabled":e.disabled,"is-focused":r===O.value}]),"aria-selected":e.value===v.modelValue,"aria-disabled":e.disabled||void 0,tabindex:e.disabled?-1:0,onClick:t=>K(e),onKeydown:t=>Z(t,e,r)},[(0,i.renderSlot)(t.$slots,`option`,{option:e,selected:e.value===v.modelValue,focused:r===O.value},()=>[e.code?((0,i.openBlock)(),(0,i.createBlock)(n.default,{key:0,code:e.code,"base-url":v.flagBaseUrl,size:v.size},null,8,[`code`,`base-url`,`size`])):(0,i.createCommentVNode)(``,!0),(0,i.createElementVNode)(`span`,m,[(0,i.createElementVNode)(`span`,h,(0,i.toDisplayString)(e.label),1),e.description?((0,i.openBlock)(),(0,i.createElementBlock)(`span`,g,(0,i.toDisplayString)(e.description),1)):(0,i.createCommentVNode)(``,!0)])]),e.value===v.modelValue?((0,i.openBlock)(),(0,i.createElementBlock)(`svg`,_,[...a[1]||=[(0,i.createElementVNode)(`polyline`,{points:`3.5 8.5 6.5 11.5 12.5 4.5`},null,-1)]])):(0,i.createCommentVNode)(``,!0)],42,p))),128))],8,d)):(0,i.createCommentVNode)(``,!0)]),_:3})],14,o))}});exports.default=v;
2
+ //# sourceMappingURL=TLanguageSelect.vue_vue_type_script_setup_true_lang.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TLanguageSelect.vue_vue_type_script_setup_true_lang.cjs","names":["$slots"],"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":"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"}