@nuxt/ui 3.0.0-alpha.10 → 3.0.0-alpha.12

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 (191) hide show
  1. package/.nuxt/ui/alert.ts +76 -59
  2. package/.nuxt/ui/avatar-group.ts +13 -1
  3. package/.nuxt/ui/avatar.ts +14 -2
  4. package/.nuxt/ui/badge.ts +102 -65
  5. package/.nuxt/ui/button-group.ts +13 -0
  6. package/.nuxt/ui/button.ts +124 -93
  7. package/.nuxt/ui/calendar.ts +20 -2
  8. package/.nuxt/ui/carousel.ts +5 -3
  9. package/.nuxt/ui/checkbox.ts +27 -9
  10. package/.nuxt/ui/chip.ts +36 -7
  11. package/.nuxt/ui/color-picker.ts +9 -1
  12. package/.nuxt/ui/context-menu.ts +31 -13
  13. package/.nuxt/ui/drawer.ts +11 -4
  14. package/.nuxt/ui/dropdown-menu.ts +31 -13
  15. package/.nuxt/ui/form-field.ts +9 -1
  16. package/.nuxt/ui/input-menu.ts +110 -75
  17. package/.nuxt/ui/input-number.ts +82 -51
  18. package/.nuxt/ui/input.ts +88 -53
  19. package/.nuxt/ui/kbd.ts +14 -2
  20. package/.nuxt/ui/navigation-menu.ts +193 -79
  21. package/.nuxt/ui/pin-input.ts +57 -31
  22. package/.nuxt/ui/popover.ts +1 -1
  23. package/.nuxt/ui/progress.ts +93 -54
  24. package/.nuxt/ui/radio-group.ts +25 -2
  25. package/.nuxt/ui/select-menu.ts +89 -54
  26. package/.nuxt/ui/select.ts +89 -54
  27. package/.nuxt/ui/separator.ts +52 -23
  28. package/.nuxt/ui/slideover.ts +11 -4
  29. package/.nuxt/ui/slider.ts +45 -22
  30. package/.nuxt/ui/stepper.ts +45 -23
  31. package/.nuxt/ui/switch.ts +20 -2
  32. package/.nuxt/ui/table.ts +30 -13
  33. package/.nuxt/ui/tabs.ts +67 -39
  34. package/.nuxt/ui/textarea.ts +88 -53
  35. package/.nuxt/ui/toast.ts +11 -1
  36. package/.nuxt/ui/toaster.ts +27 -11
  37. package/.nuxt/ui/tooltip.ts +1 -1
  38. package/README.md +8 -1
  39. package/cli/commands/make/component.mjs +12 -0
  40. package/cli/package.json +2 -2
  41. package/cli/templates.mjs +21 -16
  42. package/dist/chunks/component-meta.cjs +2590 -2511
  43. package/dist/chunks/component-meta.mjs +2590 -2511
  44. package/dist/client/devtools/200.html +1 -1
  45. package/dist/client/devtools/404.html +1 -1
  46. package/dist/client/devtools/_nuxt/CgoQwOTV.js +57 -0
  47. package/dist/client/devtools/_nuxt/DLmPhbaq.js +1 -0
  48. package/dist/client/devtools/_nuxt/DYuJNiSW.js +1 -0
  49. package/dist/client/devtools/_nuxt/builds/latest.json +1 -1
  50. package/dist/client/devtools/_nuxt/builds/meta/3184ffe2-7577-4441-af98-fe6e81c30e3a.json +1 -0
  51. package/dist/client/devtools/_nuxt/entry.ChJczkiN.css +1 -0
  52. package/dist/client/devtools/_nuxt/error-404.DyY3rYaL.css +1 -0
  53. package/dist/client/devtools/_nuxt/error-500.D_1PCaJh.css +1 -0
  54. package/dist/client/devtools/index.html +1 -1
  55. package/dist/module.cjs +1 -1
  56. package/dist/module.json +1 -1
  57. package/dist/module.mjs +1 -1
  58. package/dist/runtime/components/Accordion.vue +4 -3
  59. package/dist/runtime/components/Alert.vue +5 -4
  60. package/dist/runtime/components/Avatar.vue +35 -13
  61. package/dist/runtime/components/AvatarGroup.vue +4 -3
  62. package/dist/runtime/components/Badge.vue +4 -3
  63. package/dist/runtime/components/Breadcrumb.vue +3 -3
  64. package/dist/runtime/components/Button.vue +4 -3
  65. package/dist/runtime/components/ButtonGroup.vue +4 -3
  66. package/dist/runtime/components/Calendar.vue +6 -3
  67. package/dist/runtime/components/Card.vue +3 -3
  68. package/dist/runtime/components/Carousel.vue +4 -3
  69. package/dist/runtime/components/Checkbox.vue +6 -5
  70. package/dist/runtime/components/Chip.vue +5 -4
  71. package/dist/runtime/components/Collapsible.vue +6 -4
  72. package/dist/runtime/components/ColorPicker.vue +48 -16
  73. package/dist/runtime/components/CommandPalette.vue +14 -7
  74. package/dist/runtime/components/Container.vue +3 -3
  75. package/dist/runtime/components/ContextMenu.vue +4 -3
  76. package/dist/runtime/components/ContextMenuContent.vue +4 -4
  77. package/dist/runtime/components/Drawer.vue +9 -4
  78. package/dist/runtime/components/DropdownMenu.vue +4 -3
  79. package/dist/runtime/components/DropdownMenuContent.vue +4 -4
  80. package/dist/runtime/components/Form.vue +59 -29
  81. package/dist/runtime/components/FormField.vue +14 -7
  82. package/dist/runtime/components/Input.vue +7 -5
  83. package/dist/runtime/components/InputMenu.vue +19 -7
  84. package/dist/runtime/components/InputNumber.vue +9 -5
  85. package/dist/runtime/components/Kbd.vue +4 -3
  86. package/dist/runtime/components/Link.vue +3 -3
  87. package/dist/runtime/components/Modal.vue +10 -15
  88. package/dist/runtime/components/NavigationMenu.vue +97 -69
  89. package/dist/runtime/components/Pagination.vue +3 -3
  90. package/dist/runtime/components/PinInput.vue +9 -7
  91. package/dist/runtime/components/Popover.vue +9 -8
  92. package/dist/runtime/components/Progress.vue +6 -5
  93. package/dist/runtime/components/RadioGroup.vue +6 -5
  94. package/dist/runtime/components/Select.vue +12 -9
  95. package/dist/runtime/components/SelectMenu.vue +25 -8
  96. package/dist/runtime/components/Separator.vue +4 -3
  97. package/dist/runtime/components/Skeleton.vue +3 -3
  98. package/dist/runtime/components/Slideover.vue +10 -14
  99. package/dist/runtime/components/Slider.vue +6 -5
  100. package/dist/runtime/components/Stepper.vue +8 -6
  101. package/dist/runtime/components/Switch.vue +6 -5
  102. package/dist/runtime/components/Table.vue +4 -3
  103. package/dist/runtime/components/Tabs.vue +5 -3
  104. package/dist/runtime/components/Textarea.vue +7 -5
  105. package/dist/runtime/components/Toast.vue +4 -3
  106. package/dist/runtime/components/Toaster.vue +4 -3
  107. package/dist/runtime/components/Tooltip.vue +3 -3
  108. package/dist/runtime/composables/defineShortcuts.js +1 -1
  109. package/dist/runtime/composables/useFormField.d.ts +7 -3
  110. package/dist/runtime/composables/useFormField.js +18 -8
  111. package/dist/runtime/composables/useToast.d.ts +1 -1
  112. package/dist/runtime/composables/useToast.js +21 -7
  113. package/dist/runtime/index.css +1 -1
  114. package/dist/runtime/locale/ar.js +3 -1
  115. package/dist/runtime/locale/cs.js +3 -1
  116. package/dist/runtime/locale/da.js +54 -0
  117. package/dist/runtime/locale/de.js +3 -1
  118. package/dist/runtime/locale/el.js +54 -0
  119. package/dist/runtime/locale/en.js +3 -1
  120. package/dist/runtime/locale/es.js +5 -3
  121. package/dist/runtime/locale/et.d.ts +2 -0
  122. package/dist/runtime/locale/et.js +54 -0
  123. package/dist/runtime/locale/fa_ir.js +3 -1
  124. package/dist/runtime/locale/fi.d.ts +2 -0
  125. package/dist/runtime/locale/fi.js +54 -0
  126. package/dist/runtime/locale/fr.js +3 -1
  127. package/dist/runtime/locale/he.d.ts +2 -0
  128. package/dist/runtime/locale/he.js +53 -0
  129. package/dist/runtime/locale/hi.d.ts +2 -0
  130. package/dist/runtime/locale/hi.js +54 -0
  131. package/dist/runtime/locale/hu.d.ts +2 -0
  132. package/dist/runtime/locale/hu.js +54 -0
  133. package/dist/runtime/locale/id.d.ts +2 -0
  134. package/dist/runtime/locale/id.js +54 -0
  135. package/dist/runtime/locale/index.d.ts +14 -2
  136. package/dist/runtime/locale/index.js +14 -2
  137. package/dist/runtime/locale/it.js +3 -1
  138. package/dist/runtime/locale/ja.js +3 -1
  139. package/dist/runtime/locale/km.d.ts +2 -0
  140. package/dist/runtime/locale/km.js +54 -0
  141. package/dist/runtime/locale/ko.js +3 -1
  142. package/dist/runtime/locale/nb_no.d.ts +2 -0
  143. package/dist/runtime/locale/nb_no.js +54 -0
  144. package/dist/runtime/locale/nl.js +3 -1
  145. package/dist/runtime/locale/pl.js +3 -1
  146. package/dist/runtime/locale/pt.js +3 -1
  147. package/dist/runtime/locale/pt_br.js +3 -1
  148. package/dist/runtime/locale/ru.js +3 -1
  149. package/dist/runtime/locale/sk.js +3 -1
  150. package/dist/runtime/locale/sv.d.ts +2 -0
  151. package/dist/runtime/locale/sv.js +54 -0
  152. package/dist/runtime/locale/th.d.ts +2 -0
  153. package/dist/runtime/locale/th.js +54 -0
  154. package/dist/runtime/locale/tr.js +3 -1
  155. package/dist/runtime/locale/uk.d.ts +2 -0
  156. package/dist/runtime/locale/uk.js +54 -0
  157. package/dist/runtime/locale/vi.d.ts +2 -0
  158. package/dist/runtime/locale/vi.js +54 -0
  159. package/dist/runtime/locale/zh_cn.d.ts +2 -0
  160. package/dist/runtime/locale/{zh_hans.js → zh_cn.js} +4 -2
  161. package/dist/runtime/locale/zh_tw.d.ts +2 -0
  162. package/dist/runtime/locale/{zh_hant.js → zh_tw.js} +12 -10
  163. package/dist/runtime/plugins/colors.js +7 -4
  164. package/dist/runtime/types/form.d.ts +23 -14
  165. package/dist/runtime/types/form.js +2 -2
  166. package/dist/runtime/types/locale.d.ts +2 -0
  167. package/dist/runtime/utils/form.d.ts +2 -2
  168. package/dist/runtime/utils/form.js +5 -7
  169. package/dist/runtime/utils/tv.d.ts +1 -0
  170. package/dist/runtime/utils/tv.js +4 -0
  171. package/dist/runtime/vue/components/Icon.vue +2 -2
  172. package/dist/runtime/vue/components/Link.vue +2 -2
  173. package/dist/runtime/vue/plugins/head.d.ts +1 -1
  174. package/dist/runtime/vue/plugins/head.js +3 -3
  175. package/dist/shared/{ui.D_93iuwH.mjs → ui.CENZ17mM.mjs} +131 -48
  176. package/dist/shared/{ui.CwmIkD_Y.cjs → ui.ClMXjgLK.cjs} +131 -48
  177. package/dist/unplugin.cjs +5 -5
  178. package/dist/unplugin.mjs +5 -5
  179. package/dist/vite.cjs +1 -1
  180. package/dist/vite.mjs +1 -1
  181. package/package.json +55 -51
  182. package/dist/client/devtools/_nuxt/B1fTd_Rf.js +0 -1
  183. package/dist/client/devtools/_nuxt/CSUxkedg.js +0 -1
  184. package/dist/client/devtools/_nuxt/CbRTGpb7.js +0 -1
  185. package/dist/client/devtools/_nuxt/YQTlxbcT.js +0 -53
  186. package/dist/client/devtools/_nuxt/builds/meta/b3465abc-0a92-4ee7-ba6b-e5174891fcc0.json +0 -1
  187. package/dist/client/devtools/_nuxt/entry.DXXceQev.css +0 -1
  188. package/dist/client/devtools/_nuxt/error-404.BJpkZFyY.css +0 -1
  189. package/dist/client/devtools/_nuxt/error-500.D3i9VDlt.css +0 -1
  190. /package/dist/runtime/locale/{zh_hans.d.ts → da.d.ts} +0 -0
  191. /package/dist/runtime/locale/{zh_hant.d.ts → el.d.ts} +0 -0
