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 | +} |