Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit 16f92ec

Browse files
Michael ChenThomasBurleson
authored andcommitted
fix(input): Sizes textareas properly when the container is shown
Added an optional md-detect-hidden attribute for textareas inside of md-input-containers. This will check on every digest cycle whether the element was hidden and is now shown, and will auto-size the textarea properly. Fixes #1202. Closes #4726.
1 parent 8367118 commit 16f92ec

File tree

2 files changed

+104
-1
lines changed

2 files changed

+104
-1
lines changed

src/components/input/input.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ function labelDirective() {
126126
* @param {string=} placeholder An alternative approach to using aria-label when the label is not
127127
* PRESENT. The placeholder text is copied to the aria-label attribute.
128128
* @param md-no-autogrow {boolean=} When present, textareas will not grow automatically.
129+
* @param md-detect-hidden {boolean=} When present, textareas will be sized properly when they are revealed after being hidden. This is off by default for performance reasons because it guarantees a reflow every digest cycle.
129130
*
130131
* @usage
131132
* <hljs lang="html">
@@ -341,6 +342,31 @@ function inputTextareaDirective($mdUtil, $window, $mdAria) {
341342
var height = node.offsetHeight + line;
342343
node.style.height = height + 'px';
343344
}
345+
346+
// Attach a watcher to detect when the textarea gets shown.
347+
if (angular.isDefined(element.attr('md-detect-hidden'))) {
348+
349+
var handleHiddenChange = function() {
350+
var wasHidden = false;
351+
352+
return function() {
353+
var isHidden = node.offsetHeight === 0;
354+
355+
if (isHidden === false && wasHidden === true) {
356+
growTextarea();
357+
}
358+
359+
wasHidden = isHidden;
360+
};
361+
}();
362+
363+
// Check every digest cycle whether the visibility of the textarea has changed.
364+
// Queue up to run after the digest cycle is complete.
365+
scope.$watch(function() {
366+
$mdUtil.nextTick(handleHiddenChange, false);
367+
return true;
368+
});
369+
}
344370
}
345371
}
346372
}

src/components/input/input.spec.js

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
describe('md-input-container directive', function() {
2-
var $compile, pageScope;
2+
var $rootScope, $compile, $timeout, pageScope;
33

44
beforeEach(module('ngAria', 'material.components.input'));
55

@@ -9,6 +9,12 @@ describe('md-input-container directive', function() {
99
pageScope = $injector.get('$rootScope').$new();
1010
}));
1111

12+
beforeEach(inject(function($injector) {
13+
$rootScope = $injector.get('$rootScope');
14+
$compile = $injector.get('$compile');
15+
$timeout = $injector.get('$timeout');
16+
}));
17+
1218
function setup(attrs, isForm) {
1319
var container;
1420

@@ -258,4 +264,75 @@ describe('md-input-container directive', function() {
258264

259265
expect(element.hasClass('md-input-has-value')).toBe(true);
260266
});
267+
268+
describe('Textarea auto-sizing', function() {
269+
var ngElement, element, ngTextarea, textarea, scope, parentElement;
270+
271+
function createAndAppendElement(attrs) {
272+
scope = $rootScope.$new();
273+
274+
attrs = attrs || '';
275+
var template =
276+
'<div ng-hide="parentHidden">' +
277+
'<md-input-container>' +
278+
'<label>Biography</label>' +
279+
'<textarea ' + attrs + '>Single line</textarea>' +
280+
'</md-input-container>' +
281+
'</div>';
282+
parentElement = $compile(template)(scope);
283+
ngElement = parentElement.find('md-input-container');
284+
element = ngElement[0];
285+
ngTextarea = ngElement.find('textarea');
286+
textarea = ngTextarea[0];
287+
document.body.appendChild(parentElement[0]);
288+
}
289+
290+
afterEach(function() {
291+
document.body.removeChild(parentElement[0]);
292+
});
293+
294+
it('should auto-size the textarea as the user types', function() {
295+
createAndAppendElement();
296+
var oldHeight = textarea.offsetHeight;
297+
ngTextarea.val('Multiple\nlines\nof\ntext');
298+
ngTextarea.triggerHandler('input');
299+
scope.$apply();
300+
$timeout.flush();
301+
var newHeight = textarea.offsetHeight;
302+
expect(newHeight).toBeGreaterThan(oldHeight);
303+
});
304+
305+
it('should not auto-size if md-no-autogrow is present', function() {
306+
createAndAppendElement('md-no-autogrow');
307+
var oldHeight = textarea.offsetHeight;
308+
ngTextarea.val('Multiple\nlines\nof\ntext');
309+
ngTextarea.triggerHandler('input');
310+
scope.$apply();
311+
$timeout.flush();
312+
var newHeight = textarea.offsetHeight;
313+
expect(newHeight).toEqual(oldHeight);
314+
});
315+
316+
it('should auto-size when revealed if md-detect-hidden is present', function() {
317+
createAndAppendElement('md-detect-hidden');
318+
319+
var oldHeight = textarea.offsetHeight;
320+
321+
scope.parentHidden = true;
322+
ngTextarea.val('Multiple\nlines\nof\ntext');
323+
ngTextarea.triggerHandler('input');
324+
scope.$apply();
325+
$timeout.flush();
326+
327+
// Textarea should still be hidden.
328+
expect(textarea.offsetHeight).toBe(0);
329+
330+
scope.parentHidden = false;
331+
scope.$apply();
332+
333+
$timeout.flush();
334+
var newHeight = textarea.offsetHeight;
335+
expect(textarea.offsetHeight).toBeGreaterThan(oldHeight);
336+
});
337+
});
261338
});

0 commit comments

Comments
 (0)