@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.
Files changed (86) hide show
  1. package/auto-imports.d.ts +2 -2
  2. package/dist/components/VvAccordion/VvAccordion.es.js +8 -7
  3. package/dist/components/VvAccordionGroup/VvAccordionGroup.es.js +8 -7
  4. package/dist/components/VvAction/VvAction.es.js +8 -7
  5. package/dist/components/VvAlert/VvAlert.es.js +8 -7
  6. package/dist/components/VvAlertGroup/VvAlertGroup.es.js +8 -7
  7. package/dist/components/VvAlertGroup/VvAlertGroup.vue.d.ts +6 -6
  8. package/dist/components/VvAlertGroup/index.d.ts +2 -2
  9. package/dist/components/VvAvatar/VvAvatar.es.js +8 -7
  10. package/dist/components/VvAvatarGroup/VvAvatarGroup.es.js +8 -7
  11. package/dist/components/VvBadge/VvBadge.es.js +8 -7
  12. package/dist/components/VvBreadcrumb/VvBreadcrumb.es.js +8 -7
  13. package/dist/components/VvButton/VvButton.es.js +11 -10
  14. package/dist/components/VvButtonGroup/VvButtonGroup.es.js +8 -7
  15. package/dist/components/VvCard/VvCard.es.js +8 -7
  16. package/dist/components/VvCheckbox/VvCheckbox.es.js +11 -10
  17. package/dist/components/VvCheckboxGroup/VvCheckboxGroup.es.js +13 -12
  18. package/dist/components/VvCheckboxGroup/VvCheckboxGroup.umd.js +1 -1
  19. package/dist/components/VvCombobox/VvCombobox.es.js +158 -140
  20. package/dist/components/VvCombobox/VvCombobox.umd.js +1 -1
  21. package/dist/components/VvCombobox/VvCombobox.vue.d.ts +306 -90
  22. package/dist/components/VvCombobox/index.d.ts +108 -31
  23. package/dist/components/VvDialog/VvDialog.es.js +16 -11
  24. package/dist/components/VvDialog/VvDialog.umd.js +1 -1
  25. package/dist/components/VvDropdown/VvDropdown.es.js +19 -16
  26. package/dist/components/VvDropdown/VvDropdown.umd.js +1 -1
  27. package/dist/components/VvDropdown/VvDropdown.vue.d.ts +299 -91
  28. package/dist/components/VvDropdown/index.d.ts +99 -30
  29. package/dist/components/VvDropdownAction/VvDropdownAction.es.js +8 -7
  30. package/dist/components/VvDropdownOptgroup/VvDropdownOptgroup.es.js +8 -7
  31. package/dist/components/VvDropdownOption/VvDropdownOption.es.js +8 -7
  32. package/dist/components/VvInputText/VvInputText.es.js +193 -103
  33. package/dist/components/VvInputText/VvInputText.umd.js +1 -1
  34. package/dist/components/VvInputText/VvInputText.vue.d.ts +19 -37
  35. package/dist/components/VvInputText/index.d.ts +15 -33
  36. package/dist/components/VvNav/VvNav.es.js +8 -7
  37. package/dist/components/VvProgress/VvProgress.es.js +8 -7
  38. package/dist/components/VvRadio/VvRadio.es.js +11 -10
  39. package/dist/components/VvRadioGroup/VvRadioGroup.es.js +13 -12
  40. package/dist/components/VvRadioGroup/VvRadioGroup.umd.js +1 -1
  41. package/dist/components/VvSelect/VvSelect.es.js +14 -13
  42. package/dist/components/VvSelect/VvSelect.umd.js +1 -1
  43. package/dist/components/VvSelect/VvSelect.vue.d.ts +1 -1
  44. package/dist/components/VvTab/VvTab.es.js +8 -7
  45. package/dist/components/VvTextarea/VvTextarea.es.js +16 -12
  46. package/dist/components/VvTextarea/VvTextarea.umd.js +1 -1
  47. package/dist/components/VvTooltip/VvTooltip.es.js +8 -7
  48. package/dist/components/index.es.js +359 -245
  49. package/dist/components/index.umd.js +1 -1
  50. package/dist/composables/useOptions.d.ts +1 -1
  51. package/dist/constants.d.ts +4 -0
  52. package/dist/directives/index.es.js +8 -7
  53. package/dist/directives/v-tooltip.es.js +8 -7
  54. package/dist/icons.es.js +3 -3
  55. package/dist/icons.umd.js +1 -1
  56. package/dist/props/index.d.ts +100 -31
  57. package/dist/stories/AccordionGroup/AccordionGroup.stories.d.ts +2 -2
  58. package/dist/stories/AccordionGroup/AccordionGroupSlots.stories.d.ts +18 -18
  59. package/dist/stories/Combobox/Combobox.settings.d.ts +8 -0
  60. package/dist/stories/Combobox/ComboboxOptions.stories.d.ts +1 -0
  61. package/dist/stories/InputText/InputText.settings.d.ts +31 -9
  62. package/dist/stories/InputText/InputText.stories.d.ts +0 -1
  63. package/dist/stories/InputText/InputTextMask.stories.d.ts +12 -0
  64. package/package.json +61 -60
  65. package/src/assets/icons/detailed.json +1 -1
  66. package/src/assets/icons/normal.json +1 -1
  67. package/src/assets/icons/simple.json +1 -1
  68. package/src/components/VvCombobox/VvCombobox.vue +51 -42
  69. package/src/components/VvCombobox/index.ts +13 -0
  70. package/src/components/VvDialog/VvDialog.vue +6 -2
  71. package/src/components/VvDropdown/VvDropdown.vue +18 -16
  72. package/src/components/VvInputText/VvInputText.vue +170 -55
  73. package/src/components/VvInputText/index.ts +32 -34
  74. package/src/components/VvSelect/VvSelect.vue +4 -4
  75. package/src/components/VvTextarea/VvTextarea.vue +8 -5
  76. package/src/composables/useOptions.ts +2 -2
  77. package/src/constants.ts +5 -0
  78. package/src/props/index.ts +7 -11
  79. package/src/stories/Combobox/Combobox.settings.ts +8 -0
  80. package/src/stories/Combobox/Combobox.test.ts +1 -1
  81. package/src/stories/Combobox/ComboboxOptions.stories.ts +18 -0
  82. package/src/stories/InputText/InputText.settings.ts +36 -15
  83. package/src/stories/InputText/InputText.stories.ts +4 -12
  84. package/src/stories/InputText/InputText.test.ts +31 -15
  85. package/src/stories/InputText/InputTextMask.stories.ts +122 -0
  86. 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 (searchable.value) {
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 (searchable.value) {
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: loading.value,
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 = computed(() => {
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 getOptionSelected(option: string | Option) {
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
- let selectedValues: Array<typeof props.modelValue> = []
219
- if (Array.isArray(props.modelValue)) {
220
- selectedValues = props.modelValue as Array<typeof props.modelValue>
221
- } else if (props.modelValue) {
222
- selectedValues = [props.modelValue]
223
- }
224
- const options = props.options.reduce((acc, value) => {
225
- if (isGroup(value)) {
226
- return [...acc, ...getOptionGrouped(value)]
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
- if (isGroup(option)) {
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: loading.value,
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: searchable.value
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="searchable || $slots['dropdown::before']"
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.length">
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
- disabled: getOptionDisabled(item),
501
- selected: getOptionSelected(item),
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: getOptionSelected(item),
521
- disabled: getOptionDisabled(item),
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
- disabled: getOptionDisabled(option),
532
- selected: getOptionSelected(option),
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: getOptionSelected(option),
551
- disabled: getOptionDisabled(option),
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?.showModal()
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?.close()
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, strategy, middlewareData, placement } = useFloating(
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 === 'fixed',
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
- | 'top'
196
- | 'right'
197
- | 'bottom'
198
- | 'left',
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: 'bottom',
206
- right: 'left',
207
- bottom: 'top',
208
- left: 'right',
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 { Mask } from 'maska'
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
- // debounce
58
- const localModelValue = useDebouncedInput(
59
- modelValue,
60
- emit,
61
- props.debounce,
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
- getter: (value) => {
64
- if (mask.value) {
65
- return mask.value.masked(value ?? '')
67
+ emit,
68
+ onAccept: () => {
69
+ if (!maskReady.value) {
70
+ return
66
71
  }
67
- return value
68
- },
69
- setter: (value) => {
70
- if (mask.value) {
71
- value = mask.value.unmasked(value)
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 (props.type === INPUT_TYPES.NUMBER) {
74
- return Number(value)
143
+ if (!localModelValue.value && !unmasked.value) {
144
+ return
75
145
  }
76
- return value
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 = undefined
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: props.count,
157
- upperLimit: Number(props.maxlength),
158
- lowerLimit: Number(props.minlength),
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"