Commit 8df3390e4160f5d4d1e67e4aeaf14135252e8619

Authored by 梁灏
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 +}