1
+ <script lang="ts" setup>
2
+ import { Icon } from ' @/common'
3
+
4
+ import { BN } from ' @/utils/math.util'
5
+ import { computed , getCurrentInstance , ref , useAttrs , useSlots } from ' vue'
6
+
7
+ type INPUT_TYPES = ' text' | ' number' | ' password'
8
+
9
+ const props = withDefaults (
10
+ defineProps <{
11
+ modelValue: string | number
12
+ label? : string
13
+ placeholder? : string
14
+ type? : INPUT_TYPES
15
+ errorMessage? : string
16
+ }>(),
17
+ {
18
+ type: ' text' ,
19
+ label: ' ' ,
20
+ placeholder: ' ' ,
21
+ errorMessage: ' ' ,
22
+ },
23
+ )
24
+
25
+ const emit = defineEmits <{
26
+ (e : ' update:modelValue' , value : number | string ): void
27
+ }>()
28
+
29
+ const attrs = useAttrs ()
30
+
31
+ const slots = useSlots ()
32
+
33
+ const uid = getCurrentInstance ()?.uid
34
+
35
+ const isPasswordShown = ref (false )
36
+
37
+ const isNumberType = computed (() => props .type === ' number' )
38
+ const isPasswordType = computed (() => props .type === ' password' )
39
+
40
+ const min = computed ((): string => (attrs ?.min as string ) || ' ' )
41
+ const max = computed ((): string => (attrs ?.max as string ) || ' ' )
42
+
43
+ const isDisabled = computed (() =>
44
+ [' ' , ' disabled' , true ].includes (attrs .disabled as string | boolean ),
45
+ )
46
+
47
+ const isReadonly = computed (() =>
48
+ [' ' , ' readonly' , true ].includes (attrs .readonly as string | boolean ),
49
+ )
50
+
51
+ const listeners = computed (() => ({
52
+ input : (event : Event ) => {
53
+ const eventTarget = event .target as HTMLInputElement
54
+ if (isNumberType .value ) {
55
+ eventTarget .value = normalizeRange (eventTarget .value )
56
+ }
57
+ if (props .modelValue === eventTarget .value ) return
58
+
59
+ emit (' update:modelValue' , eventTarget .value )
60
+ },
61
+ }))
62
+
63
+ const inputClasses = computed (() =>
64
+ [
65
+ ... (slots .nodeLeft ? [' input-field--node-left' ] : []),
66
+ ... (slots .nodeRight || isPasswordType .value
67
+ ? [' input-field--node-right' ]
68
+ : []),
69
+ ... (isDisabled .value ? [' input-field--disabled' ] : []),
70
+ ... (isReadonly .value ? [' input-field--readonly' ] : []),
71
+ ... (props .errorMessage ? [' input-field--error' ] : []),
72
+ ].join (' ' ),
73
+ )
74
+
75
+ const normalizeRange = (value : string | number ): string => {
76
+ let result = value
77
+
78
+ if (min .value && new BN (value ).compare (min .value ) < 0 ) {
79
+ result = min .value
80
+ } else if (max .value && new BN (value ).compare (max .value ) > 0 ) {
81
+ result = max .value
82
+ }
83
+
84
+ return result as string
85
+ }
86
+
87
+ const setHeightCSSVar = (element : HTMLElement ) => {
88
+ element .style .setProperty (
89
+ ' --field-error-msg-height' ,
90
+ ` ${element .scrollHeight }px ` ,
91
+ )
92
+ }
93
+ </script >
94
+
1
95
<template >
2
96
<div class =" input-field" :class =" inputClasses" >
3
97
<label v-if =" label" :for =" `input-field--${uid}`" class =" input-field__label" >
4
98
{{ label }}
5
99
</label >
6
100
<div class =" input-field__input-wrp" >
101
+ <div v-if =" $slots.nodeLeft" class =" input-field__node-left-wrp" >
102
+ <slot name =" nodeLeft" />
103
+ </div >
7
104
<input
8
105
class =" input-field__input"
9
106
:id =" `input-field--${uid}`"
17
114
:max =" max"
18
115
:disabled =" isDisabled || isReadonly"
19
116
/>
20
- <div v-if =" isPasswordType || iconName" class =" input-field__icon-wrp" >
117
+ <div
118
+ v-if =" $slots.nodeRight || isPasswordType"
119
+ class =" input-field__node-right-wrp"
120
+ >
21
121
<button
22
- type =" button"
23
122
v-if =" isPasswordType"
123
+ type =" button"
24
124
@click =" isPasswordShown = !isPasswordShown"
25
125
>
26
126
<icon
27
- class =" input-field__icon "
127
+ class =" input-field__password-icon "
28
128
:name =" isPasswordShown ? $icons.eye : $icons.eyeOff"
29
129
/>
30
130
</button >
31
- <icon v-else class = " input-field__icon " : name =" iconName " />
131
+ <slot v-else name =" nodeRight " />
32
132
</div >
33
133
</div >
34
134
<transition
43
143
</div >
44
144
</template >
45
145
46
- <script lang="ts">
47
- import { Icon } from ' @/common'
48
-
49
- import { BN } from ' @/utils/math.util'
50
- import {
51
- computed ,
52
- defineComponent ,
53
- getCurrentInstance ,
54
- PropType ,
55
- ref ,
56
- } from ' vue'
57
- import { ICON_NAMES } from ' @/enums'
58
-
59
- enum INPUT_TYPES {
60
- text = ' text' ,
61
- password = ' password' ,
62
- number = ' number' ,
63
- }
64
-
65
- enum EVENTS {
66
- updateModelValue = ' update:model-value' ,
67
- }
68
-
69
- enum SCHEMES {
70
- iconLeft = ' icon-left' ,
71
- }
72
-
73
- export default defineComponent ({
74
- name: ' input-field' ,
75
- components: { Icon },
76
- props: {
77
- modelValue: { type: [String , Number ], default: ' ' },
78
- label: { type: String , default: ' ' },
79
- placeholder: { type: String , default: ' ' },
80
- type: {
81
- type: String as PropType <INPUT_TYPES >,
82
- default: INPUT_TYPES .text ,
83
- },
84
- schemes: { type: String as PropType <SCHEMES >, default: ' ' },
85
- errorMessage: { type: String , default: ' ' },
86
- iconName: { type: String as PropType <ICON_NAMES >, default: ' ' },
87
- },
88
- emits: Object .values (EVENTS ),
89
- setup(props , { emit , attrs }) {
90
- const uid = getCurrentInstance ()?.uid
91
- const isPasswordShown = ref (false )
92
-
93
- const isNumberType = computed (() => props .type === INPUT_TYPES .number )
94
- const isPasswordType = computed (() => props .type === INPUT_TYPES .password )
95
-
96
- const min = computed ((): string => (attrs ?.min as string ) || ' ' )
97
- const max = computed ((): string => (attrs ?.max as string ) || ' ' )
98
-
99
- const isDisabled = computed (() =>
100
- [' ' , ' disabled' , true ].includes (attrs .disabled as string | boolean ),
101
- )
102
-
103
- const isReadonly = computed (() =>
104
- [' ' , ' readonly' , true ].includes (attrs .readonly as string | boolean ),
105
- )
106
-
107
- const listeners = computed (() => ({
108
- input : (event : Event ) => {
109
- const eventTarget = event .target as HTMLInputElement
110
- if (isNumberType .value ) {
111
- eventTarget .value = normalizeRange (eventTarget .value )
112
- }
113
- if (props .modelValue === eventTarget .value ) return
114
-
115
- emit (EVENTS .updateModelValue , eventTarget .value )
116
- },
117
- }))
118
-
119
- const inputClasses = computed (() => {
120
- const _schemes = props .schemes
121
- const classList = [
122
- ... (_schemes ? [_schemes .split (' ' )] : []),
123
- ... (isDisabled .value ? [' disabled' ] : []),
124
- ... (isReadonly .value ? [' readonly' ] : []),
125
- ... (props .errorMessage ? [' error' ] : []),
126
- ... (props .iconName || isPasswordType ? [' iconed' ] : []),
127
- ]
128
-
129
- return classList .map (el => ` input-field--${el } ` ).join (' ' )
130
- })
131
-
132
- const normalizeRange = (value : string | number ): string => {
133
- let result = value
134
-
135
- if (min .value && new BN (value ).compare (min .value ) < 0 ) {
136
- result = min .value
137
- } else if (max .value && new BN (value ).compare (max .value ) > 0 ) {
138
- result = max .value
139
- }
140
-
141
- return result as string
142
- }
143
-
144
- const setHeightCSSVar = (element : HTMLElement ) => {
145
- element .style .setProperty (
146
- ' --field-error-msg-height' ,
147
- ` ${element .scrollHeight }px ` ,
148
- )
149
- }
150
-
151
- return {
152
- uid ,
153
- isPasswordShown ,
154
-
155
- listeners ,
156
- isDisabled ,
157
- isReadonly ,
158
- min ,
159
- max ,
160
- inputClasses ,
161
- isPasswordType ,
162
-
163
- setHeightCSSVar ,
164
- }
165
- },
166
- })
167
- </script >
168
-
169
146
<style lang="scss" scoped>
170
147
.input-field {
171
148
display : flex ;
@@ -246,13 +223,12 @@ export default defineComponent({
246
223
border-color : var (--field-error );
247
224
}
248
225
249
- .input-field--iconed & {
250
- padding-right : calc (var (--field-padding-right ) * 3 );
226
+ .input-field--node-left & {
227
+ padding-left : calc (var (--field-padding-left ) * 3 );
251
228
}
252
229
253
- .input-field--icon-left & {
254
- padding-right : var (--field-padding-right );
255
- padding-left : calc (var (--field-padding-right ) * 3 );
230
+ .input-field--node-right & {
231
+ padding-right : calc (var (--field-padding-right ) * 3 );
256
232
}
257
233
258
234
& :not ([disabled ]):focus {
@@ -266,21 +242,27 @@ export default defineComponent({
266
242
}
267
243
}
268
244
269
- .input-field__icon-wrp {
270
- display : flex ;
271
- justify-content : center ;
272
- align-items : center ;
245
+ .input-field__node-left-wrp {
246
+ overflow : hidden ;
273
247
position : absolute ;
274
248
top : 50% ;
275
- right : calc (var (--field-padding-right ) * 3 / 2 );
276
- transform : translate (50% , -50% );
277
-
278
- .input-field--icon-left & {
279
- right : 0 ;
280
- left : calc (var (--field-padding-right ) * 3 / 2 );
281
- transform : translate (-50% , -50% );
282
- width : max-content ;
283
- }
249
+ left : var (--field-padding-left );
250
+ transform : translateY (-50% );
251
+ color : inherit ;
252
+ max-height : 100% ;
253
+ }
254
+
255
+ .input-field__node-right-wrp {
256
+ position : absolute ;
257
+ top : 50% ;
258
+ right : var (--field-padding-right );
259
+ transform : translateY (-50% );
260
+ color : inherit ;
261
+ }
262
+
263
+ .input-field__password-icon {
264
+ max-width : toRem (24 );
265
+ max-height : toRem (24 );
284
266
}
285
267
286
268
.input-field__icon {
0 commit comments