@nuxt/ui 3.0.0-alpha.11 → 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 (112) hide show
  1. package/.nuxt/ui/alert.ts +1 -1
  2. package/.nuxt/ui/avatar.ts +1 -1
  3. package/.nuxt/ui/button.ts +0 -1
  4. package/.nuxt/ui/context-menu.ts +1 -1
  5. package/.nuxt/ui/dropdown-menu.ts +1 -1
  6. package/.nuxt/ui/navigation-menu.ts +79 -11
  7. package/cli/package.json +2 -2
  8. package/cli/templates.mjs +6 -6
  9. package/dist/chunks/component-meta.cjs +2590 -2511
  10. package/dist/chunks/component-meta.mjs +2590 -2511
  11. package/dist/client/devtools/200.html +1 -1
  12. package/dist/client/devtools/404.html +1 -1
  13. package/dist/client/devtools/_nuxt/{CQ-XZFu7.js → CgoQwOTV.js} +26 -23
  14. package/dist/client/devtools/_nuxt/{DNZFyo26.js → DLmPhbaq.js} +1 -1
  15. package/dist/client/devtools/_nuxt/{Dtz-o9GB.js → DYuJNiSW.js} +1 -1
  16. package/dist/client/devtools/_nuxt/builds/latest.json +1 -1
  17. package/dist/client/devtools/_nuxt/builds/meta/3184ffe2-7577-4441-af98-fe6e81c30e3a.json +1 -0
  18. package/dist/client/devtools/_nuxt/entry.ChJczkiN.css +1 -0
  19. package/dist/client/devtools/_nuxt/error-404.DyY3rYaL.css +1 -0
  20. package/dist/client/devtools/_nuxt/error-500.D_1PCaJh.css +1 -0
  21. package/dist/client/devtools/index.html +1 -1
  22. package/dist/module.cjs +1 -1
  23. package/dist/module.json +1 -1
  24. package/dist/module.mjs +1 -1
  25. package/dist/runtime/components/Accordion.vue +2 -2
  26. package/dist/runtime/components/Alert.vue +2 -2
  27. package/dist/runtime/components/Avatar.vue +31 -10
  28. package/dist/runtime/components/AvatarGroup.vue +2 -2
  29. package/dist/runtime/components/Badge.vue +2 -2
  30. package/dist/runtime/components/Breadcrumb.vue +2 -2
  31. package/dist/runtime/components/Button.vue +2 -2
  32. package/dist/runtime/components/ButtonGroup.vue +2 -2
  33. package/dist/runtime/components/Calendar.vue +4 -2
  34. package/dist/runtime/components/Card.vue +2 -2
  35. package/dist/runtime/components/Carousel.vue +2 -2
  36. package/dist/runtime/components/Checkbox.vue +4 -4
  37. package/dist/runtime/components/Chip.vue +2 -2
  38. package/dist/runtime/components/Collapsible.vue +2 -2
  39. package/dist/runtime/components/ColorPicker.vue +46 -15
  40. package/dist/runtime/components/CommandPalette.vue +2 -2
  41. package/dist/runtime/components/Container.vue +2 -2
  42. package/dist/runtime/components/ContextMenu.vue +2 -2
  43. package/dist/runtime/components/ContextMenuContent.vue +1 -1
  44. package/dist/runtime/components/Drawer.vue +2 -2
  45. package/dist/runtime/components/DropdownMenu.vue +2 -2
  46. package/dist/runtime/components/DropdownMenuContent.vue +1 -1
  47. package/dist/runtime/components/Form.vue +46 -17
  48. package/dist/runtime/components/FormField.vue +12 -6
  49. package/dist/runtime/components/Input.vue +5 -4
  50. package/dist/runtime/components/InputMenu.vue +6 -5
  51. package/dist/runtime/components/InputNumber.vue +7 -4
  52. package/dist/runtime/components/Kbd.vue +2 -2
  53. package/dist/runtime/components/Link.vue +2 -2
  54. package/dist/runtime/components/Modal.vue +2 -2
  55. package/dist/runtime/components/NavigationMenu.vue +89 -66
  56. package/dist/runtime/components/Pagination.vue +2 -2
  57. package/dist/runtime/components/PinInput.vue +5 -4
  58. package/dist/runtime/components/Popover.vue +2 -2
  59. package/dist/runtime/components/Progress.vue +2 -2
  60. package/dist/runtime/components/RadioGroup.vue +4 -4
  61. package/dist/runtime/components/Select.vue +9 -7
  62. package/dist/runtime/components/SelectMenu.vue +5 -4
  63. package/dist/runtime/components/Separator.vue +2 -2
  64. package/dist/runtime/components/Skeleton.vue +2 -2
  65. package/dist/runtime/components/Slideover.vue +2 -2
  66. package/dist/runtime/components/Slider.vue +4 -4
  67. package/dist/runtime/components/Stepper.vue +2 -2
  68. package/dist/runtime/components/Switch.vue +4 -4
  69. package/dist/runtime/components/Table.vue +2 -2
  70. package/dist/runtime/components/Tabs.vue +2 -2
  71. package/dist/runtime/components/Textarea.vue +5 -4
  72. package/dist/runtime/components/Toast.vue +2 -2
  73. package/dist/runtime/components/Toaster.vue +2 -2
  74. package/dist/runtime/components/Tooltip.vue +2 -2
  75. package/dist/runtime/composables/useFormField.d.ts +6 -2
  76. package/dist/runtime/composables/useFormField.js +18 -11
  77. package/dist/runtime/composables/useToast.d.ts +1 -1
  78. package/dist/runtime/composables/useToast.js +21 -7
  79. package/dist/runtime/index.css +1 -1
  80. package/dist/runtime/locale/es.js +2 -2
  81. package/dist/runtime/locale/he.js +53 -0
  82. package/dist/runtime/locale/hi.js +54 -0
  83. package/dist/runtime/locale/hu.d.ts +2 -0
  84. package/dist/runtime/locale/hu.js +54 -0
  85. package/dist/runtime/locale/index.d.ts +5 -2
  86. package/dist/runtime/locale/index.js +5 -2
  87. package/dist/runtime/locale/km.d.ts +2 -0
  88. package/dist/runtime/locale/km.js +54 -0
  89. package/dist/runtime/locale/nb_no.d.ts +2 -0
  90. package/dist/runtime/locale/nb_no.js +54 -0
  91. package/dist/runtime/locale/zh_cn.d.ts +2 -0
  92. package/dist/runtime/locale/{zh_hans.js → zh_cn.js} +1 -1
  93. package/dist/runtime/locale/zh_tw.d.ts +2 -0
  94. package/dist/runtime/locale/{zh_hant.js → zh_tw.js} +1 -1
  95. package/dist/runtime/plugins/colors.js +7 -4
  96. package/dist/runtime/types/form.d.ts +21 -12
  97. package/dist/runtime/utils/form.js +3 -5
  98. package/dist/runtime/utils/tv.js +2 -2
  99. package/dist/runtime/vue/components/Link.vue +2 -2
  100. package/dist/shared/{ui.BKsd22-Q.mjs → ui.CENZ17mM.mjs} +72 -16
  101. package/dist/shared/{ui.DCjJwb6l.cjs → ui.ClMXjgLK.cjs} +72 -16
  102. package/dist/unplugin.cjs +1 -1
  103. package/dist/unplugin.mjs +1 -1
  104. package/dist/vite.cjs +1 -1
  105. package/dist/vite.mjs +1 -1
  106. package/package.json +22 -23
  107. package/dist/client/devtools/_nuxt/builds/meta/1f5fbd7b-9d97-480e-9ae4-e02b4ff7018b.json +0 -1
  108. package/dist/client/devtools/_nuxt/entry.qOdop8tq.css +0 -1
  109. package/dist/client/devtools/_nuxt/error-404.D-rS9BDk.css +0 -1
  110. package/dist/client/devtools/_nuxt/error-500.CfcCHs91.css +0 -1
  111. /package/dist/runtime/locale/{zh_hans.d.ts → he.d.ts} +0 -0
  112. /package/dist/runtime/locale/{zh_hant.d.ts → hi.d.ts} +0 -0
