Update OOUI to v0.30.1
[lhc/web/wiklou.git] / resources / lib / ooui / oojs-ui-core.js
index aea7f5e..c6c3706 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOUI v0.29.3
+ * OOUI v0.30.1
  * https://www.mediawiki.org/wiki/OOUI
  *
- * Copyright 2011–2018 OOUI Team and other contributors.
+ * Copyright 2011–2019 OOUI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2018-11-01T02:03:33Z
+ * Date: 2019-01-10T07:00:09Z
  */
 ( function ( OO ) {
 
@@ -377,6 +377,8 @@ OO.ui.infuse = function ( idOrNode, config ) {
                'ooui-dialog-process-retry': 'Try again',
                // Label for process dialog retry action button, visible when describing only warnings
                'ooui-dialog-process-continue': 'Continue',
+               // Label for button in combobox input that triggers its dropdown
+               'ooui-combobox-button-label': 'Dropdown for combobox',
                // Label for the file selection widget's select file button
                'ooui-selectfile-button-select': 'Select a file',
                // Label for the file selection widget if file selection is not supported
@@ -424,7 +426,7 @@ OO.ui.infuse = function ( idOrNode, config ) {
         *             label: OO.ui.msg( 'ooui-dialog-message-accept' ),
         *             icon: 'check'
         *         } );
-        *         $( 'body' ).append( button.$element );
+        *         $( document.body ).append( button.$element );
         *
         *         // A button displaying "OK" in Urdu
         *         $.i18n().locale = 'ur';
@@ -432,7 +434,7 @@ OO.ui.infuse = function ( idOrNode, config ) {
         *             label: OO.ui.msg( 'ooui-dialog-message-accept' ),
         *             icon: 'check'
         *         } );
-        *         $( 'body' ).append( button.$element );
+        *         $( document.body ).append( button.$element );
         *     } );
         *
         * @param {string} key Message key
