+/**
+ * @class
+ * @extends OO.ui.TextInputWidget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {number} [rows] Number of visible lines in textarea. If used with `autosize`,
+ * specifies minimum number of rows to display.
+ * @cfg {string} [labelPosition='after'] The position of the inline label relative to that of
+ * @cfg {boolean} [autosize=false] Automatically resize the text input to fit its content.
+ * Use the #maxRows config to specify a maximum number of displayed rows.
+ * @cfg {boolean} [maxRows] Maximum number of rows to display when #autosize is set to true.
+ * Defaults to the maximum of `10` and `2 * rows`, or `10` if `rows` isn't provided.
+ */
+OO.ui.MultilineTextInputWidget = function OoUiMultilineTextInputWidget( config ) {
+ config = $.extend( {
+ type: 'text'
+ }, config );
+ config.multiline = false;
+ // Parent constructor
+ OO.ui.MultilineTextInputWidget.parent.call( this, config );
+
+ // Properties
+ this.multiline = true;
+ this.autosize = !!config.autosize;
+ this.minRows = config.rows !== undefined ? config.rows : '';
+ this.maxRows = config.maxRows || Math.max( 2 * ( this.minRows || 0 ), 10 );
+
+ // Clone for resizing
+ if ( this.autosize ) {
+ this.$clone = this.$input
+ .clone()
+ .insertAfter( this.$input )
+ .attr( 'aria-hidden', 'true' )
+ .addClass( 'oo-ui-element-hidden' );
+ }
+
+ // Events
+ this.connect( this, {
+ change: 'onChange'
+ } );
+
+ // Initialization
+ if ( this.multiline && config.rows ) {
+ this.$input.attr( 'rows', config.rows );
+ }
+ if ( this.autosize ) {
+ this.isWaitingToBeAttached = true;
+ this.installParentChangeDetector();
+ }
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.MultilineTextInputWidget, OO.ui.TextInputWidget );
+
+/* Static Methods */
+
+/**
+ * @inheritdoc
+ */
+OO.ui.MultilineTextInputWidget.static.gatherPreInfuseState = function ( node, config ) {
+ var state = OO.ui.MultilineTextInputWidget.parent.static.gatherPreInfuseState( node, config );
+ state.scrollTop = config.$input.scrollTop();
+ return state;
+};
+
+/* Methods */
+
+/**
+ * @inheritdoc
+ */
+OO.ui.MultilineTextInputWidget.prototype.onElementAttach = function () {
+ OO.ui.MultilineTextInputWidget.parent.prototype.onElementAttach.call( this );
+ this.adjustSize();
+};
+
+/**
+ * Handle change events.
+ *
+ * @private
+ */
+OO.ui.MultilineTextInputWidget.prototype.onChange = function () {
+ this.adjustSize();
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.MultilineTextInputWidget.prototype.updatePosition = function () {
+ OO.ui.MultilineTextInputWidget.parent.prototype.updatePosition.call( this );
+ this.adjustSize();
+};
+
+/**
+ * Override TextInputWidget so it doesn't emit the 'enter' event.
+ *
+ * @private
+ * @param {jQuery.Event} e Key press event
+ */
+OO.ui.MultilineTextInputWidget.prototype.onKeyPress = function () {
+ return;
+};
+
+/**
+ * Automatically adjust the size of the text input.
+ *
+ * This only affects multiline inputs that are {@link #autosize autosized}.
+ *
+ * @chainable
+ * @fires resize
+ */
+OO.ui.MultilineTextInputWidget.prototype.adjustSize = function () {
+ var scrollHeight, innerHeight, outerHeight, maxInnerHeight, measurementError,
+ idealHeight, newHeight, scrollWidth, property;
+
+ if ( this.$input.val() !== this.valCache ) {
+ if ( this.autosize ) {
+ this.$clone
+ .val( this.$input.val() )
+ .attr( 'rows', this.minRows )
+ // Set inline height property to 0 to measure scroll height
+ .css( 'height', 0 );
+
+ this.$clone.removeClass( 'oo-ui-element-hidden' );
+
+ this.valCache = this.$input.val();
+
+ scrollHeight = this.$clone[ 0 ].scrollHeight;
+
+ // Remove inline height property to measure natural heights
+ this.$clone.css( 'height', '' );
+ innerHeight = this.$clone.innerHeight();
+ outerHeight = this.$clone.outerHeight();
+
+ // Measure max rows height
+ this.$clone
+ .attr( 'rows', this.maxRows )
+ .css( 'height', 'auto' )
+ .val( '' );
+ maxInnerHeight = this.$clone.innerHeight();
+
+ // Difference between reported innerHeight and scrollHeight with no scrollbars present.
+ // This is sometimes non-zero on Blink-based browsers, depending on zoom level.
+ measurementError = maxInnerHeight - this.$clone[ 0 ].scrollHeight;
+ idealHeight = Math.min( maxInnerHeight, scrollHeight + measurementError );
+
+ this.$clone.addClass( 'oo-ui-element-hidden' );
+
+ // Only apply inline height when expansion beyond natural height is needed
+ // Use the difference between the inner and outer height as a buffer
+ newHeight = idealHeight > innerHeight ? idealHeight + ( outerHeight - innerHeight ) : '';
+ if ( newHeight !== this.styleHeight ) {
+ this.$input.css( 'height', newHeight );
+ this.styleHeight = newHeight;
+ this.emit( 'resize' );
+ }
+ }
+ scrollWidth = this.$input[ 0 ].offsetWidth - this.$input[ 0 ].clientWidth;
+ if ( scrollWidth !== this.scrollWidth ) {
+ property = this.$element.css( 'direction' ) === 'rtl' ? 'left' : 'right';
+ // Reset
+ this.$label.css( { right: '', left: '' } );
+ this.$indicator.css( { right: '', left: '' } );
+
+ if ( scrollWidth ) {
+ this.$indicator.css( property, scrollWidth );
+ if ( this.labelPosition === 'after' ) {
+ this.$label.css( property, scrollWidth );
+ }
+ }
+
+ this.scrollWidth = scrollWidth;
+ this.positionLabel();
+ }
+ }
+ return this;
+};
+
+/**
+ * @inheritdoc
+ * @protected
+ */
+OO.ui.MultilineTextInputWidget.prototype.getInputElement = function () {
+ return $( '<textarea>' );
+};
+
+/**
+ * Check if the input supports multiple lines.
+ *
+ * @return {boolean}
+ */
+OO.ui.MultilineTextInputWidget.prototype.isMultiline = function () {
+ return !!this.multiline;
+};
+
+/**
+ * Check if the input automatically adjusts its size.
+ *
+ * @return {boolean}
+ */
+OO.ui.MultilineTextInputWidget.prototype.isAutosizing = function () {
+ return !!this.autosize;
+};
+