737 lines
22 KiB
JavaScript
737 lines
22 KiB
JavaScript
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS-IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
/**
|
|
* @fileoverview A content-aware textarea control that grows and shrinks
|
|
* automatically. This implementation extends {@link goog.ui.Control}.
|
|
* This code is inspired by Dojo Dijit's Textarea implementation with
|
|
* modifications to support native (when available) textarea resizing and
|
|
* minHeight and maxHeight enforcement.
|
|
*
|
|
* @see ../demos/textarea.html
|
|
*/
|
|
|
|
goog.provide('goog.ui.Textarea');
|
|
goog.provide('goog.ui.Textarea.EventType');
|
|
|
|
goog.require('goog.asserts');
|
|
goog.require('goog.dom');
|
|
goog.require('goog.dom.classlist');
|
|
goog.require('goog.events.EventType');
|
|
goog.require('goog.style');
|
|
goog.require('goog.ui.Control');
|
|
goog.require('goog.ui.TextareaRenderer');
|
|
goog.require('goog.userAgent');
|
|
|
|
|
|
|
|
/**
|
|
* A textarea control to handle growing/shrinking with textarea.value.
|
|
*
|
|
* @param {string} content Text to set as the textarea's value.
|
|
* @param {goog.ui.TextareaRenderer=} opt_renderer Renderer used to render or
|
|
* decorate the textarea. Defaults to {@link goog.ui.TextareaRenderer}.
|
|
* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper, used for
|
|
* document interaction.
|
|
* @constructor
|
|
* @extends {goog.ui.Control}
|
|
*/
|
|
goog.ui.Textarea = function(content, opt_renderer, opt_domHelper) {
|
|
goog.ui.Control.call(this, content, opt_renderer ||
|
|
goog.ui.TextareaRenderer.getInstance(), opt_domHelper);
|
|
|
|
this.setHandleMouseEvents(false);
|
|
this.setAllowTextSelection(true);
|
|
this.hasUserInput_ = (content != '');
|
|
if (!content) {
|
|
this.setContentInternal('');
|
|
}
|
|
};
|
|
goog.inherits(goog.ui.Textarea, goog.ui.Control);
|
|
goog.tagUnsealableClass(goog.ui.Textarea);
|
|
|
|
|
|
/**
|
|
* Some UAs will shrink the textarea automatically, some won't.
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.NEEDS_HELP_SHRINKING_ = goog.userAgent.GECKO ||
|
|
goog.userAgent.WEBKIT ||
|
|
(goog.userAgent.IE && goog.userAgent.isDocumentModeOrHigher(11));
|
|
|
|
|
|
/**
|
|
* True if the resizing function is executing, false otherwise.
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.isResizing_ = false;
|
|
|
|
|
|
/**
|
|
* Represents if we have focus on the textarea element, used only
|
|
* to render the placeholder if we don't have native placeholder
|
|
* support.
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.hasFocusForPlaceholder_ = false;
|
|
|
|
|
|
/**
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.hasUserInput_ = false;
|
|
|
|
|
|
/**
|
|
* The height of the textarea as last measured.
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.height_ = 0;
|
|
|
|
|
|
/**
|
|
* A maximum height for the textarea. When set to 0, the default, there is no
|
|
* enforcement of this value during resize.
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.maxHeight_ = 0;
|
|
|
|
|
|
/**
|
|
* A minimum height for the textarea. When set to 0, the default, there is no
|
|
* enforcement of this value during resize.
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.minHeight_ = 0;
|
|
|
|
|
|
/**
|
|
* Whether or not textarea rendering characteristics have been discovered.
|
|
* Specifically we determine, at runtime:
|
|
* If the padding and border box is included in offsetHeight.
|
|
* @see {goog.ui.Textarea.prototype.needsPaddingBorderFix_}
|
|
* If the padding and border box is included in scrollHeight.
|
|
* @see {goog.ui.Textarea.prototype.scrollHeightIncludesPadding_} and
|
|
* @see {goog.ui.Textarea.prototype.scrollHeightIncludesBorder_}
|
|
* TODO(user): See if we can determine goog.ui.Textarea.NEEDS_HELP_SHRINKING_.
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.hasDiscoveredTextareaCharacteristics_ = false;
|
|
|
|
|
|
/**
|
|
* If a user agent doesn't correctly support the box-sizing:border-box CSS
|
|
* value then we'll need to adjust our height calculations.
|
|
* @see {goog.ui.Textarea.prototype.discoverTextareaCharacteristics_}
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.needsPaddingBorderFix_ = false;
|
|
|
|
|
|
/**
|
|
* Whether or not scrollHeight of a textarea includes the padding box.
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.scrollHeightIncludesPadding_ = false;
|
|
|
|
|
|
/**
|
|
* Whether or not scrollHeight of a textarea includes the border box.
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.scrollHeightIncludesBorder_ = false;
|
|
|
|
|
|
/**
|
|
* For storing the padding box size during enterDocument, to prevent possible
|
|
* measurement differences that can happen after text zooming.
|
|
* Note: runtime padding changes will cause problems with this.
|
|
* @type {goog.math.Box}
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.paddingBox_;
|
|
|
|
|
|
/**
|
|
* For storing the border box size during enterDocument, to prevent possible
|
|
* measurement differences that can happen after text zooming.
|
|
* Note: runtime border width changes will cause problems with this.
|
|
* @type {goog.math.Box}
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.borderBox_;
|
|
|
|
|
|
/**
|
|
* Default text content for the textarea when it is unchanged and unfocussed.
|
|
* We use the placeholder attribute for all browsers that have support for
|
|
* it (new in HTML5 for the following browsers:
|
|
*
|
|
* Internet Explorer 10.0
|
|
* Firefox 4.0
|
|
* Opera 11.6
|
|
* Chrome 4.0
|
|
* Safari 5.0
|
|
*
|
|
* For older browsers, we save the placeholderText_ and set it as the element's
|
|
* value and add the TEXTAREA_PLACEHOLDER_CLASS to indicate that it's a
|
|
* placeholder string.
|
|
* @type {string}
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.placeholderText_ = '';
|
|
|
|
|
|
/**
|
|
* Constants for event names.
|
|
* @enum {string}
|
|
*/
|
|
goog.ui.Textarea.EventType = {
|
|
RESIZE: 'resize'
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the default text for the textarea.
|
|
* @param {string} text The default text for the textarea.
|
|
*/
|
|
goog.ui.Textarea.prototype.setPlaceholder = function(text) {
|
|
this.placeholderText_ = text;
|
|
if (this.getElement()) {
|
|
this.restorePlaceholder_();
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {number} The padding plus the border box height.
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.getPaddingBorderBoxHeight_ = function() {
|
|
var paddingBorderBoxHeight = this.paddingBox_.top + this.paddingBox_.bottom +
|
|
this.borderBox_.top + this.borderBox_.bottom;
|
|
return paddingBorderBoxHeight;
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {number} The minHeight value.
|
|
*/
|
|
goog.ui.Textarea.prototype.getMinHeight = function() {
|
|
return this.minHeight_;
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {number} The minHeight value with a potential padding fix.
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.getMinHeight_ = function() {
|
|
var minHeight = this.minHeight_;
|
|
var textarea = this.getElement();
|
|
if (minHeight && textarea && this.needsPaddingBorderFix_) {
|
|
minHeight -= this.getPaddingBorderBoxHeight_();
|
|
}
|
|
return minHeight;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets a minimum height for the textarea, and calls resize if rendered.
|
|
* @param {number} height New minHeight value.
|
|
*/
|
|
goog.ui.Textarea.prototype.setMinHeight = function(height) {
|
|
this.minHeight_ = height;
|
|
this.resize();
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {number} The maxHeight value.
|
|
*/
|
|
goog.ui.Textarea.prototype.getMaxHeight = function() {
|
|
return this.maxHeight_;
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {number} The maxHeight value with a potential padding fix.
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.getMaxHeight_ = function() {
|
|
var maxHeight = this.maxHeight_;
|
|
var textarea = this.getElement();
|
|
if (maxHeight && textarea && this.needsPaddingBorderFix_) {
|
|
maxHeight -= this.getPaddingBorderBoxHeight_();
|
|
}
|
|
return maxHeight;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets a maximum height for the textarea, and calls resize if rendered.
|
|
* @param {number} height New maxHeight value.
|
|
*/
|
|
goog.ui.Textarea.prototype.setMaxHeight = function(height) {
|
|
this.maxHeight_ = height;
|
|
this.resize();
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the textarea's value.
|
|
* @param {*} value The value property for the textarea, will be cast to a
|
|
* string by the browser when setting textarea.value.
|
|
*/
|
|
goog.ui.Textarea.prototype.setValue = function(value) {
|
|
this.setContent(String(value));
|
|
};
|
|
|
|
|
|
/**
|
|
* Gets the textarea's value.
|
|
* @return {string} value The value of the textarea.
|
|
*/
|
|
goog.ui.Textarea.prototype.getValue = function() {
|
|
// We potentially have the placeholder stored in the value.
|
|
// If a client of this class sets this.getElement().value directly
|
|
// we don't set the this.hasUserInput_ boolean. Thus, we need to
|
|
// explicitly check if the value != the placeholder text. This has
|
|
// the unfortunate edge case of:
|
|
// If the client sets this.getElement().value to the placeholder
|
|
// text, we'll return the empty string.
|
|
// The normal use case shouldn't be an issue, however, since the
|
|
// default placeholderText is the empty string. Also, if the end user
|
|
// inputs text, then this.hasUserInput_ will always be true.
|
|
if (this.getElement().value != this.placeholderText_ ||
|
|
this.supportsNativePlaceholder_() || this.hasUserInput_) {
|
|
// We don't do anything fancy here.
|
|
return this.getElement().value;
|
|
}
|
|
return '';
|
|
};
|
|
|
|
|
|
/** @override */
|
|
goog.ui.Textarea.prototype.setContent = function(content) {
|
|
goog.ui.Textarea.superClass_.setContent.call(this, content);
|
|
this.hasUserInput_ = (content != '');
|
|
this.resize();
|
|
};
|
|
|
|
|
|
/** @override **/
|
|
goog.ui.Textarea.prototype.setEnabled = function(enable) {
|
|
goog.ui.Textarea.superClass_.setEnabled.call(this, enable);
|
|
this.getElement().disabled = !enable;
|
|
};
|
|
|
|
|
|
/**
|
|
* Resizes the textarea vertically.
|
|
*/
|
|
goog.ui.Textarea.prototype.resize = function() {
|
|
if (this.getElement()) {
|
|
this.grow_();
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {boolean} True if the element supports the placeholder attribute.
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.supportsNativePlaceholder_ = function() {
|
|
goog.asserts.assert(this.getElement());
|
|
return 'placeholder' in this.getElement();
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the value of the textarea element to the default text.
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.restorePlaceholder_ = function() {
|
|
if (!this.placeholderText_) {
|
|
// Return early if there is no placeholder to mess with.
|
|
return;
|
|
}
|
|
// Check again in case something changed since this was scheduled.
|
|
// We check that the element is still there since this is called by a timer
|
|
// and the dispose method may have been called prior to this.
|
|
if (this.supportsNativePlaceholder_()) {
|
|
this.getElement().placeholder = this.placeholderText_;
|
|
} else if (this.getElement() && !this.hasUserInput_ &&
|
|
!this.hasFocusForPlaceholder_) {
|
|
// We only want to set the value + placeholder CSS if we actually have
|
|
// some placeholder text to show.
|
|
goog.dom.classlist.add(
|
|
goog.asserts.assert(this.getElement()),
|
|
goog.ui.Textarea.TEXTAREA_PLACEHOLDER_CLASS);
|
|
this.getElement().value = this.placeholderText_;
|
|
}
|
|
};
|
|
|
|
|
|
/** @override **/
|
|
goog.ui.Textarea.prototype.enterDocument = function() {
|
|
goog.ui.Textarea.base(this, 'enterDocument');
|
|
var textarea = this.getElement();
|
|
|
|
// Eliminates the vertical scrollbar and changes the box-sizing mode for the
|
|
// textarea to the border-box (aka quirksmode) paradigm.
|
|
goog.style.setStyle(textarea, {
|
|
'overflowY': 'hidden',
|
|
'overflowX': 'auto',
|
|
'boxSizing': 'border-box',
|
|
'MsBoxSizing': 'border-box',
|
|
'WebkitBoxSizing': 'border-box',
|
|
'MozBoxSizing': 'border-box'});
|
|
|
|
this.paddingBox_ = goog.style.getPaddingBox(textarea);
|
|
this.borderBox_ = goog.style.getBorderBox(textarea);
|
|
|
|
this.getHandler().
|
|
listen(textarea, goog.events.EventType.SCROLL, this.grow_).
|
|
listen(textarea, goog.events.EventType.FOCUS, this.grow_).
|
|
listen(textarea, goog.events.EventType.KEYUP, this.grow_).
|
|
listen(textarea, goog.events.EventType.MOUSEUP, this.mouseUpListener_).
|
|
listen(textarea, goog.events.EventType.BLUR, this.blur_);
|
|
|
|
this.restorePlaceholder_();
|
|
this.resize();
|
|
};
|
|
|
|
|
|
/**
|
|
* Gets the textarea's content height + padding height + border height.
|
|
* This is done by getting the scrollHeight and adjusting from there.
|
|
* In the end this result is what we want the new offsetHeight to equal.
|
|
* @return {number} The height of the textarea.
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.getHeight_ = function() {
|
|
this.discoverTextareaCharacteristics_();
|
|
var textarea = this.getElement();
|
|
// Because enterDocument can be called even when the component is rendered
|
|
// without being in a document, we may not have cached the correct paddingBox
|
|
// data on render(). We try to make up for this here.
|
|
if (isNaN(this.paddingBox_.top)) {
|
|
this.paddingBox_ = goog.style.getPaddingBox(textarea);
|
|
this.borderBox_ = goog.style.getBorderBox(textarea);
|
|
}
|
|
// Accounts for a possible (though unlikely) horizontal scrollbar.
|
|
var height = this.getElement().scrollHeight +
|
|
this.getHorizontalScrollBarHeight_();
|
|
if (this.needsPaddingBorderFix_) {
|
|
height -= this.getPaddingBorderBoxHeight_();
|
|
} else {
|
|
if (!this.scrollHeightIncludesPadding_) {
|
|
var paddingBox = this.paddingBox_;
|
|
var paddingBoxHeight = paddingBox.top + paddingBox.bottom;
|
|
height += paddingBoxHeight;
|
|
}
|
|
if (!this.scrollHeightIncludesBorder_) {
|
|
var borderBox = goog.style.getBorderBox(textarea);
|
|
var borderBoxHeight = borderBox.top + borderBox.bottom;
|
|
height += borderBoxHeight;
|
|
}
|
|
}
|
|
return height;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the textarea's height.
|
|
* @param {number} height The height to set.
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.setHeight_ = function(height) {
|
|
if (this.height_ != height) {
|
|
this.height_ = height;
|
|
this.getElement().style.height = height + 'px';
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the textarea's rows attribute to be the number of newlines + 1.
|
|
* This is necessary when the textarea is hidden, in which case scrollHeight
|
|
* is not available.
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.setHeightToEstimate_ = function() {
|
|
var textarea = this.getElement();
|
|
textarea.style.height = 'auto';
|
|
var newlines = textarea.value.match(/\n/g) || [];
|
|
textarea.rows = newlines.length + 1;
|
|
this.height_ = 0;
|
|
};
|
|
|
|
|
|
/**
|
|
* Gets the the height of (possibly present) horizontal scrollbar.
|
|
* @return {number} The height of the horizontal scrollbar.
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.getHorizontalScrollBarHeight_ =
|
|
function() {
|
|
var textarea = this.getElement();
|
|
var height = textarea.offsetHeight - textarea.clientHeight;
|
|
if (!this.scrollHeightIncludesPadding_) {
|
|
var paddingBox = this.paddingBox_;
|
|
var paddingBoxHeight = paddingBox.top + paddingBox.bottom;
|
|
height -= paddingBoxHeight;
|
|
}
|
|
if (!this.scrollHeightIncludesBorder_) {
|
|
var borderBox = goog.style.getBorderBox(textarea);
|
|
var borderBoxHeight = borderBox.top + borderBox.bottom;
|
|
height -= borderBoxHeight;
|
|
}
|
|
// Prevent negative number results, which sometimes show up.
|
|
return height > 0 ? height : 0;
|
|
};
|
|
|
|
|
|
/**
|
|
* In order to assess the correct height for a textarea, we need to know
|
|
* whether the scrollHeight (the full height of the text) property includes
|
|
* the values for padding and borders. We can also test whether the
|
|
* box-sizing: border-box setting is working and then tweak accordingly.
|
|
* Instead of hardcoding a list of currently known behaviors and testing
|
|
* for quirksmode, we do a runtime check out of the flow. The performance
|
|
* impact should be very small.
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.discoverTextareaCharacteristics_ = function() {
|
|
if (!this.hasDiscoveredTextareaCharacteristics_) {
|
|
var textarea = /** @type {!Element} */ (this.getElement().cloneNode(false));
|
|
// We need to overwrite/write box model specific styles that might
|
|
// affect height.
|
|
goog.style.setStyle(textarea, {
|
|
'position': 'absolute',
|
|
'height': 'auto',
|
|
'top': '-9999px',
|
|
'margin': '0',
|
|
'padding': '1px',
|
|
'border': '1px solid #000',
|
|
'overflow': 'hidden'
|
|
});
|
|
goog.dom.appendChild(this.getDomHelper().getDocument().body, textarea);
|
|
var initialScrollHeight = textarea.scrollHeight;
|
|
|
|
textarea.style.padding = '10px';
|
|
var paddingScrollHeight = textarea.scrollHeight;
|
|
this.scrollHeightIncludesPadding_ = paddingScrollHeight >
|
|
initialScrollHeight;
|
|
|
|
initialScrollHeight = paddingScrollHeight;
|
|
textarea.style.borderWidth = '10px';
|
|
var borderScrollHeight = textarea.scrollHeight;
|
|
this.scrollHeightIncludesBorder_ = borderScrollHeight > initialScrollHeight;
|
|
|
|
// Tests if border-box sizing is working or not.
|
|
textarea.style.height = '100px';
|
|
var offsetHeightAtHeight100 = textarea.offsetHeight;
|
|
if (offsetHeightAtHeight100 != 100) {
|
|
this.needsPaddingBorderFix_ = true;
|
|
}
|
|
|
|
goog.dom.removeNode(textarea);
|
|
this.hasDiscoveredTextareaCharacteristics_ = true;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* The CSS class name to add to the input when the user has not entered a
|
|
* value.
|
|
*/
|
|
goog.ui.Textarea.TEXTAREA_PLACEHOLDER_CLASS =
|
|
goog.getCssName('textarea-placeholder-input');
|
|
|
|
|
|
/**
|
|
* Called when the element goes out of focus.
|
|
* @param {goog.events.Event=} opt_e The browser event.
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.blur_ = function(opt_e) {
|
|
if (!this.supportsNativePlaceholder_()) {
|
|
this.hasFocusForPlaceholder_ = false;
|
|
if (this.getElement().value == '') {
|
|
// Only transition to the default text if we have
|
|
// no user input.
|
|
this.hasUserInput_ = false;
|
|
this.restorePlaceholder_();
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Resizes the textarea to grow/shrink to match its contents.
|
|
* @param {goog.events.Event=} opt_e The browser event.
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.grow_ = function(opt_e) {
|
|
if (this.isResizing_) {
|
|
return;
|
|
}
|
|
var textarea = this.getElement();
|
|
// If the element is getting focus and we don't support placeholders
|
|
// natively, then remove the placeholder class.
|
|
if (!this.supportsNativePlaceholder_() && opt_e &&
|
|
opt_e.type == goog.events.EventType.FOCUS) {
|
|
// We must have a textarea element, since we're growing it.
|
|
// Remove the placeholder CSS + set the value to empty if we're currently
|
|
// showing the placeholderText_ value if this is the first time we're
|
|
// getting focus.
|
|
if (textarea.value == this.placeholderText_ &&
|
|
this.placeholderText_ &&
|
|
!this.hasFocusForPlaceholder_) {
|
|
goog.dom.classlist.remove(
|
|
textarea, goog.ui.Textarea.TEXTAREA_PLACEHOLDER_CLASS);
|
|
textarea.value = '';
|
|
}
|
|
this.hasFocusForPlaceholder_ = true;
|
|
this.hasUserInput_ = (textarea.value != '');
|
|
}
|
|
var shouldCallShrink = false;
|
|
this.isResizing_ = true;
|
|
var oldHeight = this.height_;
|
|
if (textarea.scrollHeight) {
|
|
var setMinHeight = false;
|
|
var setMaxHeight = false;
|
|
var newHeight = this.getHeight_();
|
|
var currentHeight = textarea.offsetHeight;
|
|
var minHeight = this.getMinHeight_();
|
|
var maxHeight = this.getMaxHeight_();
|
|
if (minHeight && newHeight < minHeight) {
|
|
this.setHeight_(minHeight);
|
|
setMinHeight = true;
|
|
} else if (maxHeight && newHeight > maxHeight) {
|
|
this.setHeight_(maxHeight);
|
|
// If the content is greater than the height, we'll want the vertical
|
|
// scrollbar back.
|
|
textarea.style.overflowY = '';
|
|
setMaxHeight = true;
|
|
} else if (currentHeight != newHeight) {
|
|
this.setHeight_(newHeight);
|
|
// Makes sure that height_ is at least set.
|
|
} else if (!this.height_) {
|
|
this.height_ = newHeight;
|
|
}
|
|
if (!setMinHeight && !setMaxHeight &&
|
|
goog.ui.Textarea.NEEDS_HELP_SHRINKING_) {
|
|
shouldCallShrink = true;
|
|
}
|
|
} else {
|
|
this.setHeightToEstimate_();
|
|
}
|
|
this.isResizing_ = false;
|
|
|
|
if (shouldCallShrink) {
|
|
this.shrink_();
|
|
}
|
|
if (oldHeight != this.height_) {
|
|
this.dispatchEvent(goog.ui.Textarea.EventType.RESIZE);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Resizes the textarea to shrink to fit its contents. The way this works is
|
|
* by increasing the padding of the textarea by 1px (it's important here that
|
|
* we're in box-sizing: border-box mode). If the size of the textarea grows,
|
|
* then the box is filled up to the padding box with text.
|
|
* If it doesn't change, then we can shrink.
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.shrink_ = function() {
|
|
var textarea = this.getElement();
|
|
if (!this.isResizing_) {
|
|
this.isResizing_ = true;
|
|
var scrollHeight = textarea.scrollHeight;
|
|
if (!scrollHeight) {
|
|
this.setHeightToEstimate_();
|
|
} else {
|
|
var currentHeight = this.getHeight_();
|
|
var minHeight = this.getMinHeight_();
|
|
var maxHeight = this.getMaxHeight_();
|
|
if (!(minHeight && currentHeight <= minHeight)) {
|
|
// Nudge the padding by 1px.
|
|
var paddingBox = this.paddingBox_;
|
|
textarea.style.paddingBottom = paddingBox.bottom + 1 + 'px';
|
|
var heightAfterNudge = this.getHeight_();
|
|
// If the one px of padding had no effect, then we can shrink.
|
|
if (heightAfterNudge == currentHeight) {
|
|
textarea.style.paddingBottom = paddingBox.bottom + scrollHeight +
|
|
'px';
|
|
textarea.scrollTop = 0;
|
|
var shrinkToHeight = this.getHeight_() - scrollHeight;
|
|
if (shrinkToHeight >= minHeight) {
|
|
this.setHeight_(shrinkToHeight);
|
|
} else {
|
|
this.setHeight_(minHeight);
|
|
}
|
|
}
|
|
textarea.style.paddingBottom = paddingBox.bottom + 'px';
|
|
}
|
|
}
|
|
this.isResizing_ = false;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* We use this listener to check if the textarea has been natively resized
|
|
* and if so we reset minHeight so that we don't ever shrink smaller than
|
|
* the user's manually set height. Note that we cannot check size on mousedown
|
|
* and then just compare here because we cannot capture mousedown on
|
|
* the textarea resizer, while mouseup fires reliably.
|
|
* @param {goog.events.BrowserEvent} e The mousedown event.
|
|
* @private
|
|
*/
|
|
goog.ui.Textarea.prototype.mouseUpListener_ = function(e) {
|
|
var textarea = this.getElement();
|
|
var height = textarea.offsetHeight;
|
|
|
|
// This solves for when the MSIE DropShadow filter is enabled,
|
|
// as it affects the offsetHeight value, even with MsBoxSizing:border-box.
|
|
if (textarea['filters'] && textarea['filters'].length) {
|
|
var dropShadow =
|
|
textarea['filters']['item']('DXImageTransform.Microsoft.DropShadow');
|
|
if (dropShadow) {
|
|
height -= dropShadow['offX'];
|
|
}
|
|
}
|
|
|
|
if (height != this.height_) {
|
|
this.minHeight_ = height;
|
|
this.height_ = height;
|
|
}
|
|
};
|