@@ -481,7 +483,7 @@ OO.ui.deferMsg = function () {
  * @return {string} Resolved message
  */
 OO.ui.resolveMsg = function ( msg ) {
-       if ( $.isFunction( msg ) ) {
+       if ( typeof msg === 'function' ) {
                return msg();
        }
        return msg;
@@ -568,7 +570,7 @@ OO.ui.getViewportSpacing = function () {
 OO.ui.getDefaultOverlay = function () {
        if ( !OO.ui.$defaultOverlay ) {
                OO.ui.$defaultOverlay = $( '<div>' ).addClass( 'oo-ui-defaultOverlay' );
-               $( 'body' ).append( OO.ui.$defaultOverlay );
+               $( document.body ).append( OO.ui.$defaultOverlay );
        }
        return OO.ui.$defaultOverlay;
 };
@@ -697,6 +699,13 @@ OO.ui.Element.static.tagName = 'div';
  */
 OO.ui.Element.static.infuse = function ( idOrNode, config ) {
        var obj = OO.ui.Element.static.unsafeInfuse( idOrNode, config, false );
+
+       if ( typeof idOrNode === 'string' ) {
+               // IDs deprecated since 0.29.7
+               OO.ui.warnDeprecation(
+                       'Passing a string ID to infuse is deprecated. Use an HTMLElement or jQuery collection instead.'
+               );
+       }
        // Verify that the type matches up.
        // FIXME: uncomment after T89721 is fixed, see T90929.
        /*
@@ -953,7 +962,7 @@ OO.ui.Element.static.getWindow = function ( obj ) {
 OO.ui.Element.static.getDir = function ( obj ) {
        var isDoc, isWin;
 
-       if ( obj instanceof jQuery ) {
+       if ( obj instanceof $ ) {
                obj = obj[ 0 ];
        }
        isDoc = obj.nodeType === Node.DOCUMENT_NODE;
@@ -1139,7 +1148,10 @@ OO.ui.Element.static.getScrollLeft = ( function () {
        var rtlScrollType = null;
 
        function test() {
-               var $definer = $( '<div dir="rtl" style="font-size: 14px; width: 1px; height: 1px; position: absolute; top: -1000px; overflow: scroll">A</div>' ),
+               var $definer = $( '<div>' ).attr( {
+                               dir: 'rtl',
+                               style: 'font-size: 14px; width: 1px; height: 1px; position: absolute; top: -1000px; overflow: scroll;'
+                       } ).text( 'A' ),
                        definer = $definer[ 0 ];
 
                $definer.appendTo( 'body' );
@@ -1327,6 +1339,7 @@ OO.ui.Element.static.scrollIntoView = function ( el, config ) {
                }
        }
        if ( !$.isEmptyObject( animations ) ) {
+               // eslint-disable-next-line jquery/no-animate
                $container.stop( true ).animate( animations, config.duration === undefined ? 'fast' : config.duration );
                $container.queue( function ( next ) {
                        deferred.resolve();
@@ -1361,6 +1374,7 @@ OO.ui.Element.static.reconsiderScrollbars = function ( el ) {
                el.removeChild( el.firstChild );
        }
        // Force reflow
+       // eslint-disable-next-line no-void
        void el.offsetHeight;
        // Reattach all children
        for ( i = 0, len = nodes.length; i < len; i++ ) {
@@ -1379,6 +1393,7 @@ OO.ui.Element.static.reconsiderScrollbars = function ( el ) {
  * @param {boolean} [show] Make element visible, omit to toggle visibility
  * @fires visible
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.Element.prototype.toggle = function ( show ) {
        show = show === undefined ? !this.visible : !!show;
@@ -1415,6 +1430,7 @@ OO.ui.Element.prototype.getData = function () {
  *
  * @param {Mixed} data Element data
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.Element.prototype.setData = function ( data ) {
        this.data = data;
@@ -1426,6 +1442,7 @@ OO.ui.Element.prototype.setData = function ( data ) {
  *
  * @param {string} id
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.Element.prototype.setElementId = function ( id ) {
        this.elementId = id;
@@ -1458,7 +1475,7 @@ OO.ui.Element.prototype.supports = function ( methods ) {
 
        methods = Array.isArray( methods ) ? methods : [ methods ];
        for ( i = 0, len = methods.length; i < len; i++ ) {
-               if ( $.isFunction( this[ methods[ i ] ] ) ) {
+               if ( typeof this[ methods[ i ] ] === 'function' ) {
                        support++;
                }
        }
@@ -1539,6 +1556,7 @@ OO.ui.Element.prototype.getElementGroup = function () {
  *
  * @param {OO.ui.mixin.GroupElement|null} group Group element, null if none
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.Element.prototype.setElementGroup = function ( group ) {
        this.elementGroup = group;
@@ -1644,6 +1662,7 @@ OO.mixinClass( OO.ui.Layout, OO.EventEmitter );
  * Reset scroll offsets
  *
  * @chainable
+ * @return {OO.ui.Layout} The layout, for chaining
  */
 OO.ui.Layout.prototype.resetScroll = function () {
        this.$element[ 0 ].scrollTop = 0;
@@ -1728,6 +1747,7 @@ OO.ui.Widget.prototype.isDisabled = function () {
  *
  * @param {boolean} disabled Disable widget
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.Widget.prototype.setDisabled = function ( disabled ) {
        var isDisabled;
@@ -1750,6 +1770,7 @@ OO.ui.Widget.prototype.setDisabled = function ( disabled ) {
  * Update the disabled state, in case of changes in parent widget.
  *
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.Widget.prototype.updateDisabled = function () {
        this.setDisabled( this.disabled );
@@ -1883,28 +1904,28 @@ OO.ui.Theme.prototype.getDialogTransitionDuration = function () {
 /**
  * The TabIndexedElement class is an attribute mixin used to add additional functionality to an
  * element created by another class. The mixin provides a ‘tabIndex’ property, which specifies the
- * order in which users will navigate through the focusable elements via the "tab" key.
+ * order in which users will navigate through the focusable elements via the “tab” key.
  *
  *     @example
  *     // TabIndexedElement is mixed into the ButtonWidget class
  *     // to provide a tabIndex property.
  *     var button1 = new OO.ui.ButtonWidget( {
- *         label: 'fourth',
- *         tabIndex: 4
- *     } );
- *     var button2 = new OO.ui.ButtonWidget( {
- *         label: 'second',
- *         tabIndex: 2
- *     } );
- *     var button3 = new OO.ui.ButtonWidget( {
- *         label: 'third',
- *         tabIndex: 3
- *     } );
- *     var button4 = new OO.ui.ButtonWidget( {
- *         label: 'first',
- *         tabIndex: 1
- *     } );
- *     $( 'body' ).append( button1.$element, button2.$element, button3.$element, button4.$element );
+ *             label: 'fourth',
+ *             tabIndex: 4
+ *         } ),
+ *         button2 = new OO.ui.ButtonWidget( {
+ *             label: 'second',
+ *             tabIndex: 2
+ *         } ),
+ *         button3 = new OO.ui.ButtonWidget( {
+ *             label: 'third',
+ *             tabIndex: 3
+ *         } ),
+ *         button4 = new OO.ui.ButtonWidget( {
+ *             label: 'first',
+ *             tabIndex: 1
+ *         } );
+ *     $( document.body ).append( button1.$element, button2.$element, button3.$element, button4.$element );
  *
  * @abstract
  * @class
@@ -1949,6 +1970,7 @@ OO.initClass( OO.ui.mixin.TabIndexedElement );
  *
  * @param {jQuery} $tabIndexed Element that should use the tabindex functionality
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.TabIndexedElement.prototype.setTabIndexedElement = function ( $tabIndexed ) {
        var tabIndex = this.tabIndex;
@@ -1965,6 +1987,7 @@ OO.ui.mixin.TabIndexedElement.prototype.setTabIndexedElement = function ( $tabIn
  *
  * @param {string|number|null} tabIndex Tabindex value, or `null` for no tabindex
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.TabIndexedElement.prototype.setTabIndex = function ( tabIndex ) {
        tabIndex = /^-?\d+$/.test( tabIndex ) ? Number( tabIndex ) : null;
@@ -1983,6 +2006,7 @@ OO.ui.mixin.TabIndexedElement.prototype.setTabIndex = function ( tabIndex ) {
  *
  * @private
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.TabIndexedElement.prototype.updateTabIndex = function () {
        if ( this.$tabIndexed ) {
@@ -2074,6 +2098,7 @@ OO.ui.mixin.TabIndexedElement.prototype.isLabelableNode = function ( $node ) {
  * Focus this element.
  *
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.TabIndexedElement.prototype.focus = function () {
        if ( !this.isDisabled() ) {
@@ -2086,6 +2111,7 @@ OO.ui.mixin.TabIndexedElement.prototype.focus = function () {
  * Blur this element.
  *
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.TabIndexedElement.prototype.blur = function () {
        this.$tabIndexed.blur();
@@ -2101,7 +2127,7 @@ OO.ui.mixin.TabIndexedElement.prototype.simulateLabelClick = function () {
 
 /**
  * ButtonElement is often mixed into other classes to generate a button, which is a clickable
- * interface element that can be configured with access keys for accessibility.
+ * interface element that can be configured with access keys for keyboard interaction.
  * See the [OOUI documentation on MediaWiki] [1] for examples.
  *
  * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Buttons_and_Switches#Buttons
@@ -2209,6 +2235,7 @@ OO.ui.mixin.ButtonElement.prototype.setButtonElement = function ( $button ) {
  *
  * @protected
  * @param {jQuery.Event} e Mouse down event
+ * @return {undefined/boolean} False to prevent default if event is handled
  */
 OO.ui.mixin.ButtonElement.prototype.onMouseDown = function ( e ) {
        if ( this.isDisabled() || e.which !== OO.ui.MouseButtons.LEFT ) {
@@ -2251,6 +2278,7 @@ OO.ui.mixin.ButtonElement.prototype.onMouseUp = function () {
  * @protected
  * @param {jQuery.Event} e Mouse click event
  * @fires click
+ * @return {undefined/boolean} False to prevent default if event is handled
  */
 OO.ui.mixin.ButtonElement.prototype.onClick = function ( e ) {
        if ( !this.isDisabled() && e.which === OO.ui.MouseButtons.LEFT ) {
@@ -2303,6 +2331,7 @@ OO.ui.mixin.ButtonElement.prototype.onKeyUp = function () {
  * @protected
  * @param {jQuery.Event} e Key press event
  * @fires click
+ * @return {undefined/boolean} False to prevent default if event is handled
  */
 OO.ui.mixin.ButtonElement.prototype.onKeyPress = function ( e ) {
        if ( !this.isDisabled() && ( e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) {
@@ -2326,6 +2355,7 @@ OO.ui.mixin.ButtonElement.prototype.isFramed = function () {
  *
  * @param {boolean} [framed] Make button framed, omit to toggle
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.ButtonElement.prototype.toggleFramed = function ( framed ) {
        framed = framed === undefined ? !this.framed : !!framed;
@@ -2352,6 +2382,7 @@ OO.ui.mixin.ButtonElement.prototype.toggleFramed = function ( framed ) {
  * @protected
  * @param {boolean} value Make button active
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.ButtonElement.prototype.setActive = function ( value ) {
        this.active = !!value;
@@ -2488,6 +2519,7 @@ OO.ui.mixin.GroupElement.prototype.findItemsFromData = function ( data ) {
  * @param {OO.ui.Element[]} items An array of items to add to the group
  * @param {number} [index] Index of the insertion point
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.GroupElement.prototype.addItems = function ( items, index ) {
        // Mixin method
@@ -2548,6 +2580,7 @@ OO.ui.mixin.GroupElement.prototype.insertItemElements = function ( itemWidget, i
  *
  * @param {OO.ui.Element[]} items An array of items to remove
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.GroupElement.prototype.removeItems = function ( items ) {
        var i, len, item, index;
@@ -2576,6 +2609,7 @@ OO.ui.mixin.GroupElement.prototype.removeItems = function ( items ) {
  * To remove only a subset of items from a group, use the #removeItems method.
  *
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.GroupElement.prototype.clearItems = function () {
        var i, len;
@@ -2722,10 +2756,11 @@ OO.ui.mixin.LabelElement.prototype.setLabelElement = function ( $label ) {
  * @param {jQuery|string|OO.ui.HtmlSnippet|Function|null} label Label nodes; text; a function that returns nodes or
  *  text; or null for no label
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.LabelElement.prototype.setLabel = function ( label ) {
        label = typeof label === 'function' ? OO.ui.resolveMsg( label ) : label;
-       label = ( ( typeof label === 'string' || label instanceof jQuery ) && label.length ) || ( label instanceof OO.ui.HtmlSnippet && label.toString().length ) ? label : null;
+       label = ( ( typeof label === 'string' || label instanceof $ ) && label.length ) || ( label instanceof OO.ui.HtmlSnippet && label.toString().length ) ? label : null;
 
        if ( this.label !== label ) {
                if ( this.$label ) {
@@ -2745,6 +2780,7 @@ OO.ui.mixin.LabelElement.prototype.setLabel = function ( label ) {
  *
  * @param {boolean} invisibleLabel
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.LabelElement.prototype.setInvisibleLabel = function ( invisibleLabel ) {
        invisibleLabel = !!invisibleLabel;
@@ -2768,6 +2804,7 @@ OO.ui.mixin.LabelElement.prototype.setInvisibleLabel = function ( invisibleLabel
  * @param {string} query Substring of text to highlight
  * @param {Function} [compare] Optional string comparator, e.g. Intl.Collator().compare
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.LabelElement.prototype.setHighlightedQuery = function ( text, query, compare ) {
        return this.setLabel( this.constructor.static.highlightQuery( text, query, compare ) );
@@ -2802,7 +2839,7 @@ OO.ui.mixin.LabelElement.prototype.setLabelContent = function ( label ) {
                }
        } else if ( label instanceof OO.ui.HtmlSnippet ) {
                this.$label.html( label.toString() );
-       } else if ( label instanceof jQuery ) {
+       } else if ( label instanceof $ ) {
                this.$label.empty().append( label );
        } else {
                this.$label.empty();
@@ -2829,7 +2866,7 @@ OO.ui.mixin.LabelElement.prototype.setLabelContent = function ( label ) {
  *  value using a jQuery selection. For example:
  *
  *      // Use a <div> tag instead of a <span>
- *     $icon: $("<div>")
+ *     $icon: $( '<div>' )
  *     // Use an existing icon element instead of the one generated by the class
  *     $icon: this.$element
  *     // Use an icon element from a child widget
@@ -2856,6 +2893,11 @@ OO.ui.mixin.IconElement = function OoUiMixinIconElement( config ) {
        this.icon = null;
        this.iconTitle = null;
 
+       // `iconTitle`s are deprecated since 0.30.0
+       if ( config.iconTitle !== undefined ) {
+               OO.ui.warnDeprecation( 'IconElement: Widgets with iconTitle set are deprecated, use title instead. See T76638 for details.' );
+       }
+
        // Initialization
        this.setIcon( config.icon || this.constructor.static.icon );
        this.setIconTitle( config.iconTitle || this.constructor.static.iconTitle );
@@ -2933,6 +2975,7 @@ OO.ui.mixin.IconElement.prototype.setIconElement = function ( $icon ) {
  * @param {Object|string|null} icon A symbolic icon name, a {@link #icon map of icon names} keyed
  *  by language code, or `null` to remove the icon.
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.IconElement.prototype.setIcon = function ( icon ) {
        icon = OO.isPlainObject( icon ) ? OO.ui.getLocalValue( icon, null, 'default' ) : icon;
@@ -2965,6 +3008,8 @@ OO.ui.mixin.IconElement.prototype.setIcon = function ( icon ) {
  * @param {string|Function|null} iconTitle A text string used as the icon title,
  *  a function that returns title text, or `null` for no title.
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
+ * @deprecated
  */
 OO.ui.mixin.IconElement.prototype.setIconTitle = function ( iconTitle ) {
        iconTitle =
@@ -2982,6 +3027,12 @@ OO.ui.mixin.IconElement.prototype.setIconTitle = function ( iconTitle ) {
                }
        }
 
+       // `setIconTitle is deprecated since 0.30.0
+       if ( iconTitle !== null ) {
+               // Avoid a warning when this is called from the constructor with no iconTitle set
+               OO.ui.warnDeprecation( 'IconElement: setIconTitle is deprecated, use setTitle of TitledElement instead. See T76638 for details.' );
+       }
+
        return this;
 };
 
@@ -3041,6 +3092,11 @@ OO.ui.mixin.IndicatorElement = function OoUiMixinIndicatorElement( config ) {
        this.indicator = null;
        this.indicatorTitle = null;
 
+       // `indicatorTitle`s are deprecated since 0.30.0
+       if ( config.indicatorTitle !== undefined ) {
+               OO.ui.warnDeprecation( 'IndicatorElement: Widgets with indicatorTitle set are deprecated, use title instead. See T76638 for details.' );
+       }
+
        // Initialization
        this.setIndicator( config.indicator || this.constructor.static.indicator );
        this.setIndicatorTitle( config.indicatorTitle || this.constructor.static.indicatorTitle );
@@ -3105,6 +3161,7 @@ OO.ui.mixin.IndicatorElement.prototype.setIndicatorElement = function ( $indicat
  *
  * @param {string|null} indicator Symbolic name of indicator, or `null` for no indicator
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.IndicatorElement.prototype.setIndicator = function ( indicator ) {
        indicator = typeof indicator === 'string' && indicator.length ? indicator.trim() : null;
@@ -3138,6 +3195,8 @@ OO.ui.mixin.IndicatorElement.prototype.setIndicator = function ( indicator ) {
  * @param {string|Function|null} indicatorTitle Indicator title text, a function that returns text, or
  *   `null` for no indicator title
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
+ * @deprecated
  */
 OO.ui.mixin.IndicatorElement.prototype.setIndicatorTitle = function ( indicatorTitle ) {
        indicatorTitle =
@@ -3155,6 +3214,12 @@ OO.ui.mixin.IndicatorElement.prototype.setIndicatorTitle = function ( indicatorT
                }
        }
 
+       // `setIndicatorTitle is deprecated since 0.30.0
+       if ( indicatorTitle !== null ) {
+               // Avoid a warning when this is called from the constructor with no indicatorTitle set
+               OO.ui.warnDeprecation( 'IndicatorElement: setIndicatorTitle is deprecated, use setTitle of TitledElement instead. See T76638 for details.' );
+       }
+
        return this;
 };
 
@@ -3187,7 +3252,7 @@ OO.ui.mixin.IndicatorElement.prototype.getIndicatorTitle = function () {
  *
  * The library currently contains the following styling flags for general use:
  *
- * - **progressive**:  Progressive styling is applied to convey that the widget will move the user forward in a process.
+ * - **progressive**: Progressive styling is applied to convey that the widget will move the user forward in a process.
  * - **destructive**: Destructive styling is applied to convey that the widget will remove something.
  *
  * The flags affect the appearance of the buttons:
@@ -3195,14 +3260,14 @@ OO.ui.mixin.IndicatorElement.prototype.getIndicatorTitle = function () {
  *     @example
  *     // FlaggedElement is mixed into ButtonWidget to provide styling flags
  *     var button1 = new OO.ui.ButtonWidget( {
- *         label: 'Progressive',
- *         flags: 'progressive'
- *     } );
- *     var button2 = new OO.ui.ButtonWidget( {
- *         label: 'Destructive',
- *         flags: 'destructive'
- *     } );
- *     $( 'body' ).append( button1.$element, button2.$element );
+ *             label: 'Progressive',
+ *             flags: 'progressive'
+ *         } ),
+ *         button2 = new OO.ui.ButtonWidget( {
+ *             label: 'Destructive',
+ *             flags: 'destructive'
+ *         } );
+ *     $( document.body ).append( button1.$element, button2.$element );
  *
  * {@link OO.ui.ActionWidget ActionWidgets}, which are a special kind of button that execute an action, use these flags: **primary** and **safe**.
  * Please see the [OOUI documentation on MediaWiki] [1] for more information.
@@ -3293,6 +3358,7 @@ OO.ui.mixin.FlaggedElement.prototype.getFlags = function () {
  * Clear all flags.
  *
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  * @fires flag
  */
 OO.ui.mixin.FlaggedElement.prototype.clearFlags = function () {
@@ -3325,6 +3391,7 @@ OO.ui.mixin.FlaggedElement.prototype.clearFlags = function () {
  *  or an object keyed by flag name with a boolean value that indicates whether the flag should
  *  be added (`true`) or removed (`false`).
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  * @fires flag
  */
 OO.ui.mixin.FlaggedElement.prototype.setFlags = function ( flags ) {
@@ -3391,13 +3458,13 @@ OO.ui.mixin.FlaggedElement.prototype.setFlags = function ( flags ) {
  * the mouse over the element. Titles are not visible on touch devices.
  *
  *     @example
- *     // TitledElement provides a 'title' attribute to the
- *     // ButtonWidget class
+ *     // TitledElement provides a `title` attribute to the
+ *     // ButtonWidget class.
  *     var button = new OO.ui.ButtonWidget( {
  *         label: 'Button with Title',
  *         title: 'I am a button'
  *     } );
- *     $( 'body' ).append( button.$element );
+ *     $( document.body ).append( button.$element );
  *
  * @abstract
  * @class
@@ -3465,6 +3532,7 @@ OO.ui.mixin.TitledElement.prototype.setTitledElement = function ( $titled ) {
  *
  * @param {string|Function|null} title Title text, a function that returns text, or `null` for no title
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.TitledElement.prototype.setTitle = function ( title ) {
        title = typeof title === 'function' ? OO.ui.resolveMsg( title ) : title;
@@ -3483,6 +3551,7 @@ OO.ui.mixin.TitledElement.prototype.setTitle = function ( title ) {
  *
  * @protected
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.TitledElement.prototype.updateTitle = function () {
        var title = this.getTitle();
@@ -3510,19 +3579,19 @@ OO.ui.mixin.TitledElement.prototype.getTitle = function () {
 };
 
 /**
- * AccessKeyedElement is mixed into other classes to provide an `accesskey` attribute.
+ * AccessKeyedElement is mixed into other classes to provide an `accesskey` HTML attribute.
  * Accesskeys allow an user to go to a specific element by using
  * a shortcut combination of a browser specific keys + the key
  * set to the field.
  *
  *     @example
- *     // AccessKeyedElement provides an 'accesskey' attribute to the
- *     // ButtonWidget class
+ *     // AccessKeyedElement provides an `accesskey` attribute to the
+ *     // ButtonWidget class.
  *     var button = new OO.ui.ButtonWidget( {
  *         label: 'Button with Accesskey',
  *         accessKey: 'k'
  *     } );
- *     $( 'body' ).append( button.$element );
+ *     $( document.body ).append( button.$element );
  *
  * @abstract
  * @class
@@ -3595,6 +3664,7 @@ OO.ui.mixin.AccessKeyedElement.prototype.setAccessKeyedElement = function ( $acc
  *
  * @param {string|Function|null} accessKey Key, a function that returns a key, or `null` for no accesskey
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.AccessKeyedElement.prototype.setAccessKey = function ( accessKey ) {
        accessKey = typeof accessKey === 'string' ? OO.ui.resolveMsg( accessKey ) : null;
@@ -3662,13 +3732,13 @@ OO.ui.mixin.AccessKeyedElement.prototype.formatTitleWithAccessKey = function ( t
  * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Buttons_and_Switches
  *
  *     @example
- *     // A button widget
+ *     // A button widget.
  *     var button = new OO.ui.ButtonWidget( {
  *         label: 'Button with Icon',
  *         icon: 'trash',
  *         title: 'Remove'
  *     } );
- *     $( 'body' ).append( button.$element );
+ *     $( document.body ).append( button.$element );
  *
  * NOTE: HTML form buttons should use the OO.ui.ButtonInputWidget class.
  *
@@ -3785,6 +3855,8 @@ OO.ui.ButtonWidget.prototype.getNoFollow = function () {
  * Set hyperlink location.
  *
  * @param {string|null} href Hyperlink location, null to remove
+ * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.ButtonWidget.prototype.setHref = function ( href ) {
        href = typeof href === 'string' ? href : null;
@@ -3806,6 +3878,7 @@ OO.ui.ButtonWidget.prototype.setHref = function ( href ) {
  *
  * @private
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.ButtonWidget.prototype.updateHref = function () {
        if ( this.href !== null && !this.isDisabled() ) {
@@ -3831,6 +3904,7 @@ OO.ui.ButtonWidget.prototype.onDisable = function () {
  * Set hyperlink target.
  *
  * @param {string|null} target Hyperlink target, null to remove
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.ButtonWidget.prototype.setTarget = function ( target ) {
        target = typeof target === 'string' ? target : null;
@@ -3851,6 +3925,7 @@ OO.ui.ButtonWidget.prototype.setTarget = function ( target ) {
  * Set search engine traversal hint.
  *
  * @param {boolean} noFollow True if search engines should avoid traversing this hyperlink
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.ButtonWidget.prototype.setNoFollow = function ( noFollow ) {
        noFollow = typeof noFollow === 'boolean' ? noFollow : true;
@@ -3883,27 +3958,28 @@ OO.ui.ButtonWidget.prototype.setNoFollow = function ( noFollow ) {
  * removed, and cleared from the group.
  *
  *     @example
- *     // Example: A ButtonGroupWidget with two buttons
+ *     // A ButtonGroupWidget with two buttons.
  *     var button1 = new OO.ui.PopupButtonWidget( {
- *         label: 'Select a category',
- *         icon: 'menu',
- *         popup: {
- *             $content: $( '<p>List of categories...</p>' ),
- *             padded: true,
- *             align: 'left'
- *         }
- *     } );
- *     var button2 = new OO.ui.ButtonWidget( {
- *         label: 'Add item'
- *     });
- *     var buttonGroup = new OO.ui.ButtonGroupWidget( {
- *         items: [button1, button2]
- *     } );
- *     $( 'body' ).append( buttonGroup.$element );
+ *             label: 'Select a category',
+ *             icon: 'menu',
+ *             popup: {
+ *                 $content: $( '<p>List of categories…</p>' ),
+ *                 padded: true,
+ *                 align: 'left'
+ *             }
+ *         } ),
+ *         button2 = new OO.ui.ButtonWidget( {
+ *             label: 'Add item'
+ *         } ),
+ *         buttonGroup = new OO.ui.ButtonGroupWidget( {
+ *             items: [ button1, button2 ]
+ *         } );
+ *     $( document.body ).append( buttonGroup.$element );
  *
  * @class
  * @extends OO.ui.Widget
  * @mixins OO.ui.mixin.GroupElement
+ * @mixins OO.ui.mixin.TitledElement
  *
  * @constructor
  * @param {Object} [config] Configuration options
@@ -3918,6 +3994,7 @@ OO.ui.ButtonGroupWidget = function OoUiButtonGroupWidget( config ) {
 
        // Mixin constructors
        OO.ui.mixin.GroupElement.call( this, $.extend( {}, config, { $group: this.$element } ) );
+       OO.ui.mixin.TitledElement.call( this, config );
 
        // Initialization
        this.$element.addClass( 'oo-ui-buttonGroupWidget' );
@@ -3930,6 +4007,7 @@ OO.ui.ButtonGroupWidget = function OoUiButtonGroupWidget( config ) {
 
 OO.inheritClass( OO.ui.ButtonGroupWidget, OO.ui.Widget );
 OO.mixinClass( OO.ui.ButtonGroupWidget, OO.ui.mixin.GroupElement );
+OO.mixinClass( OO.ui.ButtonGroupWidget, OO.ui.mixin.TitledElement );
 
 /* Static Properties */
 
@@ -3945,6 +4023,7 @@ OO.ui.ButtonGroupWidget.static.tagName = 'span';
  * Focus the widget
  *
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.ButtonGroupWidget.prototype.focus = function () {
        if ( !this.isDisabled() ) {
@@ -3968,16 +4047,16 @@ OO.ui.ButtonGroupWidget.prototype.simulateLabelClick = function () {
  * for a list of icons included in the library.
  *
  *     @example
- *     // An icon widget with a label
+ *     // An IconWidget with a label via LabelWidget.
  *     var myIcon = new OO.ui.IconWidget( {
- *         icon: 'help',
- *         title: 'Help'
- *      } );
- *      // Create a label.
- *      var iconLabel = new OO.ui.LabelWidget( {
- *          label: 'Help'
- *      } );
- *      $( 'body' ).append( myIcon.$element, iconLabel.$element );
+ *             icon: 'help',
+ *             title: 'Help'
+ *          } ),
+ *          // Create a label.
+ *          iconLabel = new OO.ui.LabelWidget( {
+ *              label: 'Help'
+ *          } );
+ *      $( document.body ).append( myIcon.$element, iconLabel.$element );
  *
  * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Icons,_Indicators,_and_Labels#Icons
  *
@@ -4033,17 +4112,18 @@ OO.ui.IconWidget.static.tagName = 'span';
  * indicators included in the library, please see the [OOUI documentation on MediaWiki][1].
  *
  *     @example
- *     // Example of an indicator widget
+ *     // An indicator widget.
  *     var indicator1 = new OO.ui.IndicatorWidget( {
- *         indicator: 'required'
- *     } );
- *
- *     // Create a fieldset layout to add a label
- *     var fieldset = new OO.ui.FieldsetLayout();
+ *             indicator: 'required'
+ *         } ),
+ *         // Create a fieldset layout to add a label.
+ *         fieldset = new OO.ui.FieldsetLayout();
  *     fieldset.addItems( [
- *         new OO.ui.FieldLayout( indicator1, { label: 'A required indicator:' } )
+ *         new OO.ui.FieldLayout( indicator1, {
+ *             label: 'A required indicator:'
+ *         } )
  *     ] );
- *     $( 'body' ).append( fieldset.$element );
+ *     $( document.body ).append( fieldset.$element );
  *
  * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Icons,_Indicators,_and_Labels#Indicators
  *
@@ -4104,20 +4184,20 @@ OO.ui.IndicatorWidget.static.tagName = 'span';
  * will come into focus when the label is clicked.
  *
  *     @example
- *     // Examples of LabelWidgets
+ *     // Two LabelWidgets.
  *     var label1 = new OO.ui.LabelWidget( {
- *         label: 'plaintext label'
- *     } );
- *     var label2 = new OO.ui.LabelWidget( {
- *         label: $( '<a href="default.html">jQuery label</a>' )
- *     } );
- *     // Create a fieldset layout with fields for each example
- *     var fieldset = new OO.ui.FieldsetLayout();
+ *             label: 'plaintext label'
+ *         } ),
+ *         label2 = new OO.ui.LabelWidget( {
+ *             label: $( '<a>' ).attr( 'href', 'default.html' ).text( 'jQuery label' )
+ *         } ),
+ *         // Create a fieldset layout with fields for each example.
+ *         fieldset = new OO.ui.FieldsetLayout();
  *     fieldset.addItems( [
  *         new OO.ui.FieldLayout( label1 ),
  *         new OO.ui.FieldLayout( label2 )
  *     ] );
- *     $( 'body' ).append( fieldset.$element );
+ *     $( document.body ).append( fieldset.$element );
  *
  * @class
  * @extends OO.ui.Widget
@@ -4215,7 +4295,7 @@ OO.ui.LabelWidget.static.tagName = 'label';
  *     };
  *
  *     var windowManager = new OO.ui.WindowManager();
- *     $( 'body' ).append( windowManager.$element );
+ *     $( document.body ).append( windowManager.$element );
  *
  *     var dialog = new MessageDialog();
  *     windowManager.addWindows( [ dialog ] );
@@ -4276,6 +4356,7 @@ OO.ui.mixin.PendingElement.prototype.isPending = function () {
  * (i.e., the number of calls to #pushPending and #popPending is the same).
  *
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.PendingElement.prototype.pushPending = function () {
        if ( this.pending === 0 ) {
@@ -4292,6 +4373,7 @@ OO.ui.mixin.PendingElement.prototype.pushPending = function () {
  * (i.e., the number of calls to #pushPending and #popPending is the same).
  *
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.PendingElement.prototype.popPending = function () {
        if ( this.pending === 1 ) {
@@ -4432,6 +4514,7 @@ OO.ui.mixin.FloatableElement.prototype.setHorizontalPosition = function ( positi
  *
  * @param {boolean} [positioning] Enable positioning, omit to toggle
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.FloatableElement.prototype.togglePositioning = function ( positioning ) {
        var closestScrollableOfContainer;
@@ -4450,11 +4533,6 @@ OO.ui.mixin.FloatableElement.prototype.togglePositioning = function ( positionin
        if ( this.positioning !== positioning ) {
                this.positioning = positioning;
 
-               this.needsCustomPosition =
-                       this.verticalPosition !== 'below' ||
-                       this.horizontalPosition !== 'start' ||
-                       !OO.ui.contains( this.$floatableContainer[ 0 ], this.$floatable[ 0 ] );
-
                closestScrollableOfContainer = OO.ui.Element.static.getClosestScrollableContainer( this.$floatableContainer[ 0 ] );
                // If the scrollable is the root, we have to listen to scroll events
                // on the window because of browser inconsistencies.
@@ -4565,6 +4643,7 @@ OO.ui.mixin.FloatableElement.prototype.isFloatableOutOfView = function () {
  * This should only be done when both of them are attached to the DOM and visible.
  *
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.FloatableElement.prototype.position = function () {
        if ( !this.positioning ) {
@@ -4593,10 +4672,6 @@ OO.ui.mixin.FloatableElement.prototype.position = function () {
                this.$floatable.removeClass( 'oo-ui-element-hidden' );
        }
 
-       if ( !this.needsCustomPosition ) {
-               return this;
-       }
-
        this.$floatable.css( this.computePosition() );
 
        // We updated the position, so re-evaluate the clipping state.
@@ -4817,6 +4892,7 @@ OO.ui.mixin.ClippableElement.prototype.setClippableContainer = function ( $clipp
  *
  * @param {boolean} [clipping] Enable clipping, omit to toggle
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.ClippableElement.prototype.toggleClipping = function ( clipping ) {
        clipping = clipping === undefined ? !this.clipping : !!clipping;
@@ -4971,6 +5047,7 @@ OO.ui.mixin.ClippableElement.prototype.getVerticalAnchorEdge = function () {
  * beyond the edge, something reasonable will happen before clip() is called.
  *
  * @chainable
+ * @return {OO.ui.Element} The element, for chaining
  */
 OO.ui.mixin.ClippableElement.prototype.clip = function () {
        var extraHeight, extraWidth, viewportSpacing,
@@ -5078,6 +5155,7 @@ OO.ui.mixin.ClippableElement.prototype.clip = function () {
                // The order matters here. If overflow is not set first, Chrome displays bogus scrollbars. See T157672.
                // Forcing a reflow is a smaller workaround than calling reconsiderScrollbars() for this case.
                this.$clippable.css( 'overflowX', 'scroll' );
+               // eslint-disable-next-line no-void
                void this.$clippable[ 0 ].offsetHeight; // Force reflow
                this.$clippable.css( {
                        width: Math.max( 0, allotedWidth ),
@@ -5094,6 +5172,7 @@ OO.ui.mixin.ClippableElement.prototype.clip = function () {
                // The order matters here. If overflow is not set first, Chrome displays bogus scrollbars. See T157672.
                // Forcing a reflow is a smaller workaround than calling reconsiderScrollbars() for this case.
                this.$clippable.css( 'overflowY', 'scroll' );
+               // eslint-disable-next-line no-void
                void this.$clippable[ 0 ].offsetHeight; // Force reflow
                this.$clippable.css( {
                        height: Math.max( 0, allotedHeight ),
@@ -5126,14 +5205,14 @@ OO.ui.mixin.ClippableElement.prototype.clip = function () {
  * Unlike most widgets, PopupWidget is initially hidden and must be shown by calling #toggle.
  *
  *     @example
- *     // A popup widget.
+ *     // A PopupWidget.
  *     var popup = new OO.ui.PopupWidget( {
  *         $content: $( '<p>Hi there!</p>' ),
  *         padded: true,
  *         width: 300
  *     } );
  *
- *     $( 'body' ).append( popup.$element );
+ *     $( document.body ).append( popup.$element );
  *     // To display the popup, toggle the visibility to 'true'.
  *     popup.toggle( true );
  *
@@ -5235,7 +5314,7 @@ OO.ui.PopupWidget = function OoUiPopupWidget( config ) {
                .append( this.$popup, this.$anchor );
        // Move content, which was added to #$element by OO.ui.Widget, to the body
        // FIXME This is gross, we should use '$body' or something for the config
-       if ( config.$content instanceof jQuery ) {
+       if ( config.$content instanceof $ ) {
                this.$body.append( config.$content );
        }
 
@@ -5901,7 +5980,7 @@ OO.ui.mixin.PopupElement.prototype.getPopup = function () {
  * which is used to display additional information or options.
  *
  *     @example
- *     // Example of a popup button.
+ *     // A PopupButtonWidget.
  *     var popupButton = new OO.ui.PopupButtonWidget( {
  *         label: 'Popup button with options',
  *         icon: 'menu',
@@ -5912,7 +5991,7 @@ OO.ui.mixin.PopupElement.prototype.getPopup = function () {
  *         }
  *     } );
  *     // Append the button to the DOM.
- *     $( 'body' ).append( popupButton.$element );
+ *     $( document.body ).append( popupButton.$element );
  *
  * @class
  * @extends OO.ui.ButtonWidget
@@ -5998,6 +6077,7 @@ OO.mixinClass( OO.ui.mixin.GroupWidget, OO.ui.mixin.GroupElement );
  *
  * @param {boolean} disabled Disable widget
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.mixin.GroupWidget.prototype.setDisabled = function ( disabled ) {
        var i, len;
@@ -6053,6 +6133,7 @@ OO.ui.mixin.ItemWidget.prototype.isDisabled = function () {
  *
  * @param {OO.ui.mixin.GroupElement|null} group Group element, null if none
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.mixin.ItemWidget.prototype.setElementGroup = function ( group ) {
        // Parent method
@@ -6079,6 +6160,7 @@ OO.ui.mixin.ItemWidget.prototype.setElementGroup = function ( group ) {
  * @mixins OO.ui.mixin.LabelElement
  * @mixins OO.ui.mixin.FlaggedElement
  * @mixins OO.ui.mixin.AccessKeyedElement
+ * @mixins OO.ui.mixin.TitledElement
  *
  * @constructor
  * @param {Object} [config] Configuration options
@@ -6095,6 +6177,7 @@ OO.ui.OptionWidget = function OoUiOptionWidget( config ) {
        OO.ui.mixin.LabelElement.call( this, config );
        OO.ui.mixin.FlaggedElement.call( this, config );
        OO.ui.mixin.AccessKeyedElement.call( this, config );
+       OO.ui.mixin.TitledElement.call( this, config );
 
        // Properties
        this.selected = false;
@@ -6119,6 +6202,7 @@ OO.mixinClass( OO.ui.OptionWidget, OO.ui.mixin.ItemWidget );
 OO.mixinClass( OO.ui.OptionWidget, OO.ui.mixin.LabelElement );
 OO.mixinClass( OO.ui.OptionWidget, OO.ui.mixin.FlaggedElement );
 OO.mixinClass( OO.ui.OptionWidget, OO.ui.mixin.AccessKeyedElement );
+OO.mixinClass( OO.ui.OptionWidget, OO.ui.mixin.TitledElement );
 
 /* Static Properties */
 
@@ -6227,6 +6311,7 @@ OO.ui.OptionWidget.prototype.isPressed = function () {
  *
  * @param {boolean} [state=false] Select option
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.OptionWidget.prototype.setSelected = function ( state ) {
        if ( this.constructor.static.selectable ) {
@@ -6250,6 +6335,7 @@ OO.ui.OptionWidget.prototype.setSelected = function ( state ) {
  *
  * @param {boolean} [state=false] Highlight option
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.OptionWidget.prototype.setHighlighted = function ( state ) {
        if ( this.constructor.static.highlightable ) {
@@ -6268,6 +6354,7 @@ OO.ui.OptionWidget.prototype.setHighlighted = function ( state ) {
  *
  * @param {boolean} [state=false] Press option
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.OptionWidget.prototype.setPressed = function ( state ) {
        if ( this.constructor.static.pressable ) {
@@ -6301,7 +6388,7 @@ OO.ui.OptionWidget.prototype.getMatchText = function () {
  * information, please see the [OOUI documentation on MediaWiki][1].
  *
  *     @example
- *     // Example of a select widget with three options
+ *     // A select widget with three options.
  *     var select = new OO.ui.SelectWidget( {
  *         items: [
  *             new OO.ui.OptionWidget( {
@@ -6318,7 +6405,7 @@ OO.ui.OptionWidget.prototype.getMatchText = function () {
  *             } )
  *         ]
  *     } );
- *     $( 'body' ).append( select.$element );
+ *     $( document.body ).append( select.$element );
  *
  * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Selects_and_Options
  *
@@ -6479,6 +6566,7 @@ OO.ui.SelectWidget.prototype.onFocus = function ( event ) {
  *
  * @private
  * @param {jQuery.Event} e Mouse down event
+ * @return {undefined/boolean} False to prevent default if event is handled
  */
 OO.ui.SelectWidget.prototype.onMouseDown = function ( e ) {
        var item;
@@ -6501,6 +6589,7 @@ OO.ui.SelectWidget.prototype.onMouseDown = function ( e ) {
  *
  * @private
  * @param {MouseEvent} e Mouse up event
+ * @return {undefined/boolean} False to prevent default if event is handled
  */
 OO.ui.SelectWidget.prototype.onDocumentMouseUp = function ( e ) {
        var item;
@@ -6559,6 +6648,7 @@ OO.ui.SelectWidget.prototype.onMouseMove = function () {
  *
  * @private
  * @param {jQuery.Event} e Mouse over event
+ * @return {undefined/boolean} False to prevent default if event is handled
  */
 OO.ui.SelectWidget.prototype.onMouseOver = function ( e ) {
        var item;
@@ -6577,6 +6667,7 @@ OO.ui.SelectWidget.prototype.onMouseOver = function ( e ) {
  *
  * @private
  * @param {jQuery.Event} e Mouse over event
+ * @return {undefined/boolean} False to prevent default if event is handled
  */
 OO.ui.SelectWidget.prototype.onMouseLeave = function () {
        if ( !this.isDisabled() ) {
@@ -6716,6 +6807,7 @@ OO.ui.SelectWidget.prototype.clearKeyPressBuffer = function () {
  *
  * @protected
  * @param {KeyboardEvent} e Key press event
+ * @return {undefined/boolean} False to prevent default if event is handled
  */
 OO.ui.SelectWidget.prototype.onDocumentKeyPress = function ( e ) {
        var c, filter, item;
@@ -6727,7 +6819,9 @@ OO.ui.SelectWidget.prototype.onDocumentKeyPress = function ( e ) {
                }
                return;
        }
+       // eslint-disable-next-line no-restricted-properties
        if ( String.fromCodePoint ) {
+               // eslint-disable-next-line no-restricted-properties
                c = String.fromCodePoint( e.charCode );
        } else {
                c = String.fromCharCode( e.charCode );
@@ -6784,7 +6878,9 @@ OO.ui.SelectWidget.prototype.onKeyPress = function () {
 OO.ui.SelectWidget.prototype.getItemMatcher = function ( s, exact ) {
        var re;
 
+       // eslint-disable-next-line no-restricted-properties
        if ( s.normalize ) {
+               // eslint-disable-next-line no-restricted-properties
                s = s.normalize();
        }
        s = exact ? s.trim() : s.replace( /^\s+/, '' );
@@ -6795,7 +6891,9 @@ OO.ui.SelectWidget.prototype.getItemMatcher = function ( s, exact ) {
        re = new RegExp( re, 'i' );
        return function ( item ) {
                var matchText = item.getMatchText();
+               // eslint-disable-next-line no-restricted-properties
                if ( matchText.normalize ) {
+                       // eslint-disable-next-line no-restricted-properties
                        matchText = matchText.normalize();
                }
                return re.test( matchText );
@@ -6923,6 +7021,7 @@ OO.ui.SelectWidget.prototype.togglePressed = function ( pressed ) {
  * @param {OO.ui.OptionWidget} [item] Item to highlight, omit for no highlight
  * @fires highlight
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.SelectWidget.prototype.highlightItem = function ( item ) {
        var i, len, highlighted,
@@ -6994,6 +7093,7 @@ OO.ui.SelectWidget.prototype.getItemFromLabel = function ( label, prefix ) {
  * @param {boolean} [prefix=false] Allow a prefix match, if only a single item matches
  * @fires select
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.SelectWidget.prototype.selectItemByLabel = function ( label, prefix ) {
        var itemFromLabel = this.getItemFromLabel( label, !!prefix );
@@ -7010,6 +7110,7 @@ OO.ui.SelectWidget.prototype.selectItemByLabel = function ( label, prefix ) {
  * @param {Object|string} [data] Value of the item to select, omit to deselect all
  * @fires select
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.SelectWidget.prototype.selectItemByData = function ( data ) {
        var itemFromData = this.findItemFromData( data );
@@ -7026,7 +7127,8 @@ OO.ui.SelectWidget.prototype.selectItemByData = function ( data ) {
  * @param {OO.ui.OptionWidget} [item] Item to select, omit to deselect all
  * @fires select
  * @chainable
- */
+ * @return {OO.ui.Widget} The widget, for chaining
+*/
 OO.ui.SelectWidget.prototype.selectItem = function ( item ) {
        var i, len, selected,
                changed = false;
@@ -7062,6 +7164,7 @@ OO.ui.SelectWidget.prototype.selectItem = function ( item ) {
  * @param {OO.ui.OptionWidget} [item] Item to press, omit to depress all
  * @fires press
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.SelectWidget.prototype.pressItem = function ( item ) {
        var i, len, pressed,
@@ -7094,6 +7197,7 @@ OO.ui.SelectWidget.prototype.pressItem = function ( item ) {
  * @param {OO.ui.OptionWidget} item Item to choose
  * @fires choose
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.SelectWidget.prototype.chooseItem = function ( item ) {
        if ( item ) {
@@ -7161,6 +7265,7 @@ OO.ui.SelectWidget.prototype.findFirstSelectableItem = function () {
  * @param {number} [index] Index to insert items after
  * @fires add
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.SelectWidget.prototype.addItems = function ( items, index ) {
        // Mixin method
@@ -7180,6 +7285,7 @@ OO.ui.SelectWidget.prototype.addItems = function ( items, index ) {
  * @param {OO.ui.OptionWidget[]} items Items to remove
  * @fires remove
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.SelectWidget.prototype.removeItems = function ( items ) {
        var i, len, item;
@@ -7207,6 +7313,7 @@ OO.ui.SelectWidget.prototype.removeItems = function ( items ) {
  *
  * @fires remove
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.SelectWidget.prototype.clearItems = function () {
        var items = this.items.slice();
@@ -7225,7 +7332,7 @@ OO.ui.SelectWidget.prototype.clearItems = function () {
 /**
  * Set the DOM element which has focus while the user is interacting with this SelectWidget.
  *
- * Currently this is just used to set `aria-activedescendant` on it.
+ * This is used to set `aria-activedescendant` and `aria-expanded` on it.
  *
  * @protected
  * @param {jQuery} $focusOwner
@@ -7242,7 +7349,7 @@ OO.ui.SelectWidget.prototype.setFocusOwner = function ( $focusOwner ) {
  * [OOUI documentation on MediaWiki][1].
  *
  *     @example
- *     // Decorated options in a select widget
+ *     // Decorated options in a select widget.
  *     var select = new OO.ui.SelectWidget( {
  *         items: [
  *             new OO.ui.DecoratedOptionWidget( {
@@ -7257,7 +7364,7 @@ OO.ui.SelectWidget.prototype.setFocusOwner = function ( $focusOwner ) {
  *             } )
  *         ]
  *     } );
- *     $( 'body' ).append( select.$element );
+ *     $( document.body ).append( select.$element );
  *
  * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Selects_and_Options
  *
@@ -7336,7 +7443,7 @@ OO.ui.MenuOptionWidget.static.scrollIntoViewOnSelect = true;
  * {@link OO.ui.MenuOptionWidget menu options}. MenuSectionOptionWidgets cannot be highlighted or selected.
  *
  *     @example
- *     var myDropdown = new OO.ui.DropdownWidget( {
+ *     var dropdown = new OO.ui.DropdownWidget( {
  *         menu: {
  *             items: [
  *                 new OO.ui.MenuSectionOptionWidget( {
@@ -7360,7 +7467,7 @@ OO.ui.MenuOptionWidget.static.scrollIntoViewOnSelect = true;
  *             ]
  *         }
  *     } );
- *     $( 'body' ).append( myDropdown.$element );
+ *     $( document.body ).append( dropdown.$element );
  *
  * @class
  * @extends OO.ui.DecoratedOptionWidget
@@ -7480,6 +7587,7 @@ OO.ui.MenuSelectWidget = function OoUiMenuSelectWidget( config ) {
        // TODO: Find a better way to handle post-constructor setup
        this.visible = false;
        this.$element.addClass( 'oo-ui-element-hidden' );
+       this.$focusOwner.attr( 'aria-expanded', 'false' );
 };
 
 /* Setup */
@@ -7610,6 +7718,10 @@ OO.ui.MenuSelectWidget.prototype.updateItemVisibility = function () {
                        this.scrollItemIntoView( this.items[ 0 ] );
                }
 
+               if ( !anyVisible ) {
+                       this.highlightItem( null );
+               }
+
                this.$element.toggleClass( 'oo-ui-menuSelectWidget-invisible', !anyVisible );
 
                if ( this.highlightOnFilter ) {
@@ -7691,6 +7803,7 @@ OO.ui.MenuSelectWidget.prototype.unbindDocumentKeyPressListener = function () {
  *
  * @param {OO.ui.OptionWidget} item Item to choose
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.MenuSelectWidget.prototype.chooseItem = function ( item ) {
        OO.ui.MenuSelectWidget.parent.prototype.chooseItem.call( this, item );
@@ -7848,7 +7961,7 @@ OO.ui.MenuSelectWidget.prototype.toggle = function ( visible ) {
  * OO.ui.DropdownInputWidget instead.
  *
  *     @example
- *     // Example: A DropdownWidget with a menu that contains three options
+ *     // A DropdownWidget with a menu that contains three options.
  *     var dropDown = new OO.ui.DropdownWidget( {
  *         label: 'Dropdown menu: Select a menu option',
  *         menu: {
@@ -7869,11 +7982,11 @@ OO.ui.MenuSelectWidget.prototype.toggle = function ( visible ) {
  *         }
  *     } );
  *
- *     $( 'body' ).append( dropDown.$element );
+ *     $( document.body ).append( dropDown.$element );
  *
  *     dropDown.getMenu().selectItemByData( 'b' );
  *
- *     dropDown.getMenu().findSelectedItem().getData(); // returns 'b'
+ *     dropDown.getMenu().findSelectedItem().getData(); // Returns 'b'.
  *
  * For more information, please see the [OOUI documentation on MediaWiki] [1].
  *
@@ -7903,7 +8016,7 @@ OO.ui.DropdownWidget = function OoUiDropdownWidget( config ) {
        OO.ui.DropdownWidget.parent.call( this, config );
 
        // Properties (must be set before TabIndexedElement constructor call)
-       this.$handle = $( '<span>' );
+       this.$handle = $( '<button>' );
        this.$overlay = ( config.$overlay === true ? OO.ui.getDefaultOverlay() : config.$overlay ) || this.$element;
 
        // Mixin constructors
@@ -7936,9 +8049,8 @@ OO.ui.DropdownWidget = function OoUiDropdownWidget( config ) {
        this.$handle
                .addClass( 'oo-ui-dropdownWidget-handle' )
                .attr( {
-                       role: 'combobox',
                        'aria-owns': this.menu.getElementId(),
-                       'aria-autocomplete': 'list'
+                       'aria-haspopup': 'listbox'
                } )
                .append( this.$icon, this.$label, this.$indicator );
        this.$element
@@ -7984,7 +8096,7 @@ OO.ui.DropdownWidget.prototype.onMenuSelect = function ( item ) {
        selectedLabel = item.getLabel();
 
        // If the label is a DOM element, clone it, because setLabel will append() it
-       if ( selectedLabel instanceof jQuery ) {
+       if ( selectedLabel instanceof $ ) {
                selectedLabel = selectedLabel.clone();
        }
 
@@ -7999,10 +8111,6 @@ OO.ui.DropdownWidget.prototype.onMenuSelect = function ( item ) {
  */
 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()
-       );
 };
 
 /**
@@ -8010,6 +8118,7 @@ OO.ui.DropdownWidget.prototype.onMenuToggle = function ( isVisible ) {
  *
  * @private
  * @param {jQuery.Event} e Mouse click event
+ * @return {undefined/boolean} False to prevent default if event is handled
  */
 OO.ui.DropdownWidget.prototype.onClick = function ( e ) {
        if ( !this.isDisabled() && e.which === OO.ui.MouseButtons.LEFT ) {
@@ -8023,6 +8132,7 @@ OO.ui.DropdownWidget.prototype.onClick = function ( e ) {
  *
  * @private
  * @param {jQuery.Event} e Key down event
+ * @return {undefined/boolean} False to prevent default if event is handled
  */
 OO.ui.DropdownWidget.prototype.onKeyDown = function ( e ) {
        if (
@@ -8152,23 +8262,21 @@ OO.ui.RadioOptionWidget.prototype.setDisabled = function ( disabled ) {
  *     @example
  *     // A RadioSelectWidget with RadioOptions.
  *     var option1 = new OO.ui.RadioOptionWidget( {
- *         data: 'a',
- *         label: 'Selected radio option'
- *     } );
- *
- *     var option2 = new OO.ui.RadioOptionWidget( {
- *         data: 'b',
- *         label: 'Unselected radio option'
- *     } );
- *
- *     var radioSelect=new OO.ui.RadioSelectWidget( {
- *         items: [ option1, option2 ]
- *      } );
+ *             data: 'a',
+ *             label: 'Selected radio option'
+ *         } ),
+ *         option2 = new OO.ui.RadioOptionWidget( {
+ *             data: 'b',
+ *             label: 'Unselected radio option'
+ *         } );
+ *         radioSelect = new OO.ui.RadioSelectWidget( {
+ *             items: [ option1, option2 ]
+ *         } );
  *
  *     // Select 'option 1' using the RadioSelectWidget's selectItem() method.
  *     radioSelect.selectItem( option1 );
  *
- *     $( 'body' ).append( radioSelect.$element );
+ *     $( document.body ).append( radioSelect.$element );
  *
  * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Selects_and_Options
 
@@ -8216,6 +8324,7 @@ OO.mixinClass( OO.ui.RadioSelectWidget, OO.ui.mixin.TabIndexedElement );
  * @extends OO.ui.Widget
  * @mixins OO.ui.mixin.ItemWidget
  * @mixins OO.ui.mixin.LabelElement
+ * @mixins OO.ui.mixin.TitledElement
  *
  * @constructor
  * @param {Object} [config] Configuration options
@@ -8231,6 +8340,7 @@ OO.ui.MultioptionWidget = function OoUiMultioptionWidget( config ) {
        // Mixin constructors
        OO.ui.mixin.ItemWidget.call( this );
        OO.ui.mixin.LabelElement.call( this, config );
+       OO.ui.mixin.TitledElement.call( this, config );
 
        // Properties
        this.selected = null;
@@ -8247,6 +8357,7 @@ OO.ui.MultioptionWidget = function OoUiMultioptionWidget( config ) {
 OO.inheritClass( OO.ui.MultioptionWidget, OO.ui.Widget );
 OO.mixinClass( OO.ui.MultioptionWidget, OO.ui.mixin.ItemWidget );
 OO.mixinClass( OO.ui.MultioptionWidget, OO.ui.mixin.LabelElement );
+OO.mixinClass( OO.ui.MultioptionWidget, OO.ui.mixin.TitledElement );
 
 /* Events */
 
@@ -8276,6 +8387,7 @@ OO.ui.MultioptionWidget.prototype.isSelected = function () {
  *
  * @param {boolean} [state=false] Select option
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.MultioptionWidget.prototype.setSelected = function ( state ) {
        state = !!state;
@@ -8298,6 +8410,7 @@ OO.ui.MultioptionWidget.prototype.setSelected = function ( state ) {
  * @abstract
  * @extends OO.ui.Widget
  * @mixins OO.ui.mixin.GroupWidget
+ * @mixins OO.ui.mixin.TitledElement
  *
  * @constructor
  * @param {Object} [config] Configuration options
@@ -8312,6 +8425,7 @@ OO.ui.MultiselectWidget = function OoUiMultiselectWidget( config ) {
 
        // Mixin constructors
        OO.ui.mixin.GroupWidget.call( this, config );
+       OO.ui.mixin.TitledElement.call( this, config );
 
        // Events
        this.aggregate( { change: 'select' } );
@@ -8332,6 +8446,7 @@ OO.ui.MultiselectWidget = function OoUiMultiselectWidget( config ) {
 
 OO.inheritClass( OO.ui.MultiselectWidget, OO.ui.Widget );
 OO.mixinClass( OO.ui.MultiselectWidget, OO.ui.mixin.GroupWidget );
+OO.mixinClass( OO.ui.MultiselectWidget, OO.ui.mixin.TitledElement );
 
 /* Events */
 
@@ -8376,6 +8491,7 @@ OO.ui.MultiselectWidget.prototype.findSelectedItemsData = function () {
  *
  * @param {OO.ui.MultioptionWidget[]} items Items to select
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.MultiselectWidget.prototype.selectItems = function ( items ) {
        this.items.forEach( function ( item ) {
@@ -8390,6 +8506,7 @@ OO.ui.MultiselectWidget.prototype.selectItems = function ( items ) {
  *
  * @param {Object[]|string[]} datas Values of items to select
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.MultiselectWidget.prototype.selectItemsByData = function ( datas ) {
        var items,
@@ -8517,21 +8634,18 @@ OO.ui.CheckboxMultioptionWidget.prototype.onKeyDown = function ( e ) {
  *     @example
  *     // A CheckboxMultiselectWidget with CheckboxMultioptions.
  *     var option1 = new OO.ui.CheckboxMultioptionWidget( {
- *         data: 'a',
- *         selected: true,
- *         label: 'Selected checkbox'
- *     } );
- *
- *     var option2 = new OO.ui.CheckboxMultioptionWidget( {
- *         data: 'b',
- *         label: 'Unselected checkbox'
- *     } );
- *
- *     var multiselect=new OO.ui.CheckboxMultiselectWidget( {
- *         items: [ option1, option2 ]
- *      } );
- *
- *     $( 'body' ).append( multiselect.$element );
+ *             data: 'a',
+ *             selected: true,
+ *             label: 'Selected checkbox'
+ *         } ),
+ *         option2 = new OO.ui.CheckboxMultioptionWidget( {
+ *             data: 'b',
+ *             label: 'Unselected checkbox'
+ *         } ),
+ *         multiselect = new OO.ui.CheckboxMultiselectWidget( {
+ *             items: [ option1, option2 ]
+ *         } );
+ *     $( document.body ).append( multiselect.$element );
  *
  * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Selects_and_Options
  *
@@ -8647,6 +8761,7 @@ OO.ui.CheckboxMultiselectWidget.prototype.onClick = function ( e ) {
  * Focus the widget
  *
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.CheckboxMultiselectWidget.prototype.focus = function () {
        var item;
@@ -8685,13 +8800,19 @@ OO.ui.CheckboxMultiselectWidget.prototype.simulateLabelClick = function () {
  *     } );
  *     var progressBar2 = new OO.ui.ProgressBarWidget();
  *
- *     // Create a FieldsetLayout to layout progress bars
+ *     // Create a FieldsetLayout to layout progress bars.
  *     var fieldset = new OO.ui.FieldsetLayout;
  *     fieldset.addItems( [
- *        new OO.ui.FieldLayout( progressBar1, {label: 'Determinate', align: 'top'}),
- *        new OO.ui.FieldLayout( progressBar2, {label: 'Indeterminate', align: 'top'})
+ *         new OO.ui.FieldLayout( progressBar1, {
+ *             label: 'Determinate',
+ *             align: 'top'
+ *         } ),
+ *         new OO.ui.FieldLayout( progressBar2, {
+ *             label: 'Indeterminate',
+ *             align: 'top'
+ *         } )
  *     ] );
- *     $( 'body' ).append( fieldset.$element );
+ *     $( document.body ).append( fieldset.$element );
  *
  * @class
  * @extends OO.ui.Widget
@@ -8926,6 +9047,7 @@ OO.ui.InputWidget.prototype.getValue = function () {
  *
  * @param {string} dir Text directionality: 'ltr', 'rtl' or 'auto'
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.InputWidget.prototype.setDir = function ( dir ) {
        this.$input.prop( 'dir', dir );
@@ -8938,6 +9060,7 @@ OO.ui.InputWidget.prototype.setDir = function ( dir ) {
  * @param {string} value New value
  * @fires change
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.InputWidget.prototype.setValue = function ( value ) {
        value = this.cleanUpValue( value );
@@ -8995,6 +9118,7 @@ OO.ui.InputWidget.prototype.setDisabled = function ( state ) {
  *
  * @param {string} id
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.InputWidget.prototype.setInputId = function ( id ) {
        this.$input.attr( 'id', id );
@@ -9015,7 +9139,7 @@ OO.ui.InputWidget.prototype.restorePreInfuseState = function ( state ) {
 };
 
 /**
- * Data widget intended for creating 'hidden'-type inputs.
+ * Data widget intended for creating `<input type="hidden">` inputs.
  *
  * @class
  * @extends OO.ui.Widget
@@ -9067,7 +9191,7 @@ OO.ui.HiddenInputWidget.static.tagName = 'input';
  *         icon: 'check',
  *         value: 'check'
  *     } );
- *     $( 'body' ).append( button.$element );
+ *     $( document.body ).append( button.$element );
  *
  * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs#Button_inputs
  *
@@ -9077,7 +9201,6 @@ OO.ui.HiddenInputWidget.static.tagName = 'input';
  * @mixins OO.ui.mixin.IconElement
  * @mixins OO.ui.mixin.IndicatorElement
  * @mixins OO.ui.mixin.LabelElement
- * @mixins OO.ui.mixin.TitledElement
  *
  * @constructor
  * @param {Object} [config] Configuration options
@@ -9107,7 +9230,6 @@ OO.ui.ButtonInputWidget = function OoUiButtonInputWidget( config ) {
        OO.ui.mixin.IconElement.call( this, config );
        OO.ui.mixin.IndicatorElement.call( this, config );
        OO.ui.mixin.LabelElement.call( this, config );
-       OO.ui.mixin.TitledElement.call( this, $.extend( {}, config, { $titled: this.$input } ) );
 
        // Initialization
        if ( !config.useInputTag ) {
@@ -9123,7 +9245,6 @@ OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.mixin.ButtonElement );
 OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.mixin.IconElement );
 OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.mixin.IndicatorElement );
 OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.mixin.LabelElement );
-OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.mixin.TitledElement );
 
 /* Static Properties */
 
@@ -9153,6 +9274,7 @@ OO.ui.ButtonInputWidget.prototype.getInputElement = function ( config ) {
  * @param {jQuery|string|Function|null} label Label nodes, text, a function that returns nodes or
  *  text, or `null` for no label
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.ButtonInputWidget.prototype.setLabel = function ( label ) {
        if ( typeof label === 'function' ) {
@@ -9179,6 +9301,7 @@ OO.ui.ButtonInputWidget.prototype.setLabel = function ( label ) {
  *
  * @param {string} value New value
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.ButtonInputWidget.prototype.setValue = function ( value ) {
        if ( !this.useInputTag ) {
@@ -9205,28 +9328,28 @@ OO.ui.ButtonInputWidget.prototype.getInputId = function () {
  * This widget can be used inside an HTML form, such as a OO.ui.FormLayout.
  *
  *     @example
- *     // An example of selected, unselected, and disabled checkbox inputs
- *     var checkbox1=new OO.ui.CheckboxInputWidget( {
- *          value: 'a',
- *          selected: true
- *     } );
- *     var checkbox2=new OO.ui.CheckboxInputWidget( {
- *         value: 'b'
- *     } );
- *     var checkbox3=new OO.ui.CheckboxInputWidget( {
- *         value:'c',
- *         disabled: true
- *     } );
- *     // Create a fieldset layout with fields for each checkbox.
- *     var fieldset = new OO.ui.FieldsetLayout( {
- *         label: 'Checkboxes'
- *     } );
+ *     // An example of selected, unselected, and disabled checkbox inputs.
+ *     var checkbox1 = new OO.ui.CheckboxInputWidget( {
+ *             value: 'a',
+ *              selected: true
+ *         } ),
+ *         checkbox2 = new OO.ui.CheckboxInputWidget( {
+ *             value: 'b'
+ *         } ),
+ *         checkbox3 = new OO.ui.CheckboxInputWidget( {
+ *             value:'c',
+ *             disabled: true
+ *         } ),
+ *         // Create a fieldset layout with fields for each checkbox.
+ *         fieldset = new OO.ui.FieldsetLayout( {
+ *             label: 'Checkboxes'
+ *         } );
  *     fieldset.addItems( [
  *         new OO.ui.FieldLayout( checkbox1, { label: 'Selected checkbox', align: 'inline' } ),
  *         new OO.ui.FieldLayout( checkbox2, { label: 'Unselected checkbox', align: 'inline' } ),
  *         new OO.ui.FieldLayout( checkbox3, { label: 'Disabled checkbox', align: 'inline' } ),
  *     ] );
- *     $( 'body' ).append( fieldset.$element );
+ *     $( document.body ).append( fieldset.$element );
  *
  * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs
  *
@@ -9309,6 +9432,7 @@ OO.ui.CheckboxInputWidget.prototype.onEdit = function () {
  *
  * @param {boolean} state `true` for selected
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.CheckboxInputWidget.prototype.setSelected = function ( state ) {
        state = !!state;
@@ -9375,7 +9499,7 @@ OO.ui.CheckboxInputWidget.prototype.restorePreInfuseState = function ( state ) {
  * This and OO.ui.RadioSelectInputWidget support the same configuration options.
  *
  *     @example
- *     // Example: A DropdownInputWidget with three options
+ *     // A DropdownInputWidget with three options.
  *     var dropdownInput = new OO.ui.DropdownInputWidget( {
  *         options: [
  *             { data: 'a', label: 'First' },
@@ -9383,7 +9507,7 @@ OO.ui.CheckboxInputWidget.prototype.restorePreInfuseState = function ( state ) {
  *             { data: 'c', label: 'Third' }
  *         ]
  *     } );
- *     $( 'body' ).append( dropdownInput.$element );
+ *     $( document.body ).append( dropdownInput.$element );
  *
  * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs
  *
@@ -9425,6 +9549,7 @@ OO.ui.DropdownInputWidget = function OoUiDropdownInputWidget( config ) {
                .addClass( 'oo-ui-dropdownInputWidget' )
                .append( this.dropdownWidget.$element );
        this.setTabIndexedElement( this.dropdownWidget.$tabIndexed );
+       this.setTitledElement( this.dropdownWidget.$handle );
 };
 
 /* Setup */
@@ -9485,6 +9610,7 @@ OO.ui.DropdownInputWidget.prototype.setDisabled = function ( state ) {
  *
  * @param {Object[]} options Array of menu options in the format `{ data: …, label: … }`
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.DropdownInputWidget.prototype.setOptions = function ( options ) {
        var value = this.getValue();
@@ -9645,7 +9771,7 @@ OO.ui.DropdownInputWidget.prototype.blur = function () {
  *         new OO.ui.FieldLayout( radio2, { label: 'Unselected', align: 'inline' } ),
  *         new OO.ui.FieldLayout( radio3, { label: 'Disabled', align: 'inline' } ),
  *     ] );
- *     $( 'body' ).append( fieldset.$element );
+ *     $( document.body ).append( fieldset.$element );
  *
  * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs
  *
@@ -9716,6 +9842,7 @@ OO.ui.RadioInputWidget.prototype.onEdit = function () {
  *
  * @param {boolean} state `true` for selected
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.RadioInputWidget.prototype.setSelected = function ( state ) {
        // RadioInputWidget doesn't track its state.
@@ -9768,7 +9895,7 @@ OO.ui.RadioInputWidget.prototype.restorePreInfuseState = function ( state ) {
  * This and OO.ui.DropdownInputWidget support the same configuration options.
  *
  *     @example
- *     // Example: A RadioSelectInputWidget with three options
+ *     // A RadioSelectInputWidget with three options
  *     var radioSelectInput = new OO.ui.RadioSelectInputWidget( {
  *         options: [
  *             { data: 'a', label: 'First' },
@@ -9776,7 +9903,7 @@ OO.ui.RadioInputWidget.prototype.restorePreInfuseState = function ( state ) {
  *             { data: 'c', label: 'Third' }
  *         ]
  *     } );
- *     $( 'body' ).append( radioSelectInput.$element );
+ *     $( document.body ).append( radioSelectInput.$element );
  *
  * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs
  *
@@ -9886,6 +10013,7 @@ OO.ui.RadioSelectInputWidget.prototype.setDisabled = function ( state ) {
  *
  * @param {Object[]} options Array of menu options in the format `{ data: …, label: … }`
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.RadioSelectInputWidget.prototype.setOptions = function ( options ) {
        var value = this.getValue();
@@ -9946,15 +10074,15 @@ OO.ui.RadioSelectInputWidget.prototype.blur = function () {
  * more information about input widgets.
  *
  *     @example
- *     // Example: A CheckboxMultiselectInputWidget with three options
+ *     // A CheckboxMultiselectInputWidget with three options.
  *     var multiselectInput = new OO.ui.CheckboxMultiselectInputWidget( {
  *         options: [
  *             { data: 'a', label: 'First' },
- *             { data: 'b', label: 'Second'},
+ *             { data: 'b', label: 'Second' },
  *             { data: 'c', label: 'Third' }
  *         ]
  *     } );
- *     $( 'body' ).append( multiselectInput.$element );
+ *     $( document.body ).append( multiselectInput.$element );
  *
  * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs
  *
@@ -10102,6 +10230,7 @@ OO.ui.CheckboxMultiselectInputWidget.prototype.setDisabled = function ( state )
  *
  * @param {Object[]} options Array of menu options in the format `{ data: …, label: …, disabled: … }`
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.CheckboxMultiselectInputWidget.prototype.setOptions = function ( options ) {
        var value = this.getValue();
@@ -10188,11 +10317,11 @@ OO.ui.CheckboxMultiselectInputWidget.prototype.focus = function () {
  * This widget can be used inside an HTML form, such as a OO.ui.FormLayout.
  *
  *     @example
- *     // Example of a text input widget
+ *     // A TextInputWidget.
  *     var textInput = new OO.ui.TextInputWidget( {
  *         value: 'Text input'
  *     } )
- *     $( 'body' ).append( textInput.$element );
+ *     $( document.body ).append( textInput.$element );
  *
  * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs
  *
@@ -10250,7 +10379,6 @@ OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) {
        this.readOnly = false;
        this.required = false;
        this.validate = null;
-       this.styleHeight = null;
        this.scrollWidth = null;
 
        this.setValidation( config.validate );
@@ -10336,6 +10464,7 @@ OO.ui.TextInputWidget.static.validationPatterns = {
  *
  * @private
  * @param {jQuery.Event} e Mouse down event
+ * @return {undefined/boolean} False to prevent default if event is handled
  */
 OO.ui.TextInputWidget.prototype.onIconMouseDown = function ( e ) {
        if ( e.which === OO.ui.MouseButtons.LEFT ) {
@@ -10349,6 +10478,7 @@ OO.ui.TextInputWidget.prototype.onIconMouseDown = function ( e ) {
  *
  * @private
  * @param {jQuery.Event} e Mouse down event
+ * @return {undefined/boolean} False to prevent default if event is handled
  */
 OO.ui.TextInputWidget.prototype.onIndicatorMouseDown = function ( e ) {
        if ( e.which === OO.ui.MouseButtons.LEFT ) {
@@ -10432,6 +10562,7 @@ OO.ui.TextInputWidget.prototype.isReadOnly = function () {
  *
  * @param {boolean} state Make input read-only
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.TextInputWidget.prototype.setReadOnly = function ( state ) {
        this.readOnly = !!state;
@@ -10453,6 +10584,7 @@ OO.ui.TextInputWidget.prototype.isRequired = function () {
  *
  * @param {boolean} state Make input required
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.TextInputWidget.prototype.setRequired = function ( state ) {
        this.required = !!state;
@@ -10581,6 +10713,7 @@ OO.ui.TextInputWidget.prototype.getSaneType = function ( config ) {
  * @param {number} from Select from offset
  * @param {number} [to] Select to offset, defaults to from
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.TextInputWidget.prototype.selectRange = function ( from, to ) {
        var isBackwards, start, end,
@@ -10640,6 +10773,7 @@ OO.ui.TextInputWidget.prototype.getInputLength = function () {
  * Focus the input and select the entire text.
  *
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.TextInputWidget.prototype.select = function () {
        return this.selectRange( 0, this.getInputLength() );
@@ -10649,6 +10783,7 @@ OO.ui.TextInputWidget.prototype.select = function () {
  * Focus the input and move the cursor to the start.
  *
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.TextInputWidget.prototype.moveCursorToStart = function () {
        return this.selectRange( 0 );
@@ -10658,6 +10793,7 @@ OO.ui.TextInputWidget.prototype.moveCursorToStart = function () {
  * Focus the input and move the cursor to the end.
  *
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.TextInputWidget.prototype.moveCursorToEnd = function () {
        return this.selectRange( this.getInputLength() );
@@ -10668,6 +10804,7 @@ OO.ui.TextInputWidget.prototype.moveCursorToEnd = function () {
  *
  * @param {string} content Content to be inserted
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.TextInputWidget.prototype.insertContent = function ( content ) {
        var start, end,
@@ -10688,6 +10825,7 @@ OO.ui.TextInputWidget.prototype.insertContent = function ( content ) {
  * @param {string} pre Content to be inserted before the selection
  * @param {string} post Content to be inserted after the selection
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.TextInputWidget.prototype.encapsulateContent = function ( pre, post ) {
        var start, end,
@@ -10779,7 +10917,7 @@ OO.ui.TextInputWidget.prototype.getValidity = function () {
        // Run our checks if the browser thinks the field is valid
        if ( this.validate instanceof Function ) {
                result = this.validate( this.getValue() );
-               if ( result && $.isFunction( result.promise ) ) {
+               if ( result && typeof result.promise === 'function' ) {
                        return result.promise().then( function ( valid ) {
                                return rejectOrResolve( valid );
                        } );
@@ -10796,6 +10934,7 @@ OO.ui.TextInputWidget.prototype.getValidity = function () {
  *
  * @param {string} labelPosition Label position, 'before' or 'after'
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.TextInputWidget.prototype.setLabelPosition = function ( labelPosition ) {
        this.labelPosition = labelPosition;
@@ -10814,6 +10953,7 @@ OO.ui.TextInputWidget.prototype.setLabelPosition = function ( labelPosition ) {
  * something causes the label to be mispositioned.
  *
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.TextInputWidget.prototype.updatePosition = function () {
        var after = this.labelPosition === 'after';
@@ -10834,6 +10974,7 @@ OO.ui.TextInputWidget.prototype.updatePosition = function () {
  *
  * @private
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.TextInputWidget.prototype.positionLabel = function () {
        var after, rtl, property, newCss;
@@ -10869,6 +11010,12 @@ OO.ui.TextInputWidget.prototype.positionLabel = function () {
 };
 
 /**
+ * SearchInputWidgets are TextInputWidgets with `type="search"` assigned and feature a
+ * {@link OO.ui.mixin.IconElement search icon} by default.
+ * Please see the [OOUI documentation on MediaWiki] [1] for more information and examples.
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs#SearchInputWidget
+ *
  * @class
  * @extends OO.ui.TextInputWidget
  *
@@ -10963,6 +11110,23 @@ OO.ui.SearchInputWidget.prototype.setReadOnly = function ( state ) {
 };
 
 /**
+ * MultilineTextInputWidgets, like HTML textareas, are featuring customization options to
+ * configure number of rows visible. In addition, these widgets can be autosized to fit user
+ * inputs and can show {@link OO.ui.mixin.IconElement icons} and
+ * {@link OO.ui.mixin.IndicatorElement indicators}.
+ * Please see the [OOUI documentation on MediaWiki] [1] for more information and examples.
+ *
+ * This widget can be used inside an HTML form, such as a OO.ui.FormLayout.
+ *
+ *     @example
+ *     // A MultilineTextInputWidget.
+ *     var multilineTextInput = new OO.ui.MultilineTextInputWidget( {
+ *         value: 'Text input on multiple lines'
+ *     } )
+ *     $( 'body' ).append( multilineTextInput.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs#MultilineTextInputWidget
+ *
  * @class
  * @extends OO.ui.TextInputWidget
  *
@@ -10984,6 +11148,7 @@ OO.ui.MultilineTextInputWidget = function OoUiMultilineTextInputWidget( config )
 
        // Properties
        this.autosize = !!config.autosize;
+       this.styleHeight = null;
        this.minRows = config.rows !== undefined ? config.rows : '';
        this.maxRows = config.maxRows || Math.max( 2 * ( this.minRows || 0 ), 10 );
 
@@ -11077,6 +11242,7 @@ OO.ui.MultilineTextInputWidget.prototype.onKeyPress = function ( e ) {
  * This only affects multiline inputs that are {@link #autosize autosized}.
  *
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  * @fires resize
  */
 OO.ui.MultilineTextInputWidget.prototype.adjustSize = function () {
@@ -11192,7 +11358,7 @@ OO.ui.MultilineTextInputWidget.prototype.restorePreInfuseState = function ( stat
  * For more information about menus and options, please see the [OOUI documentation on MediaWiki][1].
  *
  *     @example
- *     // Example: A ComboBoxInputWidget.
+ *     // A ComboBoxInputWidget.
  *     var comboBox = new OO.ui.ComboBoxInputWidget( {
  *         value: 'Option 1',
  *         options: [
@@ -11201,7 +11367,7 @@ OO.ui.MultilineTextInputWidget.prototype.restorePreInfuseState = function ( stat
  *             { data: 'Option 3' }
  *         ]
  *     } );
- *     $( 'body' ).append( comboBox.$element );
+ *     $( document.body ).append( comboBox.$element );
  *
  *     @example
  *     // Example: A ComboBoxInputWidget with additional option labels.
@@ -11222,7 +11388,7 @@ OO.ui.MultilineTextInputWidget.prototype.restorePreInfuseState = function ( stat
  *             }
  *         ]
  *     } );
- *     $( 'body' ).append( comboBox.$element );
+ *     $( document.body ).append( comboBox.$element );
  *
  * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Selects_and_Options#Menu_selects_and_options
  *
@@ -11259,7 +11425,9 @@ OO.ui.ComboBoxInputWidget = function OoUiComboBoxInputWidget( config ) {
        this.$overlay = ( config.$overlay === true ? OO.ui.getDefaultOverlay() : config.$overlay ) || this.$element;
        this.dropdownButton = new OO.ui.ButtonWidget( {
                classes: [ 'oo-ui-comboBoxInputWidget-dropdownButton' ],
+               label: OO.ui.msg( 'ooui-combobox-button-label' ),
                indicator: 'down',
+               invisibleLabel: true,
                disabled: this.disabled
        } );
        this.menu = new OO.ui.MenuSelectWidget( $.extend(
@@ -11293,6 +11461,9 @@ OO.ui.ComboBoxInputWidget = function OoUiComboBoxInputWidget( config ) {
                'aria-owns': this.menu.getElementId(),
                'aria-autocomplete': 'list'
        } );
+       this.dropdownButton.$button.attr( {
+               'aria-controls': this.menu.getElementId()
+       } );
        // Do not override options set via config.menu.items
        if ( config.options !== undefined ) {
                this.setOptions( config.options );
@@ -11427,6 +11598,7 @@ OO.ui.ComboBoxInputWidget.prototype.setDisabled = function ( disabled ) {
  *
  * @param {Object[]} options Array of menu options in the format `{ data: …, label: … }`
  * @chainable
+ * @return {OO.ui.Widget} The widget, for chaining
  */
 OO.ui.ComboBoxInputWidget.prototype.setOptions = function ( options ) {
        this.getMenu()
@@ -11643,6 +11815,7 @@ OO.ui.FieldLayout.prototype.makeMessage = function ( kind, text ) {
  * @private
  * @param {string} value Alignment mode, either 'left', 'right', 'top' or 'inline'
  * @chainable
+ * @return {OO.ui.BookletLayout} The layout, for chaining
  */
 OO.ui.FieldLayout.prototype.setAlignment = function ( value ) {
        if ( value !== this.align ) {
@@ -11700,6 +11873,7 @@ OO.ui.FieldLayout.prototype.setAlignment = function ( value ) {
  * @param {Array} errors Error messages about the widget, which will be displayed below the widget.
  *  The array may contain strings or OO.ui.HtmlSnippet instances.
  * @chainable
+ * @return {OO.ui.BookletLayout} The layout, for chaining
  */
 OO.ui.FieldLayout.prototype.setErrors = function ( errors ) {
        this.errors = errors.slice();
@@ -11713,6 +11887,7 @@ OO.ui.FieldLayout.prototype.setErrors = function ( errors ) {
  * @param {Array} notices Notices about the widget, which will be displayed below the widget.
  *  The array may contain strings or OO.ui.HtmlSnippet instances.
  * @chainable
+ * @return {OO.ui.BookletLayout} The layout, for chaining
  */
 OO.ui.FieldLayout.prototype.setNotices = function ( notices ) {
        this.notices = notices.slice();
@@ -11846,7 +12021,7 @@ OO.ui.FieldLayout.prototype.createHelpElement = function ( help, $overlay ) {
  *         }
  *     );
  *
- *     $( 'body' ).append( actionFieldLayout.$element );
+ *     $( document.body ).append( actionFieldLayout.$element );
  *
  * @class
  * @extends OO.ui.FieldLayout
@@ -11917,7 +12092,7 @@ OO.inheritClass( OO.ui.ActionFieldLayout, OO.ui.FieldLayout );
  *             label: 'Field Two'
  *         } )
  *     ] );
- *     $( 'body' ).append( fieldset.$element );
+ *     $( document.body ).append( fieldset.$element );
  *
  * [1]: https://www.mediawiki.org/wiki/OOUI/Layouts/Fields_and_Fieldsets
  *
@@ -12049,7 +12224,7 @@ OO.ui.FieldsetLayout.static.tagName = 'fieldset';
  *         action: '/api/formhandler',
  *         method: 'get'
  *     } )
- *     $( 'body' ).append( form.$element );
+ *     $( document.body ).append( form.$element );
  *
  * @class
  * @extends OO.ui.Layout
@@ -12125,6 +12300,7 @@ OO.ui.FormLayout.static.tagName = 'form';
  * @private
  * @param {jQuery.Event} e Submit event
  * @fires submit
+ * @return {OO.ui.FormLayout} The layout, for chaining
  */
 OO.ui.FormLayout.prototype.onFormSubmit = function () {
        if ( this.emit( 'submit' ) ) {
@@ -12144,7 +12320,7 @@ OO.ui.FormLayout.prototype.onFormSubmit = function () {
  *         padded: true,
  *         $content: $( '<p>A panel layout with padding and a frame.</p>' )
  *     } );
- *     $( 'body' ).append( panel.$element );
+ *     $( document.body ).append( panel.$element );
  *
  * @class
  * @extends OO.ui.Layout
@@ -12214,7 +12390,7 @@ OO.ui.PanelLayout.prototype.focus = function () {
  *         new OO.ui.TextInputWidget( { value: 'Text' } )
  *       ]
  *     } );
- *     $( 'body' ).append( layout.$element );
+ *     $( document.body ).append( layout.$element );
  *
  * @class
  * @extends OO.ui.Layout
@@ -12252,14 +12428,14 @@ OO.mixinClass( OO.ui.HorizontalLayout, OO.ui.mixin.GroupElement );
  * (to adjust the value in increments) to allow the user to enter a number.
  *
  *     @example
- *     // Example: A NumberInputWidget.
+ *     // A NumberInputWidget.
  *     var numberInput = new OO.ui.NumberInputWidget( {
  *         label: 'NumberInputWidget',
  *         input: { value: 5 },
  *         min: 1,
  *         max: 10
  *     } );
- *     $( 'body' ).append( numberInput.$element );
+ *     $( document.body ).append( numberInput.$element );
  *
  * @class
  * @extends OO.ui.TextInputWidget
@@ -12539,6 +12715,7 @@ OO.ui.NumberInputWidget.prototype.onButtonClick = function ( dir ) {
  *
  * @private
  * @param {jQuery.Event} event
+ * @return {undefined/boolean} False to prevent default if event is handled
  */
 OO.ui.NumberInputWidget.prototype.onWheel = function ( event ) {
        var delta = 0;
@@ -12581,6 +12758,7 @@ OO.ui.NumberInputWidget.prototype.onWheel = function ( event ) {
  *
  * @private
  * @param {jQuery.Event} e Key down event
+ * @return {undefined/boolean} False to prevent default if event is handled
  */
 OO.ui.NumberInputWidget.prototype.onKeyDown = function ( e ) {
        if ( !this.isDisabled() ) {