@@ -7,9 +7,9 @@ import theme from '#build/ui/checkbox'
7
7
  import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
8
8
  import { tv } from '../utils/tv'
9
9
 
10
- const appConfig = _appConfig as AppConfig & { ui: { checkbox: Partial<typeof theme> } }
10
+ const appConfigCheckbox = _appConfig as AppConfig & { ui: { checkbox: Partial<typeof theme> } }
11
11
 
12
- const checkbox = tv({ extend: tv(theme), ...(appConfig.ui?.checkbox || {}) })
12
+ const checkbox = tv({ extend: tv(theme), ...(appConfigCheckbox.ui?.checkbox || {}) })
13
13
 
14
14
  type CheckboxVariants = VariantProps<typeof checkbox>
15
15
 
@@ -66,7 +66,7 @@ const modelValue = defineModel<boolean | 'indeterminate'>({ default: undefined }
66
66
  const rootProps = useForwardProps(reactivePick(props, 'required', 'value', 'defaultValue'))
67
67
 
68
68
  const appConfig = useAppConfig()
69
- const { id: _id, emitFormChange, emitFormInput, size, color, name, disabled } = useFormField<CheckboxProps>(props)
69
+ const { id: _id, emitFormChange, emitFormInput, size, color, name, disabled, ariaAttrs } = useFormField<CheckboxProps>(props)
70
70
  const id = _id.value ?? useId()
71
71
 
72
72
  const ui = computed(() => checkbox({
@@ -92,7 +92,7 @@ function onUpdate(value: any) {
92
92
  <div :class="ui.container({ class: props.ui?.container })">
93
93
  <CheckboxRoot
94
94
  :id="id"
95
- v-bind="rootProps"
95
+ v-bind="{ ...rootProps, ...ariaAttrs }"
96
96
  v-model="modelValue"
97
97
  :name="name"
98
98
  :disabled="disabled"
@@ -6,9 +6,9 @@ import theme from '#build/ui/chip'
6
6
  import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
7
7
  import { tv } from '../utils/tv'
8
8
 
9
- const appConfig = _appConfig as AppConfig & { ui: { chip: Partial<typeof theme> } }
9
+ const appConfigChip = _appConfig as AppConfig & { ui: { chip: Partial<typeof theme> } }
10
10
 
11
- const chip = tv({ extend: tv(theme), ...(appConfig.ui?.chip || {}) })
11
+ const chip = tv({ extend: tv(theme), ...(appConfigChip.ui?.chip || {}) })
12
12
 
13
13
  type ChipVariants = VariantProps<typeof chip>
14
14
 
@@ -6,9 +6,9 @@ import theme from '#build/ui/collapsible'
6
6
  import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
7
7
  import { tv } from '../utils/tv'
8
8
 
9
- const appConfig = _appConfig as AppConfig & { ui: { collapsible: Partial<typeof theme> } }
9
+ const appConfigCollapsible = _appConfig as AppConfig & { ui: { collapsible: Partial<typeof theme> } }
10
10
 
11
- const collapsible = tv({ extend: tv(theme), ...(appConfig.ui?.collapsible || {}) })
11
+ const collapsible = tv({ extend: tv(theme), ...(appConfigCollapsible.ui?.collapsible || {}) })
12
12
 
13
13
  export interface CollapsibleProps extends Pick<CollapsibleRootProps, 'defaultOpen' | 'open' | 'disabled' | 'unmountOnHide'> {
14
14
  /**
@@ -5,10 +5,11 @@ import type { AppConfig } from '@nuxt/schema'
5
5
  import _appConfig from '#build/app.config'
6
6
  import theme from '#build/ui/color-picker'
7
7
  import { tv } from '../utils/tv'
8
+ import type { HSLObject } from 'colortranslator'
8
9
 
9
- const appConfig = _appConfig as AppConfig & { ui: { colorPicker: Partial<typeof theme> } }
10
+ const appConfigColorPicker = _appConfig as AppConfig & { ui: { colorPicker: Partial<typeof theme> } }
10
11
 
11
- const colorPicker = tv({ extend: tv(theme), ...(appConfig.ui?.colorPicker || {}) })
12
+ const colorPicker = tv({ extend: tv(theme), ...(appConfigColorPicker.ui?.colorPicker || {}) })
12
13
 
13
14
  type ColorPickerVariants = VariantProps<typeof colorPicker>
14
15
 
@@ -18,6 +19,27 @@ type HSVColor = {
18
19
  v: number
19
20
  }
20
21
 
22
+ function HSLtoHSV(hsl: HSLObject): HSVColor {
23
+ const x = hsl.S * (hsl.L < 50 ? hsl.L : 100 - hsl.L)
24
+ const v = hsl.L + (x / 100)
25
+
26
+ return {
27
+ h: hsl.H,
28
+ s: hsl.L === 0 ? hsl.S : 2 * x / v,
29
+ v
30
+ }
31
+ }
32
+
33
+ function HSVtoHSL(hsv: HSVColor): HSLObject {
34
+ const x = (200 - hsv.s) * hsv.v / 100
35
+
36
+ return {
37
+ H: hsv.h,
38
+ S: x === 0 || x === 200 ? 0 : Math.round(hsv.s * hsv.v / (x <= 100 ? x : 200 - x)),
39
+ L: Math.round(x / 2)
40
+ }
41
+ }
42
+
21
43
  export type ColorPickerProps = {
22
44
  /**
23
45
  * The element or component this component should render as.
@@ -40,7 +62,7 @@ export type ColorPickerProps = {
40
62
  * Format of the color
41
63
  * @defaultValue 'hex'
42
64
  */
43
- format?: 'hex' | 'rgb' | 'hsl' | 'hwb'
65
+ format?: 'hex' | 'rgb' | 'hsl' | 'cmyk' | 'lab'
44
66
  size?: ColorPickerVariants['size']
45
67
  class?: any
46
68
  ui?: Partial<typeof colorPicker.slots>
@@ -52,7 +74,7 @@ import { ref, nextTick, computed, toValue } from 'vue'
52
74
  import { Primitive } from 'reka-ui'
53
75
  import { useEventListener, useElementBounding, watchThrottled, watchPausable } from '@vueuse/core'
54
76
  import { isClient } from '@vueuse/shared'
55
- import Color from 'color'
77
+ import { ColorTranslator } from 'colortranslator'
56
78
 
57
79
  const props = withDefaults(defineProps<ColorPickerProps>(), {
58
80
  format: 'hex',
@@ -64,28 +86,37 @@ const modelValue = defineModel<string>(undefined)
64
86
  const pickedColor = computed<HSVColor>({
65
87
  get() {
66
88
  try {
67
- const color = Color(modelValue.value || props.defaultValue)
68
- return color.hsv().object() as HSVColor
89
+ const color = new ColorTranslator(modelValue.value || props.defaultValue)
90
+
91
+ return HSLtoHSV(color.HSLObject)
69
92
  } catch (_) {
70
93
  return { h: 0, s: 0, v: 100 }
71
94
  }
72
95
  },
73
96
  set(value) {
74
- const color = Color.hsv(value.h, value.s, value.v)
97
+ const color = new ColorTranslator(HSVtoHSL(value), {
98
+ decimals: 2,
99
+ labUnit: 'percent',
100
+ cmykUnit: 'percent',
101
+ cmykFunction: 'cmyk'
102
+ })
75
103
 
76
104
  switch (props.format) {
77
105
  case 'rgb':
78
- modelValue.value = color.rgb().string()
106
+ modelValue.value = color.RGB
79
107
  break
80
108
  case 'hsl':
81
- modelValue.value = color.hsl().string()
109
+ modelValue.value = color.HSL
110
+ break
111
+ case 'cmyk':
112
+ modelValue.value = color.CMYK
82
113
  break
83
- case 'hwb':
84
- modelValue.value = color.hwb().string()
114
+ case 'lab':
115
+ modelValue.value = color.CIELab
85
116
  break
86
117
  case 'hex':
87
118
  default:
88
- modelValue.value = color.hex()
119
+ modelValue.value = color.HEX
89
120
  }
90
121
  }
91
122
  })
@@ -202,18 +233,18 @@ watchThrottled([selectorThumbPosition, trackThumbPosition], () => {
202
233
  nextTick(resumeWatchColor)
203
234
  }, { throttle: () => props.throttle })
204
235
 
205
- const trackThumbColor = computed(() => Color({
236
+ const trackThumbColor = computed(() => new ColorTranslator(HSVtoHSL({
206
237
  h: normalizeHue(trackThumbPosition.value.y),
207
238
  s: 100,
208
239
  v: 100
209
- }).hex())
240
+ })).HEX)
210
241
 
211
242
  const selectorStyle = computed(() => ({
212
243
  backgroundColor: trackThumbColor.value
213
244
  }))
214
245
 
215
246
  const selectorThumbStyle = computed(() => ({
216
- backgroundColor: Color(modelValue.value || props.defaultValue).hex(),
247
+ backgroundColor: new ColorTranslator(modelValue.value || props.defaultValue).HEX,
217
248
  left: `${selectorThumbPosition.value.x}%`,
218
249
  top: `${selectorThumbPosition.value.y}%`
219
250
  }))
@@ -11,9 +11,9 @@ import { tv } from '../utils/tv'
11
11
  import type { AvatarProps, ButtonProps, ChipProps, KbdProps, InputProps } from '../types'
12
12
  import type { DynamicSlots, PartialString } from '../types/utils'
13
13
 
14
- const appConfig = _appConfig as AppConfig & { ui: { commandPalette: Partial<typeof theme> } }
14
+ const appConfigCommandPalette = _appConfig as AppConfig & { ui: { commandPalette: Partial<typeof theme> } }
15
15
 
16
- const commandPalette = tv({ extend: tv(theme), ...(appConfig.ui?.commandPalette || {}) })
16
+ const commandPalette = tv({ extend: tv(theme), ...(appConfigCommandPalette.ui?.commandPalette || {}) })
17
17
 
18
18
  export interface CommandPaletteItem {
19
19
  prefix?: string
@@ -5,9 +5,9 @@ import theme from '#build/ui/container'
5
5
  import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
6
6
  import { tv } from '../utils/tv'
7
7
 
8
- const appConfig = _appConfig as AppConfig & { ui: { container: Partial<typeof theme> } }
8
+ const appConfigContainer = _appConfig as AppConfig & { ui: { container: Partial<typeof theme> } }
9
9
 
10
- const container = tv({ extend: tv(theme), ...(appConfig.ui?.container || {}) })
10
+ const container = tv({ extend: tv(theme), ...(appConfigContainer.ui?.container || {}) })
11
11
 
12
12
  export interface ContainerProps {
13
13
  /**
@@ -9,9 +9,9 @@ import { tv } from '../utils/tv'
9
9
  import type { AvatarProps, KbdProps, LinkProps } from '../types'
10
10
  import type { DynamicSlots, PartialString } from '../types/utils'
11
11
 
12
- const appConfig = _appConfig as AppConfig & { ui: { contextMenu: Partial<typeof theme> } }
12
+ const appConfigContextMenu = _appConfig as AppConfig & { ui: { contextMenu: Partial<typeof theme> } }
13
13
 
14
- const contextMenu = tv({ extend: tv(theme), ...(appConfig.ui?.contextMenu || {}) })
14
+ const contextMenu = tv({ extend: tv(theme), ...(appConfigContextMenu.ui?.contextMenu || {}) })
15
15
 
16
16
  type ContextMenuVariants = VariantProps<typeof contextMenu>
17
17
 
@@ -42,7 +42,7 @@ const emits = defineEmits<ContextMenuContentEmits>()
42
42
  const slots = defineSlots<ContextMenuSlots<T>>()
43
43
 
44
44
  const appConfig = useAppConfig()
45
- const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'class', 'ui'), emits)
45
+ const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'labelKey', 'checkedIcon', 'loadingIcon', 'class', 'ui', 'uiOverride'), emits)
46
46
  const proxySlots = omit(slots, ['default']) as Record<string, ContextMenuSlots<T>[string]>
47
47
 
48
48
  const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: ContextMenuItem, active?: boolean, index: number }>()
@@ -7,9 +7,9 @@ import theme from '#build/ui/drawer'
7
7
  import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
8
8
  import { tv } from '../utils/tv'
9
9
 
10
- const appConfig = _appConfig as AppConfig & { ui: { drawer: Partial<typeof theme> } }
10
+ const appConfigDrawer = _appConfig as AppConfig & { ui: { drawer: Partial<typeof theme> } }
11
11
 
12
- const drawer = tv({ extend: tv(theme), ...(appConfig.ui?.drawer || {}) })
12
+ const drawer = tv({ extend: tv(theme), ...(appConfigDrawer.ui?.drawer || {}) })
13
13
 
14
14
  export interface DrawerProps extends Pick<DrawerRootProps, 'activeSnapPoint' | 'closeThreshold' | 'defaultOpen' | 'direction' | 'fadeFromIndex' | 'fixed' | 'modal' | 'nested' | 'direction' | 'open' | 'scrollLockTimeout' | 'shouldScaleBackground' | 'snapPoints'> {
15
15
  /**
@@ -9,9 +9,9 @@ import { tv } from '../utils/tv'
9
9
  import type { AvatarProps, KbdProps, LinkProps } from '../types'
10
10
  import type { DynamicSlots, PartialString } from '../types/utils'
11
11
 
12
- const appConfig = _appConfig as AppConfig & { ui: { dropdownMenu: Partial<typeof theme> } }
12
+ const appConfigDropdownMenu = _appConfig as AppConfig & { ui: { dropdownMenu: Partial<typeof theme> } }
13
13
 
14
- const dropdownMenu = tv({ extend: tv(theme), ...(appConfig.ui?.dropdownMenu || {}) })
14
+ const dropdownMenu = tv({ extend: tv(theme), ...(appConfigDropdownMenu.ui?.dropdownMenu || {}) })
15
15
 
16
16
  type DropdownMenuVariants = VariantProps<typeof dropdownMenu>
17
17
 
@@ -48,7 +48,7 @@ const emits = defineEmits<DropdownMenuContentEmits>()
48
48
  const slots = defineSlots<DropdownMenuContentSlots<T>>()
49
49
 
50
50
  const appConfig = useAppConfig()
51
- const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'class', 'ui'), emits)
51
+ const contentProps = useForwardPropsEmits(reactiveOmit(props, 'sub', 'items', 'portal', 'labelKey', 'checkedIcon', 'loadingIcon', 'class', 'ui', 'uiOverride'), emits)
52
52
  const proxySlots = omit(slots, ['default']) as Record<string, DropdownMenuContentSlots<T>[string]>
53
53
 
54
54
  const [DefineItemTemplate, ReuseItemTemplate] = createReusableTemplate<{ item: DropdownMenuItem, active?: boolean, index: number }>()
@@ -5,10 +5,11 @@ import theme from '#build/ui/form'
5
5
  import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
6
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
@@ -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 => ({
@@ -121,12 +142,12 @@ async function getErrors(): Promise<FormErrorWithId[]> {
121
142
  return resolveErrorIds(errs)
122
143
  }
123
144
 
124
- async function _validate(opts: { name?: string | string[], silent?: boolean, nested?: boolean } = { silent: false, nested: true }): Promise<T | false> {
125
- 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)[]
126
147
 
127
148
  const nestedValidatePromises = !names && opts.nested
128
149
  ? Array.from(nestedForms.value.values()).map(
129
- ({ validate }) => validate().then(() => undefined).catch((error: Error) => {
150
+ ({ validate }) => validate(opts).then(() => undefined).catch((error: Error) => {
130
151
  if (!(error instanceof FormValidationException)) {
131
152
  throw error
132
153
  }
@@ -151,13 +172,17 @@ async function _validate(opts: { name?: string | string[], silent?: boolean, nes
151
172
  errors.value = await getErrors()
152
173
  }
153
174
 
154
- const childErrors = (await Promise.all(nestedValidatePromises)).filter(val => val)
175
+ const childErrors = (await Promise.all(nestedValidatePromises)).filter(val => val !== undefined)
155
176
 
156
177
  if (errors.value.length + childErrors.length > 0) {
157
178
  if (opts.silent) return false
158
179
  throw new FormValidationException(formId, errors.value, childErrors)
159
180
  }
160
181
 
182
+ if (opts.transform) {
183
+ Object.assign(props.state, transformedState.value)
184
+ }
185
+
161
186
  return props.state as T
162
187
  }
163
188
 
@@ -170,8 +195,7 @@ async function onSubmitWrapper(payload: Event) {
170
195
  const event = payload as FormSubmitEvent<any>
171
196
 
172
197
  try {
173
- await _validate({ nested: true })
174
- event.data = props.schema ? transformedState.value : props.state
198
+ event.data = await _validate({ nested: true, transform: true })
175
199
  await props.onSubmit?.(event)
176
200
  } catch (error) {
177
201
  if (!(error instanceof FormValidationException)) {
@@ -200,7 +224,7 @@ defineExpose<Form<T>>({
200
224
  validate: _validate,
201
225
  errors,
202
226
 
203
- setErrors(errs: FormError[], name?: string) {
227
+ setErrors(errs: FormError[], name?: keyof T) {
204
228
  if (name) {
205
229
  errors.value = errors.value
206
230
  .filter(error => error.name !== name)
@@ -214,7 +238,7 @@ defineExpose<Form<T>>({
214
238
  await onSubmitWrapper(new Event('submit'))
215
239
  },
216
240
 
217
- getErrors(name?: string) {
241
+ getErrors(name?: keyof T) {
218
242
  if (name) {
219
243
  return errors.value.filter(err => err.name === name)
220
244
  }
@@ -229,7 +253,12 @@ defineExpose<Form<T>>({
229
253
  }
230
254
  },
231
255
 
232
- 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>>
233
262
  })
234
263
  </script>
235
264
 
@@ -6,9 +6,9 @@ import theme from '#build/ui/form-field'
6
6
  import { tv } from '../utils/tv'
7
7
  import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
8
8
 
9
- const appConfig = _appConfig as AppConfig & { ui: { formField: Partial<typeof theme> } }
9
+ const appConfigFormField = _appConfig as AppConfig & { ui: { formField: Partial<typeof theme> } }
10
10
 
11
- const formField = tv({ extend: tv(theme), ...(appConfig.ui?.formField || {}) })
11
+ const formField = tv({ extend: tv(theme), ...(appConfigFormField.ui?.formField || {}) })
12
12
 
13
13
  type FormFieldVariants = VariantProps<typeof formField>
14
14
 
@@ -66,6 +66,9 @@ const formErrors = inject<Ref<FormError[]> | null>('form-errors', null)
66
66
  const error = computed(() => props.error || formErrors?.value?.find(error => error.name === props.name || (props.errorPattern && error.name.match(props.errorPattern)))?.message)
67
67
 
68
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
69
72
 
70
73
  provide(inputIdInjectionKey, id)
71
74
 
@@ -75,7 +78,10 @@ provide(formFieldInjectionKey, computed(() => ({
75
78
  size: props.size,
76
79
  eagerValidation: props.eagerValidation,
77
80
  validateOnInputDelay: props.validateOnInputDelay,
78
- errorPattern: props.errorPattern
81
+ errorPattern: props.errorPattern,
82
+ hint: props.hint,
83
+ description: props.description,
84
+ ariaId
79
85
  }) as FormFieldInjectedOptions<FormFieldProps>))
80
86
  </script>
81
87
 
@@ -88,14 +94,14 @@ provide(formFieldInjectionKey, computed(() => ({
88
94
  {{ label }}
89
95
  </slot>
90
96
  </Label>
91
- <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 })">
92
98
  <slot name="hint" :hint="hint">
93
99
  {{ hint }}
94
100
  </slot>
95
101
  </span>
96
102
  </div>
97
103
 
98
- <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 })">
99
105
  <slot name="description" :description="description">
100
106
  {{ description }}
101
107
  </slot>
@@ -105,7 +111,7 @@ provide(formFieldInjectionKey, computed(() => ({
105
111
  <div :class="[(label || !!slots.label || description || !!slots.description) && ui.container({ class: props.ui?.container })]">
106
112
  <slot :error="error" />
107
113
 
108
- <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 })">
109
115
  <slot name="error" :error="error">
110
116
  {{ error }}
111
117
  </slot>
@@ -9,9 +9,9 @@ import { tv } from '../utils/tv'
9
9
  import type { AvatarProps } from '../types'
10
10
  import type { PartialString } from '../types/utils'
11
11
 
12
- const appConfig = _appConfig as AppConfig & { ui: { input: Partial<typeof theme> } }
12
+ const appConfigInput = _appConfig as AppConfig & { ui: { input: Partial<typeof theme> } }
13
13
 
14
- const input = tv({ extend: tv(theme), ...(appConfig.ui?.input || {}) })
14
+ const input = tv({ extend: tv(theme), ...(appConfigInput.ui?.input || {}) })
15
15
 
16
16
  type InputVariants = VariantProps<typeof input>
17
17
 
@@ -75,7 +75,7 @@ const slots = defineSlots<InputSlots>()
75
75
 
76
76
  const [modelValue, modelModifiers] = defineModel<string | number>()
77
77
 
78
- const { emitFormBlur, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled } = useFormField<InputProps>(props, { deferInputValidation: true })
78
+ const { emitFormBlur, emitFormInput, emitFormChange, size: formGroupSize, color, id, name, highlight, disabled, emitFormFocus, ariaAttrs } = useFormField<InputProps>(props, { deferInputValidation: true })
79
79
  const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
80
80
  const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(props)
81
81
 
@@ -166,10 +166,11 @@ onMounted(() => {
166
166
  :disabled="disabled"
167
167
  :required="required"
168
168
  :autocomplete="autocomplete"
169
- v-bind="$attrs"
169
+ v-bind="{ ...$attrs, ...ariaAttrs }"
170
170
  @input="onInput"
171
171
  @blur="onBlur"
172
172
  @change="onChange"
173
+ @focus="emitFormFocus"
173
174
  >
174
175
 
175
176
  <slot />
@@ -11,9 +11,9 @@ import { tv } from '../utils/tv'
11
11
  import type { AvatarProps, ChipProps, InputProps } from '../types'
12
12
  import type { PartialString, MaybeArrayOfArray, MaybeArrayOfArrayItem, SelectModelValue, SelectModelValueEmits, SelectItemKey } from '../types/utils'
13
13
 
14
- const appConfig = _appConfig as AppConfig & { ui: { inputMenu: Partial<typeof theme> } }
14
+ const appConfigInputMenu = _appConfig as AppConfig & { ui: { inputMenu: Partial<typeof theme> } }
15
15
 
16
- const inputMenu = tv({ extend: tv(theme), ...(appConfig.ui?.inputMenu || {}) })
16
+ const inputMenu = tv({ extend: tv(theme), ...(appConfigInputMenu.ui?.inputMenu || {}) })
17
17
 
18
18
  export interface InputMenuItem {
19
19
  label?: string
@@ -178,7 +178,7 @@ const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', '
178
178
  const contentProps = toRef(() => defu(props.content, { side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' }) as ComboboxContentProps)
179
179
  const arrowProps = toRef(() => props.arrow as ComboboxArrowProps)
180
180
 
181
- 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)
182
182
  const { orientation, size: buttonGroupSize } = useButtonGroup<InputProps>(props)
183
183
  const { isLeading, isTrailing, leadingIconName, trailingIconName } = useComponentIcons(toRef(() => defu(props, { trailingIcon: appConfig.ui.icons.chevronDown })))
184
184
 
@@ -279,6 +279,7 @@ function onBlur(event: FocusEvent) {
279
279
 
280
280
  function onFocus(event: FocusEvent) {
281
281
  emits('focus', event)
282
+ emitFormFocus()
282
283
  }
283
284
 
284
285
  function onUpdateOpen(value: boolean) {
@@ -365,7 +366,7 @@ defineExpose({
365
366
  <ComboboxInput v-model="searchTerm" :display-value="displayValue" as-child>
366
367
  <TagsInputInput
367
368
  ref="inputRef"
368
- v-bind="$attrs"
369
+ v-bind="{ ...$attrs, ...ariaAttrs }"
369
370
  :placeholder="placeholder"
370
371
  :required="required"
371
372
  :class="ui.tagsInput({ class: props.ui?.tagsInput })"
@@ -379,7 +380,7 @@ defineExpose({
379
380
  ref="inputRef"
380
381
  v-model="searchTerm"
381
382
  :display-value="displayValue"
382
- v-bind="$attrs"
383
+ v-bind="{ ...$attrs, ...ariaAttrs }"
383
384
  :type="type"
384
385
  :placeholder="placeholder"
385
386
  :required="required"
@@ -7,9 +7,9 @@ import theme from '#build/ui/input-number'
7
7
  import { tv } from '../utils/tv'
8
8
  import type { ButtonProps } from '../types'
9
9
 
10
- const appConfig = _appConfig as AppConfig & { ui: { inputNumber: Partial<typeof theme> } }
10
+ const appConfigInputNumber = _appConfig as AppConfig & { ui: { inputNumber: Partial<typeof theme> } }
11
11
 
12
- const inputNumber = tv({ extend: tv(theme), ...(appConfig.ui?.inputNumber || {}) })
12
+ const inputNumber = tv({ extend: tv(theme), ...(appConfigInputNumber.ui?.inputNumber || {}) })
13
13
 
14
14
  type InputNumberVariants = VariantProps<typeof inputNumber>
15
15
 
@@ -78,6 +78,7 @@ export interface InputNumberSlots {
78
78
  import { onMounted, ref, computed } from 'vue'
79
79
  import { NumberFieldRoot, NumberFieldInput, NumberFieldDecrement, NumberFieldIncrement, useForwardPropsEmits } from 'reka-ui'
80
80
  import { reactivePick } from '@vueuse/core'
81
+ import { useAppConfig } from '#imports'
81
82
  import { useFormField } from '../composables/useFormField'
82
83
  import { useLocale } from '../composables/useLocale'
83
84
  import UButton from './Button.vue'
@@ -92,7 +93,8 @@ defineSlots<InputNumberSlots>()
92
93
 
93
94
  const rootProps = useForwardPropsEmits(reactivePick(props, 'as', 'modelValue', 'defaultValue', 'min', 'max', 'step', 'formatOptions'), emits)
94
95
 
95
- 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)
96
98
 
97
99
  const { t, code: codeLocale } = useLocale()
98
100
  const locale = computed(() => props.locale || codeLocale.value)
@@ -152,12 +154,13 @@ defineExpose({
152
154
  @update:model-value="onUpdate"
153
155
  >
154
156
  <NumberFieldInput
155
- v-bind="$attrs"
157
+ v-bind="{ ...$attrs, ...ariaAttrs }"
156
158
  ref="inputRef"
157
159
  :placeholder="placeholder"
158
160
  :required="required"
159
161
  :class="ui.base({ class: props.ui?.base })"
160
162
  @blur="onBlur"
163
+ @focus="emitFormFocus"
161
164
  />
162
165
 
163
166
  <div :class="ui.increment({ class: props.ui?.increment })">
@@ -7,9 +7,9 @@ import type { KbdKey } from '../composables/useKbd'
7
7
  import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
8
8
  import { tv } from '../utils/tv'
9
9
 
10
- const appConfig = _appConfig as AppConfig & { ui: { kbd: Partial<typeof theme> } }
10
+ const appConfigKbd = _appConfig as AppConfig & { ui: { kbd: Partial<typeof theme> } }
11
11
 
12
- const kbd = tv({ extend: tv(theme), ...(appConfig.ui?.kbd || {}) })
12
+ const kbd = tv({ extend: tv(theme), ...(appConfigKbd.ui?.kbd || {}) })
13
13
 
14
14
  type KbdVariants = VariantProps<typeof kbd>
15
15
 
@@ -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
  /**
@@ -7,9 +7,9 @@ import { extendDevtoolsMeta } from '../composables/extendDevtoolsMeta'
7
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