@volverjs/ui-vue 0.0.10-beta.24 → 0.0.10-beta.26

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 (121) hide show
  1. package/auto-imports.d.ts +1 -0
  2. package/dist/components/VvAccordion/VvAccordion.es.js +14 -12
  3. package/dist/components/VvAccordion/VvAccordion.umd.js +1 -1
  4. package/dist/components/VvAccordionGroup/VvAccordionGroup.es.js +169 -89
  5. package/dist/components/VvAccordionGroup/VvAccordionGroup.umd.js +1 -1
  6. package/dist/components/VvAccordionGroup/VvAccordionGroup.vue.d.ts +31 -5
  7. package/dist/components/VvAccordionGroup/index.d.ts +7 -4
  8. package/dist/components/VvAction/VvAction.es.js +14 -12
  9. package/dist/components/VvAction/VvAction.umd.js +1 -1
  10. package/dist/components/VvAction/VvAction.vue.d.ts +2 -11
  11. package/dist/components/VvAction/index.d.ts +1 -5
  12. package/dist/components/VvAlert/VvAlert.es.js +14 -12
  13. package/dist/components/VvAlert/VvAlert.umd.js +1 -1
  14. package/dist/components/VvAlertGroup/VvAlertGroup.es.js +14 -12
  15. package/dist/components/VvAlertGroup/VvAlertGroup.umd.js +1 -1
  16. package/dist/components/VvAvatar/VvAvatar.es.js +14 -12
  17. package/dist/components/VvAvatar/VvAvatar.umd.js +1 -1
  18. package/dist/components/VvAvatarGroup/VvAvatarGroup.es.js +14 -12
  19. package/dist/components/VvAvatarGroup/VvAvatarGroup.umd.js +1 -1
  20. package/dist/components/VvBadge/VvBadge.es.js +14 -12
  21. package/dist/components/VvBadge/VvBadge.umd.js +1 -1
  22. package/dist/components/VvBreadcrumb/VvBreadcrumb.es.js +14 -12
  23. package/dist/components/VvBreadcrumb/VvBreadcrumb.umd.js +1 -1
  24. package/dist/components/VvBreadcrumb/VvBreadcrumb.vue.d.ts +1 -1
  25. package/dist/components/VvButton/VvButton.es.js +14 -12
  26. package/dist/components/VvButton/VvButton.umd.js +1 -1
  27. package/dist/components/VvButton/VvButton.vue.d.ts +2 -19
  28. package/dist/components/VvButton/index.d.ts +1 -5
  29. package/dist/components/VvButtonGroup/VvButtonGroup.es.js +14 -12
  30. package/dist/components/VvButtonGroup/VvButtonGroup.umd.js +1 -1
  31. package/dist/components/VvCard/VvCard.es.js +14 -12
  32. package/dist/components/VvCard/VvCard.umd.js +1 -1
  33. package/dist/components/VvCheckbox/VvCheckbox.es.js +14 -12
  34. package/dist/components/VvCheckbox/VvCheckbox.umd.js +1 -1
  35. package/dist/components/VvCheckboxGroup/VvCheckboxGroup.es.js +14 -12
  36. package/dist/components/VvCheckboxGroup/VvCheckboxGroup.umd.js +1 -1
  37. package/dist/components/VvCombobox/VvCombobox.es.js +58 -34
  38. package/dist/components/VvCombobox/VvCombobox.umd.js +1 -1
  39. package/dist/components/VvCombobox/VvCombobox.vue.d.ts +2 -8
  40. package/dist/components/VvCombobox/index.d.ts +9 -2
  41. package/dist/components/VvDialog/VvDialog.es.js +14 -12
  42. package/dist/components/VvDialog/VvDialog.umd.js +1 -1
  43. package/dist/components/VvDropdown/VvDropdown.es.js +24 -16
  44. package/dist/components/VvDropdown/VvDropdown.umd.js +1 -1
  45. package/dist/components/VvDropdown/VvDropdownAction.vue.d.ts +2 -19
  46. package/dist/components/VvDropdown/VvDropdownItem.vue.d.ts +13 -1
  47. package/dist/components/VvDropdown/VvDropdownOption.vue.d.ts +9 -0
  48. package/dist/components/VvDropdown/index.d.ts +37 -0
  49. package/dist/components/VvDropdownAction/VvDropdownAction.es.js +64 -15
  50. package/dist/components/VvDropdownAction/VvDropdownAction.umd.js +1 -1
  51. package/dist/components/VvDropdownItem/VvDropdownItem.es.js +322 -1
  52. package/dist/components/VvDropdownItem/VvDropdownItem.umd.js +1 -1
  53. package/dist/components/VvDropdownOptgroup/VvDropdownOptgroup.es.js +14 -12
  54. package/dist/components/VvDropdownOptgroup/VvDropdownOptgroup.umd.js +1 -1
  55. package/dist/components/VvDropdownOption/VvDropdownOption.es.js +131 -76
  56. package/dist/components/VvDropdownOption/VvDropdownOption.umd.js +1 -1
  57. package/dist/components/VvInputFile/VvInputFile.es.js +14 -12
  58. package/dist/components/VvInputFile/VvInputFile.umd.js +1 -1
  59. package/dist/components/VvInputFile/index.d.ts +5 -5
  60. package/dist/components/VvInputText/VvInputText.es.js +979 -107
  61. package/dist/components/VvInputText/VvInputText.umd.js +1 -1
  62. package/dist/components/VvInputText/VvInputText.vue.d.ts +44 -1
  63. package/dist/components/VvInputText/index.d.ts +28 -0
  64. package/dist/components/VvNav/VvNav.es.js +14 -12
  65. package/dist/components/VvNav/VvNav.umd.js +1 -1
  66. package/dist/components/VvNav/VvNav.vue.d.ts +1 -1
  67. package/dist/components/VvNavItem/VvNavItem.es.js +14 -12
  68. package/dist/components/VvNavItem/VvNavItem.umd.js +1 -1
  69. package/dist/components/VvProgress/VvProgress.es.js +14 -12
  70. package/dist/components/VvProgress/VvProgress.umd.js +1 -1
  71. package/dist/components/VvRadio/VvRadio.es.js +14 -12
  72. package/dist/components/VvRadio/VvRadio.umd.js +1 -1
  73. package/dist/components/VvRadioGroup/VvRadioGroup.es.js +14 -12
  74. package/dist/components/VvRadioGroup/VvRadioGroup.umd.js +1 -1
  75. package/dist/components/VvSelect/VvSelect.es.js +14 -12
  76. package/dist/components/VvSelect/VvSelect.umd.js +1 -1
  77. package/dist/components/VvTab/VvTab.es.js +14 -12
  78. package/dist/components/VvTab/VvTab.umd.js +1 -1
  79. package/dist/components/VvTab/VvTab.vue.d.ts +1 -1
  80. package/dist/components/VvTextarea/VvTextarea.es.js +14 -12
  81. package/dist/components/VvTextarea/VvTextarea.umd.js +1 -1
  82. package/dist/components/VvTooltip/VvTooltip.es.js +14 -12
  83. package/dist/components/VvTooltip/VvTooltip.umd.js +1 -1
  84. package/dist/components/index.es.js +435 -195
  85. package/dist/components/index.umd.js +1 -1
  86. package/dist/composables/dropdown/useInjectDropdown.d.ts +3 -23
  87. package/dist/composables/dropdown/useProvideDropdown.d.ts +2 -3
  88. package/dist/composables/usePersistence.d.ts +3 -0
  89. package/dist/constants.d.ts +24 -21
  90. package/dist/directives/index.es.js +14 -12
  91. package/dist/directives/index.umd.js +1 -1
  92. package/dist/directives/v-tooltip.es.js +14 -12
  93. package/dist/directives/v-tooltip.umd.js +1 -1
  94. package/dist/icons.es.js +3 -3
  95. package/dist/icons.umd.js +1 -1
  96. package/dist/props/index.d.ts +11 -11
  97. package/dist/stories/AccordionGroup/AccordionGroup.stories.d.ts +51 -9
  98. package/dist/stories/AccordionGroup/AccordionGroupSlots.stories.d.ts +327 -64
  99. package/dist/stories/Button/Button.settings.d.ts +0 -1
  100. package/dist/types/nav.d.ts +1 -2
  101. package/package.json +1 -1
  102. package/src/assets/icons/detailed.json +1 -1
  103. package/src/assets/icons/normal.json +1 -1
  104. package/src/assets/icons/simple.json +1 -1
  105. package/src/components/VvAccordionGroup/VvAccordionGroup.vue +86 -69
  106. package/src/components/VvAccordionGroup/index.ts +3 -5
  107. package/src/components/VvCombobox/VvCombobox.vue +2 -0
  108. package/src/components/VvDropdown/VvDropdown.vue +11 -2
  109. package/src/components/VvDropdown/VvDropdownItem.vue +4 -1
  110. package/src/components/VvDropdown/VvDropdownOption.vue +3 -21
  111. package/src/components/VvDropdown/index.ts +35 -1
  112. package/src/components/VvInputText/VvInputText.vue +98 -3
  113. package/src/components/VvInputText/index.ts +24 -1
  114. package/src/composables/dropdown/useProvideDropdown.ts +4 -4
  115. package/src/composables/usePersistence.ts +76 -0
  116. package/src/constants.ts +23 -18
  117. package/src/props/index.ts +12 -7
  118. package/src/stories/AccordionGroup/AccordionGroup.settings.ts +2 -2
  119. package/src/stories/AccordionGroup/AccordionGroup.test.ts +5 -5
  120. package/src/stories/Button/Button.settings.ts +1 -4
  121. package/src/types/nav.ts +1 -3
