@@ -4,27 +4,53 @@ type Complete<T> = {
4
4
[ P in keyof Required < T > ] : Pick < T , P > extends Required < Pick < T , P > > ? T [ P ] : T [ P ] | undefined
5
5
}
6
6
7
- interface InputWithMirror extends HTMLInputElement {
7
+ interface AutowidthInput extends HTMLInputElement {
8
8
mirror : HTMLElement
9
+ options : Complete < InputAutoWidthOptions >
10
+ windowResizeHandler ?: ( ) => void
11
+ sizerFunc ?: ( e ?: Event ) => ( ) => void
9
12
}
10
13
11
14
export interface InputAutoWidthOptions {
12
15
maxWidth ?: string
13
16
minWidth ?: string
14
17
comfortZone ?: string
18
+ watchWindowSize ?: boolean
19
+ windowResizeHandlerDebounceTime ?: number
20
+ disableNonInputWarning ?: boolean
15
21
}
16
22
17
23
const defaults : Complete < InputAutoWidthOptions > = {
18
- maxWidth : 'none' ,
19
- minWidth : 'none' ,
24
+ maxWidth : undefined ,
25
+ minWidth : undefined ,
20
26
comfortZone : '0px' ,
27
+ watchWindowSize : true ,
28
+ windowResizeHandlerDebounceTime : 150 ,
29
+ disableNonInputWarning : false ,
21
30
} as const
22
31
23
- const checkWidth = ( el : InputWithMirror , options : Complete < InputAutoWidthOptions > ) => {
24
- const mirror : HTMLElement = el . mirror
32
+ export const debounce = < T extends ( ...args : any [ ] ) => any > ( callback : T , waitFor : number ) => {
33
+ let timeout : ReturnType < typeof setTimeout >
34
+ return ( ...args : Parameters < T > ) : ReturnType < T > => {
35
+ let result : any
36
+ timeout && clearTimeout ( timeout )
37
+ timeout = setTimeout ( ( ) => {
38
+ result = callback ( ...args )
39
+ } , waitFor )
40
+ return result
41
+ }
42
+ }
43
+
44
+ const checkWidth = ( el : AutowidthInput ) => {
45
+ const { mirror, options } = el
25
46
26
- el . style . maxWidth = options . maxWidth || 'none'
27
- el . style . minWidth = options . minWidth || 'none'
47
+ if ( options . maxWidth ) {
48
+ el . style . maxWidth = options . maxWidth
49
+ }
50
+
51
+ if ( options . minWidth ) {
52
+ el . style . minWidth = options . minWidth
53
+ }
28
54
29
55
let val = el . value
30
56
@@ -45,54 +71,81 @@ const checkWidth = (el: InputWithMirror, options: Complete<InputAutoWidthOptions
45
71
}
46
72
}
47
73
48
- const mergeDefaultWithOptions = ( options : InputAutoWidthOptions ) => Object . assign ( { } , defaults , options )
74
+ const copyStylesToMirror = ( el : AutowidthInput ) => {
75
+ const styles = window . getComputedStyle ( el )
76
+ const { options } = el
77
+
78
+ Object . assign ( el . mirror . style , {
79
+ position : 'absolute' ,
80
+ top : '0' ,
81
+ left : '0' ,
82
+ visibility : 'hidden' ,
83
+ height : '0' ,
84
+ overflow : 'hidden' ,
85
+ whiteSpace : 'pre' ,
86
+ fontSize : styles . fontSize ,
87
+ fontFamily : styles . fontFamily ,
88
+ fontWeight : styles . fontWeight ,
89
+ fontStyle : styles . fontStyle ,
90
+ letterSpacing : styles . letterSpacing ,
91
+ textTransform : styles . textTransform ,
92
+ paddingRight : `calc(${ options . comfortZone } + ${ styles . paddingRight } + ${ styles . borderRightWidth } )` ,
93
+ paddingLeft : `calc(${ styles . paddingLeft } + ${ styles . borderLeftWidth } )` ,
94
+ } )
95
+ }
96
+
97
+ const copyStylesAndCheckWidth = ( el : AutowidthInput ) => {
98
+ copyStylesToMirror ( el )
99
+ checkWidth ( el )
100
+ }
101
+
102
+ const mergeDefaultsWithOptions = ( options : InputAutoWidthOptions ) : Complete < InputAutoWidthOptions > =>
103
+ Object . assign ( { } , defaults , options )
49
104
50
105
export default {
51
- beforeMount : function ( el : HTMLElement ) {
52
- if ( el . tagName . toLocaleUpperCase ( ) !== 'INPUT' ) {
106
+ beforeMount : function ( el : AutowidthInput , binding : DirectiveBinding ) {
107
+ el . options = mergeDefaultsWithOptions ( binding . value as InputAutoWidthOptions )
108
+
109
+ if ( ! el . options . disableNonInputWarning && el . tagName . toLocaleUpperCase ( ) !== 'INPUT' ) {
53
110
throw new Error ( 'v-input-autowidth can only be used on input elements.' )
54
111
}
55
112
} ,
56
- mounted : function ( el : InputWithMirror , binding : DirectiveBinding , vnode : VNode ) {
57
- const options : Complete < InputAutoWidthOptions > = mergeDefaultWithOptions ( binding . value )
113
+ mounted : function ( el : AutowidthInput , binding : DirectiveBinding , vnode : VNode ) {
58
114
const hasVModel = Object . prototype . hasOwnProperty . call ( vnode . props , '@onUpdate:modelValue' )
59
- const styles = window . getComputedStyle ( el )
60
115
61
- el . mirror = document . createElement ( 'div' )
62
-
63
- Object . assign ( el . mirror . style , {
64
- position : 'absolute' ,
65
- top : '0' ,
66
- left : '0' ,
67
- visibility : 'hidden' ,
68
- height : '0' ,
69
- overflow : 'hidden' ,
70
- whiteSpace : 'pre' ,
71
- fontSize : styles . fontSize ,
72
- fontFamily : styles . fontFamily ,
73
- fontWeight : styles . fontWeight ,
74
- fontStyle : styles . fontStyle ,
75
- letterSpacing : styles . letterSpacing ,
76
- textTransform : styles . textTransform ,
77
- paddingRight : `calc(${ options . comfortZone } + ${ styles . paddingRight } + ${ styles . borderRightWidth } )` ,
78
- paddingLeft : `calc(${ styles . paddingLeft } + ${ styles . borderLeftWidth } )` ,
79
- } )
116
+ el . sizerFunc = ( _e ?: Event ) => checkWidth . bind ( null , el )
80
117
118
+ el . mirror = document . createElement ( 'div' )
119
+ copyStylesToMirror ( el )
81
120
el . mirror . setAttribute ( 'aria-hidden' , 'true' )
82
-
83
121
document . body . appendChild ( el . mirror )
84
122
85
- checkWidth ( el , options )
123
+ copyStylesAndCheckWidth ( el )
86
124
87
125
if ( ! hasVModel ) {
88
- el . addEventListener ( 'input' , checkWidth . bind ( null , el , options ) )
126
+ el . addEventListener ( 'input' , el . sizerFunc )
127
+ }
128
+
129
+ if ( el . options . watchWindowSize && el . options . windowResizeHandlerDebounceTime !== undefined ) {
130
+ const windowResizeHandler = ( _e ?: Event ) => copyStylesAndCheckWidth ( el )
131
+ el . windowResizeHandler = debounce ( windowResizeHandler , el . options . windowResizeHandlerDebounceTime )
132
+ window . addEventListener ( 'resize' , el . windowResizeHandler , { passive : true } )
89
133
}
90
134
} ,
91
- updated : function ( el : InputWithMirror , binding : DirectiveBinding ) {
92
- checkWidth ( el , mergeDefaultWithOptions ( binding . value ) )
135
+ updated : function ( el : AutowidthInput ) {
136
+ if ( el . sizerFunc ) {
137
+ el . sizerFunc ( ) ( )
138
+ }
93
139
} ,
94
- unmounted : function ( el : InputWithMirror , binding : DirectiveBinding ) {
140
+ unmounted : function ( el : AutowidthInput ) {
95
141
document . body . removeChild ( el . mirror )
96
- el . removeEventListener ( 'input' , checkWidth . bind ( null , el , mergeDefaultWithOptions ( binding . value ) ) )
142
+
143
+ if ( el . sizerFunc ) {
144
+ el . removeEventListener ( 'input' , el . sizerFunc )
145
+ }
146
+
147
+ if ( el . options . watchWindowSize && el . windowResizeHandler ) {
148
+ window . removeEventListener ( 'resize' , el . windowResizeHandler )
149
+ }
97
150
} ,
98
151
} as ObjectDirective
0 commit comments