/*!
- * OOjs UI v0.22.1
+ * OOjs UI v0.22.3
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2017 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2017-05-31T19:07:36Z
+ * Date: 2017-07-11T22:12:33Z
*/
( function ( OO ) {
scrollTop = body.scrollTop;
body.scrollTop = 1;
- if ( body.scrollTop === 1 ) {
+ // In some browsers (observed in Chrome 56 on Linux Mint 18.1),
+ // body.scrollTop doesn't become exactly 1, but a fractional value like 0.76
+ if ( Math.round( body.scrollTop ) === 1 ) {
body.scrollTop = scrollTop;
OO.ui.scrollableElement = 'body';
} else {
* @cfg {jQuery} [$tabIndexed] The element that should use the tabindex functionality. By default,
* the functionality is applied to the element created by the class ($element). If a different element is specified, the tabindex
* functionality will be applied to it instead.
- * @cfg {number|null} [tabIndex=0] Number that specifies the element’s position in the tab-navigation
+ * @cfg {string|number|null} [tabIndex=0] Number that specifies the element’s position in the tab-navigation
* order (e.g., 1 for the first focusable element). Use 0 to use the default navigation order; use -1
* to remove the element from the tab-navigation flow.
*/
/**
* Set the value of the tabindex.
*
- * @param {number|null} tabIndex Tabindex value, or `null` for no tabindex
+ * @param {string|number|null} tabIndex Tabindex value, or `null` for no tabindex
* @chainable
*/
OO.ui.mixin.TabIndexedElement.prototype.setTabIndex = function ( tabIndex ) {
- tabIndex = typeof tabIndex === 'number' ? tabIndex : null;
+ tabIndex = /^-?\d+$/.test( tabIndex ) ? Number( tabIndex ) : null;
if ( this.tabIndex !== tabIndex ) {
this.tabIndex = tabIndex;
* // A button widget
* var button = new OO.ui.ButtonWidget( {
* label: 'Button with Icon',
- * icon: 'remove',
+ * icon: 'trash',
* iconTitle: 'Remove'
* } );
* $( 'body' ).append( button.$element );
// Properties
this.$anchor = $( '<div>' );
- // If undefined, will be computed lazily in updateDimensions()
+ // If undefined, will be computed lazily in computePosition()
this.$container = config.$container;
this.containerPadding = config.containerPadding !== undefined ? config.containerPadding : 10;
this.autoClose = !!config.autoClose;
return this.popupPosition;
};
+/**
+ * Get an ID of the body element, this can be used as the
+ * `aria-describedby` attribute for an input field.
+ *
+ * @return {string} The ID of the body element
+ */
+OO.ui.PopupWidget.prototype.getBodyId = function () {
+ var id = this.$body.attr( 'id' );
+ if ( id === undefined ) {
+ id = OO.ui.generateElementId();
+ this.$body.attr( 'id', id );
+ }
+ return id;
+};
+
/**
* PopupElement is mixed into other classes to generate a {@link OO.ui.PopupWidget popup widget}.
* A popup is a container for content. It is overlaid and positioned absolutely. By default, each
};
/**
- * Update menu item visibility after input changes.
+ * Update menu item visibility and clipping after input changes (if filterFromInput is enabled)
+ * or after items were added/removed (always).
*
* @protected
*/
OO.ui.MenuSelectWidget.prototype.updateItemVisibility = function () {
- var i, item, visible, section, sectionEmpty,
+ var i, item, visible, section, sectionEmpty, filter, exactFilter,
firstItemFound = false,
anyVisible = false,
len = this.items.length,
showAll = !this.isVisible(),
- filter = showAll ? null : this.getItemMatcher( this.$input.val() ),
- exactFilter = this.getItemMatcher( this.$input.val(), true ),
exactMatch = false;
- // Hide non-matching options, and also hide section headers if all options
- // in their section are hidden.
- for ( i = 0; i < len; i++ ) {
- item = this.items[ i ];
- if ( item instanceof OO.ui.MenuSectionOptionWidget ) {
- if ( section ) {
- // If the previous section was empty, hide its header
- section.toggle( showAll || !sectionEmpty );
- }
- section = item;
- sectionEmpty = true;
- } else if ( item instanceof OO.ui.OptionWidget ) {
- visible = showAll || filter( item );
- exactMatch = exactMatch || exactFilter( item );
- anyVisible = anyVisible || visible;
- sectionEmpty = sectionEmpty && !visible;
- item.toggle( visible );
- if ( this.highlightOnFilter && visible && !firstItemFound ) {
- // Highlight the first item in the list
- this.highlightItem( item );
- firstItemFound = true;
+ if ( this.$input && this.filterFromInput ) {
+ filter = showAll ? null : this.getItemMatcher( this.$input.val() );
+ exactFilter = this.getItemMatcher( this.$input.val(), true );
+
+ // Hide non-matching options, and also hide section headers if all options
+ // in their section are hidden.
+ for ( i = 0; i < len; i++ ) {
+ item = this.items[ i ];
+ if ( item instanceof OO.ui.MenuSectionOptionWidget ) {
+ if ( section ) {
+ // If the previous section was empty, hide its header
+ section.toggle( showAll || !sectionEmpty );
+ }
+ section = item;
+ sectionEmpty = true;
+ } else if ( item instanceof OO.ui.OptionWidget ) {
+ visible = showAll || filter( item );
+ exactMatch = exactMatch || exactFilter( item );
+ anyVisible = anyVisible || visible;
+ sectionEmpty = sectionEmpty && !visible;
+ item.toggle( visible );
+ if ( this.highlightOnFilter && visible && !firstItemFound ) {
+ // Highlight the first item in the list
+ this.highlightItem( item );
+ firstItemFound = true;
+ }
}
}
- }
- // Process the final section
- if ( section ) {
- section.toggle( showAll || !sectionEmpty );
- }
+ // Process the final section
+ if ( section ) {
+ section.toggle( showAll || !sectionEmpty );
+ }
- if ( anyVisible && this.items.length && !exactMatch ) {
- this.scrollItemIntoView( this.items[ 0 ] );
- }
+ if ( anyVisible && this.items.length && !exactMatch ) {
+ this.scrollItemIntoView( this.items[ 0 ] );
+ }
- this.$element.toggleClass( 'oo-ui-menuSelectWidget-invisible', !anyVisible );
+ this.$element.toggleClass( 'oo-ui-menuSelectWidget-invisible', !anyVisible );
+ }
// Reevaluate clipping
this.clip();
// Parent method
OO.ui.MenuSelectWidget.parent.prototype.addItems.call( this, items, index );
- // Reevaluate clipping
- this.clip();
+ this.updateItemVisibility();
return this;
};
// Parent method
OO.ui.MenuSelectWidget.parent.prototype.removeItems.call( this, items );
- // Reevaluate clipping
- this.clip();
+ this.updateItemVisibility();
return this;
};
// Parent method
OO.ui.MenuSelectWidget.parent.prototype.clearItems.call( this );
- // Reevaluate clipping
- this.clip();
+ this.updateItemVisibility();
return this;
};
this.togglePositioning( !!this.$floatableContainer );
this.toggleClipping( true );
+ this.$focusOwner.attr( 'aria-expanded', 'true' );
+
if ( this.getSelectedItem() ) {
this.$focusOwner.attr( 'aria-activedescendant', this.getSelectedItem().getElementId() );
this.getSelectedItem().scrollElementIntoView( { duration: 0 } );
this.$focusOwner.removeAttr( 'aria-activedescendant' );
this.unbindKeyDownListener();
this.unbindKeyPressListener();
+ this.$focusOwner.attr( 'aria-expanded', 'false' );
this.getElementDocument().removeEventListener( 'mousedown', this.onDocumentMouseDownHandler, true );
this.togglePositioning( false );
this.toggleClipping( false );
*/
OO.ui.DropdownWidget.prototype.onMenuToggle = function ( isVisible ) {
this.$element.toggleClass( 'oo-ui-dropdownWidget-open', isVisible );
+ this.$handle.attr(
+ 'aria-expanded',
+ this.$element.hasClass( 'oo-ui-dropdownWidget-open' ).toString()
+ );
};
/**
// Initialization
this.setOptions( config.options || [] );
+ // Set the value again, after we did setOptions(). The call from parent doesn't work because the
+ // widget has no valid options when it happens.
+ this.setValue( config.value );
this.$element
.addClass( 'oo-ui-dropdownInputWidget' )
.append( this.dropdownWidget.$element );
* Handles menu select events.
*
* @private
- * @param {OO.ui.MenuOptionWidget} item Selected menu item
+ * @param {OO.ui.MenuOptionWidget|null} item Selected menu item
*/
OO.ui.DropdownInputWidget.prototype.onMenuSelect = function ( item ) {
- this.setValue( item.getData() );
+ this.setValue( item ? item.getData() : '' );
};
/**
OO.ui.DropdownInputWidget.prototype.setValue = function ( value ) {
var selected;
value = this.cleanUpValue( value );
- this.dropdownWidget.getMenu().selectItemByData( value );
// Only allow setting values that are actually present in the dropdown
- selected = this.dropdownWidget.getMenu().getSelectedItem();
+ selected = this.dropdownWidget.getMenu().getItemFromData( value ) ||
+ this.dropdownWidget.getMenu().getFirstSelectableItem();
+ this.dropdownWidget.getMenu().selectItem( selected );
value = selected ? selected.getData() : '';
OO.ui.DropdownInputWidget.parent.prototype.setValue.call( this, value );
return this;
* @constructor
* @param {Object} [config] Configuration options
* @cfg {string} [type='text'] The value of the HTML `type` attribute: 'text', 'password'
- * 'email', 'url' or 'number'. Ignored if `multiline` is true.
+ * 'email', 'url' or 'number'.
* @cfg {string} [placeholder] Placeholder text
* @cfg {boolean} [autofocus=false] Use an HTML `autofocus` attribute to
* instruct the browser to focus this widget.
* @cfg {boolean} [readOnly=false] Prevent changes to the value of the text input.
* @cfg {number} [maxLength] Maximum number of characters allowed in the input.
- * @cfg {boolean} [multiline=false] Allow multiple lines of text
- * @cfg {number} [rows] If multiline, number of visible lines in textarea. If used with `autosize`,
- * specifies minimum number of rows to display.
- * @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 {number} [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.
* @cfg {string} [labelPosition='after'] The position of the inline label relative to that of
* the value or placeholder text: `'before'` or `'after'`
* @cfg {boolean} [required=false] Mark the field as required. Implies `indicator: 'required'`.
labelPosition: 'after'
}, config );
+ if ( config.multiline ) {
+ OO.ui.warnDeprecation( 'TextInputWidget: config.multiline is deprecated. Use the MultilineTextInputWidget instead. See T130434 for details.' );
+ return new OO.ui.MultilineTextInputWidget( config );
+ }
+
// Parent constructor
OO.ui.TextInputWidget.parent.call( this, config );
this.type = this.getSaneType( config );
this.readOnly = false;
this.required = false;
- this.multiline = !!config.multiline;
- this.autosize = !!config.autosize;
- this.minRows = config.rows !== undefined ? config.rows : '';
- this.maxRows = config.maxRows || Math.max( 2 * ( this.minRows || 0 ), 10 );
this.validate = null;
this.styleHeight = null;
this.scrollWidth = null;
- // Clone for resizing
- if ( this.autosize ) {
- this.$clone = this.$input
- .clone()
- .insertAfter( this.$input )
- .attr( 'aria-hidden', 'true' )
- .addClass( 'oo-ui-element-hidden' );
- }
-
this.setValidation( config.validate );
this.setLabelPosition( config.labelPosition );
this.$icon.on( 'mousedown', this.onIconMouseDown.bind( this ) );
this.$indicator.on( 'mousedown', this.onIndicatorMouseDown.bind( this ) );
this.on( 'labelChange', this.updatePosition.bind( this ) );
- this.connect( this, {
- change: 'onChange'
- } );
this.on( 'change', OO.ui.debounce( this.onDebouncedChange.bind( this ), 250 ) );
// Initialization
}.bind( this )
} );
}
- if ( this.multiline && config.rows ) {
- this.$input.attr( 'rows', config.rows );
- }
- if ( this.label || config.autosize ) {
+ if ( this.label ) {
this.isWaitingToBeAttached = true;
this.installParentChangeDetector();
}
*/
OO.ui.TextInputWidget.static.gatherPreInfuseState = function ( node, config ) {
var state = OO.ui.TextInputWidget.parent.static.gatherPreInfuseState( node, config );
- if ( config.multiline ) {
- state.scrollTop = config.$input.scrollTop();
- }
return state;
};
/**
* An `enter` event is emitted when the user presses 'enter' inside the text box.
*
- * Not emitted if the input is multiline.
- *
* @event enter
*/
-/**
- * A `resize` event is emitted when autosize is set and the widget resizes
- *
- * @event resize
- */
-
/* Methods */
/**
*
* @private
* @param {jQuery.Event} e Key press event
- * @fires enter If enter key is pressed and input is not multiline
+ * @fires enter If enter key is pressed
*/
OO.ui.TextInputWidget.prototype.onKeyPress = function ( e ) {
- if ( e.which === OO.ui.Keys.ENTER && !this.multiline ) {
+ if ( e.which === OO.ui.Keys.ENTER ) {
this.emit( 'enter', e );
}
};
this.isWaitingToBeAttached = false;
// Any previously calculated size is now probably invalid if we reattached elsewhere
this.valCache = null;
- this.adjustSize();
this.positionLabel();
};
-/**
- * Handle change events.
- *
- * @param {string} value
- * @private
- */
-OO.ui.TextInputWidget.prototype.onChange = function () {
- this.adjustSize();
-};
-
/**
* Handle debounced change events.
*
}
};
-/**
- * Automatically adjust the size of the text input.
- *
- * This only affects #multiline inputs that are {@link #autosize autosized}.
- *
- * @chainable
- * @fires resize
- */
-OO.ui.TextInputWidget.prototype.adjustSize = function () {
- var scrollHeight, innerHeight, outerHeight, maxInnerHeight, measurementError,
- idealHeight, newHeight, scrollWidth, property;
-
- if ( this.isWaitingToBeAttached ) {
- // #onElementAttach will be called soon, which calls this method
- return this;
- }
-
- if ( this.multiline && 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.TextInputWidget.prototype.getInputElement = function ( config ) {
- if ( config.multiline ) {
- return $( '<textarea>' );
- } else if ( this.getSaneType( config ) === 'number' ) {
+ if ( this.getSaneType( config ) === 'number' ) {
return $( '<input>' )
.attr( 'step', 'any' )
.attr( 'type', 'number' );
return allowedTypes.indexOf( config.type ) !== -1 ? config.type : 'text';
};
-/**
- * Check if the input supports multiple lines.
- *
- * @return {boolean}
- */
-OO.ui.TextInputWidget.prototype.isMultiline = function () {
- return !!this.multiline;
-};
-
-/**
- * Check if the input automatically adjusts its size.
- *
- * @return {boolean}
- */
-OO.ui.TextInputWidget.prototype.isAutosizing = function () {
- return !!this.autosize;
-};
-
/**
* Focus the input and select a specified range within the text.
*
this.valCache = null;
this.scrollWidth = null;
- this.adjustSize();
this.positionLabel();
return this;
// Parent constructor
OO.ui.SearchInputWidget.parent.call( this, config );
+ // Events
+ this.connect( this, {
+ change: 'onChange'
+ } );
+
// Initialization
this.$element.addClass( 'oo-ui-textInputWidget-type-search' );
this.updateSearchIndicator();
};
/**
- * @inheritdoc
+ * Handle change events.
+ *
+ * @private
*/
OO.ui.SearchInputWidget.prototype.onChange = function () {
- OO.ui.SearchInputWidget.parent.prototype.onChange.call( this );
this.updateSearchIndicator();
};
return this;
};
+/**
+ * @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;
+};
+
/**
* ComboBoxInputWidgets combine a {@link OO.ui.TextInputWidget text input} (where a value
* can be entered manually) and a {@link OO.ui.MenuSelectWidget menu of options} (from which
this.fieldWidget.connect( this, { disable: 'onFieldDisable' } );
// Initialization
+ if ( config.help ) {
+ // Set the 'aria-describedby' attribute on the fieldWidget
+ // Preference given to an input or a button
+ (
+ this.fieldWidget.$input ||
+ this.fieldWidget.$button ||
+ this.fieldWidget.$element
+ ).attr(
+ 'aria-describedby',
+ this.popupButtonWidget.getPopup().getBodyId()
+ );
+ }
if ( this.fieldWidget.getInputId() ) {
this.$label.attr( 'for', this.fieldWidget.getInputId() );
} else {
$listItem = $( '<li>' );
if ( kind === 'error' ) {
$icon = new OO.ui.IconWidget( { icon: 'alert', flags: [ 'warning' ] } ).$element;
+ $listItem.attr( 'role', 'alert' );
} else if ( kind === 'notice' ) {
$icon = new OO.ui.IconWidget( { icon: 'info' } ).$element;
} else {