@volverjs/ui-vue 0.0.9-beta.9 → 0.0.10-beta.1
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/auto-imports.d.ts +2 -2
- package/dist/components/VvAccordion/VvAccordion.es.js +8 -7
- package/dist/components/VvAccordionGroup/VvAccordionGroup.es.js +8 -7
- package/dist/components/VvAction/VvAction.es.js +8 -7
- package/dist/components/VvAlert/VvAlert.es.js +8 -7
- package/dist/components/VvAlertGroup/VvAlertGroup.es.js +8 -7
- package/dist/components/VvAlertGroup/VvAlertGroup.vue.d.ts +6 -6
- package/dist/components/VvAlertGroup/index.d.ts +2 -2
- package/dist/components/VvAvatar/VvAvatar.es.js +8 -7
- package/dist/components/VvAvatarGroup/VvAvatarGroup.es.js +8 -7
- package/dist/components/VvBadge/VvBadge.es.js +8 -7
- package/dist/components/VvBreadcrumb/VvBreadcrumb.es.js +8 -7
- package/dist/components/VvButton/VvButton.es.js +11 -10
- package/dist/components/VvButtonGroup/VvButtonGroup.es.js +8 -7
- package/dist/components/VvCard/VvCard.es.js +8 -7
- package/dist/components/VvCheckbox/VvCheckbox.es.js +11 -10
- package/dist/components/VvCheckboxGroup/VvCheckboxGroup.es.js +13 -12
- package/dist/components/VvCheckboxGroup/VvCheckboxGroup.umd.js +1 -1
- package/dist/components/VvCombobox/VvCombobox.es.js +158 -140
- package/dist/components/VvCombobox/VvCombobox.umd.js +1 -1
- package/dist/components/VvCombobox/VvCombobox.vue.d.ts +306 -90
- package/dist/components/VvCombobox/index.d.ts +108 -31
- package/dist/components/VvDialog/VvDialog.es.js +16 -11
- package/dist/components/VvDialog/VvDialog.umd.js +1 -1
- package/dist/components/VvDropdown/VvDropdown.es.js +19 -16
- package/dist/components/VvDropdown/VvDropdown.umd.js +1 -1
- package/dist/components/VvDropdown/VvDropdown.vue.d.ts +299 -91
- package/dist/components/VvDropdown/index.d.ts +99 -30
- package/dist/components/VvDropdownAction/VvDropdownAction.es.js +8 -7
- package/dist/components/VvDropdownOptgroup/VvDropdownOptgroup.es.js +8 -7
- package/dist/components/VvDropdownOption/VvDropdownOption.es.js +8 -7
- package/dist/components/VvInputText/VvInputText.es.js +193 -103
- package/dist/components/VvInputText/VvInputText.umd.js +1 -1
- package/dist/components/VvInputText/VvInputText.vue.d.ts +19 -37
- package/dist/components/VvInputText/index.d.ts +15 -33
- package/dist/components/VvNav/VvNav.es.js +8 -7
- package/dist/components/VvProgress/VvProgress.es.js +8 -7
- package/dist/components/VvRadio/VvRadio.es.js +11 -10
- package/dist/components/VvRadioGroup/VvRadioGroup.es.js +13 -12
- package/dist/components/VvRadioGroup/VvRadioGroup.umd.js +1 -1
- package/dist/components/VvSelect/VvSelect.es.js +14 -13
- package/dist/components/VvSelect/VvSelect.umd.js +1 -1
- package/dist/components/VvSelect/VvSelect.vue.d.ts +1 -1
- package/dist/components/VvTab/VvTab.es.js +8 -7
- package/dist/components/VvTextarea/VvTextarea.es.js +16 -12
- package/dist/components/VvTextarea/VvTextarea.umd.js +1 -1
- package/dist/components/VvTooltip/VvTooltip.es.js +8 -7
- package/dist/components/index.es.js +359 -245
- package/dist/components/index.umd.js +1 -1
- package/dist/composables/useOptions.d.ts +1 -1
- package/dist/constants.d.ts +4 -0
- package/dist/directives/index.es.js +8 -7
- package/dist/directives/v-tooltip.es.js +8 -7
- package/dist/icons.es.js +3 -3
- package/dist/icons.umd.js +1 -1
- package/dist/props/index.d.ts +100 -31
- package/dist/stories/AccordionGroup/AccordionGroup.stories.d.ts +2 -2
- package/dist/stories/AccordionGroup/AccordionGroupSlots.stories.d.ts +18 -18
- package/dist/stories/Combobox/Combobox.settings.d.ts +8 -0
- package/dist/stories/Combobox/ComboboxOptions.stories.d.ts +1 -0
- package/dist/stories/InputText/InputText.settings.d.ts +31 -9
- package/dist/stories/InputText/InputText.stories.d.ts +0 -1
- package/dist/stories/InputText/InputTextMask.stories.d.ts +12 -0
- package/package.json +61 -60
- 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 +51 -42
- package/src/components/VvCombobox/index.ts +13 -0
- package/src/components/VvDialog/VvDialog.vue +6 -2
- package/src/components/VvDropdown/VvDropdown.vue +18 -16
- package/src/components/VvInputText/VvInputText.vue +170 -55
- package/src/components/VvInputText/index.ts +32 -34
- package/src/components/VvSelect/VvSelect.vue +4 -4
- package/src/components/VvTextarea/VvTextarea.vue +8 -5
- package/src/composables/useOptions.ts +2 -2
- package/src/constants.ts +5 -0
- package/src/props/index.ts +7 -11
- package/src/stories/Combobox/Combobox.settings.ts +8 -0
- package/src/stories/Combobox/Combobox.test.ts +1 -1
- package/src/stories/Combobox/ComboboxOptions.stories.ts +18 -0
- package/src/stories/InputText/InputText.settings.ts +36 -15
- package/src/stories/InputText/InputText.stories.ts +4 -12
- package/src/stories/InputText/InputText.test.ts +31 -15
- package/src/stories/InputText/InputTextMask.stories.ts +122 -0
- package/src/utils/ObjectUtilities.ts +3 -2
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
<script setup lang="ts">
|
|
14
14
|
import type { Ref } from 'vue'
|
|
15
|
+
import { toRefs } from 'vue'
|
|
15
16
|
import { VvComboboxProps, VvComboboxEvents } from '.'
|
|
16
17
|
import VvIcon from '../VvIcon/VvIcon.vue'
|
|
17
18
|
import VvDropdown from '../VvDropdown/VvDropdown.vue'
|
|
@@ -84,7 +85,7 @@
|
|
|
84
85
|
const searchText = ref('')
|
|
85
86
|
const debouncedSearchText = refDebounced(
|
|
86
87
|
searchText,
|
|
87
|
-
Number(props.debounceSearch),
|
|
88
|
+
computed(() => Number(props.debounceSearch)),
|
|
88
89
|
)
|
|
89
90
|
watch(debouncedSearchText, () =>
|
|
90
91
|
emit('change:search', debouncedSearchText.value),
|
|
@@ -105,7 +106,7 @@
|
|
|
105
106
|
expanded.value = false
|
|
106
107
|
}
|
|
107
108
|
const onAfterExpand = () => {
|
|
108
|
-
if (
|
|
109
|
+
if (propsDefaults.value.searchable) {
|
|
109
110
|
if (inputSearchEl.value) {
|
|
110
111
|
inputSearchEl.value.focus({
|
|
111
112
|
preventScroll: true,
|
|
@@ -114,7 +115,7 @@
|
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
117
|
const onAfterCollapse = () => {
|
|
117
|
-
if (
|
|
118
|
+
if (propsDefaults.value.searchable) {
|
|
118
119
|
searchText.value = ''
|
|
119
120
|
}
|
|
120
121
|
}
|
|
@@ -131,7 +132,6 @@
|
|
|
131
132
|
valid,
|
|
132
133
|
invalid,
|
|
133
134
|
floating,
|
|
134
|
-
searchable,
|
|
135
135
|
} = toRefs(props)
|
|
136
136
|
const hasId = useUniqueId(id)
|
|
137
137
|
const hasHintId = computed(() => `${hasId.value}-hint`)
|
|
@@ -139,6 +139,10 @@
|
|
|
139
139
|
const hasSearchId = computed(() => `${hasId.value}-search`)
|
|
140
140
|
const hasLabelId = computed(() => `${hasId.value}-label`)
|
|
141
141
|
|
|
142
|
+
// loading
|
|
143
|
+
const localLoading = ref(false)
|
|
144
|
+
const isLoading = computed(() => localLoading.value || loading.value)
|
|
145
|
+
|
|
142
146
|
// ref
|
|
143
147
|
const dropdownEl = ref()
|
|
144
148
|
|
|
@@ -162,7 +166,7 @@
|
|
|
162
166
|
modifiers,
|
|
163
167
|
computed(() => ({
|
|
164
168
|
disabled: disabled.value,
|
|
165
|
-
loading:
|
|
169
|
+
loading: isLoading.value,
|
|
166
170
|
readonly: readonly.value,
|
|
167
171
|
'icon-before': Boolean(hasIconBefore.value),
|
|
168
172
|
'icon-after': Boolean(hasIconAfter.value),
|
|
@@ -178,12 +182,23 @@
|
|
|
178
182
|
const {
|
|
179
183
|
getOptionLabel,
|
|
180
184
|
getOptionValue,
|
|
181
|
-
getOptionDisabled,
|
|
182
185
|
getOptionGrouped,
|
|
186
|
+
isOptionDisabled,
|
|
183
187
|
} = useOptions(props)
|
|
184
188
|
|
|
185
189
|
// options filtered by search text
|
|
186
|
-
const filteredOptions =
|
|
190
|
+
const filteredOptions = computedAsync(async () => {
|
|
191
|
+
if (propsDefaults.value.searchFunction) {
|
|
192
|
+
localLoading.value = true
|
|
193
|
+
const toReturn = await Promise.resolve(
|
|
194
|
+
propsDefaults.value.searchFunction(
|
|
195
|
+
debouncedSearchText.value,
|
|
196
|
+
props.options,
|
|
197
|
+
),
|
|
198
|
+
)
|
|
199
|
+
localLoading.value = false
|
|
200
|
+
return toReturn
|
|
201
|
+
}
|
|
187
202
|
return props.options?.filter((option) => {
|
|
188
203
|
return getOptionLabel(option)
|
|
189
204
|
.toLowerCase()
|
|
@@ -195,7 +210,7 @@
|
|
|
195
210
|
* Check if an option exist into modelValue array (multiple) or is equal to modelValue (single)
|
|
196
211
|
* @param {String | Option} option
|
|
197
212
|
*/
|
|
198
|
-
function
|
|
213
|
+
function isOptionSelected(option: string | Option) {
|
|
199
214
|
if (Array.isArray(props.modelValue)) {
|
|
200
215
|
// check if contain whole option or option value
|
|
201
216
|
return (
|
|
@@ -215,26 +230,17 @@
|
|
|
215
230
|
* Check if is multiple mode, object mode or "string" mode
|
|
216
231
|
*/
|
|
217
232
|
const selectedOptions = computed(() => {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
return [...acc, value]
|
|
229
|
-
}, [] as Array<Option | string>)
|
|
230
|
-
|
|
233
|
+
const options = props.options.reduce<Array<Option | string>>(
|
|
234
|
+
(acc, value) => {
|
|
235
|
+
if (isGroup(value)) {
|
|
236
|
+
return [...acc, ...getOptionGrouped(value)]
|
|
237
|
+
}
|
|
238
|
+
return [...acc, value]
|
|
239
|
+
},
|
|
240
|
+
[],
|
|
241
|
+
)
|
|
231
242
|
return options.filter((option) => {
|
|
232
|
-
|
|
233
|
-
return getOptionGrouped(option).some((item) =>
|
|
234
|
-
selectedValues.includes(getOptionValue(item)),
|
|
235
|
-
)
|
|
236
|
-
}
|
|
237
|
-
return selectedValues.includes(getOptionValue(option))
|
|
243
|
+
return isOptionSelected(option)
|
|
238
244
|
})
|
|
239
245
|
})
|
|
240
246
|
|
|
@@ -304,7 +310,7 @@
|
|
|
304
310
|
invalid: invalid.value,
|
|
305
311
|
invalidLabel: propsDefaults.value.invalidLabel,
|
|
306
312
|
hintLabel: propsDefaults.value.hintLabel,
|
|
307
|
-
loading:
|
|
313
|
+
loading: isLoading.value,
|
|
308
314
|
loadingLabel: propsDefaults.value.loadingLabel,
|
|
309
315
|
disabled: disabled.value,
|
|
310
316
|
readonly: readonly.value,
|
|
@@ -333,7 +339,7 @@
|
|
|
333
339
|
flip: propsDefaults.value.flip,
|
|
334
340
|
autoPlacement: propsDefaults.value.autoPlacement,
|
|
335
341
|
arrow: propsDefaults.value.arrow,
|
|
336
|
-
autofocusFirst:
|
|
342
|
+
autofocusFirst: propsDefaults.value.searchable
|
|
337
343
|
? true
|
|
338
344
|
: propsDefaults.value.autofocusFirst,
|
|
339
345
|
triggerWidth: propsDefaults.value.triggerWidth,
|
|
@@ -369,7 +375,7 @@
|
|
|
369
375
|
<label
|
|
370
376
|
v-if="label"
|
|
371
377
|
:id="hasLabelId"
|
|
372
|
-
:for="searchable ? hasSearchId : undefined"
|
|
378
|
+
:for="propsDefaults.searchable ? hasSearchId : undefined"
|
|
373
379
|
>
|
|
374
380
|
{{ label }}
|
|
375
381
|
</label>
|
|
@@ -383,13 +389,15 @@
|
|
|
383
389
|
@after-collapse="onAfterCollapse"
|
|
384
390
|
>
|
|
385
391
|
<template
|
|
386
|
-
v-if="
|
|
392
|
+
v-if="
|
|
393
|
+
propsDefaults.searchable || $slots['dropdown::before']
|
|
394
|
+
"
|
|
387
395
|
#before
|
|
388
396
|
>
|
|
389
397
|
<!-- @slot Slot before dropdown items -->
|
|
390
398
|
<slot name="dropdown::before" />
|
|
391
399
|
<input
|
|
392
|
-
v-if="searchable"
|
|
400
|
+
v-if="propsDefaults.searchable && !disabled"
|
|
393
401
|
:id="hasSearchId"
|
|
394
402
|
ref="inputSearchEl"
|
|
395
403
|
v-model="searchText"
|
|
@@ -460,6 +468,7 @@
|
|
|
460
468
|
:aria-label="
|
|
461
469
|
propsDefaults.deselectActionLabel
|
|
462
470
|
"
|
|
471
|
+
type="button"
|
|
463
472
|
@click.stop="onInput(option)"
|
|
464
473
|
>
|
|
465
474
|
<VvIcon name="close" />
|
|
@@ -483,7 +492,7 @@
|
|
|
483
492
|
</div>
|
|
484
493
|
</template>
|
|
485
494
|
<template #items>
|
|
486
|
-
<template v-if="filteredOptions
|
|
495
|
+
<template v-if="!disabled && filteredOptions?.length">
|
|
487
496
|
<template
|
|
488
497
|
v-for="(option, index) in filteredOptions"
|
|
489
498
|
:key="index"
|
|
@@ -497,8 +506,8 @@
|
|
|
497
506
|
option,
|
|
498
507
|
)"
|
|
499
508
|
v-bind="{
|
|
500
|
-
|
|
501
|
-
|
|
509
|
+
selected: isOptionSelected(item),
|
|
510
|
+
disabled: isOptionDisabled(item),
|
|
502
511
|
unselectable,
|
|
503
512
|
deselectHintLabel:
|
|
504
513
|
propsDefaults.deselectHintLabel,
|
|
@@ -517,8 +526,8 @@
|
|
|
517
526
|
v-bind="{
|
|
518
527
|
option,
|
|
519
528
|
selectedOptions,
|
|
520
|
-
selected:
|
|
521
|
-
disabled:
|
|
529
|
+
selected: isOptionSelected(item),
|
|
530
|
+
disabled: isOptionDisabled(item),
|
|
522
531
|
}"
|
|
523
532
|
>
|
|
524
533
|
{{ getOptionLabel(item) }}
|
|
@@ -528,8 +537,8 @@
|
|
|
528
537
|
<VvDropdownOption
|
|
529
538
|
v-else
|
|
530
539
|
v-bind="{
|
|
531
|
-
|
|
532
|
-
|
|
540
|
+
selected: isOptionSelected(option),
|
|
541
|
+
disabled: isOptionDisabled(option),
|
|
533
542
|
unselectable,
|
|
534
543
|
deselectHintLabel:
|
|
535
544
|
propsDefaults.deselectHintLabel,
|
|
@@ -547,8 +556,8 @@
|
|
|
547
556
|
v-bind="{
|
|
548
557
|
option,
|
|
549
558
|
selectedOptions,
|
|
550
|
-
selected:
|
|
551
|
-
disabled:
|
|
559
|
+
selected: isOptionSelected(option),
|
|
560
|
+
disabled: isOptionDisabled(option),
|
|
552
561
|
}"
|
|
553
562
|
>
|
|
554
563
|
{{ getOptionLabel(option) }}
|
|
@@ -565,7 +574,7 @@
|
|
|
565
574
|
{{ propsDefaults.noOptionsLabel }}
|
|
566
575
|
</slot>
|
|
567
576
|
</VvDropdownOption>
|
|
568
|
-
<VvDropdownOption v-else modifiers="inert">
|
|
577
|
+
<VvDropdownOption v-else-if="!disabled" modifiers="inert">
|
|
569
578
|
<!-- @slot Slot for no results available -->
|
|
570
579
|
<slot name="no-results">
|
|
571
580
|
{{ propsDefaults.noResultsLabel }}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
DropdownProps,
|
|
17
17
|
LabelProps,
|
|
18
18
|
} from '../../props'
|
|
19
|
+
import type { Option } from '../../types/generic'
|
|
19
20
|
|
|
20
21
|
export const VvComboboxEvents = [
|
|
21
22
|
'update:modelValue',
|
|
@@ -90,6 +91,18 @@ export const VvComboboxProps = {
|
|
|
90
91
|
* Use input text to search on options
|
|
91
92
|
*/
|
|
92
93
|
searchable: Boolean,
|
|
94
|
+
/**
|
|
95
|
+
* Search function to filter options
|
|
96
|
+
*/
|
|
97
|
+
searchFunction: {
|
|
98
|
+
type: Function as PropType<
|
|
99
|
+
(
|
|
100
|
+
search: string,
|
|
101
|
+
options: (Option | string)[],
|
|
102
|
+
) => (Option | string)[] | Promise<(Option | string)[]>
|
|
103
|
+
>,
|
|
104
|
+
default: undefined,
|
|
105
|
+
},
|
|
93
106
|
/**
|
|
94
107
|
* On searchable select is the input search placeholder
|
|
95
108
|
*/
|
|
@@ -49,12 +49,16 @@
|
|
|
49
49
|
const transitioName = computed(() => `vv-dialog--${props.transition}`)
|
|
50
50
|
const dialogTransitionHandlers = {
|
|
51
51
|
'before-enter': () => {
|
|
52
|
-
dialogEl.value?.
|
|
52
|
+
if (!dialogEl.value?.open) {
|
|
53
|
+
dialogEl.value?.showModal()
|
|
54
|
+
}
|
|
53
55
|
emit('open')
|
|
54
56
|
emit('before-enter')
|
|
55
57
|
},
|
|
56
58
|
'after-leave': () => {
|
|
57
|
-
dialogEl.value?.
|
|
59
|
+
if (dialogEl.value?.open) {
|
|
60
|
+
dialogEl.value?.close()
|
|
61
|
+
}
|
|
58
62
|
emit('close')
|
|
59
63
|
emit('after-leave')
|
|
60
64
|
},
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
ShiftOptions,
|
|
26
26
|
SizeOptions,
|
|
27
27
|
} from '../../types/floating-ui'
|
|
28
|
+
import { Side, Strategy } from '../../constants'
|
|
28
29
|
|
|
29
30
|
// props, emit and attrs
|
|
30
31
|
const props = defineProps(VvDropdownProps)
|
|
@@ -96,7 +97,7 @@
|
|
|
96
97
|
}
|
|
97
98
|
} else if (props.flip) {
|
|
98
99
|
if (typeof props.flip === 'boolean') {
|
|
99
|
-
toReturn.push(flip())
|
|
100
|
+
toReturn.push(flip({ fallbackStrategy: 'initialPlacement' }))
|
|
100
101
|
} else {
|
|
101
102
|
toReturn.push(flip(props.flip as FlipOptions))
|
|
102
103
|
}
|
|
@@ -157,13 +158,13 @@
|
|
|
157
158
|
|
|
158
159
|
return toReturn
|
|
159
160
|
})
|
|
160
|
-
const { x, y,
|
|
161
|
+
const { x, y, middlewareData, placement, strategy } = useFloating(
|
|
161
162
|
referenceEl,
|
|
162
163
|
floatingEl,
|
|
163
164
|
{
|
|
164
165
|
whileElementsMounted: (...args) => {
|
|
165
166
|
return autoUpdate(...args, {
|
|
166
|
-
animationFrame: props.strategy ===
|
|
167
|
+
animationFrame: props.strategy === Strategy.fixed,
|
|
167
168
|
})
|
|
168
169
|
},
|
|
169
170
|
placement: computed(() => props.placement),
|
|
@@ -175,16 +176,17 @@
|
|
|
175
176
|
if (hasCustomPosition.value) {
|
|
176
177
|
return undefined
|
|
177
178
|
}
|
|
179
|
+
const width =
|
|
180
|
+
props.triggerWidth && referenceEl.value
|
|
181
|
+
? `${referenceEl.value?.offsetWidth}px`
|
|
182
|
+
: undefined
|
|
178
183
|
return {
|
|
179
184
|
position: strategy.value,
|
|
180
185
|
top: `${y.value ?? 0}px`,
|
|
181
186
|
left: `${x.value ?? 0}px`,
|
|
182
|
-
maxWidth: maxWidth.value,
|
|
187
|
+
maxWidth: width ? undefined : maxWidth.value,
|
|
183
188
|
maxHeight: maxHeight.value,
|
|
184
|
-
width
|
|
185
|
-
props.triggerWidth && referenceEl.value
|
|
186
|
-
? `${referenceEl.value.offsetWidth}px`
|
|
187
|
-
: undefined,
|
|
189
|
+
width,
|
|
188
190
|
}
|
|
189
191
|
})
|
|
190
192
|
|
|
@@ -192,20 +194,20 @@
|
|
|
192
194
|
const side = computed(
|
|
193
195
|
() =>
|
|
194
196
|
placement.value.split('-')[0] as
|
|
195
|
-
|
|
|
196
|
-
|
|
|
197
|
-
|
|
|
198
|
-
|
|
|
197
|
+
| Side.top
|
|
198
|
+
| Side.right
|
|
199
|
+
| Side.bottom
|
|
200
|
+
| Side.left,
|
|
199
201
|
)
|
|
200
202
|
const arrowPlacement = computed(() => {
|
|
201
203
|
if (hasCustomPosition.value) {
|
|
202
204
|
return undefined
|
|
203
205
|
}
|
|
204
206
|
const staticSide = {
|
|
205
|
-
top:
|
|
206
|
-
right:
|
|
207
|
-
bottom:
|
|
208
|
-
left:
|
|
207
|
+
[Side.top]: Side.bottom,
|
|
208
|
+
[Side.right]: Side.left,
|
|
209
|
+
[Side.bottom]: Side.top,
|
|
210
|
+
[Side.left]: Side.right,
|
|
209
211
|
}[side.value]
|
|
210
212
|
return {
|
|
211
213
|
left:
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
<script setup lang="ts">
|
|
8
8
|
import type { InputHTMLAttributes } from 'vue'
|
|
9
|
-
import {
|
|
9
|
+
import { useIMask } from 'vue-imask'
|
|
10
10
|
import HintSlotFactory from '../common/HintSlot'
|
|
11
11
|
import VvIcon from '../VvIcon/VvIcon.vue'
|
|
12
12
|
import VvInputTextActionsFactory from '../VvInputText/VvInputTextActions'
|
|
@@ -29,12 +29,6 @@
|
|
|
29
29
|
props,
|
|
30
30
|
)
|
|
31
31
|
|
|
32
|
-
// template refs
|
|
33
|
-
const inputEl = ref()
|
|
34
|
-
const innerEl = ref()
|
|
35
|
-
|
|
36
|
-
defineExpose({ $inner: innerEl })
|
|
37
|
-
|
|
38
32
|
// data
|
|
39
33
|
const {
|
|
40
34
|
id,
|
|
@@ -46,6 +40,12 @@
|
|
|
46
40
|
valid,
|
|
47
41
|
invalid,
|
|
48
42
|
loading,
|
|
43
|
+
debounce,
|
|
44
|
+
maxlength,
|
|
45
|
+
minlength,
|
|
46
|
+
type,
|
|
47
|
+
iMask,
|
|
48
|
+
step,
|
|
49
49
|
} = toRefs(props)
|
|
50
50
|
const hasId = useUniqueId(id)
|
|
51
51
|
const hasHintId = computed(() => `${hasId.value}-hint`)
|
|
@@ -54,35 +54,144 @@
|
|
|
54
54
|
props.floating && isEmpty(props.placeholder) ? ' ' : props.placeholder,
|
|
55
55
|
)
|
|
56
56
|
|
|
57
|
-
//
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
// template refs
|
|
58
|
+
const maskReady = ref(false)
|
|
59
|
+
const { el, mask, typed, masked, unmasked } = useIMask(
|
|
60
|
+
computed(
|
|
61
|
+
() =>
|
|
62
|
+
iMask?.value ?? {
|
|
63
|
+
mask: /./,
|
|
64
|
+
},
|
|
65
|
+
),
|
|
62
66
|
{
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
emit,
|
|
68
|
+
onAccept: () => {
|
|
69
|
+
if (!maskReady.value) {
|
|
70
|
+
return
|
|
66
71
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
emit('update:masked', masked.value)
|
|
73
|
+
if (type.value === INPUT_TYPES.NUMBER) {
|
|
74
|
+
if (masked.value === '') {
|
|
75
|
+
if (
|
|
76
|
+
localModelValue.value === null ||
|
|
77
|
+
localModelValue.value === undefined
|
|
78
|
+
) {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
localModelValue.value = undefined
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
if (typeof typed.value !== 'number') {
|
|
85
|
+
localModelValue.value = Number(typed.value)
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
localModelValue.value = typed.value
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
if (type.value === INPUT_TYPES.DATE) {
|
|
92
|
+
if (
|
|
93
|
+
el.value instanceof HTMLInputElement &&
|
|
94
|
+
el.value.type === 'date'
|
|
95
|
+
) {
|
|
96
|
+
localModelValue.value = el.value.value
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
let date = typed.value
|
|
100
|
+
if (date === null || date === '') {
|
|
101
|
+
if (!localModelValue.value) {
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
localModelValue.value = ''
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
if (!(date instanceof Date)) {
|
|
108
|
+
date = new Date(date)
|
|
109
|
+
}
|
|
110
|
+
localModelValue.value = `${date.getFullYear()}-${(
|
|
111
|
+
'0' +
|
|
112
|
+
(date.getMonth() + 1)
|
|
113
|
+
).slice(-2)}-${('0' + date.getDate()).slice(-2)}`
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
if (type.value === INPUT_TYPES.DATETIME_LOCAL) {
|
|
117
|
+
if (
|
|
118
|
+
el.value instanceof HTMLInputElement &&
|
|
119
|
+
el.value.type === 'datetime-local'
|
|
120
|
+
) {
|
|
121
|
+
localModelValue.value = el.value.value
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
let date = typed.value
|
|
125
|
+
if (date === null || date === '') {
|
|
126
|
+
if (!localModelValue.value) {
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
localModelValue.value = ''
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
if (!(typed.value instanceof Date)) {
|
|
133
|
+
date = new Date(date)
|
|
134
|
+
}
|
|
135
|
+
localModelValue.value = `${date.getFullYear()}-${(
|
|
136
|
+
'0' +
|
|
137
|
+
(date.getMonth() + 1)
|
|
138
|
+
).slice(-2)}-${('0' + date.getDate()).slice(-2)}T${(
|
|
139
|
+
'0' + date.getHours()
|
|
140
|
+
).slice(-2)}:${('0' + date.getMinutes()).slice(-2)}`
|
|
141
|
+
return
|
|
72
142
|
}
|
|
73
|
-
if (
|
|
74
|
-
return
|
|
143
|
+
if (!localModelValue.value && !unmasked.value) {
|
|
144
|
+
return
|
|
75
145
|
}
|
|
76
|
-
|
|
146
|
+
localModelValue.value = unmasked.value
|
|
77
147
|
},
|
|
78
148
|
},
|
|
79
149
|
)
|
|
150
|
+
onMounted(() => {
|
|
151
|
+
if (mask.value) {
|
|
152
|
+
maskReady.value = true
|
|
153
|
+
typed.value = localModelValue.value ?? ''
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
watch(
|
|
157
|
+
() => props.modelValue,
|
|
158
|
+
(newValue) => {
|
|
159
|
+
if (mask.value) {
|
|
160
|
+
typed.value =
|
|
161
|
+
newValue && iMask?.value?.mask === Date
|
|
162
|
+
? new Date(newValue)
|
|
163
|
+
: newValue ?? ''
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
)
|
|
167
|
+
watch(
|
|
168
|
+
() => props.masked,
|
|
169
|
+
(newValue) => {
|
|
170
|
+
masked.value = newValue ?? ''
|
|
171
|
+
},
|
|
172
|
+
)
|
|
173
|
+
const inputEl = el as Ref<HTMLInputElement>
|
|
174
|
+
const innerEl = ref()
|
|
175
|
+
|
|
176
|
+
defineExpose({ $inner: innerEl })
|
|
177
|
+
|
|
178
|
+
// debounce
|
|
179
|
+
const localModelValue = useDebouncedInput(
|
|
180
|
+
modelValue,
|
|
181
|
+
emit,
|
|
182
|
+
debounce?.value ?? 0,
|
|
183
|
+
)
|
|
80
184
|
|
|
81
185
|
// focus
|
|
82
186
|
const { focused } = useComponentFocus(inputEl, emit)
|
|
83
187
|
const isFocused = computed(
|
|
84
188
|
() => focused.value && !props.disabled && !props.readonly,
|
|
85
189
|
)
|
|
190
|
+
watch(isFocused, (newValue) => {
|
|
191
|
+
if (newValue && propsDefaults.value.selectOnFocus && inputEl.value) {
|
|
192
|
+
inputEl.value.select()
|
|
193
|
+
}
|
|
194
|
+
})
|
|
86
195
|
|
|
87
196
|
// visibility
|
|
88
197
|
const isVisible = useElementVisibility(inputEl)
|
|
@@ -113,12 +222,21 @@
|
|
|
113
222
|
const isNumber = computed(() => props.type === INPUT_TYPES.NUMBER)
|
|
114
223
|
const onStepUp = () => {
|
|
115
224
|
if (isClickable.value) {
|
|
225
|
+
if (iMask?.value) {
|
|
226
|
+
typed.value = typed.value + Number(step?.value ?? 1)
|
|
227
|
+
return
|
|
228
|
+
}
|
|
116
229
|
inputEl.value.stepUp()
|
|
117
230
|
localModelValue.value = unref(inputEl).value
|
|
118
231
|
}
|
|
119
232
|
}
|
|
120
233
|
const onStepDown = () => {
|
|
121
234
|
if (isClickable.value) {
|
|
235
|
+
if (iMask?.value) {
|
|
236
|
+
typed.value = typed.value - Number(step?.value ?? 1)
|
|
237
|
+
|
|
238
|
+
return
|
|
239
|
+
}
|
|
122
240
|
inputEl.value.stepDown()
|
|
123
241
|
localModelValue.value = unref(inputEl).value
|
|
124
242
|
}
|
|
@@ -127,7 +245,7 @@
|
|
|
127
245
|
// search
|
|
128
246
|
const isSearch = computed(() => props.type === INPUT_TYPES.SEARCH)
|
|
129
247
|
const onClear = () => {
|
|
130
|
-
localModelValue.value =
|
|
248
|
+
localModelValue.value = ''
|
|
131
249
|
}
|
|
132
250
|
|
|
133
251
|
// icons
|
|
@@ -153,9 +271,9 @@
|
|
|
153
271
|
|
|
154
272
|
// count
|
|
155
273
|
const { formatted: countFormatted } = useTextCount(localModelValue, {
|
|
156
|
-
mode:
|
|
157
|
-
upperLimit: Number(
|
|
158
|
-
lowerLimit: Number(
|
|
274
|
+
mode: count.value,
|
|
275
|
+
upperLimit: Number(maxlength?.value),
|
|
276
|
+
lowerLimit: Number(minlength?.value),
|
|
159
277
|
})
|
|
160
278
|
|
|
161
279
|
// tabindex
|
|
@@ -207,6 +325,9 @@
|
|
|
207
325
|
if (isDateTime.value && !isDirty.value && !focused.value) {
|
|
208
326
|
return INPUT_TYPES.TEXT
|
|
209
327
|
}
|
|
328
|
+
if (iMask?.value) {
|
|
329
|
+
return INPUT_TYPES.TEXT
|
|
330
|
+
}
|
|
210
331
|
return props.type
|
|
211
332
|
})()
|
|
212
333
|
const toReturn: InputHTMLAttributes = {
|
|
@@ -299,33 +420,6 @@
|
|
|
299
420
|
props,
|
|
300
421
|
)
|
|
301
422
|
|
|
302
|
-
// mask
|
|
303
|
-
const mask = ref()
|
|
304
|
-
watch(
|
|
305
|
-
[
|
|
306
|
-
() => props.mask,
|
|
307
|
-
() => props.type,
|
|
308
|
-
() => props.maskEager,
|
|
309
|
-
() => props.maskReversed,
|
|
310
|
-
() => props.maskTokens,
|
|
311
|
-
() => props.maskTokensReplace,
|
|
312
|
-
],
|
|
313
|
-
([newMask, newType, eager, reversed, tokens, tokensReplace]) => {
|
|
314
|
-
if (newMask && newType === INPUT_TYPES.TEXT) {
|
|
315
|
-
mask.value = new Mask({
|
|
316
|
-
mask: newMask,
|
|
317
|
-
eager,
|
|
318
|
-
reversed,
|
|
319
|
-
tokens,
|
|
320
|
-
tokensReplace,
|
|
321
|
-
})
|
|
322
|
-
return
|
|
323
|
-
}
|
|
324
|
-
mask.value = undefined
|
|
325
|
-
},
|
|
326
|
-
{ immediate: true },
|
|
327
|
-
)
|
|
328
|
-
|
|
329
423
|
// auto-width
|
|
330
424
|
const onClickInner = () => {
|
|
331
425
|
if (isClickable.value) {
|
|
@@ -343,6 +437,26 @@
|
|
|
343
437
|
: undefined,
|
|
344
438
|
}
|
|
345
439
|
})
|
|
440
|
+
|
|
441
|
+
// keydown
|
|
442
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
443
|
+
switch (event.code) {
|
|
444
|
+
case 'ArrowUp':
|
|
445
|
+
if (isNumber.value) {
|
|
446
|
+
onStepUp()
|
|
447
|
+
event.preventDefault()
|
|
448
|
+
}
|
|
449
|
+
break
|
|
450
|
+
|
|
451
|
+
case 'ArrowDown':
|
|
452
|
+
if (isNumber.value) {
|
|
453
|
+
onStepDown()
|
|
454
|
+
event.preventDefault()
|
|
455
|
+
}
|
|
456
|
+
break
|
|
457
|
+
}
|
|
458
|
+
emit('keydown', event)
|
|
459
|
+
}
|
|
346
460
|
</script>
|
|
347
461
|
|
|
348
462
|
<template>
|
|
@@ -368,10 +482,11 @@
|
|
|
368
482
|
<input
|
|
369
483
|
:id="hasId"
|
|
370
484
|
ref="inputEl"
|
|
371
|
-
v-model="localModelValue"
|
|
372
485
|
v-bind="hasAttrs"
|
|
373
486
|
:style="hasStyle"
|
|
374
487
|
@keyup="emit('keyup', $event)"
|
|
488
|
+
@keydown="onKeyDown"
|
|
489
|
+
@keypress="emit('keypress', $event)"
|
|
375
490
|
/>
|
|
376
491
|
<div
|
|
377
492
|
v-if="(unit || $slots.unit) && isDirty"
|