@volverjs/ui-vue 0.0.10-beta.54 → 0.0.10-beta.55
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.
- package/dist/components/VvCombobox/VvCombobox.es.js +357 -357
- package/dist/components/VvCombobox/VvCombobox.umd.js +1 -1
- package/dist/components/VvInputText/VvInputText.es.js +17 -19
- package/dist/components/VvInputText/VvInputText.umd.js +1 -1
- package/dist/components/VvTextarea/VvTextarea.es.js +966 -67
- package/dist/components/VvTextarea/VvTextarea.umd.js +1 -1
- package/dist/components/VvTextarea/VvTextarea.vue.d.ts +52 -0
- package/dist/components/VvTextarea/index.d.ts +37 -1
- package/dist/components/index.es.js +445 -295
- package/dist/components/index.umd.js +1 -1
- package/dist/icons.es.js +3 -3
- package/dist/icons.umd.js +1 -1
- package/package.json +22 -22
- package/src/assets/icons/detailed.json +1 -1
- package/src/assets/icons/normal.json +1 -1
- package/src/assets/icons/simple.json +1 -1
- package/src/components/VvCombobox/VvCombobox.vue +3 -3
- package/src/components/VvInputText/VvInputText.vue +18 -20
- package/src/components/VvTextarea/VvTextarea.vue +108 -5
- package/src/components/VvTextarea/index.ts +32 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts" generic="T extends string | Option">
|
|
2
2
|
import type { Ref } from 'vue'
|
|
3
|
-
import { toRefs } from 'vue'
|
|
4
3
|
import type { Option } from '../../types/generic'
|
|
4
|
+
import { toRefs } from 'vue'
|
|
5
|
+
import { useVvComboboxProps, type VvComboboxEvents } from '.'
|
|
5
6
|
import { DropdownRole } from '../../constants'
|
|
6
7
|
import HintSlotFactory from '../common/HintSlot'
|
|
7
8
|
import VvBadge from '../VvBadge/VvBadge.vue'
|
|
@@ -11,7 +12,6 @@ import VvDropdownOptgroup from '../VvDropdown/VvDropdownOptgroup.vue'
|
|
|
11
12
|
import VvDropdownOption from '../VvDropdown/VvDropdownOption.vue'
|
|
12
13
|
import VvIcon from '../VvIcon/VvIcon.vue'
|
|
13
14
|
import VvSelect from '../VvSelect/VvSelect.vue'
|
|
14
|
-
import { type VvComboboxEvents, useVvComboboxProps } from '.'
|
|
15
15
|
|
|
16
16
|
// props, emit and slots
|
|
17
17
|
// WARNING: This is a provisiaonal implementation, it may change in the future
|
|
@@ -31,7 +31,7 @@ const propsDefaults = useDefaults<typeof VvComboboxProps>(
|
|
|
31
31
|
const inputEl: Ref<HTMLElement | null> = ref(null)
|
|
32
32
|
const inputSearchEl: Ref<HTMLElement | null> = ref(null)
|
|
33
33
|
const wrapperEl: Ref<HTMLElement | null> = ref(null)
|
|
34
|
-
const dropdownEl = ref<typeof VvDropdown
|
|
34
|
+
const dropdownEl = ref<InstanceType<typeof VvDropdown>>()
|
|
35
35
|
|
|
36
36
|
// hint slot
|
|
37
37
|
const {
|
|
@@ -219,7 +219,7 @@ watch(
|
|
|
219
219
|
const inputEl = el as Ref<HTMLInputElement>
|
|
220
220
|
const innerEl = ref<HTMLInputElement>()
|
|
221
221
|
const wrapperEl = ref<HTMLDivElement>()
|
|
222
|
-
const
|
|
222
|
+
const suggestionsDropdownEl = ref<InstanceType<typeof VvDropdown>>()
|
|
223
223
|
|
|
224
224
|
defineExpose({ $inner: innerEl })
|
|
225
225
|
|
|
@@ -232,26 +232,24 @@ watch(isFocused, (newValue) => {
|
|
|
232
232
|
if (newValue && propsDefaults.value.selectOnFocus && inputEl.value) {
|
|
233
233
|
inputEl.value.select()
|
|
234
234
|
}
|
|
235
|
-
if (newValue) {
|
|
236
|
-
|
|
235
|
+
if (newValue && suggestions.value?.size) {
|
|
236
|
+
suggestionsDropdownEl.value?.show()
|
|
237
237
|
return
|
|
238
238
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
suggestions.value
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
)
|
|
251
|
-
}
|
|
252
|
-
suggestions.value.add(localModelValue.value)
|
|
239
|
+
if (isDirty.value && suggestions.value) {
|
|
240
|
+
const suggestionsLimit = props.maxSuggestions
|
|
241
|
+
if (
|
|
242
|
+
suggestions.value.size >= suggestionsLimit
|
|
243
|
+
&& !suggestions.value.has(localModelValue.value)
|
|
244
|
+
) {
|
|
245
|
+
suggestions.value = new Set(
|
|
246
|
+
[...suggestions.value].slice(
|
|
247
|
+
suggestions.value.size - suggestionsLimit + 1,
|
|
248
|
+
),
|
|
249
|
+
)
|
|
253
250
|
}
|
|
254
|
-
|
|
251
|
+
suggestions.value.add(localModelValue.value)
|
|
252
|
+
}
|
|
255
253
|
})
|
|
256
254
|
|
|
257
255
|
// visibility
|
|
@@ -386,7 +384,7 @@ const hasSuggestions = computed(
|
|
|
386
384
|
)
|
|
387
385
|
function onSuggestionSelect(suggestion: string) {
|
|
388
386
|
localModelValue.value = suggestion
|
|
389
|
-
|
|
387
|
+
suggestionsDropdownEl.value?.hide()
|
|
390
388
|
}
|
|
391
389
|
function onSuggestionRemove(suggestion: string) {
|
|
392
390
|
suggestions.value?.delete(suggestion)
|
|
@@ -651,7 +649,7 @@ export default {
|
|
|
651
649
|
</HintSlot>
|
|
652
650
|
<VvDropdown
|
|
653
651
|
v-if="hasSuggestions"
|
|
654
|
-
ref="
|
|
652
|
+
ref="suggestionsDropdownEl"
|
|
655
653
|
:reference="wrapperEl"
|
|
656
654
|
:autofocus-first="false"
|
|
657
655
|
:trigger-width="true"
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import type { TextareaHTMLAttributes } from 'vue'
|
|
3
3
|
import { VvTextareaEvents, VvTextareaProps } from '.'
|
|
4
4
|
import HintSlotFactory from '../common/HintSlot'
|
|
5
|
+
import VvDropdown from '../VvDropdown/VvDropdown.vue'
|
|
6
|
+
import VvDropdownOption from '../VvDropdown/VvDropdownOption.vue'
|
|
5
7
|
import VvIcon from '../VvIcon/VvIcon.vue'
|
|
6
8
|
|
|
7
9
|
// props, emit and slots
|
|
@@ -19,13 +21,17 @@ const propsDefaults = useDefaults<typeof VvTextareaProps>(
|
|
|
19
21
|
)
|
|
20
22
|
|
|
21
23
|
// template refs
|
|
22
|
-
const
|
|
24
|
+
const textareaEl = ref<HTMLTextAreaElement>()
|
|
25
|
+
const wrapperEl = ref<HTMLDivElement>()
|
|
26
|
+
const suggestionsDropdownEl = ref<InstanceType<typeof VvDropdown>>()
|
|
23
27
|
|
|
24
28
|
// data
|
|
25
29
|
const {
|
|
26
30
|
id,
|
|
27
31
|
icon,
|
|
28
32
|
iconPosition,
|
|
33
|
+
iconRemoveSuggestion,
|
|
34
|
+
labelRemoveSuggestion,
|
|
29
35
|
label,
|
|
30
36
|
modelValue,
|
|
31
37
|
count,
|
|
@@ -36,6 +42,8 @@ const {
|
|
|
36
42
|
debounce,
|
|
37
43
|
minlength,
|
|
38
44
|
maxlength,
|
|
45
|
+
storageKey,
|
|
46
|
+
storageType,
|
|
39
47
|
} = toRefs(props)
|
|
40
48
|
const hasId = useUniqueId(id)
|
|
41
49
|
const hasHintId = computed(() => `${hasId.value}-hint`)
|
|
@@ -49,12 +57,41 @@ const localModelValue = useDebouncedInput(modelValue, emit, debounce?.value)
|
|
|
49
57
|
|
|
50
58
|
// icons
|
|
51
59
|
const { hasIconBefore, hasIconAfter } = useComponentIcon(icon, iconPosition)
|
|
60
|
+
const { hasIcon: hasIconRemoveSuggestion }
|
|
61
|
+
= useComponentIcon(iconRemoveSuggestion)
|
|
52
62
|
|
|
53
63
|
// focus
|
|
54
|
-
const { focused } = useComponentFocus(
|
|
64
|
+
const { focused } = useComponentFocus(textareaEl, emit)
|
|
65
|
+
const isFocused = computed(
|
|
66
|
+
() => focused.value && !props.disabled && !props.readonly,
|
|
67
|
+
)
|
|
68
|
+
watch(isFocused, (newValue) => {
|
|
69
|
+
if (newValue && propsDefaults.value.selectOnFocus && textareaEl.value) {
|
|
70
|
+
textareaEl.value.select()
|
|
71
|
+
}
|
|
72
|
+
if (newValue && suggestions.value?.size) {
|
|
73
|
+
suggestionsDropdownEl.value?.show()
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
if (isDirty.value && suggestions.value) {
|
|
77
|
+
const suggestionsLimit = props.maxSuggestions
|
|
78
|
+
|
|
79
|
+
if (
|
|
80
|
+
suggestions.value.size >= suggestionsLimit
|
|
81
|
+
&& !suggestions.value.has(localModelValue.value)
|
|
82
|
+
) {
|
|
83
|
+
suggestions.value = new Set(
|
|
84
|
+
[...suggestions.value].slice(
|
|
85
|
+
suggestions.value.size - suggestionsLimit + 1,
|
|
86
|
+
),
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
suggestions.value.add(localModelValue.value)
|
|
90
|
+
}
|
|
91
|
+
})
|
|
55
92
|
|
|
56
93
|
// visibility
|
|
57
|
-
const isVisible = useElementVisibility(
|
|
94
|
+
const isVisible = useElementVisibility(textareaEl)
|
|
58
95
|
watch(isVisible, (newValue) => {
|
|
59
96
|
if (newValue && props.autofocus) {
|
|
60
97
|
focused.value = true
|
|
@@ -88,6 +125,41 @@ const isInvalid = computed(() => {
|
|
|
88
125
|
return undefined
|
|
89
126
|
})
|
|
90
127
|
|
|
128
|
+
// suggestions
|
|
129
|
+
const suggestions = usePersistence<Set<string>>(
|
|
130
|
+
storageKey,
|
|
131
|
+
storageType,
|
|
132
|
+
new Set(),
|
|
133
|
+
)
|
|
134
|
+
const filteredSuggestions = computed(() => {
|
|
135
|
+
if (!suggestions.value) {
|
|
136
|
+
return []
|
|
137
|
+
}
|
|
138
|
+
return [...suggestions.value]
|
|
139
|
+
.filter(
|
|
140
|
+
suggestion =>
|
|
141
|
+
isEmpty(localModelValue.value)
|
|
142
|
+
|| (`${suggestion}`
|
|
143
|
+
.toLowerCase()
|
|
144
|
+
.includes(`${localModelValue.value}`.toLowerCase())
|
|
145
|
+
&& suggestion !== localModelValue.value),
|
|
146
|
+
)
|
|
147
|
+
.reverse()
|
|
148
|
+
})
|
|
149
|
+
const hasSuggestions = computed(
|
|
150
|
+
() =>
|
|
151
|
+
storageKey?.value
|
|
152
|
+
&& suggestions.value
|
|
153
|
+
&& suggestions.value.size > 0,
|
|
154
|
+
)
|
|
155
|
+
function onSuggestionSelect(suggestion: string) {
|
|
156
|
+
localModelValue.value = suggestion
|
|
157
|
+
suggestionsDropdownEl.value?.hide()
|
|
158
|
+
}
|
|
159
|
+
function onSuggestionRemove(suggestion: string) {
|
|
160
|
+
suggestions.value?.delete(suggestion)
|
|
161
|
+
}
|
|
162
|
+
|
|
91
163
|
// hint
|
|
92
164
|
const {
|
|
93
165
|
HintSlot,
|
|
@@ -171,7 +243,7 @@ export default {
|
|
|
171
243
|
<label v-if="label" :for="hasId" class="vv-textarea__label">
|
|
172
244
|
{{ label }}
|
|
173
245
|
</label>
|
|
174
|
-
<div class="vv-textarea__wrapper">
|
|
246
|
+
<div ref="wrapperEl" class="vv-textarea__wrapper">
|
|
175
247
|
<!-- @slot Slot to replace icon before textarea -->
|
|
176
248
|
<div v-if="$slots.before" class="vv-textarea__input-before">
|
|
177
249
|
<!-- @slot Slot before input -->
|
|
@@ -185,7 +257,7 @@ export default {
|
|
|
185
257
|
/>
|
|
186
258
|
<textarea
|
|
187
259
|
:id="hasId"
|
|
188
|
-
ref="
|
|
260
|
+
ref="textareaEl"
|
|
189
261
|
v-model="localModelValue"
|
|
190
262
|
v-bind="hasAttrs"
|
|
191
263
|
@keyup="emit('keyup', $event)"
|
|
@@ -221,5 +293,36 @@ export default {
|
|
|
221
293
|
<slot name="invalid" v-bind="hintSlotScope" />
|
|
222
294
|
</template>
|
|
223
295
|
</HintSlot>
|
|
296
|
+
<VvDropdown
|
|
297
|
+
v-if="hasSuggestions"
|
|
298
|
+
ref="suggestionsDropdownEl"
|
|
299
|
+
:reference="wrapperEl"
|
|
300
|
+
:autofocus-first="false"
|
|
301
|
+
:trigger-width="true"
|
|
302
|
+
>
|
|
303
|
+
<template #items>
|
|
304
|
+
<VvDropdownOption
|
|
305
|
+
v-for="value in filteredSuggestions"
|
|
306
|
+
:key="value"
|
|
307
|
+
@click.stop="onSuggestionSelect(value)"
|
|
308
|
+
>
|
|
309
|
+
<div class="flex-1">
|
|
310
|
+
<slot name="suggestion" v-bind="{ value }">
|
|
311
|
+
{{ value }}
|
|
312
|
+
</slot>
|
|
313
|
+
</div>
|
|
314
|
+
<button
|
|
315
|
+
v-if="suggestions && hasIconRemoveSuggestion"
|
|
316
|
+
type="button"
|
|
317
|
+
tabindex="-1"
|
|
318
|
+
class="cursor-pointer"
|
|
319
|
+
:title="labelRemoveSuggestion"
|
|
320
|
+
@click.stop="onSuggestionRemove(value)"
|
|
321
|
+
>
|
|
322
|
+
<VvIcon v-bind="hasIconRemoveSuggestion" />
|
|
323
|
+
</button>
|
|
324
|
+
</VvDropdownOption>
|
|
325
|
+
</template>
|
|
326
|
+
</VvDropdown>
|
|
224
327
|
</div>
|
|
225
328
|
</template>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExtractPropTypes } from 'vue'
|
|
2
|
-
import { InputTextareaProps } from '../../props'
|
|
2
|
+
import { InputTextareaProps, StorageProps } from '../../props'
|
|
3
|
+
import { ACTION_ICONS, type VvIconProps } from '../VvIcon'
|
|
3
4
|
|
|
4
5
|
export const WRAP = {
|
|
5
6
|
hard: 'hard',
|
|
@@ -16,6 +17,7 @@ export const VvTextareaEvents = ['update:modelValue', 'focus', 'blur', 'keyup']
|
|
|
16
17
|
|
|
17
18
|
export const VvTextareaProps = {
|
|
18
19
|
...InputTextareaProps,
|
|
20
|
+
...StorageProps,
|
|
19
21
|
/**
|
|
20
22
|
* Textarea value
|
|
21
23
|
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#value
|
|
@@ -41,6 +43,35 @@ export const VvTextareaProps = {
|
|
|
41
43
|
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea#wrap
|
|
42
44
|
*/
|
|
43
45
|
spellcheck: { type: [Boolean, String], default: SPELLCHECK.default },
|
|
46
|
+
/**
|
|
47
|
+
* VvIcon name for remove suggestion button
|
|
48
|
+
* @see VVIcon
|
|
49
|
+
*/
|
|
50
|
+
iconRemoveSuggestion: {
|
|
51
|
+
type: [String, Object] as PropType<string | VvIconProps>,
|
|
52
|
+
default: ACTION_ICONS.remove,
|
|
53
|
+
},
|
|
54
|
+
/**
|
|
55
|
+
* Label for remove suggestion button
|
|
56
|
+
*/
|
|
57
|
+
labelRemoveSuggestion: {
|
|
58
|
+
type: String,
|
|
59
|
+
default: 'Remove suggestion',
|
|
60
|
+
},
|
|
61
|
+
/**
|
|
62
|
+
* Maximum number of suggestions
|
|
63
|
+
*/
|
|
64
|
+
maxSuggestions: {
|
|
65
|
+
type: Number,
|
|
66
|
+
default: 5,
|
|
67
|
+
},
|
|
68
|
+
/**
|
|
69
|
+
* Select input text on focus
|
|
70
|
+
*/
|
|
71
|
+
selectOnFocus: {
|
|
72
|
+
type: Boolean,
|
|
73
|
+
default: false,
|
|
74
|
+
},
|
|
44
75
|
/**
|
|
45
76
|
* If true, the textarea will be resizable
|
|
46
77
|
*/
|