@nuxt/ui 3.0.0-alpha.8 → 3.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (310) hide show
  1. package/.nuxt/ui/accordion.ts +2 -2
  2. package/.nuxt/ui/alert.ts +122 -95
  3. package/.nuxt/ui/avatar-group.ts +14 -2
  4. package/.nuxt/ui/avatar.ts +16 -4
  5. package/.nuxt/ui/badge.ts +139 -93
  6. package/.nuxt/ui/breadcrumb.ts +5 -5
  7. package/.nuxt/ui/button-group.ts +13 -0
  8. package/.nuxt/ui/button.ts +167 -136
  9. package/.nuxt/ui/calendar.ts +103 -0
  10. package/.nuxt/ui/card.ts +27 -1
  11. package/.nuxt/ui/carousel.ts +9 -7
  12. package/.nuxt/ui/checkbox.ts +45 -27
  13. package/.nuxt/ui/chip.ts +44 -15
  14. package/.nuxt/ui/color-picker.ts +47 -0
  15. package/.nuxt/ui/command-palette.ts +28 -18
  16. package/.nuxt/ui/container.ts +1 -1
  17. package/.nuxt/ui/context-menu.ts +64 -46
  18. package/.nuxt/ui/drawer.ts +83 -15
  19. package/.nuxt/ui/dropdown-menu.ts +65 -47
  20. package/.nuxt/ui/form-field.ts +15 -7
  21. package/.nuxt/ui/index.ts +7 -1
  22. package/.nuxt/ui/input-menu.ts +150 -116
  23. package/.nuxt/ui/input-number.ts +235 -0
  24. package/.nuxt/ui/input.ts +111 -76
  25. package/.nuxt/ui/kbd.ts +18 -6
  26. package/.nuxt/ui/link.ts +3 -3
  27. package/.nuxt/ui/modal.ts +8 -7
  28. package/.nuxt/ui/navigation-menu.ts +244 -131
  29. package/.nuxt/ui/pin-input.ts +171 -0
  30. package/.nuxt/ui/popover.ts +2 -2
  31. package/.nuxt/ui/progress.ts +111 -72
  32. package/.nuxt/ui/radio-group.ts +45 -22
  33. package/.nuxt/ui/select-menu.ts +127 -96
  34. package/.nuxt/ui/select.ts +120 -84
  35. package/.nuxt/ui/separator.ts +60 -31
  36. package/.nuxt/ui/skeleton.ts +1 -1
  37. package/.nuxt/ui/slideover.ts +17 -9
  38. package/.nuxt/ui/slider.ts +61 -38
  39. package/.nuxt/ui/stepper.ts +202 -0
  40. package/.nuxt/ui/switch.ts +40 -22
  41. package/.nuxt/ui/table.ts +49 -31
  42. package/.nuxt/ui/tabs.ts +101 -73
  43. package/.nuxt/ui/textarea.ts +111 -76
  44. package/.nuxt/ui/toast.ts +53 -33
  45. package/.nuxt/ui/toaster.ts +31 -15
  46. package/.nuxt/ui/tooltip.ts +2 -2
  47. package/.nuxt/ui/tree.ts +154 -0
  48. package/.nuxt/ui.css +92 -0
  49. package/README.md +69 -18
  50. package/cli/commands/{init.mjs → make/component.mjs} +16 -4
  51. package/cli/commands/make/index.mjs +14 -0
  52. package/cli/commands/make/locale.mjs +64 -0
  53. package/cli/index.mjs +2 -2
  54. package/cli/package.json +2 -2
  55. package/cli/templates.mjs +62 -12
  56. package/cli/utils.mjs +14 -0
  57. package/dist/module.cjs +9 -170
  58. package/dist/module.d.cts +5 -15
  59. package/dist/module.d.mts +5 -15
  60. package/dist/module.d.ts +5 -15
  61. package/dist/module.json +2 -2
  62. package/dist/module.mjs +12 -167
  63. package/dist/runtime/components/Accordion.vue +8 -39
  64. package/dist/runtime/components/Alert.vue +25 -21
  65. package/dist/runtime/components/App.vue +15 -15
  66. package/dist/runtime/components/Avatar.vue +33 -21
  67. package/dist/runtime/components/AvatarGroup.vue +5 -7
  68. package/dist/runtime/components/Badge.vue +10 -8
  69. package/dist/runtime/components/Breadcrumb.vue +13 -34
  70. package/dist/runtime/components/Button.vue +5 -8
  71. package/dist/runtime/components/ButtonGroup.vue +5 -7
  72. package/dist/runtime/components/Calendar.vue +194 -0
  73. package/dist/runtime/components/Card.vue +10 -9
  74. package/dist/runtime/components/Carousel.vue +26 -17
  75. package/dist/runtime/components/Checkbox.vue +32 -38
  76. package/dist/runtime/components/Chip.vue +6 -8
  77. package/dist/runtime/components/Collapsible.vue +10 -11
  78. package/dist/runtime/components/ColorPicker.vue +297 -0
  79. package/dist/runtime/components/CommandPalette.vue +114 -98
  80. package/dist/runtime/components/Container.vue +4 -7
  81. package/dist/runtime/components/ContextMenu.vue +16 -69
  82. package/dist/runtime/components/ContextMenuContent.vue +14 -10
  83. package/dist/runtime/components/Drawer.vue +23 -12
  84. package/dist/runtime/components/DropdownMenu.vue +18 -59
  85. package/dist/runtime/components/DropdownMenuContent.vue +14 -10
  86. package/dist/runtime/components/Form.vue +78 -49
  87. package/dist/runtime/components/FormField.vue +28 -15
  88. package/dist/runtime/components/Icon.vue +1 -1
  89. package/dist/runtime/components/Input.vue +15 -7
  90. package/dist/runtime/components/InputMenu.vue +133 -66
  91. package/dist/runtime/components/InputNumber.vue +197 -0
  92. package/dist/runtime/components/Kbd.vue +5 -6
  93. package/dist/runtime/components/Link.vue +24 -13
  94. package/dist/runtime/components/LinkBase.vue +1 -1
  95. package/dist/runtime/components/Modal.vue +56 -34
  96. package/dist/runtime/components/NavigationMenu.vue +129 -112
  97. package/dist/runtime/components/OverlayProvider.vue +28 -0
  98. package/dist/runtime/components/Pagination.vue +18 -14
  99. package/dist/runtime/components/PinInput.vue +95 -0
  100. package/dist/runtime/components/Popover.vue +28 -14
  101. package/dist/runtime/components/Progress.vue +28 -12
  102. package/dist/runtime/components/RadioGroup.vue +9 -12
  103. package/dist/runtime/components/Select.vue +61 -35
  104. package/dist/runtime/components/SelectMenu.vue +201 -130
  105. package/dist/runtime/components/Separator.vue +6 -5
  106. package/dist/runtime/components/Skeleton.vue +4 -7
  107. package/dist/runtime/components/Slideover.vue +56 -33
  108. package/dist/runtime/components/Slider.vue +8 -7
  109. package/dist/runtime/components/Stepper.vue +170 -0
  110. package/dist/runtime/components/Switch.vue +19 -23
  111. package/dist/runtime/components/Table.vue +157 -32
  112. package/dist/runtime/components/Tabs.vue +12 -31
  113. package/dist/runtime/components/Textarea.vue +18 -9
  114. package/dist/runtime/components/Toast.vue +39 -25
  115. package/dist/runtime/components/Toaster.vue +31 -24
  116. package/dist/runtime/components/Tooltip.vue +9 -12
  117. package/dist/runtime/components/Tree.vue +196 -0
  118. package/dist/runtime/composables/defineLocale.d.ts +9 -0
  119. package/dist/runtime/composables/defineLocale.js +4 -0
  120. package/dist/runtime/composables/defineShortcuts.d.ts +1 -1
  121. package/dist/runtime/composables/defineShortcuts.js +2 -2
  122. package/dist/runtime/composables/useFormField.d.ts +11 -4
  123. package/dist/runtime/composables/useFormField.js +19 -9
  124. package/dist/runtime/composables/useLocale.d.ts +4 -0
  125. package/dist/runtime/composables/useLocale.js +10 -0
  126. package/dist/runtime/composables/useOverlay.d.ts +30 -0
  127. package/dist/runtime/composables/useOverlay.js +70 -0
  128. package/dist/runtime/composables/useToast.d.ts +4 -3
  129. package/dist/runtime/composables/useToast.js +20 -6
  130. package/dist/runtime/index.css +1 -1
  131. package/dist/runtime/keyframes.css +1 -1
  132. package/dist/runtime/locale/ar.d.ts +2 -0
  133. package/dist/runtime/locale/ar.js +55 -0
  134. package/dist/runtime/locale/az.d.ts +2 -0
  135. package/dist/runtime/locale/az.js +54 -0
  136. package/dist/runtime/locale/bn.d.ts +2 -0
  137. package/dist/runtime/locale/bn.js +54 -0
  138. package/dist/runtime/locale/cs.d.ts +2 -0
  139. package/dist/runtime/locale/cs.js +54 -0
  140. package/dist/runtime/locale/da.d.ts +2 -0
  141. package/dist/runtime/locale/da.js +54 -0
  142. package/dist/runtime/locale/de.d.ts +2 -0
  143. package/dist/runtime/locale/de.js +54 -0
  144. package/dist/runtime/locale/el.d.ts +2 -0
  145. package/dist/runtime/locale/el.js +54 -0
  146. package/dist/runtime/locale/en.d.ts +2 -0
  147. package/dist/runtime/locale/en.js +54 -0
  148. package/dist/runtime/locale/es.d.ts +2 -0
  149. package/dist/runtime/locale/es.js +54 -0
  150. package/dist/runtime/locale/et.d.ts +2 -0
  151. package/dist/runtime/locale/et.js +54 -0
  152. package/dist/runtime/locale/fa_ir.d.ts +2 -0
  153. package/dist/runtime/locale/fa_ir.js +55 -0
  154. package/dist/runtime/locale/fi.d.ts +2 -0
  155. package/dist/runtime/locale/fi.js +54 -0
  156. package/dist/runtime/locale/fr.d.ts +2 -0
  157. package/dist/runtime/locale/fr.js +54 -0
  158. package/dist/runtime/locale/he.d.ts +2 -0
  159. package/dist/runtime/locale/he.js +53 -0
  160. package/dist/runtime/locale/hi.d.ts +2 -0
  161. package/dist/runtime/locale/hi.js +54 -0
  162. package/dist/runtime/locale/hu.d.ts +2 -0
  163. package/dist/runtime/locale/hu.js +54 -0
  164. package/dist/runtime/locale/id.d.ts +2 -0
  165. package/dist/runtime/locale/id.js +54 -0
  166. package/dist/runtime/locale/index.d.ts +34 -0
  167. package/dist/runtime/locale/index.js +34 -0
  168. package/dist/runtime/locale/it.d.ts +2 -0
  169. package/dist/runtime/locale/it.js +54 -0
  170. package/dist/runtime/locale/ja.d.ts +2 -0
  171. package/dist/runtime/locale/ja.js +54 -0
  172. package/dist/runtime/locale/km.d.ts +2 -0
  173. package/dist/runtime/locale/km.js +54 -0
  174. package/dist/runtime/locale/ko.d.ts +2 -0
  175. package/dist/runtime/locale/ko.js +54 -0
  176. package/dist/runtime/locale/nb_no.d.ts +2 -0
  177. package/dist/runtime/locale/nb_no.js +54 -0
  178. package/dist/runtime/locale/nl.d.ts +2 -0
  179. package/dist/runtime/locale/nl.js +54 -0
  180. package/dist/runtime/locale/pl.d.ts +2 -0
  181. package/dist/runtime/locale/pl.js +54 -0
  182. package/dist/runtime/locale/pt.d.ts +2 -0
  183. package/dist/runtime/locale/pt.js +54 -0
  184. package/dist/runtime/locale/pt_br.d.ts +2 -0
  185. package/dist/runtime/locale/pt_br.js +54 -0
  186. package/dist/runtime/locale/ru.d.ts +2 -0
  187. package/dist/runtime/locale/ru.js +54 -0
  188. package/dist/runtime/locale/sk.d.ts +2 -0
  189. package/dist/runtime/locale/sk.js +54 -0
  190. package/dist/runtime/locale/sv.d.ts +2 -0
  191. package/dist/runtime/locale/sv.js +54 -0
  192. package/dist/runtime/locale/th.d.ts +2 -0
  193. package/dist/runtime/locale/th.js +54 -0
  194. package/dist/runtime/locale/tr.d.ts +2 -0
  195. package/dist/runtime/locale/tr.js +54 -0
  196. package/dist/runtime/locale/uk.d.ts +2 -0
  197. package/dist/runtime/locale/uk.js +54 -0
  198. package/dist/runtime/locale/vi.d.ts +2 -0
  199. package/dist/runtime/locale/vi.js +54 -0
  200. package/dist/runtime/locale/zh_cn.d.ts +2 -0
  201. package/dist/runtime/locale/zh_cn.js +54 -0
  202. package/dist/runtime/locale/zh_tw.d.ts +2 -0
  203. package/dist/runtime/locale/zh_tw.js +54 -0
  204. package/dist/runtime/plugins/colors.js +8 -5
  205. package/dist/runtime/types/form.d.ts +29 -15
  206. package/dist/runtime/types/form.js +2 -2
  207. package/dist/runtime/types/index.d.ts +8 -0
  208. package/dist/runtime/types/index.js +8 -0
  209. package/dist/runtime/types/locale.d.ts +56 -0
  210. package/dist/runtime/types/locale.js +0 -0
  211. package/dist/runtime/types/utils.d.ts +7 -3
  212. package/dist/runtime/utils/form.d.ts +5 -9
  213. package/dist/runtime/utils/form.js +113 -50
  214. package/dist/runtime/utils/index.d.ts +1 -1
  215. package/dist/runtime/utils/index.js +15 -2
  216. package/dist/runtime/utils/link.d.ts +1 -1
  217. package/dist/runtime/utils/locale.d.ts +15 -0
  218. package/dist/runtime/utils/locale.js +25 -0
  219. package/dist/runtime/utils/tv.d.ts +1 -0
  220. package/dist/runtime/utils/tv.js +4 -0
  221. package/dist/runtime/vue/components/Icon.vue +2 -2
  222. package/dist/runtime/vue/components/Link.vue +87 -23
  223. package/dist/runtime/vue/composables/useAppConfig.d.ts +1 -0
  224. package/dist/runtime/vue/composables/useAppConfig.js +2 -0
  225. package/dist/runtime/vue/plugins/head.d.ts +1 -1
  226. package/dist/runtime/vue/plugins/head.js +3 -3
  227. package/dist/runtime/vue/stubs.d.ts +19 -0
  228. package/dist/runtime/vue/stubs.js +37 -0
  229. package/dist/shared/{ui.BufKhDiD.mjs → ui.DSPu6Y8j.mjs} +1071 -409
  230. package/dist/shared/{ui.BU7wzH0D.cjs → ui.DfK2RX6G.cjs} +1074 -409
  231. package/dist/unplugin.cjs +62 -26
  232. package/dist/unplugin.d.cts +17 -3
  233. package/dist/unplugin.d.mts +17 -3
  234. package/dist/unplugin.d.ts +17 -3
  235. package/dist/unplugin.mjs +61 -25
  236. package/dist/vite.cjs +3 -2
  237. package/dist/vite.d.cts +2 -0
  238. package/dist/vite.d.mts +2 -0
  239. package/dist/vite.d.ts +2 -0
  240. package/dist/vite.mjs +3 -2
  241. package/package.json +110 -67
  242. package/dist/chunks/component-meta.cjs +0 -73264
  243. package/dist/chunks/component-meta.mjs +0 -73262
  244. package/dist/client/devtools/200.html +0 -1
  245. package/dist/client/devtools/404.html +0 -1
  246. package/dist/client/devtools/_fonts/rP2Wp2ywxg089UriCZaSExd86J3t9jz86MvyyKK58UfivUw4aw-SK92LTmKP0.woff2 +0 -0
  247. package/dist/client/devtools/_fonts/rP2Wp2ywxg089UriCZaSExd86J3t9jz86MvyyKy58UfivUw-9uYm8d48E4.woff2 +0 -0
  248. package/dist/client/devtools/_fonts/rP2Yp2ywxg089UriI5-g4vlH9VoD8Cmcqbu0-K6z9mXg-VNw7NHvqnp.woff2 +0 -0
  249. package/dist/client/devtools/_fonts/rP2Yp2ywxg089UriI5-g4vlH9VoD8Cmcqbu6-K6z9mXgjU0-EkYdMs3MAX.woff2 +0 -0
  250. package/dist/client/devtools/_fonts/rP2rp2ywxg089UriCZaSExd86J3t9jz86Mvy4qCRAL19DksVat-7DW3w-R7n06NbhGy.woff +0 -0
  251. package/dist/client/devtools/_fonts/rP2rp2ywxg089UriCZaSExd86J3t9jz86Mvy4qCRAL19DksVat-JDG3w-leGNfkTmsj.woff +0 -0
  252. package/dist/client/devtools/_fonts/rP2rp2ywxg089UriCZaSExd86J3t9jz86Mvy4qCRAL19DksVat-JDW3w-ciElMF03ku.woff +0 -0
  253. package/dist/client/devtools/_fonts/rP2rp2ywxg089UriCZaSExd86J3t9jz86Mvy4qCRAL19DksVat8JCm3w-sB722Uoa0f.woff +0 -0
  254. package/dist/client/devtools/_fonts/rP2rp2ywxg089UriCZaSExd86J3t9jz86Mvy4qCRAL19DksVat8JDW3w-PBDI1JDjwV.woff +0 -0
  255. package/dist/client/devtools/_fonts/rP2rp2ywxg089UriCZaSExd86J3t9jz86Mvy4qCRAL19DksVat8gCm3w-0q1hqVn09O.woff +0 -0
  256. package/dist/client/devtools/_fonts/rP2rp2ywxg089UriCZaSExd86J3t9jz86Mvy4qCRAL19DksVat9XCm3w-ByvzuLy2fa.woff +0 -0
  257. package/dist/client/devtools/_fonts/rP2rp2ywxg089UriCZaSExd86J3t9jz86Mvy4qCRAL19DksVat9uCm3w-5TKCIs6EEw.woff +0 -0
  258. package/dist/client/devtools/_fonts/rP2rp2ywxg089UriCZaSExd86J3t9jz86Mvy4qCRAL19DksVat_XDW3w-r2swRDGfn2.woff +0 -0
  259. package/dist/client/devtools/_fonts/rP2tp2ywxg089UriI5-g4vlH9VoD8CmcqZG40F9JadbnoEwAC5thTQ-ldB60l5U8r.woff +0 -0
  260. package/dist/client/devtools/_fonts/rP2tp2ywxg089UriI5-g4vlH9VoD8CmcqZG40F9JadbnoEwAIpthTQ-wbFKGFDrgo.woff +0 -0
  261. package/dist/client/devtools/_fonts/rP2tp2ywxg089UriI5-g4vlH9VoD8CmcqZG40F9JadbnoEwAIpxhTQ-LLUBv5jskj.woff +0 -0
  262. package/dist/client/devtools/_fonts/rP2tp2ywxg089UriI5-g4vlH9VoD8CmcqZG40F9JadbnoEwARZthTQ-F71RdTRbNg.woff +0 -0
  263. package/dist/client/devtools/_fonts/rP2tp2ywxg089UriI5-g4vlH9VoD8CmcqZG40F9JadbnoEwA_JxhTQ-6nh65gZkc0.woff +0 -0
  264. package/dist/client/devtools/_fonts/rP2tp2ywxg089UriI5-g4vlH9VoD8CmcqZG40F9JadbnoEwAfJthTQ-aMoWEJLmTt.woff +0 -0
  265. package/dist/client/devtools/_fonts/rP2tp2ywxg089UriI5-g4vlH9VoD8CmcqZG40F9JadbnoEwAkJxhTQ-WMBvmLOgKm.woff +0 -0
  266. package/dist/client/devtools/_fonts/rP2tp2ywxg089UriI5-g4vlH9VoD8CmcqZG40F9JadbnoEwAop1hTQ-utGC2hnWq2.woff +0 -0
  267. package/dist/client/devtools/_fonts/rP2tp2ywxg089UriI5-g4vlH9VoD8CmcqZG40F9JadbnoEwAopxhTQ-YpyTieVa5B.woff +0 -0
  268. package/dist/client/devtools/_nuxt/Bj9hdafK.js +0 -1
  269. package/dist/client/devtools/_nuxt/DM3_wAgz.js +0 -62
  270. package/dist/client/devtools/_nuxt/builds/latest.json +0 -1
  271. package/dist/client/devtools/_nuxt/builds/meta/e6b425f7-b919-4014-915b-0ea07c316790.json +0 -1
  272. package/dist/client/devtools/_nuxt/entry.DaJP-ZMB.css +0 -24
  273. package/dist/client/devtools/_nuxt/error-404.B0Nmv3fV.css +0 -1
  274. package/dist/client/devtools/_nuxt/error-500.C8fgTxVz.css +0 -1
  275. package/dist/client/devtools/_nuxt/hKiujHsR.js +0 -1
  276. package/dist/client/devtools/favicon.svg +0 -1
  277. package/dist/client/devtools/index.html +0 -1
  278. package/dist/devtools/runtime/DevtoolsRenderer.vue +0 -53
  279. package/dist/devtools/runtime/examples/AvatarGroupExample.vue +0 -7
  280. package/dist/devtools/runtime/examples/ButtonGroupExample.vue +0 -8
  281. package/dist/devtools/runtime/examples/CardExample.vue +0 -13
  282. package/dist/devtools/runtime/examples/CarouselExample.vue +0 -13
  283. package/dist/devtools/runtime/examples/ChipExample.vue +0 -5
  284. package/dist/devtools/runtime/examples/CollapsibleExample.vue +0 -8
  285. package/dist/devtools/runtime/examples/CommandPaletteExample.vue +0 -29
  286. package/dist/devtools/runtime/examples/ContainerExample.vue +0 -5
  287. package/dist/devtools/runtime/examples/ContextMenuExample.vue +0 -5
  288. package/dist/devtools/runtime/examples/DrawerExample.vue +0 -8
  289. package/dist/devtools/runtime/examples/DropdownMenuExample.vue +0 -5
  290. package/dist/devtools/runtime/examples/FormExample.vue +0 -30
  291. package/dist/devtools/runtime/examples/FormFieldExample.vue +0 -5
  292. package/dist/devtools/runtime/examples/LinkExample.vue +0 -5
  293. package/dist/devtools/runtime/examples/ModalExample.vue +0 -8
  294. package/dist/devtools/runtime/examples/PopoverExample.vue +0 -8
  295. package/dist/devtools/runtime/examples/SkeletonExample.vue +0 -3
  296. package/dist/devtools/runtime/examples/SlideoverExample.vue +0 -8
  297. package/dist/devtools/runtime/examples/ToasterExample.vue +0 -11
  298. package/dist/devtools/runtime/examples/TooltipExample.vue +0 -5
  299. package/dist/runtime/components/ModalProvider.vue +0 -12
  300. package/dist/runtime/components/SlideoverProvider.vue +0 -12
  301. package/dist/runtime/composables/extendDevtoolsMeta.d.ts +0 -7
  302. package/dist/runtime/composables/extendDevtoolsMeta.js +0 -2
  303. package/dist/runtime/composables/useModal.d.ts +0 -17
  304. package/dist/runtime/composables/useModal.js +0 -46
  305. package/dist/runtime/composables/useSlideover.d.ts +0 -17
  306. package/dist/runtime/composables/useSlideover.js +0 -46
  307. package/dist/runtime/plugins/modal.d.ts +0 -2
  308. package/dist/runtime/plugins/modal.js +0 -10
  309. package/dist/runtime/plugins/slideover.d.ts +0 -2
  310. package/dist/runtime/plugins/slideover.js +0 -10