@@ -1,14 +1,15 @@
1
1
  <script lang="ts">
2
- import { tv } from 'tailwind-variants'
3
2
  import type { AppConfig } from '@nuxt/schema'
4
3
  import _appConfig from '#build/app.config'
5
4
  import theme from '#build/ui/form'
6
5
  import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
6
+ import { tv } from '../utils/tv'
7
7
  import type { FormSchema, FormError, FormInputEvents, FormErrorEvent, FormSubmitEvent, FormEvent, Form, FormErrorWithId } from '../types/form'
8
+ import type { DeepReadonly } from 'vue'
8
9
 
9
- const appConfig = _appConfig as AppConfig & { ui: { form: Partial<typeof theme> } }
10
+ const appConfigForm = _appConfig as AppConfig & { ui: { form: Partial<typeof theme> } }
10
11
 
11
- const form = tv({ extend: tv(theme), ...(appConfig.ui?.form || {}) })
12
+ const form = tv({ extend: tv(theme), ...(appConfigForm.ui?.form || {}) })
12
13
 
13
14
  export interface FormProps<T extends object> {
14
15
  id?: string | number
@@ -38,7 +39,7 @@ extendDevtoolsMeta({ example: 'FormExample' })
38
39
  import { provide, inject, nextTick, ref, onUnmounted, onMounted, computed, useId, readonly } from 'vue'
39
40
  import { useEventBus } from '@vueuse/core'
40
41
  import { formOptionsInjectionKey, formInputsInjectionKey, formBusInjectionKey, formLoadingInjectionKey } from '../composables/useFormField'
41
- import { parseSchema } from '../utils/form'
42
+ import { validateSchema } from '../utils/form'
42
43
  import { FormValidationException } from '../types/form'
43
44
 
44
45
  const props = withDefaults(defineProps<FormProps<T>>(), {
@@ -52,7 +53,7 @@ defineSlots<FormSlots>()
52
53
 
53
54
  const formId = props.id ?? useId() as string
54
55
 
55
- const bus = useEventBus<FormEvent>(`form-${formId}`)
56
+ const bus = useEventBus<FormEvent<T>>(`form-${formId}`)
56
57
  const parentBus = inject(
57
58
  formBusInjectionKey,
58
59
  undefined
@@ -60,7 +61,7 @@ const parentBus = inject(
60
61
 
61
62
  provide(formBusInjectionKey, bus)
62
63
 
63
- const nestedForms = ref<Map<string | number, { validate: () => any }>>(new Map())
64
+ const nestedForms = ref<Map<string | number, { validate: typeof _validate }>>(new Map())
64
65
 
65
66
  onMounted(async () => {
66
67
  bus.on(async (event) => {
@@ -68,8 +69,24 @@ onMounted(async () => {
68
69
  nestedForms.value.set(event.formId, { validate: event.validate })
69
70
  } else if (event.type === 'detach') {
70
71
  nestedForms.value.delete(event.formId)
71
- } else if (props.validateOn?.includes(event.type as FormInputEvents)) {
72
- await _validate({ name: event.name, silent: true, nested: false })
72
+ } else if (props.validateOn?.includes(event.type)) {
73
+ if (event.type !== 'input') {
74
+ await _validate({ name: event.name, silent: true, nested: false })
75
+ } else if (event.eager || blurredFields.has(event.name)) {
76
+ await _validate({ name: event.name, silent: true, nested: false })
77
+ }
78
+ }
79
+
80
+ if (event.type === 'blur') {
81
+ blurredFields.add(event.name)
82
+ }
83
+
84
+ if (event.type === 'change' || event.type === 'input' || event.type === 'blur' || event.type === 'focus') {
85
+ touchedFields.add(event.name)
86
+ }
87
+
88
+ if (event.type === 'change' || event.type === 'input') {
89
+ dirtyFields.add(event.name)
73
90
  }
74
91
  })
75
92
  })
@@ -94,8 +111,12 @@ onUnmounted(() => {
94
111
  const errors = ref<FormErrorWithId[]>([])
95
112
  provide('form-errors', errors)
96
113
 
97
- const inputs = ref<Record<string, { id?: string, pattern?: RegExp }>>({})
98
- provide(formInputsInjectionKey, inputs)
114
+ const inputs = ref<{ [P in keyof T]?: { id?: string, pattern?: RegExp } }>({})
115
+ provide(formInputsInjectionKey, inputs as any)
116
+
117
+ const dirtyFields = new Set<keyof T>()
118
+ const touchedFields = new Set<keyof T>()
119
+ const blurredFields = new Set<keyof T>()
99
120
 
100
121
  function resolveErrorIds(errs: FormError[]): FormErrorWithId[] {
101
122
  return errs.map(err => ({
@@ -104,34 +125,35 @@ function resolveErrorIds(errs: FormError[]): FormErrorWithId[] {
104
125
  }))
105
126
  }
106
127
 
107
- const parsedValue = ref<T | null>(null)
128
+ const transformedState = ref<T | null>(null)
129
+
108
130
  async function getErrors(): Promise<FormErrorWithId[]> {
109
131
  let errs = props.validate ? (await props.validate(props.state)) ?? [] : []
110
132
 
111
133
  if (props.schema) {
112
- const { errors, result } = await parseSchema(props.state, props.schema as FormSchema<typeof props.state>)
134
+ const { errors, result } = await validateSchema(props.state, props.schema as FormSchema<typeof props.state>)
113
135
  if (errors) {
114
136
  errs = errs.concat(errors)
115
137
  } else {
116
- parsedValue.value = result
138
+ transformedState.value = result
117
139
  }
118
140
  }
119
141
 
120
142
  return resolveErrorIds(errs)
121
143
  }
122
144
 
123
- async function _validate(opts: { name?: string | string[], silent?: boolean, nested?: boolean } = { silent: false, nested: true }): Promise<T | false> {
124
- const names = opts.name && !Array.isArray(opts.name) ? [opts.name] : opts.name as string[]
145
+ async function _validate(opts: { name?: keyof T | (keyof T)[], silent?: boolean, nested?: boolean, transform?: boolean } = { silent: false, nested: true, transform: false }): Promise<T | false> {
146
+ const names = opts.name && !Array.isArray(opts.name) ? [opts.name] : opts.name as (keyof T)[]
125
147
 
126
148
  const nestedValidatePromises = !names && opts.nested
127
149
  ? Array.from(nestedForms.value.values()).map(
128
- ({ validate }) => validate().then(() => undefined).catch((error: Error) => {
129
- if (!(error instanceof FormValidationException)) {
130
- throw error
131
- }
132
- return error
133
- })
134
- )
150
+ ({ validate }) => validate(opts).then(() => undefined).catch((error: Error) => {
151
+ if (!(error instanceof FormValidationException)) {
152
+ throw error
153
+ }
154
+ return error
155
+ })
156
+ )
135
157
  : []
136
158
 
137
159
  if (names) {
@@ -150,13 +172,17 @@ async function _validate(opts: { name?: string | string[], silent?: boolean, nes
150
172
  errors.value = await getErrors()
151
173
  }
152
174
 
153
- const childErrors = (await Promise.all(nestedValidatePromises)).filter(val => val)
175
+ const childErrors = (await Promise.all(nestedValidatePromises)).filter(val => val !== undefined)
154
176
 
155
177
  if (errors.value.length + childErrors.length > 0) {
156
178
  if (opts.silent) return false
157
179
  throw new FormValidationException(formId, errors.value, childErrors)
158
180
  }
159
181
 
182
+ if (opts.transform) {
183
+ Object.assign(props.state, transformedState.value)
184
+ }
185
+
160
186
  return props.state as T
161
187
  }
162
188
 
@@ -169,8 +195,7 @@ async function onSubmitWrapper(payload: Event) {
169
195
  const event = payload as FormSubmitEvent<any>
170
196
 
171
197
  try {
172
- await _validate({ nested: true })
173
- event.data = props.schema ? parsedValue.value : props.state
198
+ event.data = await _validate({ nested: true, transform: true })
174
199
  await props.onSubmit?.(event)
175
200
  } catch (error) {
176
201
  if (!(error instanceof FormValidationException)) {
@@ -180,7 +205,7 @@ async function onSubmitWrapper(payload: Event) {
180
205
  const errorEvent: FormErrorEvent = {
181
206
  ...event,
182
207
  errors: error.errors,
183
- childrens: error.childrens
208
+ children: error.children
184
209
  }
185
210
  emits('error', errorEvent)
186
211
  }
@@ -199,7 +224,7 @@ defineExpose<Form<T>>({
199
224
  validate: _validate,
200
225
  errors,
201
226
 
202
- setErrors(errs: FormError[], name?: string) {
227
+ setErrors(errs: FormError[], name?: keyof T) {
203
228
  if (name) {
204
229
  errors.value = errors.value
205
230
  .filter(error => error.name !== name)
@@ -213,7 +238,7 @@ defineExpose<Form<T>>({
213
238
  await onSubmitWrapper(new Event('submit'))
214
239
  },
215
240
 
216
- getErrors(name?: string) {
241
+ getErrors(name?: keyof T) {
217
242
  if (name) {
218
243
  return errors.value.filter(err => err.name === name)
219
244
  }
@@ -228,7 +253,12 @@ defineExpose<Form<T>>({
228
253
  }
229
254
  },
230
255
 
231
- disabled
256
+ disabled,
257
+ dirty: computed(() => !!dirtyFields.size),
258
+
259
+ dirtyFields: readonly(dirtyFields) as DeepReadonly<Set<keyof T>>,
260
+ blurredFields: readonly(blurredFields) as DeepReadonly<Set<keyof T>>,
261
+ touchedFields: readonly(touchedFields) as DeepReadonly<Set<keyof T>>
232
262
  })
233
263
  </script>
234
264
 
@@ -1,13 +1,14 @@
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 { tv } from '../utils/tv'
6
7
  import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
7
8
 
8
- const appConfig = _appConfig as AppConfig & { ui: { formField: Partial<typeof theme> } }
9
+ const appConfigFormField = _appConfig as AppConfig & { ui: { formField: Partial<typeof theme> } }
9
10
 
10
- const formField = tv({ extend: tv(theme), ...(appConfig.ui?.formField || {}) })
11
+ const formField = tv({ extend: tv(theme), ...(appConfigFormField.ui?.formField || {}) })
11
12
 
12
13
  type FormFieldVariants = VariantProps<typeof formField>
13
14
 
@@ -65,6 +66,9 @@ const formErrors = inject<Ref<FormError[]> | null>('form-errors', null)
65
66
  const error = computed(() => props.error || formErrors?.value?.find(error => error.name === props.name || (props.errorPattern && error.name.match(props.errorPattern)))?.message)
66
67
 
67
68
  const id = ref(useId())
69
+ // Copies id's initial value to bind aria-attributes such as aria-describedby.
70
+ // This is required for the RadioGroup component which unsets the id value.
71
+ const ariaId = id.value
68
72
 
69
73
  provide(inputIdInjectionKey, id)
70
74
 
@@ -74,7 +78,10 @@ provide(formFieldInjectionKey, computed(() => ({
74
78
  size: props.size,
75
79
  eagerValidation: props.eagerValidation,
76
80
  validateOnInputDelay: props.validateOnInputDelay,
77
- errorPattern: props.errorPattern
81
+ errorPattern: props.errorPattern,
82
+ hint: props.hint,
83
+ description: props.description,
84
+ ariaId
78
85
  }) as FormFieldInjectedOptions<FormFieldProps>))
79
86
  </script>
80
87
 
@@ -87,14 +94,14 @@ provide(formFieldInjectionKey, computed(() => ({
87
94
  {{ label }}
88
95
  </slot>
89
96
  </Label>
90
- <span v-if="hint || !!slots.hint" :class="ui.hint({ class: props.ui?.hint })">
97
+ <span v-if="hint || !!slots.hint" :id="`${ariaId}-hint`" :class="ui.hint({ class: props.ui?.hint })">
91
98
  <slot name="hint" :hint="hint">
92
99
  {{ hint }}
93
100
  </slot>
94
101
  </span>
95
102
  </div>
96
103
 
97
- <p v-if="description || !!slots.description" :class="ui.description({ class: props.ui?.description })">
104
+ <p v-if="description || !!slots.description" :id="`${ariaId}-description`" :class="ui.description({ class: props.ui?.description })">
98
105
  <slot name="description" :description="description">
99
106
  {{ description }}
100
107
  </slot>
@@ -104,7 +111,7 @@ provide(formFieldInjectionKey, computed(() => ({
104
111
  <div :class="[(label || !!slots.label || description || !!slots.description) && ui.container({ class: props.ui?.container })]">
105
112
  <slot :error="error" />
106
113
 
107
- <p v-if="(typeof error === 'string' && error) || !!slots.error" :class="ui.error({ class: props.ui?.error })">
114
+ <p v-if="(typeof error === 'string' && error) || !!slots.error" :id="`${ariaId}-error`" :class="ui.error({ class: props.ui?.error })">
108
115
  <slot name="error" :error="error">
109
116
  {{ error }}
110
117
  </slot>
@@ -1,16 +1,17 @@
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
 
@@ -74,7 +75,7 @@ const slots = defineSlots<InputSlots>()
74
75
 
75
76
  const [modelValue, modelModifiers] = defineModel<string | number>()
76
77
 
77
- 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 })
78
79
  const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
79
80
  const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)
80
81
 
@@ -165,10 +166,11 @@ onMounted(() => {
165
166
  :disabled="disabled"
166
167
  :required="required"
167
168
  :autocomplete="autocomplete"
168
- v-bind="$attrs"
169
+ v-bind="{ ...$attrs, ...ariaAttrs }"
169
170
  @input="onInput"
170
171
  @blur="onBlur"
171
172
  @change="onChange"
173
+ @focus="emitFormFocus"
172
174
  >
173
175
 
174
176
  <slot />
@@ -1,18 +1,19 @@
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 { 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
9
  import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
10
+ import { tv } from '../utils/tv'
10
11
  import type { AvatarProps, ChipProps, InputProps } from '../types'
11
12
  import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
12
13
 
13
- const appConfig = _appConfig as AppConfig & { ui: { inputMenu: Partial<typeof theme> } }
14
+ const appConfigInputMenu = _appConfig as AppConfig & { ui: { inputMenu: Partial<typeof theme> } }
14
15
 
15
- const inputMenu = tv({ extend: tv(theme), ...(appConfig.ui?.inputMenu || {}) })
16
+ const inputMenu = tv({ extend: tv(theme), ...(appConfigInputMenu.ui?.inputMenu || {}) })
16
17
 
17
18
  export interface InputMenuItem {
18
19
  label?: string
@@ -144,6 +145,7 @@ extendDevtoolsMeta({ defaultProps: { items: ['Option 1', 'Option 2', 'Option 3']
144
145
  import { computed, ref, toRef, onMounted, toRaw } from 'vue'
145
146
  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'
146
147
  import { defu } from 'defu'
148
+ import { isEqual } from 'ohash'
147
149
  import { reactivePick, createReusableTemplate } from '@vueuse/core'
148
150
  import { useAppConfig } from '#imports'
149
151
  import { useButtonGroup } from '../composables/useButtonGroup'
@@ -176,7 +178,7 @@ const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', '
176
178
  const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }) as ComboboxContentProps)
177
179
  const arrowProps = toRef(() => props.arrow as ComboboxArrowProps)
178
180
 
179
- const { emitFormBlur, emitFormChange, emitFormInput, size: formGroupSize, color, id, name, highlight, disabled } = useFormField<InputProps>(props)
181
+ const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, size: formGroupSize, color, id, name, highlight, disabled, ariaAttrs } = useFormField<InputProps>(props)
180
182
  const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
181
183
  const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, { trailingIcon: appConfig.ui.icons.chevronDown })))
182
184
 
@@ -201,7 +203,7 @@ function displayValue(value: T): string {
201
203
  return value && (typeof value === 'object' ? get(value, props.labelKey as string) : value)
202
204
  }
203
205
 
204
- const item = items.value.find(item => compare(typeof item === 'object' ? get(item, props.valueKey as string) : item, value))
206
+ const item = items.value.find(item => compare(typeof item === 'object' ? get(item as Record<string, any>, props.valueKey as string) : item, value))
205
207
  return item && (typeof item === 'object' ? get(item, props.labelKey as string) : item)
206
208
  }
207
209
 
@@ -277,6 +279,7 @@ function onBlur(event: FocusEvent) {
277
279
 
278
280
  function onFocus(event: FocusEvent) {
279
281
  emits('focus', event)
282
+ emitFormFocus()
280
283
  }
281
284
 
282
285
  function onUpdateOpen(value: boolean) {
@@ -290,6 +293,14 @@ function onUpdateOpen(value: boolean) {
290
293
  }
291
294
  }
292
295
 
296
+ function onRemoveTag(event: any) {
297
+ if (props.multiple) {
298
+ const modelValue = props.modelValue as SelectModelValue<T, V, true>
299
+ const filteredValue = modelValue.filter(value => !isEqual(value, event))
300
+ emits('update:modelValue', filteredValue as SelectModelValue<T, V, M>)
301
+ }
302
+ }
303
+
293
304
  defineExpose({
294
305
  inputRef
295
306
  })
@@ -336,6 +347,7 @@ defineExpose({
336
347
  as-child
337
348
  @blur="onBlur"
338
349
  @focus="onFocus"
350
+ @remove-tag="onRemoveTag"
339
351
  >
340
352
  <TagsInputItem v-for="(item, index) in tags" :key="index" :value="(item as string)" :class="ui.tagsItem({ class: props.ui?.tagsItem })">
341
353
  <TagsInputItemText :class="ui.tagsItemText({ class: props.ui?.tagsItemText })">
@@ -354,7 +366,7 @@ defineExpose({
354
366
  <ComboboxInput v-model="searchTerm" :display-value="displayValue" as-child>
355
367
  <TagsInputInput
356
368
  ref="inputRef"
357
- v-bind="$attrs"
369
+ v-bind="{ ...$attrs, ...ariaAttrs }"
358
370
  :placeholder="placeholder"
359
371
  :required="required"
360
372
  :class="ui.tagsInput({ class: props.ui?.tagsInput })"
@@ -368,7 +380,7 @@ defineExpose({
368
380
  ref="inputRef"
369
381
  v-model="searchTerm"
370
382
  :display-value="displayValue"
371
- v-bind="$attrs"
383
+ v-bind="{ ...$attrs, ...ariaAttrs }"
372
384
  :type="type"
373
385
  :placeholder="placeholder"
374
386
  :required="required"
@@ -1,14 +1,15 @@
1
1
  <script lang="ts">
2
- import { tv, type VariantProps } from 'tailwind-variants'
2
+ import type { VariantProps } from 'tailwind-variants'
3
3
  import type { NumberFieldRootProps } from 'reka-ui'
4
4
  import type { AppConfig } from '@nuxt/schema'
5
5
  import _appConfig from '#build/app.config'
6
6
  import theme from '#build/ui/input-number'
7
+ import { tv } from '../utils/tv'
7
8
  import type { ButtonProps } from '../types'
8
9
 
9
- const appConfig = _appConfig as AppConfig & { ui: { inputNumber: Partial<typeof theme> } }
10
+ const appConfigInputNumber = _appConfig as AppConfig & { ui: { inputNumber: Partial<typeof theme> } }
10
11
 
11
- const inputNumber = tv({ extend: tv(theme), ...(appConfig.ui?.inputNumber || {}) })
12
+ const inputNumber = tv({ extend: tv(theme), ...(appConfigInputNumber.ui?.inputNumber || {}) })
12
13
 
13
14
  type InputNumberVariants = VariantProps<typeof inputNumber>
14
15
 
@@ -77,6 +78,7 @@ export interface InputNumberSlots {
77
78
  import { onMounted, ref, computed } from 'vue'
78
79
  import { NumberFieldRoot, NumberFieldInput, NumberFieldDecrement, NumberFieldIncrement, useForwardPropsEmits } from 'reka-ui'
79
80
  import { reactivePick } from '@vueuse/core'
81
+ import { useAppConfig } from '#imports'
80
82
  import { useFormField } from '../composables/useFormField'
81
83
  import { useLocale } from '../composables/useLocale'
82
84
  import UButton from './Button.vue'
@@ -91,7 +93,8 @@ defineSlots<InputNumberSlots>()
91
93
 
92
94
  const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'min', 'max', 'step', 'formatOptions'), emits)
93
95
 
94
- const { emitFormBlur, emitFormChange, emitFormInput, id, color, size, name, highlight, disabled } = useFormField<InputNumberProps>(props)
96
+ const appConfig = useAppConfig()
97
+ const { emitFormBlur, emitFormFocus, emitFormChange, emitFormInput, id, color, size, name, highlight, disabled, ariaAttrs } = useFormField<InputNumberProps>(props)
95
98
 
96
99
  const { t, code: codeLocale } = useLocale()
97
100
  const locale = computed(() => props.locale || codeLocale.value)
@@ -151,12 +154,13 @@ defineExpose({
151
154
  @update:model-value="onUpdate"
152
155
  >
153
156
  <NumberFieldInput
154
- v-bind="$attrs"
157
+ v-bind="{ ...$attrs, ...ariaAttrs }"
155
158
  ref="inputRef"
156
159
  :placeholder="placeholder"
157
160
  :required="required"
158
161
  :class="ui.base({ class: props.ui?.base })"
159
162
  @blur="onBlur"
163
+ @focus="emitFormFocus"
160
164
  />
161
165
 
162
166
  <div :class="ui.increment({ class: props.ui?.increment })">
@@ -1,14 +1,15 @@
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/kbd'
6
6
  import type { KbdKey } from '../composables/useKbd'
7
7
  import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
8
+ import { tv } from '../utils/tv'
8
9
 
9
- const appConfig = _appConfig as AppConfig & { ui: { kbd: Partial<typeof theme> } }
10
+ const appConfigKbd = _appConfig as AppConfig & { ui: { kbd: Partial<typeof theme> } }
10
11
 
11
- const kbd = tv({ extend: tv(theme), ...(appConfig.ui?.kbd || {}) })
12
+ const kbd = tv({ extend: tv(theme), ...(appConfigKbd.ui?.kbd || {}) })
12
13
 
13
14
  type KbdVariants = VariantProps<typeof kbd>
14
15
 
@@ -1,10 +1,10 @@
1
1
  <script lang="ts">
2
2
  import type { ButtonHTMLAttributes } from 'vue'
3
- import { tv } from 'tailwind-variants'
4
3
  import type { AppConfig } from '@nuxt/schema'
5
4
  import _appConfig from '#build/app.config'
6
5
  import type { RouterLinkProps, RouteLocationRaw } from 'vue-router'
7
6
  import theme from '#build/ui/link'
7
+ import { tv } from '../utils/tv'
8
8
  import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
9
9
 
10
10
  interface NuxtLinkProps extends Omit<RouterLinkProps, 'to'> {
@@ -53,9 +53,9 @@ interface NuxtLinkProps extends Omit<RouterLinkProps, 'to'> {
53
53
  noPrefetch?: boolean
54
54
  }
55
55
 
56
- const appConfig = _appConfig as AppConfig & { ui: { link: Partial<typeof theme> } }
56
+ const appConfigLink = _appConfig as AppConfig & { ui: { link: Partial<typeof theme> } }
57
57
 
58
- const link = tv({ extend: tv(theme), ...(appConfig.ui?.link || {}) })
58
+ const link = tv({ extend: tv(theme), ...(appConfigLink.ui?.link || {}) })
59
59
 
60
60
  export interface LinkProps extends NuxtLinkProps {
61
61
  /**
@@ -1,15 +1,15 @@
1
1
  <script lang="ts">
2
- import { tv } from 'tailwind-variants'
3
2
  import type { DialogRootProps, DialogRootEmits, DialogContentProps } from 'reka-ui'
4
3
  import type { AppConfig } from '@nuxt/schema'
5
4
  import _appConfig from '#build/app.config'
6
5
  import theme from '#build/ui/modal'
7
6
  import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
7
+ import { tv } from '../utils/tv'
8
8
  import type { ButtonProps } from '../types'
9
9
 
10
- const appConfig = _appConfig as AppConfig & { ui: { modal: Partial<typeof theme> } }
10
+ const appConfigModal = _appConfig as AppConfig & { ui: { modal: Partial<typeof theme> } }
11
11
 
12
- const modal = tv({ extend: tv(theme), ...(appConfig.ui?.modal || {}) })
12
+ const modal = tv({ extend: tv(theme), ...(appConfigModal.ui?.modal || {}) })
13
13
 
14
14
  export interface ModalProps extends DialogRootProps {
15
15
  title?: string
@@ -48,10 +48,10 @@ export interface ModalProps extends DialogRootProps {
48
48
  */
49
49
  closeIcon?: string
50
50
  /**
51
- * When `true`, the modal will not close when clicking outside.
52
- * @defaultValue false
51
+ * When `false`, the modal will not close when clicking outside or pressing escape.
52
+ * @defaultValue true
53
53
  */
54
- preventClose?: boolean
54
+ dismissible?: boolean
55
55
  class?: any
56
56
  ui?: Partial<typeof modal.slots>
57
57
  }
@@ -85,7 +85,8 @@ const props = withDefaults(defineProps<ModalProps>(), {
85
85
  portal: true,
86
86
  overlay: true,
87
87
  transition: true,
88
- modal: true
88
+ modal: true,
89
+ dismissible: true
89
90
  })
90
91
  const emits = defineEmits<ModalEmits>()
91
92
  const slots = defineSlots<ModalSlots>()
@@ -96,7 +97,7 @@ const appConfig = useAppConfig()
96
97
  const rootProps = useForwardPropsEmits(reactivePick(props, 'open', 'defaultOpen', 'modal'), emits)
97
98
  const contentProps = toRef(() => props.content)
98
99
  const contentEvents = computed(() => {
99
- if (props.preventClose) {
100
+ if (!props.dismissible) {
100
101
  return {
101
102
  pointerDownOutside: (e: Event) => e.preventDefault(),
102
103
  interactOutside: (e: Event) => e.preventDefault(),
@@ -104,13 +105,7 @@ const contentEvents = computed(() => {
104
105
  }
105
106
  }
106
107
 
107
- return {
108
- interactOutside: (e: Event) => {
109
- if (e.target instanceof Element && e.target.closest('[data-sonner-toaster]')) {
110
- return e.preventDefault()
111
- }
112
- }
113
- }
108
+ return {}
114
109
  })
115
110
 
116
111
  const ui = computed(() => modal({