Commit 8df3390e4160f5d4d1e67e4aeaf14135252e8619
1 parent
1902505a
update calcTextareaHeight
Showing
2 changed files
with
265 additions
and
103 deletions
Show diff stats
examples/routers/input.vue
@@ -80,64 +80,67 @@ | @@ -80,64 +80,67 @@ | ||
80 | <!--<br>--> | 80 | <!--<br>--> |
81 | <!--</div>--> | 81 | <!--</div>--> |
82 | 82 | ||
83 | - <div> | ||
84 | - <Input | ||
85 | - v-model="value" | ||
86 | - size="small" | ||
87 | - prefix="ios-contact" | ||
88 | - suffix="ios-search" | ||
89 | - placeholder="Enter something..." | ||
90 | - style="width: 300px"></Input> | ||
91 | - <br> | ||
92 | - <Input | ||
93 | - v-model="value" | ||
94 | - prefix="ios-contact" | ||
95 | - suffix="ios-search" | ||
96 | - placeholder="Enter something..." | ||
97 | - style="width: 300px"></Input> | ||
98 | - <br> | ||
99 | - <Input | ||
100 | - v-model="value" | ||
101 | - size="large" | ||
102 | - prefix="ios-contact" | ||
103 | - suffix="ios-search" | ||
104 | - placeholder="Enter something..." | ||
105 | - style="width: 300px"></Input> | ||
106 | - <br><br> | ||
107 | - <Input | ||
108 | - v-model="value" | ||
109 | - size="small" | ||
110 | - icon="ios-search" | ||
111 | - placeholder="Enter something..." | ||
112 | - style="width: 300px"></Input> | ||
113 | - <br> | ||
114 | - <Input | ||
115 | - v-model="value" | ||
116 | - icon="ios-search" | ||
117 | - placeholder="Enter something..." | ||
118 | - style="width: 300px"></Input> | ||
119 | - <br> | ||
120 | - <Input | ||
121 | - v-model="value" | ||
122 | - size="large" | ||
123 | - icon="ios-search" | ||
124 | - placeholder="Enter something..." | ||
125 | - style="width: 300px"></Input> | ||
126 | - <br><br><br> | ||
127 | - <Input v-model="value" placeholder="Enter something..." style="width: 300px"> | ||
128 | - <Icon type="ios-alarm-outline" slot="suffix" /> | ||
129 | - <Icon type="ios-aperture" slot="prefix" /> | ||
130 | - </Input> | ||
131 | - <br><br><br><br> | ||
132 | - <Input v-model="value" search enter-button style="width: 300px" @on-search="hs" size="small" /> | ||
133 | - <br> | ||
134 | - <Input v-model="value" search enter-button style="width: 300px" @on-search="hs" /> | ||
135 | - <br> | ||
136 | - <Input v-model="value" search enter-button style="width: 300px" @on-search="hs" size="large" /> | ||
137 | - <br><br> | ||
138 | - <Input v-model="value" search style="width: 300px" @on-search="hs" /> | ||
139 | - <br><br> | ||
140 | - <Input v-model="value" search enter-button="Search" style="width: 300px" @on-search="hs" /> | 83 | + <!--<div>--> |
84 | + <!--<Input--> | ||
85 | + <!--v-model="value"--> | ||
86 | + <!--size="small"--> | ||
87 | + <!--prefix="ios-contact"--> | ||
88 | + <!--suffix="ios-search"--> | ||
89 | + <!--placeholder="Enter something..."--> | ||
90 | + <!--style="width: 300px"></Input>--> | ||
91 | + <!--<br>--> | ||
92 | + <!--<Input--> | ||
93 | + <!--v-model="value"--> | ||
94 | + <!--prefix="ios-contact"--> | ||
95 | + <!--suffix="ios-search"--> | ||
96 | + <!--placeholder="Enter something..."--> | ||
97 | + <!--style="width: 300px"></Input>--> | ||
98 | + <!--<br>--> | ||
99 | + <!--<Input--> | ||
100 | + <!--v-model="value"--> | ||
101 | + <!--size="large"--> | ||
102 | + <!--prefix="ios-contact"--> | ||
103 | + <!--suffix="ios-search"--> | ||
104 | + <!--placeholder="Enter something..."--> | ||
105 | + <!--style="width: 300px"></Input>--> | ||
106 | + <!--<br><br>--> | ||
107 | + <!--<Input--> | ||
108 | + <!--v-model="value"--> | ||
109 | + <!--size="small"--> | ||
110 | + <!--icon="ios-search"--> | ||
111 | + <!--placeholder="Enter something..."--> | ||
112 | + <!--style="width: 300px"></Input>--> | ||
113 | + <!--<br>--> | ||
114 | + <!--<Input--> | ||
115 | + <!--v-model="value"--> | ||
116 | + <!--icon="ios-search"--> | ||
117 | + <!--placeholder="Enter something..."--> | ||
118 | + <!--style="width: 300px"></Input>--> | ||
119 | + <!--<br>--> | ||
120 | + <!--<Input--> | ||
121 | + <!--v-model="value"--> | ||
122 | + <!--size="large"--> | ||
123 | + <!--icon="ios-search"--> | ||
124 | + <!--placeholder="Enter something..."--> | ||
125 | + <!--style="width: 300px"></Input>--> | ||
126 | + <!--<br><br><br>--> | ||
127 | + <!--<Input v-model="value" placeholder="Enter something..." style="width: 300px">--> | ||
128 | + <!--<Icon type="ios-alarm-outline" slot="suffix" />--> | ||
129 | + <!--<Icon type="ios-aperture" slot="prefix" />--> | ||
130 | + <!--</Input>--> | ||
131 | + <!--<br><br><br><br>--> | ||
132 | + <!--<Input v-model="value" search enter-button style="width: 300px" @on-search="hs" size="small" />--> | ||
133 | + <!--<br>--> | ||
134 | + <!--<Input v-model="value" search enter-button style="width: 300px" @on-search="hs" />--> | ||
135 | + <!--<br>--> | ||
136 | + <!--<Input v-model="value" search enter-button style="width: 300px" @on-search="hs" size="large" />--> | ||
137 | + <!--<br><br>--> | ||
138 | + <!--<Input v-model="value" search style="width: 300px" @on-search="hs" />--> | ||
139 | + <!--<br><br>--> | ||
140 | + <!--<Input v-model="value" search enter-button="Search" style="width: 300px" @on-search="hs" />--> | ||
141 | + <!--</div>--> | ||
142 | + <div style="width: 200px"> | ||
143 | + <Input v-model="value7" type="textarea" :autosize="true" placeholder="Enter something..."></Input> | ||
141 | </div> | 144 | </div> |
142 | </template> | 145 | </template> |
143 | <script> | 146 | <script> |
@@ -150,7 +153,8 @@ | @@ -150,7 +153,8 @@ | ||
150 | value13: '', | 153 | value13: '', |
151 | select1: 'http', | 154 | select1: 'http', |
152 | select2: 'com', | 155 | select2: 'com', |
153 | - select3: 'day' | 156 | + select3: 'day', |
157 | + value7: `` | ||
154 | } | 158 | } |
155 | }, | 159 | }, |
156 | methods: { | 160 | methods: { |
src/utils/calcTextareaHeight.js
1 | // Thanks to | 1 | // Thanks to |
2 | // https://github.com/andreypopp/react-textarea-autosize/ | 2 | // https://github.com/andreypopp/react-textarea-autosize/ |
3 | -// https://github.com/ElemeFE/element/blob/master/packages/input/src/calcTextareaHeight.js | ||
4 | 3 | ||
5 | -let hiddenTextarea; | 4 | +// let hiddenTextarea; |
5 | +// | ||
6 | +// const HIDDEN_STYLE = ` | ||
7 | +// height:0 !important; | ||
8 | +// min-height:0 !important; | ||
9 | +// max-height:none !important; | ||
10 | +// visibility:hidden !important; | ||
11 | +// overflow:hidden !important; | ||
12 | +// position:absolute !important; | ||
13 | +// z-index:-1000 !important; | ||
14 | +// top:0 !important; | ||
15 | +// right:0 !important | ||
16 | +// `; | ||
17 | +// | ||
18 | +// const CONTEXT_STYLE = [ | ||
19 | +// 'letter-spacing', | ||
20 | +// 'line-height', | ||
21 | +// 'padding-top', | ||
22 | +// 'padding-bottom', | ||
23 | +// 'font-family', | ||
24 | +// 'font-weight', | ||
25 | +// 'font-size', | ||
26 | +// 'text-rendering', | ||
27 | +// 'text-transform', | ||
28 | +// 'width', | ||
29 | +// 'text-indent', | ||
30 | +// 'padding-left', | ||
31 | +// 'padding-right', | ||
32 | +// 'border-width', | ||
33 | +// 'box-sizing' | ||
34 | +// ]; | ||
35 | +// | ||
36 | +// function calculateNodeStyling(node) { | ||
37 | +// const style = window.getComputedStyle(node); | ||
38 | +// | ||
39 | +// const boxSizing = style.getPropertyValue('box-sizing'); | ||
40 | +// | ||
41 | +// const paddingSize = ( | ||
42 | +// parseFloat(style.getPropertyValue('padding-bottom')) + | ||
43 | +// parseFloat(style.getPropertyValue('padding-top')) | ||
44 | +// ); | ||
45 | +// | ||
46 | +// const borderSize = ( | ||
47 | +// parseFloat(style.getPropertyValue('border-bottom-width')) + | ||
48 | +// parseFloat(style.getPropertyValue('border-top-width')) | ||
49 | +// ); | ||
50 | +// | ||
51 | +// const contextStyle = CONTEXT_STYLE | ||
52 | +// .map(name => `${name}:${style.getPropertyValue(name)}`) | ||
53 | +// .join(';'); | ||
54 | +// | ||
55 | +// return {contextStyle, paddingSize, borderSize, boxSizing}; | ||
56 | +// } | ||
57 | +// | ||
58 | +// export default function calcTextareaHeight(targetNode, minRows = null, maxRows = null) { | ||
59 | +// if (!hiddenTextarea) { | ||
60 | +// hiddenTextarea = document.createElement('textarea'); | ||
61 | +// document.body.appendChild(hiddenTextarea); | ||
62 | +// } | ||
63 | +// | ||
64 | +// let { | ||
65 | +// paddingSize, | ||
66 | +// borderSize, | ||
67 | +// boxSizing, | ||
68 | +// contextStyle | ||
69 | +// } = calculateNodeStyling(targetNode); | ||
70 | +// | ||
71 | +// hiddenTextarea.setAttribute('style', `${contextStyle};${HIDDEN_STYLE}`); | ||
72 | +// hiddenTextarea.value = targetNode.value || targetNode.placeholder || ''; | ||
73 | +// | ||
74 | +// let height = hiddenTextarea.scrollHeight; | ||
75 | +// let minHeight = -Infinity; | ||
76 | +// let maxHeight = Infinity; | ||
77 | +// let overflowY; | ||
78 | +// | ||
79 | +// if (boxSizing === 'border-box') { | ||
80 | +// height = height + borderSize; | ||
81 | +// } else if (boxSizing === 'content-box') { | ||
82 | +// height = height - paddingSize; | ||
83 | +// } | ||
84 | +// | ||
85 | +// hiddenTextarea.value = ''; | ||
86 | +// let singleRowHeight = hiddenTextarea.scrollHeight - paddingSize; | ||
87 | +// | ||
88 | +// if (minRows !== null) { | ||
89 | +// minHeight = singleRowHeight * minRows; | ||
90 | +// if (boxSizing === 'border-box') { | ||
91 | +// minHeight = minHeight + paddingSize + borderSize; | ||
92 | +// } | ||
93 | +// height = Math.max(minHeight, height); | ||
94 | +// } | ||
95 | +// if (maxRows !== null) { | ||
96 | +// maxHeight = singleRowHeight * maxRows; | ||
97 | +// if (boxSizing === 'border-box') { | ||
98 | +// maxHeight = maxHeight + paddingSize + borderSize; | ||
99 | +// } | ||
100 | +// overflowY = height > maxHeight ? '' : 'hidden'; | ||
101 | +// height = Math.min(maxHeight, height); | ||
102 | +// } | ||
103 | +// | ||
104 | +// if (!maxRows) { | ||
105 | +// overflowY = 'hidden'; | ||
106 | +// } | ||
107 | +// | ||
108 | +// return { | ||
109 | +// height: `${height}px`, | ||
110 | +// minHeight: `${minHeight}px`, | ||
111 | +// maxHeight: `${maxHeight}px`, | ||
112 | +// overflowY | ||
113 | +// }; | ||
114 | +// } | ||
6 | 115 | ||
7 | -const HIDDEN_STYLE = ` | ||
8 | - height:0 !important; | ||
9 | - min-height:0 !important; | ||
10 | - max-height:none !important; | ||
11 | - visibility:hidden !important; | ||
12 | - overflow:hidden !important; | ||
13 | - position:absolute !important; | ||
14 | - z-index:-1000 !important; | ||
15 | - top:0 !important; | ||
16 | - right:0 !important | 116 | +const HIDDEN_TEXTAREA_STYLE = ` |
117 | + min-height:0 !important; | ||
118 | + max-height:none !important; | ||
119 | + height:0 !important; | ||
120 | + visibility:hidden !important; | ||
121 | + overflow:hidden !important; | ||
122 | + position:absolute !important; | ||
123 | + z-index:-1000 !important; | ||
124 | + top:0 !important; | ||
125 | + right:0 !important | ||
17 | `; | 126 | `; |
18 | 127 | ||
19 | -const CONTEXT_STYLE = [ | 128 | +const SIZING_STYLE = [ |
20 | 'letter-spacing', | 129 | 'letter-spacing', |
21 | 'line-height', | 130 | 'line-height', |
22 | 'padding-top', | 131 | 'padding-top', |
@@ -31,13 +140,29 @@ const CONTEXT_STYLE = [ | @@ -31,13 +140,29 @@ const CONTEXT_STYLE = [ | ||
31 | 'padding-left', | 140 | 'padding-left', |
32 | 'padding-right', | 141 | 'padding-right', |
33 | 'border-width', | 142 | 'border-width', |
34 | - 'box-sizing' | 143 | + 'box-sizing', |
35 | ]; | 144 | ]; |
36 | 145 | ||
37 | -function calculateNodeStyling(node) { | 146 | +let computedStyleCache = {}; |
147 | +let hiddenTextarea; | ||
148 | + | ||
149 | +function calculateNodeStyling(node, useCache = false) { | ||
150 | + const nodeRef = ( | ||
151 | + node.getAttribute('id') || | ||
152 | + node.getAttribute('data-reactid') || | ||
153 | + node.getAttribute('name')); | ||
154 | + | ||
155 | + if (useCache && computedStyleCache[nodeRef]) { | ||
156 | + return computedStyleCache[nodeRef]; | ||
157 | + } | ||
158 | + | ||
38 | const style = window.getComputedStyle(node); | 159 | const style = window.getComputedStyle(node); |
39 | 160 | ||
40 | - const boxSizing = style.getPropertyValue('box-sizing'); | 161 | + const boxSizing = ( |
162 | + style.getPropertyValue('box-sizing') || | ||
163 | + style.getPropertyValue('-moz-box-sizing') || | ||
164 | + style.getPropertyValue('-webkit-box-sizing') | ||
165 | + ); | ||
41 | 166 | ||
42 | const paddingSize = ( | 167 | const paddingSize = ( |
43 | parseFloat(style.getPropertyValue('padding-bottom')) + | 168 | parseFloat(style.getPropertyValue('padding-bottom')) + |
@@ -49,60 +174,93 @@ function calculateNodeStyling(node) { | @@ -49,60 +174,93 @@ function calculateNodeStyling(node) { | ||
49 | parseFloat(style.getPropertyValue('border-top-width')) | 174 | parseFloat(style.getPropertyValue('border-top-width')) |
50 | ); | 175 | ); |
51 | 176 | ||
52 | - const contextStyle = CONTEXT_STYLE | 177 | + const sizingStyle = SIZING_STYLE |
53 | .map(name => `${name}:${style.getPropertyValue(name)}`) | 178 | .map(name => `${name}:${style.getPropertyValue(name)}`) |
54 | .join(';'); | 179 | .join(';'); |
55 | 180 | ||
56 | - return {contextStyle, paddingSize, borderSize, boxSizing}; | 181 | + const nodeInfo = { |
182 | + sizingStyle, | ||
183 | + paddingSize, | ||
184 | + borderSize, | ||
185 | + boxSizing, | ||
186 | + }; | ||
187 | + | ||
188 | + if (useCache && nodeRef) { | ||
189 | + computedStyleCache[nodeRef] = nodeInfo; | ||
190 | + } | ||
191 | + | ||
192 | + return nodeInfo; | ||
57 | } | 193 | } |
58 | 194 | ||
59 | -export default function calcTextareaHeight(targetNode, minRows = null, maxRows = null) { | 195 | +export default function calcTextareaHeight(uiTextNode, minRows = null, maxRows = null, useCache = false) { |
60 | if (!hiddenTextarea) { | 196 | if (!hiddenTextarea) { |
61 | hiddenTextarea = document.createElement('textarea'); | 197 | hiddenTextarea = document.createElement('textarea'); |
62 | document.body.appendChild(hiddenTextarea); | 198 | document.body.appendChild(hiddenTextarea); |
63 | } | 199 | } |
64 | 200 | ||
201 | + // Fix wrap="off" issue | ||
202 | + // https://github.com/ant-design/ant-design/issues/6577 | ||
203 | + if (uiTextNode.getAttribute('wrap')) { | ||
204 | + hiddenTextarea.setAttribute('wrap', uiTextNode.getAttribute('wrap')); | ||
205 | + } else { | ||
206 | + hiddenTextarea.removeAttribute('wrap'); | ||
207 | + } | ||
208 | + | ||
209 | + // Copy all CSS properties that have an impact on the height of the content in | ||
210 | + // the textbox | ||
65 | let { | 211 | let { |
66 | - paddingSize, | ||
67 | - borderSize, | ||
68 | - boxSizing, | ||
69 | - contextStyle | ||
70 | - } = calculateNodeStyling(targetNode); | 212 | + paddingSize, borderSize, |
213 | + boxSizing, sizingStyle, | ||
214 | + } = calculateNodeStyling(uiTextNode, useCache); | ||
71 | 215 | ||
72 | - hiddenTextarea.setAttribute('style', `${contextStyle};${HIDDEN_STYLE}`); | ||
73 | - hiddenTextarea.value = targetNode.value || targetNode.placeholder || ''; | 216 | + // Need to have the overflow attribute to hide the scrollbar otherwise |
217 | + // text-lines will not calculated properly as the shadow will technically be | ||
218 | + // narrower for content | ||
219 | + hiddenTextarea.setAttribute('style', `${sizingStyle};${HIDDEN_TEXTAREA_STYLE}`); | ||
220 | + hiddenTextarea.value = uiTextNode.value || uiTextNode.placeholder || ''; | ||
74 | 221 | ||
222 | + let minHeight = Number.MIN_SAFE_INTEGER; | ||
223 | + let maxHeight = Number.MAX_SAFE_INTEGER; | ||
75 | let height = hiddenTextarea.scrollHeight; | 224 | let height = hiddenTextarea.scrollHeight; |
76 | - let minHeight = -Infinity; | ||
77 | - let maxHeight = Infinity; | 225 | + let overflowY; |
78 | 226 | ||
79 | if (boxSizing === 'border-box') { | 227 | if (boxSizing === 'border-box') { |
228 | + // border-box: add border, since height = content + padding + border | ||
80 | height = height + borderSize; | 229 | height = height + borderSize; |
81 | } else if (boxSizing === 'content-box') { | 230 | } else if (boxSizing === 'content-box') { |
231 | + // remove padding, since height = content | ||
82 | height = height - paddingSize; | 232 | height = height - paddingSize; |
83 | } | 233 | } |
84 | 234 | ||
85 | - hiddenTextarea.value = ''; | ||
86 | - let singleRowHeight = hiddenTextarea.scrollHeight - paddingSize; | ||
87 | - | ||
88 | - if (minRows !== null) { | ||
89 | - minHeight = singleRowHeight * minRows; | ||
90 | - if (boxSizing === 'border-box') { | ||
91 | - minHeight = minHeight + paddingSize + borderSize; | 235 | + if (minRows !== null || maxRows !== null) { |
236 | + // measure height of a textarea with a single row | ||
237 | + hiddenTextarea.value = ' '; | ||
238 | + let singleRowHeight = hiddenTextarea.scrollHeight - paddingSize; | ||
239 | + if (minRows !== null) { | ||
240 | + minHeight = singleRowHeight * minRows; | ||
241 | + if (boxSizing === 'border-box') { | ||
242 | + minHeight = minHeight + paddingSize + borderSize; | ||
243 | + } | ||
244 | + height = Math.max(minHeight, height); | ||
92 | } | 245 | } |
93 | - height = Math.max(minHeight, height); | ||
94 | - } | ||
95 | - if (maxRows !== null) { | ||
96 | - maxHeight = singleRowHeight * maxRows; | ||
97 | - if (boxSizing === 'border-box') { | ||
98 | - maxHeight = maxHeight + paddingSize + borderSize; | 246 | + if (maxRows !== null) { |
247 | + maxHeight = singleRowHeight * maxRows; | ||
248 | + if (boxSizing === 'border-box') { | ||
249 | + maxHeight = maxHeight + paddingSize + borderSize; | ||
250 | + } | ||
251 | + overflowY = height > maxHeight ? '' : 'hidden'; | ||
252 | + height = Math.min(maxHeight, height); | ||
99 | } | 253 | } |
100 | - height = Math.min(maxHeight, height); | 254 | + } |
255 | + // Remove scroll bar flash when autosize without maxRows | ||
256 | + if (!maxRows) { | ||
257 | + overflowY = 'hidden'; | ||
101 | } | 258 | } |
102 | 259 | ||
103 | return { | 260 | return { |
104 | height: `${height}px`, | 261 | height: `${height}px`, |
105 | minHeight: `${minHeight}px`, | 262 | minHeight: `${minHeight}px`, |
106 | - maxHeight: `${maxHeight}px` | 263 | + maxHeight: `${maxHeight}px`, |
264 | + overflowY | ||
107 | }; | 265 | }; |
108 | -} | ||
109 | \ No newline at end of file | 266 | \ No newline at end of file |
267 | +} |