@@ -1,18 +1,26 @@
1
1
  <script lang="ts">
2
- import { tv, type VariantProps } from 'tailwind-variants'
2
+ import type { VariantProps } from 'tailwind-variants'
3
3
  import type { AppConfig } from '@nuxt/schema'
4
4
  import _appConfig from '#build/app.config'
5
5
  import theme from '#build/ui/form-field'
6
- import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
6
+ import { tv } from '../utils/tv'
7
7
 
8
- const appConfig = _appConfig as AppConfig & { ui: { formField: Partial<typeof theme> } }
8
+ const appConfigFormField = _appConfig as AppConfig & { ui: { formField: Partial<typeof theme> } }
9
9
 
10
- const formField = tv({ extend: tv(theme), ...(appConfig.ui?.formField || {}) })
10
+ const formField = tv({ extend: tv(theme), ...(appConfigFormField.ui?.formField || {}) })
11
11
 
12
12
  type FormFieldVariants = VariantProps<typeof formField>
13
13
 
14
14
  export interface FormFieldProps {
15
+ /**
16
+ * The element or component this component should render as.
17
+ * @defaultValue 'div'
18
+ */
19
+ as?: any
20
+ /** The name of the FormField. Also used to match form errors. */
15
21
  name?: string
22
+ /** A regular expression to match form error names. */
23
+ errorPattern?: RegExp
16
24
  label?: string
17
25
  description?: string
18
26
  help?: string
@@ -34,13 +42,11 @@ export interface FormFieldSlots {
34
42
  error(props: { error?: string | boolean }): any
35
43
  default(props: { error?: string | boolean }): any
36
44
  }
37
-
38
- extendDevtoolsMeta({ example: 'FormFieldExample', defaultProps: { label: 'Label' } })
39
45
  </script>
40
46
 
41
47
  <script setup lang="ts">
42
48
  import { computed, ref, inject, provide, type Ref, useId } from 'vue'
43
- import { Label } from 'radix-vue'
49
+ import { Primitive, Label } from 'reka-ui'
44
50
  import { formFieldInjectionKey, inputIdInjectionKey } from '../composables/useFormField'
45
51
  import type { FormError, FormFieldInjectedOptions } from '../types/form'
46
52
 
@@ -54,9 +60,12 @@ const ui = computed(() => formField({
54
60
 
55
61
  const formErrors = inject<Ref<FormError[]> | null>('form-errors', null)
56
62
 
57
- const error = computed(() => props.error || formErrors?.value?.find(error => error.name === props.name)?.message)
63
+ const error = computed(() => props.error || formErrors?.value?.find(error => error.name === props.name || (props.errorPattern && error.name.match(props.errorPattern)))?.message)
58
64
 
59
65
  const id = ref(useId())
66
+ // Copies id's initial value to bind aria-attributes such as aria-describedby.
67
+ // This is required for the RadioGroup component which unsets the id value.
68
+ const ariaId = id.value
60
69
 
61
70
  provide(inputIdInjectionKey, id)
62
71
 
@@ -65,12 +74,16 @@ provide(formFieldInjectionKey, computed(() => ({
65
74
  name: props.name,
66
75
  size: props.size,
67
76
  eagerValidation: props.eagerValidation,
68
- validateOnInputDelay: props.validateOnInputDelay
77
+ validateOnInputDelay: props.validateOnInputDelay,
78
+ errorPattern: props.errorPattern,
79
+ hint: props.hint,
80
+ description: props.description,
81
+ ariaId
69
82
  }) as FormFieldInjectedOptions<FormFieldProps>))
70
83
  </script>
71
84
 
72
85
  <template>
73
- <div :class="ui.root({ class: [props.class, props.ui?.root] })">
86
+ <Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
74
87
  <div :class="ui.wrapper({ class: props.ui?.wrapper })">
75
88
  <div v-if="label || !!slots.label" :class="ui.labelWrapper({ class: props.ui?.labelWrapper })">
76
89
  <Label :for="id" :class="ui.label({ class: props.ui?.label })">
@@ -78,24 +91,24 @@ provide(formFieldInjectionKey, computed(() => ({
78
91
  {{ label }}
79
92
  </slot>
80
93
  </Label>
81
- <span v-if="hint || !!slots.hint" :class="ui.hint({ class: props.ui?.hint })">
94
+ <span v-if="hint || !!slots.hint" :id="`${ariaId}-hint`" :class="ui.hint({ class: props.ui?.hint })">
82
95
  <slot name="hint" :hint="hint">
83
96
  {{ hint }}
84
97
  </slot>
85
98
  </span>
86
99
  </div>
87
100
 
88
- <p v-if="description || !!slots.description" :class="ui.description({ class: props.ui?.description })">
101
+ <p v-if="description || !!slots.description" :id="`${ariaId}-description`" :class="ui.description({ class: props.ui?.description })">
89
102
  <slot name="description" :description="description">
90
103
  {{ description }}
91
104
  </slot>
92
105
  </p>
93
106
  </div>
94
107
 
95
- <div :class="[label && ui.container({ class: props.ui?.container })]">
108
+ <div :class="[(label || !!slots.label || description || !!slots.description) && ui.container({ class: props.ui?.container })]">
96
109
  <slot :error="error" />
97
110
 
98
- <p v-if="(typeof error === 'string' && error) || !!slots.error" :class="ui.error({ class: props.ui?.error })">
111
+ <p v-if="(typeof error === 'string' && error) || !!slots.error" :id="`${ariaId}-error`" :class="ui.error({ class: props.ui?.error })">
99
112
  <slot name="error" :error="error">
100
113
  {{ error }}
101
114
  </slot>
@@ -106,5 +119,5 @@ provide(formFieldInjectionKey, computed(() => ({
106
119
  </slot>
107
120
  </p>
108
121
  </div>
109
- </div>
122
+ </Primitive>
110
123
  </template>
@@ -13,7 +13,7 @@ export interface IconProps {
13
13
  </script>
14
14
 
15
15
  <script setup lang="ts">
16
- import { useForwardProps } from 'radix-vue'
16
+ import { useForwardProps } from 'reka-ui'
17
17
  import { reactivePick } from '@vueuse/core'
18
18
 
19
19
  const props = defineProps<IconProps>()
@@ -1,20 +1,26 @@
1
1
  <script lang="ts">
2
2
  import type { InputHTMLAttributes } from 'vue'
3
- import { tv, type VariantProps } from 'tailwind-variants'
3
+ import type { VariantProps } from 'tailwind-variants'
4
4
  import type { AppConfig } from '@nuxt/schema'
5
5
  import _appConfig from '#build/app.config'
6
6
  import theme from '#build/ui/input'
7
7
  import type { UseComponentIconsProps } from '../composables/useComponentIcons'
8
+ import { tv } from '../utils/tv'
8
9
  import type { AvatarProps } from '../types'
9
10
  import type { PartialString } from '../types/utils'
10
11
 
11
- const appConfig = _appConfig as AppConfig & { ui: { input: Partial<typeof theme> } }
12
+ const appConfigInput = _appConfig as AppConfig & { ui: { input: Partial<typeof theme> } }
12
13
 
13
- const input = tv({ extend: tv(theme), ...(appConfig.ui?.input || {}) })
14
+ const input = tv({ extend: tv(theme), ...(appConfigInput.ui?.input || {}) })
14
15
 
15
16
  type InputVariants = VariantProps<typeof input>
16
17
 
17
18
  export interface InputProps extends UseComponentIconsProps {
19
+ /**
20
+ * The element or component this component should render as.
21
+ * @defaultValue 'div'
22
+ */
23
+ as?: any
18
24
  id?: string
19
25
  name?: string
20
26
  type?: InputHTMLAttributes['type']
@@ -49,6 +55,7 @@ export interface InputSlots {
49
55
 
50
56
  <script setup lang="ts">
51
57
  import { ref, computed, onMounted } from 'vue'
58
+ import { Primitive } from 'reka-ui'
52
59
  import { useButtonGroup } from '../composables/useButtonGroup'
53
60
  import { useComponentIcons } from '../composables/useComponentIcons'
54
61
  import { useFormField } from '../composables/useFormField'
@@ -68,7 +75,7 @@ const slots = defineSlots<InputSlots>()
68
75
 
69
76
  const [modelValue, modelModifiers] = defineModel<string | number>()
70
77
 
71
- const { emitFormBlur, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled } = useFormField<InputProps>(props)
78
+ const { emitFormBlur, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled, emitFormFocus, ariaAttrs } = useFormField<InputProps>(props, { deferInputValidation: true })
72
79
  const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
73
80
  const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)
74
81
 
@@ -147,7 +154,7 @@ onMounted(() => {
147
154
  </script>
148
155
 
149
156
  <template>
150
- <div :class="ui.root({ class: [props.class, props.ui?.root] })">
157
+ <Primitive :as="as" :class="ui.root({ class: [props.class, props.ui?.root] })">
151
158
  <input
152
159
  :id="id"
153
160
  ref="inputRef"
@@ -159,10 +166,11 @@ onMounted(() => {
159
166
  :disabled="disabled"
160
167
  :required="required"
161
168
  :autocomplete="autocomplete"
162
- v-bind="$attrs"
169
+ v-bind="{ ...$attrs, ...ariaAttrs }"
163
170
  @input="onInput"
164
171
  @blur="onBlur"
165
172
  @change="onChange"
173
+ @focus="emitFormFocus"
166
174
  >
167
175
 
168
176
  <slot />
@@ -179,5 +187,5 @@ onMounted(() => {
179
187
  <UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon({ class: props.ui?.trailingIcon })" />
180
188
  </slot>
181
189
  </span>
182
- </div>
190
+ </Primitive>
183
191
  </template>
@@ -1,18 +1,18 @@
1
1
  <script lang="ts">
2
2
  import type { InputHTMLAttributes } from 'vue'
3
- import { tv, type VariantProps } from 'tailwind-variants'
4
- import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxArrowProps } from 'radix-vue'
3
+ import type { VariantProps } from 'tailwind-variants'
4
+ import type { ComboboxRootProps, ComboboxRootEmits, ComboboxContentProps, ComboboxArrowProps, AcceptableValue } from 'reka-ui'
5
5
  import type { AppConfig } from '@nuxt/schema'
6
6
  import _appConfig from '#build/app.config'
7
7
  import theme from '#build/ui/input-menu'
8
8
  import type { UseComponentIconsProps } from '../composables/useComponentIcons'
9
- import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
9
+ import { tv } from '../utils/tv'
10
10
  import type { AvatarProps, ChipProps, InputProps } from '../types'
11
- import type { AcceptableValue, ArrayOrWrapped, PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
11
+ import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
12
12
 
13
- const appConfig = _appConfig as AppConfig & { ui: { inputMenu: Partial<typeof theme> } }
13
+ const appConfigInputMenu = _appConfig as AppConfig & { ui: { inputMenu: Partial<typeof theme> } }
14
14
 
15
- const inputMenu = tv({ extend: tv(theme), ...(appConfig.ui?.inputMenu || {}) })
15
+ const inputMenu = tv({ extend: tv(theme), ...(appConfigInputMenu.ui?.inputMenu || {}) })
16
16
 
17
17
  export interface InputMenuItem {
18
18
  label?: string
@@ -30,7 +30,7 @@ export interface InputMenuItem {
30
30
 
31
31
  type InputMenuVariants = VariantProps<typeof inputMenu>
32
32
 
33
- export interface InputMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<InputMenuItem | AcceptableValue> = MaybeArrayOfArray<InputMenuItem | AcceptableValue>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false> extends Pick<ComboboxRootProps<T>, 'defaultValue' | 'selectedValue' | 'open' | 'defaultOpen' | 'searchTerm' | 'disabled' | 'name' | 'resetSearchTermOnBlur'>, UseComponentIconsProps {
33
+ export interface InputMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<InputMenuItem | AcceptableValue | boolean> = MaybeArrayOfArray<InputMenuItem | AcceptableValue | boolean>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false> extends Pick<ComboboxRootProps<T>, 'open' | 'defaultOpen' | 'disabled' | 'name' | 'resetSearchTermOnBlur' | 'highlightOnHover'>, UseComponentIconsProps {
34
34
  /**
35
35
  * The element or component this component should render as.
36
36
  * @defaultValue 'div'
@@ -64,7 +64,7 @@ export interface InputMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends Ma
64
64
  deleteIcon?: string
65
65
  /**
66
66
  * The content of the menu.
67
- * @defaultValue { side: 'bottom', sideOffset: 8, position: 'popper' }
67
+ * @defaultValue { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }
68
68
  */
69
69
  content?: Omit<ComboboxContentProps, 'as' | 'asChild' | 'forceMount'>
70
70
  /**
@@ -77,12 +77,6 @@ export interface InputMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends Ma
77
77
  * @defaultValue true
78
78
  */
79
79
  portal?: boolean
80
- /**
81
- * Whether to filter items or not, can be an array of fields to filter.
82
- * When `false`, items will not be filtered which is useful for custom filtering.
83
- * @defaultValue ['label']
84
- */
85
- filter?: boolean | string[]
86
80
  /**
87
81
  * When `items` is an array of objects, select the field to use as the value instead of the object itself.
88
82
  * @defaultValue undefined
@@ -92,29 +86,47 @@ export interface InputMenuProps<T extends MaybeArrayOfArrayItem<I>, I extends Ma
92
86
  * When `items` is an array of objects, select the field to use as the label.
93
87
  * @defaultValue 'label'
94
88
  */
95
- labelKey?: keyof T
89
+ labelKey?: V
96
90
  items?: I
91
+ /** The value of the InputMenu when initially rendered. Use when you do not need to control the state of the InputMenu. */
92
+ defaultValue?: SelectModelValue<T, V, M>
93
+ /** The controlled value of the InputMenu. Can be binded-with with `v-model`. */
94
+ modelValue?: SelectModelValue<T, V, M>
95
+ /** Whether multiple options can be selected or not. */
96
+ multiple?: M & boolean
97
97
  /** Highlight the ring color like a focus state. */
98
98
  highlight?: boolean
99
+ /**
100
+ * Determines if custom user input that does not exist in options can be added.
101
+ * @defaultValue false
102
+ */
103
+ createItem?: boolean | 'always' | { position?: 'top' | 'bottom', when?: 'empty' | 'always' }
104
+ /**
105
+ * Fields to filter items by.
106
+ * @defaultValue [labelKey]
107
+ */
108
+ filterFields?: string[]
109
+ /**
110
+ * When `true`, disable the default filters, useful for custom filtering (useAsyncData, useFetch, etc.).
111
+ * @defaultValue false
112
+ */
113
+ ignoreFilter?: boolean
99
114
  class?: any
100
115
  ui?: PartialString<typeof inputMenu.slots>
101
- /** The controlled value of the Combobox. Can be binded-with with `v-model`. */
102
- modelValue?: SelectModelValue<T, V, M>
103
- /** Whether multiple options can be selected or not. */
104
- multiple?: M
105
116
  }
106
117
 
107
118
  export type InputMenuEmits<T, V, M extends boolean> = Omit<ComboboxRootEmits<T>, 'update:modelValue'> & {
108
119
  change: [payload: Event]
109
120
  blur: [payload: FocusEvent]
110
121
  focus: [payload: FocusEvent]
122
+ create: [item: string]
111
123
  } & SelectModelValueEmits<T, V, M>
112
124
 
113
125
  type SlotProps<T> = (props: { item: T, index: number }) => any
114
126
 
115
- export interface InputMenuSlots<T> {
116
- 'leading'(props: { modelValue: T, open: boolean, ui: any }): any
117
- 'trailing'(props: { modelValue: T, open: boolean, ui: any }): any
127
+ export interface InputMenuSlots<T, M extends boolean> {
128
+ 'leading'(props: { modelValue?: M extends true ? T[] : T, open: boolean, ui: any }): any
129
+ 'trailing'(props: { modelValue?: M extends true ? T[] : T, open: boolean, ui: any }): any
118
130
  'empty'(props: { searchTerm?: string }): any
119
131
  'item': SlotProps<T>
120
132
  'item-leading': SlotProps<T>
@@ -122,22 +134,22 @@ export interface InputMenuSlots<T> {
122
134
  'item-trailing': SlotProps<T>
123
135
  'tags-item-text': SlotProps<T>
124
136
  'tags-item-delete': SlotProps<T>
137
+ 'create-item-label'(props: { item: string }): any
125
138
  }
126
-
127
- extendDevtoolsMeta({ defaultProps: { items: ['Option 1', 'Option 2', 'Option 3'] } })
128
139
  </script>
129
140
 
130
- <script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<InputMenuItem | AcceptableValue> = MaybeArrayOfArray<InputMenuItem | AcceptableValue>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false">
131
- import { computed, ref, toRef, onMounted } from 'vue'
132
- import { ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxViewport, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, TagsInputRoot, TagsInputItem, TagsInputItemText, TagsInputItemDelete, TagsInputInput, useForwardPropsEmits } from 'radix-vue'
141
+ <script setup lang="ts" generic="T extends MaybeArrayOfArrayItem<I>, I extends MaybeArrayOfArray<InputMenuItem | AcceptableValue | boolean> = MaybeArrayOfArray<InputMenuItem | AcceptableValue | boolean>, V extends SelectItemKey<T> | undefined = undefined, M extends boolean = false">
142
+ import { computed, ref, toRef, onMounted, toRaw } from 'vue'
143
+ import { ComboboxRoot, ComboboxArrow, ComboboxAnchor, ComboboxInput, ComboboxTrigger, ComboboxPortal, ComboboxContent, ComboboxViewport, ComboboxEmpty, ComboboxGroup, ComboboxLabel, ComboboxSeparator, ComboboxItem, ComboboxItemIndicator, TagsInputRoot, TagsInputItem, TagsInputItemText, TagsInputItemDelete, TagsInputInput, useForwardPropsEmits, useFilter } from 'reka-ui'
133
144
  import { defu } from 'defu'
134
- import * as isEqual from 'fast-deep-equal'
135
- import { reactivePick } from '@vueuse/core'
145
+ import { isEqual } from 'ohash'
146
+ import { reactivePick, createReusableTemplate } from '@vueuse/core'
136
147
  import { useAppConfig } from '#imports'
137
148
  import { useButtonGroup } from '../composables/useButtonGroup'
138
149
  import { useComponentIcons } from '../composables/useComponentIcons'
139
150
  import { useFormField } from '../composables/useFormField'
140
- import { get, escapeRegExp } from '../utils'
151
+ import { useLocale } from '../composables/useLocale'
152
+ import { get, compare } from '../utils'
141
153
  import UIcon from './Icon.vue'
142
154
  import UAvatar from './Avatar.vue'
143
155
  import UChip from './Chip.vue'
@@ -148,25 +160,29 @@ const props = withDefaults(defineProps<InputMenuProps<T, I, V, M>>(), {
148
160
  type: 'text',
149
161
  autofocusDelay: 0,
150
162
  portal: true,
151
- filter: () => ['label'],
152
- labelKey: 'label' as keyof T
163
+ labelKey: 'label' as never
153
164
  })
154
165
  const emits = defineEmits<InputMenuEmits<T, V, M>>()
155
- const slots = defineSlots<InputMenuSlots<T>>()
166
+ const slots = defineSlots<InputMenuSlots<T, M>>()
156
167
 
157
168
  const searchTerm = defineModel<string>('searchTerm', { default: '' })
158
169
 
170
+ const { t } = useLocale()
159
171
  const appConfig = useAppConfig()
160
- const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'selectedValue', 'open', 'defaultOpen', 'multiple', 'resetSearchTermOnBlur'), emits)
161
- const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, position: 'popper' }) as ComboboxContentProps)
172
+ const { contains } = useFilter({ sensitivity: 'base' })
173
+
174
+ const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'open', 'defaultOpen', 'multiple', 'resetSearchTermOnBlur', 'highlightOnHover', 'ignoreFilter'), emits)
175
+ const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }) as ComboboxContentProps)
162
176
  const arrowProps = toRef(() => props.arrow as ComboboxArrowProps)
163
177
 
164
- const { emitFormBlur, emitFormChange, emitFormInput, size: formGroupSize, color, id, name, highlight, disabled } = useFormField<InputProps>(props)
178
+ const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, size: formGroupSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField<InputProps>(props)
165
179
  const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
166
180
  const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, { trailingIcon: appConfig.ui.icons.chevronDown })))
167
181
 
168
182
  const inputSize = computed(() => buttonGroupSize.value || formGroupSize.value)
169
183
 
184
+ const [DefineCreateItemTemplate, ReuseCreateItemTemplate] = createReusableTemplate()
185
+
170
186
  const ui = computed(() => inputMenu({
171
187
  color: color.value,
172
188
  variant: props.variant,
@@ -179,36 +195,54 @@ const ui = computed(() => inputMenu({
179
195
  buttonGroup: orientation.value
180
196
  }))
181
197
 
182
- function displayValue(value: AcceptableValue): string {
183
- const item = items.value.find(item => props.valueKey ? isEqual.default(get(item as Record<string, any>, props.valueKey as string), value) : isEqual.default(item, value))
198
+ function displayValue(value: T): string {
199
+ if (!props.valueKey) {
200
+ return value && (typeof value === 'object' ? get(value, props.labelKey as string) : value)
201
+ }
184
202
 
203
+ const item = items.value.find(item => compare(typeof item === 'object' ? get(item as Record<string, any>, props.valueKey as string) : item, value))
185
204
  return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item)
186
205
  }
187
206
 
188
- function filterFunction(items: ArrayOrWrapped<AcceptableValue>, searchTerm: string): ArrayOrWrapped<AcceptableValue> {
189
- if (props.filter === false) {
190
- return items
207
+ const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0]) ? props.items : [props.items]) as InputMenuItem[][] : [])
208
+ // eslint-disable-next-line vue/no-dupe-keys
209
+ const items = computed(() => groups.value.flatMap(group => group) as T[])
210
+
211
+ const filteredGroups = computed(() => {
212
+ if (props.ignoreFilter || !searchTerm.value) {
213
+ return groups.value
191
214
  }
192
215
 
193
- const fields = Array.isArray(props.filter) ? props.filter : [props.labelKey]
194
- const escapedSearchTerm = escapeRegExp(searchTerm)
216
+ const fields = Array.isArray(props.filterFields) ? props.filterFields : [props.labelKey] as string[]
195
217
 
196
- return items.filter((item) => {
218
+ return groups.value.map(items => items.filter((item) => {
197
219
  if (typeof item !== 'object') {
198
- return String(item).search(new RegExp(escapedSearchTerm, 'i')) !== -1
220
+ return contains(item, searchTerm.value)
199
221
  }
200
222
 
201
- return fields.some((field) => {
202
- const child = get(item, field as string)
223
+ if (item.type && ['label', 'separator'].includes(item.type)) {
224
+ return true
225
+ }
203
226
 
204
- return child !== null && child !== undefined && String(child).search(new RegExp(escapedSearchTerm, 'i')) !== -1
205
- })
206
- }) as ArrayOrWrapped<T>
207
- }
227
+ return fields.some(field => contains(get(item, field), searchTerm.value))
228
+ })).filter(group => group.filter(item => !item.type || !['label', 'separator'].includes(item.type)).length > 0)
229
+ })
230
+ const filteredItems = computed(() => filteredGroups.value.flatMap(group => group) as T[])
208
231
 
209
- const groups = computed(() => props.items?.length ? (Array.isArray(props.items[0]) ? props.items : [props.items]) as InputMenuItem[][] : [])
210
- // eslint-disable-next-line vue/no-dupe-keys
211
- const items = computed(() => groups.value.flatMap(group => group) as T[])
232
+ const createItem = computed(() => {
233
+ if (!props.createItem || !searchTerm.value) {
234
+ return false
235
+ }
236
+
237
+ const newItem = props.valueKey ? { [props.valueKey]: searchTerm.value } as T : searchTerm.value
238
+
239
+ if ((typeof props.createItem === 'object' && props.createItem.when === 'always') || props.createItem === 'always') {
240
+ return !filteredItems.value.find(item => compare(item, newItem, props.valueKey))
241
+ }
242
+
243
+ return !filteredItems.value.length
244
+ })
245
+ const createItemPosition = computed(() => typeof props.createItem === 'object' ? props.createItem.position : 'bottom')
212
246
 
213
247
  const inputRef = ref<InstanceType<typeof ComboboxInput> | null>(null)
214
248
 
@@ -225,6 +259,9 @@ onMounted(() => {
225
259
  })
226
260
 
227
261
  function onUpdate(value: any) {
262
+ if (toRaw(props.modelValue) === value) {
263
+ return
264
+ }
228
265
  // @ts-expect-error - 'target' does not exist in type 'EventInit'
229
266
  const event = new Event('change', { target: { value } })
230
267
  emits('change', event)
@@ -239,6 +276,7 @@ function onBlur(event: FocusEvent) {
239
276
 
240
277
  function onFocus(event: FocusEvent) {
241
278
  emits('focus', event)
279
+ emitFormFocus()
242
280
  }
243
281
 
244
282
  function onUpdateOpen(value: boolean) {
@@ -252,23 +290,46 @@ function onUpdateOpen(value: boolean) {
252
290
  }
253
291
  }
254
292
 
293
+ function onRemoveTag(event: any) {
294
+ if (props.multiple) {
295
+ const modelValue = props.modelValue as SelectModelValue<T, V, true>
296
+ const filteredValue = modelValue.filter(value => !isEqual(value, event))
297
+ emits('update:modelValue', filteredValue as SelectModelValue<T, V, M>)
298
+ }
299
+ }
300
+
255
301
  defineExpose({
256
302
  inputRef
257
303
  })
258
304
  </script>
259
305
 
306
+ <!-- eslint-disable vue/no-template-shadow -->
260
307
  <template>
308
+ <DefineCreateItemTemplate>
309
+ <ComboboxGroup :class="ui.group({ class: props.ui?.group })">
310
+ <ComboboxItem
311
+ :class="ui.item({ class: props.ui?.item })"
312
+ :value="searchTerm"
313
+ @select.prevent="emits('create', searchTerm)"
314
+ >
315
+ <span :class="ui.itemLabel({ class: props.ui?.itemLabel })">
316
+ <slot name="create-item-label" :item="searchTerm">
317
+ {{ t('inputMenu.create', { label: searchTerm }) }}
318
+ </slot>
319
+ </span>
320
+ </ComboboxItem>
321
+ </ComboboxGroup>
322
+ </DefineCreateItemTemplate>
323
+
261
324
  <ComboboxRoot
262
325
  :id="id"
263
326
  v-slot="{ modelValue, open }"
264
327
  v-bind="rootProps"
265
- v-model:search-term="searchTerm"
266
328
  :name="name"
267
329
  :disabled="disabled"
268
- :display-value="displayValue"
269
- :filter-function="filterFunction"
270
330
  :class="ui.root({ class: [props.class, props.ui?.root] })"
271
331
  :as-child="!!multiple"
332
+ ignore-filter
272
333
  @update:model-value="onUpdate"
273
334
  @update:open="onUpdateOpen"
274
335
  @keydown.enter="$event.preventDefault()"
@@ -276,13 +337,14 @@ defineExpose({
276
337
  <ComboboxAnchor :as-child="!multiple" :class="ui.base({ class: props.ui?.base })">
277
338
  <TagsInputRoot
278
339
  v-if="multiple"
279
- v-slot="{ modelValue: tags }: { modelValue: AcceptableValue[] }"
340
+ v-slot="{ modelValue: tags }"
280
341
  :model-value="(modelValue as string[])"
281
342
  :disabled="disabled"
282
343
  delimiter=""
283
344
  as-child
284
345
  @blur="onBlur"
285
346
  @focus="onFocus"
347
+ @remove-tag="onRemoveTag"
286
348
  >
287
349
  <TagsInputItem v-for="(item, index) in tags" :key="index" :value="(item as string)" :class="ui.tagsItem({ class: props.ui?.tagsItem })">
288
350
  <TagsInputItemText :class="ui.tagsItemText({ class: props.ui?.tagsItemText })">
@@ -298,10 +360,10 @@ defineExpose({
298
360
  </TagsInputItemDelete>
299
361
  </TagsInputItem>
300
362
 
301
- <ComboboxInput as-child>
363
+ <ComboboxInput v-model="searchTerm" :display-value="displayValue" as-child>
302
364
  <TagsInputInput
303
365
  ref="inputRef"
304
- v-bind="$attrs"
366
+ v-bind="{ ...$attrs, ...ariaAttrs }"
305
367
  :placeholder="placeholder"
306
368
  :required="required"
307
369
  :class="ui.tagsInput({ class: props.ui?.tagsInput })"
@@ -313,24 +375,25 @@ defineExpose({
313
375
  <ComboboxInput
314
376
  v-else
315
377
  ref="inputRef"
316
- v-bind="$attrs"
378
+ v-model="searchTerm"
379
+ :display-value="displayValue"
380
+ v-bind="{ ...$attrs, ...ariaAttrs }"
317
381
  :type="type"
318
382
  :placeholder="placeholder"
319
383
  :required="required"
320
- :class="ui.base({ class: props.ui?.base })"
321
384
  @blur="onBlur"
322
385
  @focus="onFocus"
323
386
  />
324
387
 
325
388
  <span v-if="isLeading || !!avatar || !!slots.leading" :class="ui.leading({ class: props.ui?.leading })">
326
- <slot name="leading" :model-value="(modelValue as T)" :open="open" :ui="ui">
389
+ <slot name="leading" :model-value="(modelValue as M extends true ? T[] : T)" :open="open" :ui="ui">
327
390
  <UIcon v-if="isLeading && leadingIconName" :name="leadingIconName" :class="ui.leadingIcon({ class: props.ui?.leadingIcon })" />
328
391
  <UAvatar v-else-if="!!avatar" :size="((props.ui?.itemLeadingAvatarSize || ui.itemLeadingAvatarSize()) as AvatarProps['size'])" v-bind="avatar" :class="ui.itemLeadingAvatar({ class: props.ui?.itemLeadingAvatar })" />
329
392
  </slot>
330
393
  </span>
331
394
 
332
395
  <ComboboxTrigger v-if="isTrailing || !!slots.trailing" :class="ui.trailing({ class: props.ui?.trailing })">
333
- <slot name="trailing" :model-value="(modelValue as T)" :open="open" :ui="ui">
396
+ <slot name="trailing" :model-value="(modelValue as M extends true ? T[] : T)" :open="open" :ui="ui">
334
397
  <UIcon v-if="trailingIconName" :name="trailingIconName" :class="ui.trailingIcon({ class: props.ui?.trailingIcon })" />
335
398
  </slot>
336
399
  </ComboboxTrigger>
@@ -340,12 +403,14 @@ defineExpose({
340
403
  <ComboboxContent :class="ui.content({ class: props.ui?.content })" v-bind="contentProps">
341
404
  <ComboboxEmpty :class="ui.empty({ class: props.ui?.empty })">
342
405
  <slot name="empty" :search-term="searchTerm">
343
- {{ searchTerm ? `No results for ${searchTerm}` : 'No results' }}
406
+ {{ searchTerm ? t('inputMenu.noMatch', { searchTerm }) : t('inputMenu.noData') }}
344
407
  </slot>
345
408
  </ComboboxEmpty>
346
409
 
347
410
  <ComboboxViewport :class="ui.viewport({ class: props.ui?.viewport })">
348
- <ComboboxGroup v-for="(group, groupIndex) in groups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
411
+ <ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'top'" />
412
+
413
+ <ComboboxGroup v-for="(group, groupIndex) in filteredGroups" :key="`group-${groupIndex}`" :class="ui.group({ class: props.ui?.group })">
349
414
  <template v-for="(item, index) in group" :key="`group-${groupIndex}-${index}`">
350
415
  <ComboboxLabel v-if="item?.type === 'label'" :class="ui.label({ class: props.ui?.label })">
351
416
  {{ get(item, props.labelKey as string) }}
@@ -391,6 +456,8 @@ defineExpose({
391
456
  </ComboboxItem>
392
457
  </template>
393
458
  </ComboboxGroup>
459
+
460
+ <ReuseCreateItemTemplate v-if="createItem && createItemPosition === 'bottom'" />
394
461
  </ComboboxViewport>
395
462
 
396
463
  <ComboboxArrow v-if="!!arrow" v-bind="arrowProps" :class="ui.arrow({ class: props.ui?.arrow })" />