@volverjs/ui-vue 0.0.10-beta.53 → 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/VvCheckboxGroup/VvCheckboxGroup.es.js +13 -1
- package/dist/components/VvCheckboxGroup/VvCheckboxGroup.umd.js +1 -1
- package/dist/components/VvCheckboxGroup/VvCheckboxGroup.vue.d.ts +9 -0
- package/dist/components/VvCheckboxGroup/index.d.ts +4 -0
- package/dist/components/VvCombobox/VvCombobox.es.js +357 -357
- package/dist/components/VvCombobox/VvCombobox.umd.js +1 -1
- package/dist/components/VvInputFile/VvInputFile.es.js +17 -3
- package/dist/components/VvInputFile/VvInputFile.umd.js +1 -1
- package/dist/components/VvInputFile/VvInputFile.vue.d.ts +9 -0
- package/dist/components/VvInputFile/index.d.ts +4 -0
- package/dist/components/VvInputText/VvInputText.es.js +18 -20
- package/dist/components/VvInputText/VvInputText.umd.js +1 -1
- package/dist/components/VvRadioGroup/VvRadioGroup.es.js +13 -1
- package/dist/components/VvRadioGroup/VvRadioGroup.umd.js +1 -1
- package/dist/components/VvRadioGroup/VvRadioGroup.vue.d.ts +9 -0
- package/dist/components/VvRadioGroup/index.d.ts +4 -0
- package/dist/components/VvTextarea/VvTextarea.es.js +1296 -397
- 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 +421 -261
- package/dist/components/index.umd.js +1 -1
- package/dist/icons.es.js +3 -3
- package/dist/icons.umd.js +1 -1
- package/dist/props/index.d.ts +8 -1
- package/dist/stories/InputText/InputText.stories.d.ts +2 -0
- package/dist/stories/InputText/InputText.test.d.ts +2 -0
- package/package.json +23 -23
- 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/VvCheckbox/VvCheckbox.vue +2 -2
- package/src/components/VvCheckboxGroup/VvCheckboxGroup.vue +2 -0
- package/src/components/VvCombobox/VvCombobox.vue +3 -3
- package/src/components/VvInputFile/VvInputFile.vue +12 -8
- package/src/components/VvInputFile/index.ts +2 -0
- package/src/components/VvInputText/VvInputText.vue +20 -22
- package/src/components/VvRadio/VvRadio.vue +2 -2
- package/src/components/VvRadioGroup/VvRadioGroup.vue +2 -0
- package/src/components/VvTextarea/VvTextarea.vue +109 -6
- package/src/components/VvTextarea/index.ts +32 -1
- package/src/props/index.ts +2 -1
- package/src/stories/InputText/InputText.stories.ts +37 -1
- package/src/stories/InputText/InputText.test.ts +18 -0
- package/src/stories/Textarea/Textarea.stories.ts +1 -1
|
@@ -170,10 +170,10 @@ export default {
|
|
|
170
170
|
v-model="localModelValue"
|
|
171
171
|
type="checkbox"
|
|
172
172
|
class="vv-checkbox__input"
|
|
173
|
-
:name
|
|
173
|
+
:name
|
|
174
174
|
:disabled="isDisabled"
|
|
175
175
|
:value="hasValue"
|
|
176
|
-
:tabindex
|
|
176
|
+
:tabindex
|
|
177
177
|
:aria-invalid="isInvalid"
|
|
178
178
|
:aria-describedby="hasHintLabelOrSlot ? hasHintId : undefined"
|
|
179
179
|
:aria-errormessage="hasInvalidLabelOrSlot ? hasHintId : undefined"
|
|
@@ -43,6 +43,7 @@ const bemCssClasses = useModifiers(
|
|
|
43
43
|
computed(() => ({
|
|
44
44
|
disabled: disabled.value,
|
|
45
45
|
readonly: readonly.value,
|
|
46
|
+
required: props.required,
|
|
46
47
|
horizontal: !vertical.value,
|
|
47
48
|
valid: valid.value,
|
|
48
49
|
invalid: invalid.value,
|
|
@@ -56,6 +57,7 @@ function getOptionProps(option: string | Option, index: number) {
|
|
|
56
57
|
name: props.name,
|
|
57
58
|
label: getOptionLabel(option),
|
|
58
59
|
value: getOptionValue(option),
|
|
60
|
+
required: props.required,
|
|
59
61
|
}
|
|
60
62
|
}
|
|
61
63
|
const { HintSlot, hintSlotScope } = HintSlotFactory(propsDefaults, slots)
|
|
@@ -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 {
|
|
@@ -43,10 +43,13 @@ const bemCssClasses = useModifiers(
|
|
|
43
43
|
'vv-input-file',
|
|
44
44
|
modifiers,
|
|
45
45
|
computed(() => ({
|
|
46
|
-
'dragging': isDragging.value,
|
|
47
|
-
'loading': props.loading && !hasProgress.value,
|
|
48
46
|
'valid': props.valid === true,
|
|
49
47
|
'invalid': props.invalid === true,
|
|
48
|
+
'loading': props.loading && !hasProgress.value,
|
|
49
|
+
'disabled': props.disabled,
|
|
50
|
+
'required': props.required,
|
|
51
|
+
'readonly': props.readonly,
|
|
52
|
+
'dragging': isDragging.value,
|
|
50
53
|
'icon-before': !!hasIconBefore.value,
|
|
51
54
|
'icon-after': !!hasIconAfter.value,
|
|
52
55
|
'drop-area': hasDropArea.value,
|
|
@@ -325,18 +328,19 @@ export default {
|
|
|
325
328
|
:id="hasId"
|
|
326
329
|
ref="inputEl"
|
|
327
330
|
type="file"
|
|
328
|
-
:readonly
|
|
329
|
-
:disabled
|
|
330
|
-
:
|
|
331
|
+
:readonly
|
|
332
|
+
:disabled
|
|
333
|
+
:required
|
|
334
|
+
:placeholder
|
|
331
335
|
:aria-describedby="hasHintLabelOrSlot ? hasHintId : undefined"
|
|
332
336
|
:aria-invalid="invalid"
|
|
333
337
|
:aria-errormessage="
|
|
334
338
|
hasInvalidLabelOrSlot ? hasHintId : undefined
|
|
335
339
|
"
|
|
336
340
|
:multiple="isMultiple"
|
|
337
|
-
:accept
|
|
338
|
-
:capture
|
|
339
|
-
:name
|
|
341
|
+
:accept
|
|
342
|
+
:capture
|
|
343
|
+
:name
|
|
340
344
|
@change="onChange"
|
|
341
345
|
>
|
|
342
346
|
<progress
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
LoadingProps,
|
|
10
10
|
ModifiersProps,
|
|
11
11
|
ReadonlyProps,
|
|
12
|
+
RequiredProps,
|
|
12
13
|
ValidProps,
|
|
13
14
|
} from '../../props'
|
|
14
15
|
import { ACTION_ICONS, type VvIconProps } from '../VvIcon'
|
|
@@ -35,6 +36,7 @@ export const VvInputFileProps = {
|
|
|
35
36
|
...LoadingProps,
|
|
36
37
|
...ReadonlyProps,
|
|
37
38
|
...DisabledProps,
|
|
39
|
+
...RequiredProps,
|
|
38
40
|
...IconProps,
|
|
39
41
|
/**
|
|
40
42
|
* Input value
|
|
@@ -174,8 +174,8 @@ const { el, mask, typed, masked, unmasked } = useIMask(
|
|
|
174
174
|
},
|
|
175
175
|
},
|
|
176
176
|
)
|
|
177
|
-
function updateMaskValue(newValue: string | number | undefined) {
|
|
178
|
-
if (newValue === undefined) {
|
|
177
|
+
function updateMaskValue(newValue: string | number | undefined | null) {
|
|
178
|
+
if (newValue === undefined || newValue === null) {
|
|
179
179
|
typed.value = ''
|
|
180
180
|
unmasked.value = ''
|
|
181
181
|
return
|
|
@@ -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"
|
|
@@ -98,10 +98,10 @@ export default {
|
|
|
98
98
|
v-model="localModelValue"
|
|
99
99
|
type="radio"
|
|
100
100
|
class="vv-radio__input"
|
|
101
|
-
:name
|
|
101
|
+
:name
|
|
102
102
|
:disabled="isDisabled"
|
|
103
103
|
:value="hasValue"
|
|
104
|
-
:tabindex
|
|
104
|
+
:tabindex
|
|
105
105
|
:aria-invalid="isInvalid"
|
|
106
106
|
:aria-describedby="hasHintLabelOrSlot ? hasHintId : undefined"
|
|
107
107
|
:aria-errormessage="hasInvalidLabelOrSlot ? hasHintId : undefined"
|
|
@@ -43,6 +43,7 @@ const bemCssClasses = useModifiers(
|
|
|
43
43
|
computed(() => ({
|
|
44
44
|
disabled: disabled.value,
|
|
45
45
|
readonly: readonly.value,
|
|
46
|
+
required: props.required,
|
|
46
47
|
horizontal: !vertical.value,
|
|
47
48
|
valid: valid.value,
|
|
48
49
|
invalid: invalid.value,
|
|
@@ -56,6 +57,7 @@ function getOptionProps(option: string | Option, index: number) {
|
|
|
56
57
|
name: props.name,
|
|
57
58
|
label: getOptionLabel(option),
|
|
58
59
|
value: getOptionValue(option),
|
|
60
|
+
required: props.required,
|
|
59
61
|
}
|
|
60
62
|
}
|
|
61
63
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { TextareaHTMLAttributes } from 'vue'
|
|
3
|
+
import { VvTextareaEvents, VvTextareaProps } from '.'
|
|
3
4
|
import HintSlotFactory from '../common/HintSlot'
|
|
5
|
+
import VvDropdown from '../VvDropdown/VvDropdown.vue'
|
|
6
|
+
import VvDropdownOption from '../VvDropdown/VvDropdownOption.vue'
|
|
4
7
|
import VvIcon from '../VvIcon/VvIcon.vue'
|
|
5
|
-
import { VvTextareaEvents, VvTextareaProps } from '.'
|
|
6
8
|
|
|
7
9
|
// props, emit and slots
|
|
8
10
|
const props = defineProps(VvTextareaProps)
|
|
@@ -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
|
*/
|
package/src/props/index.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { PropType } from 'vue'
|
|
2
1
|
import type { VvIconProps } from '@/components/VvIcon'
|
|
3
2
|
import type {
|
|
4
3
|
AutoPlacementOptions,
|
|
@@ -9,6 +8,7 @@ import type {
|
|
|
9
8
|
} from '@/types/floating-ui'
|
|
10
9
|
import type { Option } from '@/types/generic'
|
|
11
10
|
import type { NavItem } from '@/types/nav'
|
|
11
|
+
import type { PropType } from 'vue'
|
|
12
12
|
import {
|
|
13
13
|
ActionTag,
|
|
14
14
|
ButtonType,
|
|
@@ -493,6 +493,7 @@ export const CheckboxRadioGroupProps = {
|
|
|
493
493
|
...ModifiersProps,
|
|
494
494
|
...LabelProps,
|
|
495
495
|
...LoadingProps,
|
|
496
|
+
...RequiredProps,
|
|
496
497
|
/**
|
|
497
498
|
* Input value
|
|
498
499
|
*/
|
|
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/vue3'
|
|
|
2
2
|
import VvInputText from '@/components/VvInputText/VvInputText.vue'
|
|
3
3
|
import { Position } from '@/constants'
|
|
4
4
|
import { argTypes, defaultArgs } from './InputText.settings'
|
|
5
|
-
import { defaultTest } from './InputText.test'
|
|
5
|
+
import { checkNullTest, checkUndefinedTest, defaultTest } from './InputText.test'
|
|
6
6
|
|
|
7
7
|
const meta: Meta<typeof VvInputText> = {
|
|
8
8
|
title: 'Components/InputText',
|
|
@@ -39,6 +39,42 @@ export const Default: Story = {
|
|
|
39
39
|
play: defaultTest,
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
export const Null: Story = {
|
|
43
|
+
args: {
|
|
44
|
+
...defaultArgs,
|
|
45
|
+
},
|
|
46
|
+
render: args => ({
|
|
47
|
+
components: { VvInputText },
|
|
48
|
+
setup() {
|
|
49
|
+
return { args }
|
|
50
|
+
},
|
|
51
|
+
data: () => ({ inputValue: null, maskedInputValue: undefined }),
|
|
52
|
+
template: /* html */ `
|
|
53
|
+
<vv-input-text v-bind="args" v-model="inputValue" v-model:masked="maskedInputValue" :data-testData="inputValue" data-testId="element" />
|
|
54
|
+
<div>Value: <span data-testId="value">{{ JSON.stringify(inputValue) }}</span></div>
|
|
55
|
+
`,
|
|
56
|
+
}),
|
|
57
|
+
play: checkNullTest,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const Undefined: Story = {
|
|
61
|
+
args: {
|
|
62
|
+
...defaultArgs,
|
|
63
|
+
},
|
|
64
|
+
render: args => ({
|
|
65
|
+
components: { VvInputText },
|
|
66
|
+
setup() {
|
|
67
|
+
return { args }
|
|
68
|
+
},
|
|
69
|
+
data: () => ({ inputValue: undefined, maskedInputValue: undefined }),
|
|
70
|
+
template: /* html */ `
|
|
71
|
+
<vv-input-text v-bind="args" v-model="inputValue" v-model:masked="maskedInputValue" :data-testData="inputValue" data-testId="element" />
|
|
72
|
+
<div>Value: <span data-testId="value">{{ inputValue === undefined ? 'undefined' : inputValue }}</span></div>
|
|
73
|
+
`,
|
|
74
|
+
}),
|
|
75
|
+
play: checkUndefinedTest,
|
|
76
|
+
}
|
|
77
|
+
|
|
42
78
|
export const Disabled: Story = {
|
|
43
79
|
...Default,
|
|
44
80
|
args: {
|
|
@@ -49,6 +49,24 @@ function valueByType(type: InputType, mask?: string, id?: string) {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
export async function checkNullTest({ canvasElement }: PlayAttributes) {
|
|
53
|
+
const element = await within(canvasElement).findByTestId('element')
|
|
54
|
+
const value = await within(canvasElement).findByTestId('value')
|
|
55
|
+
const input = element.getElementsByTagName('input')[0]
|
|
56
|
+
|
|
57
|
+
await expect(input).toHaveProperty('value', '')
|
|
58
|
+
await expect(value.innerHTML).toEqual('null')
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function checkUndefinedTest({ canvasElement }: PlayAttributes) {
|
|
62
|
+
const element = await within(canvasElement).findByTestId('element')
|
|
63
|
+
const value = await within(canvasElement).findByTestId('value')
|
|
64
|
+
const input = element.getElementsByTagName('input')[0]
|
|
65
|
+
|
|
66
|
+
await expect(input).toHaveProperty('value', '')
|
|
67
|
+
await expect(value.innerHTML).toEqual('undefined')
|
|
68
|
+
}
|
|
69
|
+
|
|
52
70
|
export async function defaultTest({ canvasElement, args }: PlayAttributes) {
|
|
53
71
|
const element = await within(canvasElement).findByTestId('element')
|
|
54
72
|
const value = await within(canvasElement).findByTestId('value')
|
|
@@ -31,7 +31,7 @@ export const Default: Story = {
|
|
|
31
31
|
<template #after v-if="args.after"><div class="flex" v-html="args.after"></div></template>
|
|
32
32
|
<template #hint v-if="args.hint"><span v-html="args.hint"></span></template>
|
|
33
33
|
</vv-textarea>
|
|
34
|
-
<div>Value: <span data-testId="value">{{inputValue}}</span></div>
|
|
34
|
+
<div>Value: <span data-testId="value">{{ inputValue }}</span></div>
|
|
35
35
|
`,
|
|
36
36
|
}),
|
|
37
37
|
play: defaultTest,
|