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 | 80 | <!--<br>--> |
| 81 | 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 | 144 | </div> |
| 142 | 145 | </template> |
| 143 | 146 | <script> |
| ... | ... | @@ -150,7 +153,8 @@ |
| 150 | 153 | value13: '', |
| 151 | 154 | select1: 'http', |
| 152 | 155 | select2: 'com', |
| 153 | - select3: 'day' | |
| 156 | + select3: 'day', | |
| 157 | + value7: `` | |
| 154 | 158 | } |
| 155 | 159 | }, |
| 156 | 160 | methods: { | ... | ... |
src/utils/calcTextareaHeight.js
| 1 | 1 | // Thanks to |
| 2 | 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 | 129 | 'letter-spacing', |
| 21 | 130 | 'line-height', |
| 22 | 131 | 'padding-top', |
| ... | ... | @@ -31,13 +140,29 @@ const CONTEXT_STYLE = [ |
| 31 | 140 | 'padding-left', |
| 32 | 141 | 'padding-right', |
| 33 | 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 | 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 | 167 | const paddingSize = ( |
| 43 | 168 | parseFloat(style.getPropertyValue('padding-bottom')) + |
| ... | ... | @@ -49,60 +174,93 @@ function calculateNodeStyling(node) { |
| 49 | 174 | parseFloat(style.getPropertyValue('border-top-width')) |
| 50 | 175 | ); |
| 51 | 176 | |
| 52 | - const contextStyle = CONTEXT_STYLE | |
| 177 | + const sizingStyle = SIZING_STYLE | |
| 53 | 178 | .map(name => `${name}:${style.getPropertyValue(name)}`) |
| 54 | 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 | 196 | if (!hiddenTextarea) { |
| 61 | 197 | hiddenTextarea = document.createElement('textarea'); |
| 62 | 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 | 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 | 224 | let height = hiddenTextarea.scrollHeight; |
| 76 | - let minHeight = -Infinity; | |
| 77 | - let maxHeight = Infinity; | |
| 225 | + let overflowY; | |
| 78 | 226 | |
| 79 | 227 | if (boxSizing === 'border-box') { |
| 228 | + // border-box: add border, since height = content + padding + border | |
| 80 | 229 | height = height + borderSize; |
| 81 | 230 | } else if (boxSizing === 'content-box') { |
| 231 | + // remove padding, since height = content | |
| 82 | 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 | 260 | return { |
| 104 | 261 | height: `${height}px`, |
| 105 | 262 | minHeight: `${minHeight}px`, |
| 106 | - maxHeight: `${maxHeight}px` | |
| 263 | + maxHeight: `${maxHeight}px`, | |
| 264 | + overflowY | |
| 107 | 265 | }; |
| 108 | -} | |
| 109 | 266 | \ No newline at end of file |
| 267 | +} | ... | ... |