@@ -16,10 +16,18 @@
16
16
 
17
17
  // props and emit
18
18
  const props = defineProps(VvAccordionGroupProps)
19
+ // eslint-disable-next-line
19
20
  const emit = defineEmits(VvAccordionGroupEvents)
20
21
 
21
22
  // data
22
- const { disabled, modifiers, itemModifiers, items } = toRefs(props)
23
+ const {
24
+ disabled,
25
+ modifiers,
26
+ itemModifiers,
27
+ items,
28
+ storageKey,
29
+ storageType,
30
+ } = toRefs(props)
23
31
  watchEffect(() => {
24
32
  if (typeof props.modelValue === 'string' && props.collapse) {
25
33
  // eslint-disable-next-line no-console
@@ -30,81 +38,81 @@
30
38
  })
31
39
 
32
40
  const accordionNames = reactive(new Set<string>())
33
- let modelValue = ref(new Set<string>())
34
- watch(
35
- () => props.storeKey,
36
- (newKey, oldKey) => {
37
- if (oldKey && oldKey !== newKey) {
38
- localStorage.removeItem(oldKey)
39
- }
40
- if (newKey) {
41
- modelValue = useLocalStorage(newKey, modelValue.value)
42
- return
43
- }
44
- modelValue = ref(new Set<string>(modelValue.value))
45
- },
46
- { immediate: true },
41
+ const storageModelValue = usePersistence<string | string[] | undefined>(
42
+ storageKey,
43
+ storageType,
47
44
  )
48
- watch(
49
- [modelValue, accordionNames, () => props.not, () => props.collapse],
50
- () => {
51
- if (props.not) {
52
- emit(
53
- 'update:modelValue',
54
- [...accordionNames].filter(
55
- (name) => !modelValue.value.has(name),
56
- ),
57
- )
58
- return
59
- }
60
- if (props.collapse) {
61
- emit('update:modelValue', [...modelValue.value])
62
- return
45
+ const localModelValue = computed({
46
+ get: () => {
47
+ if (props.modelValue !== null && props.modelValue !== undefined) {
48
+ return props.modelValue
63
49
  }
64
- emit('update:modelValue', modelValue.value.values().next().value)
50
+ return storageModelValue.value
65
51
  },
66
- {
67
- deep: true,
68
- immediate: true,
52
+ set: (newValue) => {
53
+ emit('update:modelValue', newValue)
54
+ storageModelValue.value = newValue
69
55
  },
70
- )
71
- watch(
72
- () => props.modelValue,
73
- (newValue, oldValue) => {
74
- if (
75
- newValue === undefined ||
76
- newValue === null ||
77
- JSON.stringify(newValue) === JSON.stringify(oldValue)
78
- ) {
79
- return
56
+ })
57
+ const expandedAccordions = computed<Set<string>>({
58
+ get: () => {
59
+ if (localModelValue.value === undefined) {
60
+ return new Set<string>()
80
61
  }
81
62
  let toReturn = new Set<string>()
82
63
  if (props.not) {
83
- if (typeof newValue === 'string') {
64
+ if (typeof localModelValue.value === 'string') {
84
65
  toReturn = new Set<string>(
85
- [...accordionNames].filter((name) => name !== newValue),
66
+ [...accordionNames].filter(
67
+ (name) => name !== localModelValue.value,
68
+ ),
86
69
  )
87
- } else if (Array.isArray(newValue)) {
70
+ } else if (Array.isArray(localModelValue.value)) {
88
71
  toReturn = new Set<string>(
89
72
  [...accordionNames].filter(
90
- (name) => !newValue.includes(name),
73
+ (name) =>
74
+ !(localModelValue.value as string[]).includes(
75
+ name,
76
+ ),
91
77
  ),
92
78
  )
93
79
  }
94
- } else if (typeof newValue === 'string') {
95
- toReturn = new Set<string>([newValue])
96
- } else if (Array.isArray(newValue)) {
97
- toReturn = new Set<string>(newValue)
98
- }
99
- for (const name of accordionNames) {
100
- bus.emit('toggle', { name, value: toReturn.has(name) })
80
+ } else if (typeof localModelValue.value === 'string') {
81
+ toReturn = new Set<string>([localModelValue.value])
82
+ } else if (Array.isArray(localModelValue.value)) {
83
+ toReturn = new Set<string>(localModelValue.value)
101
84
  }
102
- modelValue.value = toReturn
85
+ return toReturn
103
86
  },
104
- {
105
- immediate: true,
87
+ set: (newValue) => {
88
+ if (props.not) {
89
+ localModelValue.value = [...accordionNames].filter(
90
+ (name) => !newValue.has(name),
91
+ )
92
+ return
93
+ }
94
+ if (props.collapse) {
95
+ localModelValue.value = [...newValue]
96
+ return
97
+ }
98
+ localModelValue.value = newValue.values().next().value
106
99
  },
107
- )
100
+ })
101
+ onMounted(() => {
102
+ if (props.not && localModelValue.value === undefined) {
103
+ localModelValue.value = props.collapse
104
+ ? []
105
+ : [...accordionNames.values()].splice(1, accordionNames.size)
106
+ }
107
+ nextTick(() => {
108
+ for (const name of accordionNames) {
109
+ bus.emit('toggle', {
110
+ name,
111
+ value: expandedAccordions.value.has(name),
112
+ })
113
+ }
114
+ })
115
+ })
108
116
 
109
117
  // provide
110
118
  const bus = mitt<AccordionGroupBusEvents>()
@@ -120,19 +128,22 @@
120
128
  accordionNames.delete(name)
121
129
  })
122
130
  bus.on('toggle', ({ name, value }) => {
131
+ const newValue = new Set<string>(expandedAccordions.value)
123
132
  if (value) {
124
133
  if (!props.collapse) {
125
- for (const item of modelValue.value) {
134
+ for (const item of newValue) {
126
135
  if (item !== name) {
127
136
  bus.emit('toggle', { name: item, value: false })
128
137
  }
129
138
  }
130
- modelValue.value.clear()
139
+ newValue.clear()
131
140
  }
132
- modelValue.value.add(name)
141
+ newValue.add(name)
142
+ expandedAccordions.value = newValue
133
143
  return
134
144
  }
135
- modelValue.value.delete(name)
145
+ newValue.delete(name)
146
+ expandedAccordions.value = newValue
136
147
  })
137
148
  const expand = (name?: string | string[]) => {
138
149
  if (typeof name === 'string') {
@@ -169,7 +180,7 @@
169
180
  bus.on('collapse', ({ name }) => collapse(name))
170
181
 
171
182
  // expose
172
- defineExpose({ modelValue, expand, collapse })
183
+ defineExpose({ expandedAccordions, expand, collapse })
173
184
 
174
185
  // styles
175
186
  const bemCssClasses = useModifiers(
@@ -186,7 +197,7 @@
186
197
  <!-- @slot Default slot -->
187
198
  <slot
188
199
  v-bind="{
189
- modelValue,
200
+ expandedAccordions,
190
201
  expand,
191
202
  collapse,
192
203
  }"
@@ -200,13 +211,19 @@
200
211
  content: item.content,
201
212
  }"
202
213
  >
203
- <template #header="data">
214
+ <template
215
+ v-if="$slots[`summary::${item.name}`]"
216
+ #summary="data"
217
+ >
204
218
  <!-- @slot Slot for accordion header -->
205
- <slot v-bind="data" :name="`header::${item.name}`" />
219
+ <slot v-bind="data" :name="`summary::${item.name}`" />
206
220
  </template>
207
- <template #details="data">
221
+ <template
222
+ v-if="$slots[`content::${item.name}`]"
223
+ #default="data"
224
+ >
208
225
  <!-- @slot Slot for accordion details -->
209
- <slot v-bind="data" :name="`details::${item.name}`" />
226
+ <slot v-bind="data" :name="`content::${item.name}`" />
210
227
  </template>
211
228
  </VvAccordion>
212
229
  </slot>
@@ -1,4 +1,4 @@
1
- import { ModifiersProps } from '@/props'
1
+ import { ModifiersProps, StorageProps } from '@/props'
2
2
 
3
3
  export interface VvAccordionGroupItem {
4
4
  title: string
@@ -10,11 +10,13 @@ export interface VvAccordionGroupItem {
10
10
 
11
11
  export const VvAccordionGroupProps = {
12
12
  ...ModifiersProps,
13
+ ...StorageProps,
13
14
  /**
14
15
  * VModel
15
16
  */
16
17
  modelValue: {
17
18
  type: [String, Array] as PropType<string | string[] | undefined>,
19
+ default: undefined,
18
20
  },
19
21
  /**
20
22
  * Accordion items
@@ -43,10 +45,6 @@ export const VvAccordionGroupProps = {
43
45
  * If true, the accordion items will be opened by default
44
46
  */
45
47
  not: Boolean,
46
- /**
47
- * Enable local storage persistence
48
- */
49
- storeKey: String,
50
48
  }
51
49
 
52
50
  export const VvAccordionGroupEvents = ['update:modelValue']
@@ -526,6 +526,7 @@
526
526
  }"
527
527
  :key="i"
528
528
  class="vv-dropdown-option"
529
+ focus-on-hover
529
530
  @click.passive="onInput(item)"
530
531
  >
531
532
  <!-- @slot Slot for option customization -->
@@ -556,6 +557,7 @@
556
557
  propsDefaults.selectedHintLabel,
557
558
  }"
558
559
  class="vv-dropdown-option"
560
+ focus-on-hover
559
561
  @click.passive="onInput(option)"
560
562
  >
561
563
  <!-- @slot Slot for option customization -->
@@ -299,9 +299,7 @@
299
299
  })
300
300
  bus.on('click', toggle)
301
301
 
302
- // provide top dropdown item
303
302
  const { role, modifiers } = toRefs(props)
304
- const { itemRole } = useProvideDropdownItem({ role, expanded })
305
303
 
306
304
  // styles
307
305
  const bemCssClasses = useModifiers(
@@ -376,6 +374,17 @@
376
374
  })
377
375
  }
378
376
 
377
+ // hover
378
+ const hovered = useElementHover(floatingEl)
379
+
380
+ // provide top dropdown item
381
+ const { itemRole } = useProvideDropdownItem({
382
+ role,
383
+ expanded,
384
+ focused,
385
+ hovered,
386
+ })
387
+
379
388
  // keyboard
380
389
  onKeyStroke('Escape', (e) => {
381
390
  if (expanded.value) {
@@ -5,6 +5,9 @@
5
5
  </script>
6
6
 
7
7
  <script setup lang="ts">
8
+ import { VvDropdownItemProps } from '.'
9
+
10
+ const props = defineProps(VvDropdownItemProps)
8
11
  const { role, expanded } = useInjectedDropdownItem()
9
12
  const element = ref(null)
10
13
  useProvideDropdownAction({ expanded })
@@ -14,7 +17,7 @@
14
17
 
15
18
  // focus item on hover
16
19
  watch(hovered, (newValue) => {
17
- if (newValue) {
20
+ if (newValue && props.focusOnHover) {
18
21
  focused.value = true
19
22
  }
20
23
  })
@@ -5,30 +5,11 @@
5
5
  </script>
6
6
 
7
7
  <script setup lang="ts">
8
+ import { VvDropdownOptionProps } from '.'
8
9
  import VvDropdownItem from './VvDropdownItem.vue'
9
- import {
10
- DisabledProps,
11
- ModifiersProps,
12
- SelectedProps,
13
- UnselectableProps,
14
- } from '../../props'
15
10
 
16
11
  // props
17
- const props = defineProps({
18
- ...DisabledProps,
19
- ...SelectedProps,
20
- ...UnselectableProps,
21
- ...ModifiersProps,
22
- deselectHintLabel: {
23
- type: String,
24
- },
25
- selectHintLabel: {
26
- type: String,
27
- },
28
- selectedHintLabel: {
29
- type: String,
30
- },
31
- })
12
+ const props = defineProps(VvDropdownOptionProps)
32
13
 
33
14
  // style
34
15
  const { modifiers } = toRefs(props)
@@ -61,6 +42,7 @@
61
42
  :tabindex="disabled ? -1 : 0"
62
43
  :aria-selected="selected"
63
44
  :aria-disabled="disabled"
45
+ :focus-on-hover="focusOnHover"
64
46
  >
65
47
  <slot />
66
48
  <span class="vv-dropdown-option__hint" :title="hintLabel">
@@ -1,5 +1,12 @@
1
1
  import type { PropType } from 'vue'
2
- import { DropdownProps, IdProps, ModifiersProps } from '../../props'
2
+ import {
3
+ DropdownProps,
4
+ IdProps,
5
+ DisabledProps,
6
+ ModifiersProps,
7
+ SelectedProps,
8
+ UnselectableProps,
9
+ } from '../../props'
3
10
  import { DropdownRole } from '../../constants'
4
11
 
5
12
  export const VvDropdownProps = {
@@ -30,3 +37,30 @@ export const VvDropdownProps = {
30
37
  Object.values(DropdownRole).includes(value),
31
38
  },
32
39
  }
40
+
41
+ export const VvDropdownItemProps = {
42
+ focusOnHover: {
43
+ type: Boolean,
44
+ default: false,
45
+ },
46
+ }
47
+
48
+ export const VvDropdownOptionProps = {
49
+ ...DisabledProps,
50
+ ...SelectedProps,
51
+ ...UnselectableProps,
52
+ ...ModifiersProps,
53
+ deselectHintLabel: {
54
+ type: String,
55
+ },
56
+ selectHintLabel: {
57
+ type: String,
58
+ },
59
+ selectedHintLabel: {
60
+ type: String,
61
+ },
62
+ focusOnHover: {
63
+ type: Boolean,
64
+ default: false,
65
+ },
66
+ }
@@ -11,6 +11,8 @@
11
11
  import VvIcon from '../VvIcon/VvIcon.vue'
12
12
  import { ACTION_ICONS } from '../VvIcon'
13
13
  import VvInputTextActionsFactory from '../VvInputText/VvInputTextActions'
14
+ import VvDropdown from '../VvDropdown/VvDropdown.vue'
15
+ import VvDropdownOption from '../VvDropdown/VvDropdownOption.vue'
14
16
  import {
15
17
  VvInputTextEvents,
16
18
  VvInputTextProps,
@@ -34,6 +36,7 @@
34
36
  id,
35
37
  icon,
36
38
  iconPosition,
39
+ iconRemoveSuggestion,
37
40
  label,
38
41
  modelValue,
39
42
  count,
@@ -46,6 +49,8 @@
46
49
  type,
47
50
  iMask,
48
51
  step,
52
+ storageKey,
53
+ storageType,
49
54
  } = toRefs(props)
50
55
  const hasId = useUniqueId(id)
51
56
  const hasHintId = computed(() => `${hasId.value}-hint`)
@@ -54,7 +59,7 @@
54
59
  props.floating && isEmpty(props.placeholder) ? ' ' : props.placeholder,
55
60
  )
56
61
 
57
- // template refs
62
+ // mask
58
63
  const maskReady = ref(false)
59
64
  const { el, mask, typed, masked, unmasked } = useIMask(
60
65
  computed(
@@ -170,8 +175,12 @@
170
175
  masked.value = newValue ?? ''
171
176
  },
172
177
  )
178
+
179
+ // template refs
173
180
  const inputEl = el as Ref<HTMLInputElement>
174
- const innerEl = ref()
181
+ const innerEl = ref<HTMLInputElement>()
182
+ const wrapperEl = ref<HTMLDivElement>()
183
+ const dropdownEl = ref<typeof VvDropdown>()
175
184
 
176
185
  defineExpose({ $inner: innerEl })
177
186
 
@@ -191,6 +200,26 @@
191
200
  if (newValue && propsDefaults.value.selectOnFocus && inputEl.value) {
192
201
  inputEl.value.select()
193
202
  }
203
+ if (newValue) {
204
+ dropdownEl.value?.show()
205
+ return
206
+ }
207
+ setTimeout(() => {
208
+ if (isDirty.value && suggestions.value) {
209
+ const suggestionsLimit = props.maxSuggestions - 1
210
+ if (
211
+ suggestions.value.size > suggestionsLimit &&
212
+ !suggestions.value.has(localModelValue.value)
213
+ ) {
214
+ suggestions.value = new Set(
215
+ [...suggestions.value].slice(
216
+ suggestions.value.size - suggestionsLimit,
217
+ ),
218
+ )
219
+ }
220
+ suggestions.value.add(localModelValue.value)
221
+ }
222
+ }, 300)
194
223
  })
195
224
 
196
225
  // visibility
@@ -267,6 +296,8 @@
267
296
  }
268
297
  return undefined
269
298
  })
299
+ const { hasIcon: hasIconRemoveSuggestion } =
300
+ useComponentIcon(iconRemoveSuggestion)
270
301
 
271
302
  // count
272
303
  const { formatted: countFormatted } = useTextCount(localModelValue, {
@@ -295,6 +326,39 @@
295
326
  return undefined
296
327
  })
297
328
 
329
+ // suggestions
330
+ const suggestions = usePersistence<Set<string>>(
331
+ storageKey,
332
+ storageType,
333
+ new Set(),
334
+ )
335
+ const filteredSuggestions = computed(() => {
336
+ if (!suggestions.value) {
337
+ return []
338
+ }
339
+ return [...suggestions.value].filter(
340
+ (suggestion) =>
341
+ isEmpty(localModelValue.value) ||
342
+ (`${suggestion}`
343
+ .toLowerCase()
344
+ .includes(`${localModelValue.value}`.toLowerCase()) &&
345
+ suggestion !== localModelValue.value),
346
+ )
347
+ })
348
+ const hasSuggestions = computed(
349
+ () =>
350
+ storageKey?.value &&
351
+ suggestions.value &&
352
+ suggestions.value.size > 0,
353
+ )
354
+ const onSuggestionSelect = (suggestion: string) => {
355
+ localModelValue.value = suggestion
356
+ dropdownEl.value?.hide()
357
+ }
358
+ const onSuggestionRemove = (suggestion: string) => {
359
+ suggestions.value?.delete(suggestion)
360
+ }
361
+
298
362
  // styles
299
363
  const { modifiers } = toRefs(props)
300
364
  const bemCssClasses = useModifiers(
@@ -463,7 +527,7 @@
463
527
  <label v-if="label" :for="hasId" class="vv-input-text__label">
464
528
  {{ label }}
465
529
  </label>
466
- <div class="vv-input-text__wrapper">
530
+ <div ref="wrapperEl" class="vv-input-text__wrapper">
467
531
  <div v-if="$slots.before" class="vv-input-text__input-before">
468
532
  <!-- @slot Slot before input icon -->
469
533
  <slot name="before" v-bind="slotProps" />
@@ -542,5 +606,36 @@
542
606
  <slot name="invalid" v-bind="hintSlotScope" />
543
607
  </template>
544
608
  </HintSlot>
609
+ <VvDropdown
610
+ v-if="hasSuggestions"
611
+ ref="dropdownEl"
612
+ :reference="wrapperEl"
613
+ :autofocus-first="false"
614
+ :trigger-width="true"
615
+ >
616
+ <template #items>
617
+ <VvDropdownOption
618
+ v-for="value in filteredSuggestions"
619
+ :key="value"
620
+ @click.stop="onSuggestionSelect(value)"
621
+ >
622
+ <div class="flex-1">
623
+ <slot name="suggestion" v-bind="{ value }">
624
+ {{ value }}
625
+ </slot>
626
+ </div>
627
+ <button
628
+ v-if="suggestions && hasIconRemoveSuggestion"
629
+ type="button"
630
+ tabindex="-1"
631
+ class="cursor-pointer"
632
+ :title="labelRemoveSuggestion"
633
+ @click.stop="onSuggestionRemove(value)"
634
+ >
635
+ <VvIcon v-bind="hasIconRemoveSuggestion" />
636
+ </button>
637
+ </VvDropdownOption>
638
+ </template>
639
+ </VvDropdown>
545
640
  </div>
546
641
  </template>
@@ -1,6 +1,6 @@
1
1
  import type { ExtractPropTypes, PropType } from 'vue'
2
2
  import type { FactoryOpts } from 'imask'
3
- import { InputTextareaProps } from '../../props'
3
+ import { InputTextareaProps, StorageProps } from '../../props'
4
4
  import { type VvIconProps, ACTION_ICONS } from '../VvIcon'
5
5
 
6
6
  export const INPUT_TYPES = {
@@ -40,6 +40,7 @@ export const VvInputTextEvents = [
40
40
 
41
41
  export const VvInputTextProps = {
42
42
  ...InputTextareaProps,
43
+ ...StorageProps,
43
44
  /**
44
45
  * Input value
45
46
  * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#value
@@ -109,6 +110,14 @@ export const VvInputTextProps = {
109
110
  type: [String, Object] as PropType<string | VvIconProps>,
110
111
  default: ACTION_ICONS.clear,
111
112
  },
113
+ /**
114
+ * VvIcon name for remove suggestion button
115
+ * @see VVIcon
116
+ */
117
+ iconRemoveSuggestion: {
118
+ type: [String, Object] as PropType<string | VvIconProps>,
119
+ default: ACTION_ICONS.remove,
120
+ },
112
121
  /**
113
122
  * Label for step up button
114
123
  */
@@ -144,6 +153,13 @@ export const VvInputTextProps = {
144
153
  type: String,
145
154
  default: 'Clear',
146
155
  },
156
+ /**
157
+ * Label for remove suggestion button
158
+ */
159
+ labelRemoveSuggestion: {
160
+ type: String,
161
+ default: 'Remove suggestion',
162
+ },
147
163
  /**
148
164
  * iMask options
149
165
  * @see https://imask.js.org/guide.html
@@ -186,6 +202,13 @@ export const VvInputTextProps = {
186
202
  type: Boolean,
187
203
  default: false,
188
204
  },
205
+ /**
206
+ * Maximum number of suggestions
207
+ */
208
+ maxSuggestions: {
209
+ type: Number,
210
+ default: 5,
211
+ },
189
212
  }
190
213
 
191
214
  export type VvInputTextPropsTypes = ExtractPropTypes<typeof VvInputTextProps>
@@ -1,6 +1,7 @@
1
1
  import { type Ref, Fragment } from 'vue'
2
2
  import mitt from 'mitt'
3
3
  import {
4
+ type DropdownItemState,
4
5
  INJECTION_KEY_DROPDOWN_TRIGGER,
5
6
  INJECTION_KEY_DROPDOWN_ACTION,
6
7
  INJECTION_KEY_DROPDOWN_ITEM,
@@ -61,10 +62,9 @@ export function useProvideDropdownTrigger({
61
62
  */
62
63
  export function useProvideDropdownItem({
63
64
  role,
64
- expanded,
65
- }: {
65
+ ...others
66
+ }: Omit<DropdownItemState, 'role'> & {
66
67
  role: Ref<`${DropdownRole}`>
67
- expanded: Ref<boolean>
68
68
  }) {
69
69
  const itemRole = computed(() =>
70
70
  role.value === DropdownRole.listbox
@@ -73,7 +73,7 @@ export function useProvideDropdownItem({
73
73
  )
74
74
  provide(INJECTION_KEY_DROPDOWN_ITEM, {
75
75
  role: itemRole,
76
- expanded,
76
+ ...others,
77
77
  })
78
78
  return { itemRole }
79
79
  }