Update OOjs UI to v0.1.0-pre (d74a46ca6a)
[lhc/web/wiklou.git] / resources / lib / oojs-ui / oojs-ui.js
index dfeae9e..50f69f3 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.1.0-pre (98e770ce46)
+ * OOjs UI v0.1.0-pre (d74a46ca6a)
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2014 OOjs Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2014-09-26T22:57:14Z
+ * Date: 2014-10-15T00:40:17Z
  */
 ( function ( OO ) {
 
@@ -248,6 +248,7 @@ OO.ui.PendingElement.prototype.isPending = function () {
 OO.ui.PendingElement.prototype.pushPending = function () {
        if ( this.pending === 0 ) {
                this.$pending.addClass( 'oo-ui-pendingElement-pending' );
+               this.updateThemeClasses();
        }
        this.pending++;
 
@@ -264,6 +265,7 @@ OO.ui.PendingElement.prototype.pushPending = function () {
 OO.ui.PendingElement.prototype.popPending = function () {
        if ( this.pending === 1 ) {
                this.$pending.removeClass( 'oo-ui-pendingElement-pending' );
+               this.updateThemeClasses();
        }
        this.pending = Math.max( 0, this.pending - 1 );
 
@@ -699,6 +701,10 @@ OO.ui.Element = function OoUiElement( config ) {
        this.$ = config.$ || OO.ui.Element.getJQuery( document );
        this.$element = this.$( this.$.context.createElement( this.getTagName() ) );
        this.elementGroup = null;
+       this.debouncedUpdateThemeClassesHandler = OO.ui.bind(
+               this.debouncedUpdateThemeClasses, this
+       );
+       this.updateThemeClassesPending = false;
 
        // Initialization
        if ( $.isArray( config.classes ) ) {
@@ -1049,8 +1055,59 @@ OO.ui.Element.scrollIntoView = function ( el, config ) {
        }
 };
 
+/**
+ * Bind a handler for an event on a DOM element.
+ *
+ * Used to be for working around a jQuery bug (jqbug.com/14180),
+ * but obsolete as of jQuery 1.11.0.
+ *
+ * @static
+ * @deprecated Use jQuery#on instead.
+ * @param {HTMLElement|jQuery} el DOM element
+ * @param {string} event Event to bind
+ * @param {Function} callback Callback to call when the event fires
+ */
+OO.ui.Element.onDOMEvent = function ( el, event, callback ) {
+       $( el ).on( event, callback );
+};
+
+/**
+ * Unbind a handler bound with #static-method-onDOMEvent.
+ *
+ * @deprecated Use jQuery#off instead.
+ * @static
+ * @param {HTMLElement|jQuery} el DOM element
+ * @param {string} event Event to unbind
+ * @param {Function} [callback] Callback to unbind
+ */
+OO.ui.Element.offDOMEvent = function ( el, event, callback ) {
+       $( el ).off( event, callback );
+};
+
 /* Methods */
 
+/**
+ * Update the theme-provided classes.
+ *
+ * @localdoc This is called in element mixins and widget classes anytime state changes.
+ *   Updating is debounced, minimizing overhead of changing multiple attributes and
+ *   guaranteeing that theme updates do not occur within an element's constructor
+ */
+OO.ui.Element.prototype.updateThemeClasses = function () {
+       if ( !this.updateThemeClassesPending ) {
+               this.updateThemeClassesPending = true;
+               setTimeout( this.debouncedUpdateThemeClassesHandler );
+       }
+};
+
+/**
+ * @private
+ */
+OO.ui.Element.prototype.debouncedUpdateThemeClasses = function () {
+       OO.ui.theme.updateElementClasses( this );
+       this.updateThemeClassesPending = false;
+};
+
 /**
  * Get the HTML tag name.
  *
@@ -1146,37 +1203,6 @@ OO.ui.Element.prototype.offDOMEvent = function ( event, callback ) {
        OO.ui.Element.offDOMEvent( this.$element, event, callback );
 };
 
-( function () {
-       /**
-        * Bind a handler for an event on a DOM element.
-        *
-        * Used to be for working around a jQuery bug (jqbug.com/14180),
-        * but obsolete as of jQuery 1.11.0.
-        *
-        * @static
-        * @deprecated Use jQuery#on instead.
-        * @param {HTMLElement|jQuery} el DOM element
-        * @param {string} event Event to bind
-        * @param {Function} callback Callback to call when the event fires
-        */
-       OO.ui.Element.onDOMEvent = function ( el, event, callback ) {
-               $( el ).on( event, callback );
-       };
-
-       /**
-        * Unbind a handler bound with #static-method-onDOMEvent.
-        *
-        * @deprecated Use jQuery#off instead.
-        * @static
-        * @param {HTMLElement|jQuery} el DOM element
-        * @param {string} event Event to unbind
-        * @param {Function} [callback] Callback to unbind
-        */
-       OO.ui.Element.offDOMEvent = function ( el, event, callback ) {
-               $( el ).off( event, callback );
-       };
-}() );
-
 /**
  * Container for elements.
  *
@@ -1293,6 +1319,7 @@ OO.ui.Widget.prototype.setDisabled = function ( disabled ) {
                this.$element.toggleClass( 'oo-ui-widget-disabled', isDisabled );
                this.$element.toggleClass( 'oo-ui-widget-enabled', !isDisabled );
                this.emit( 'disable', isDisabled );
+               this.updateThemeClasses();
        }
        this.wasDisabled = isDisabled;
 
@@ -1390,12 +1417,14 @@ OO.ui.Window = function OoUiWindow( config ) {
        this.loading = null;
        this.size = config.size || this.constructor.static.size;
        this.$frame = this.$( '<div>' );
+       this.$overlay = this.$( '<div>' );
 
        // Initialization
        this.$element
                .addClass( 'oo-ui-window' )
-               .append( this.$frame );
+               .append( this.$frame, this.$overlay );
        this.$frame.addClass( 'oo-ui-window-frame' );
+       this.$overlay.addClass( 'oo-ui-window-overlay' );
 
        // NOTE: Additional intitialization will occur when #setManager is called
 };
@@ -1405,13 +1434,6 @@ OO.ui.Window = function OoUiWindow( config ) {
 OO.inheritClass( OO.ui.Window, OO.ui.Element );
 OO.mixinClass( OO.ui.Window, OO.EventEmitter );
 
-/* Events */
-
-/**
- * @event resize
- * @param {string} size Symbolic size name, e.g. 'small', 'medium', 'large', 'full'
- */
-
 /* Static Properties */
 
 /**
@@ -1878,7 +1900,7 @@ OO.ui.Window.prototype.initialize = function () {
        this.$head = this.$( '<div>' );
        this.$body = this.$( '<div>' );
        this.$foot = this.$( '<div>' );
-       this.$overlay = this.$( '<div>' );
+       this.$innerOverlay = this.$( '<div>' );
 
        // Events
        this.$element.on( 'mousedown', OO.ui.bind( this.onMouseDown, this ) );
@@ -1887,8 +1909,8 @@ OO.ui.Window.prototype.initialize = function () {
        this.$head.addClass( 'oo-ui-window-head' );
        this.$body.addClass( 'oo-ui-window-body' );
        this.$foot.addClass( 'oo-ui-window-foot' );
-       this.$overlay.addClass( 'oo-ui-window-overlay' );
-       this.$content.append( this.$head, this.$body, this.$foot, this.$overlay );
+       this.$innerOverlay.addClass( 'oo-ui-window-inner-overlay' );
+       this.$content.append( this.$head, this.$body, this.$foot, this.$innerOverlay );
 
        return this;
 };
@@ -2099,7 +2121,7 @@ OO.ui.Window.prototype.load = function () {
        doc.close();
 
        // Properties
-       this.$ = OO.ui.Element.getJQuery( doc, this.$element );
+       this.$ = OO.ui.Element.getJQuery( doc, this.$iframe );
        this.$content = this.$( '.oo-ui-window-content' ).attr( 'tabIndex', 0 );
        this.$document = this.$( doc );
 
@@ -2510,6 +2532,13 @@ OO.mixinClass( OO.ui.WindowManager, OO.EventEmitter );
  * @param {Object} data Window closing data
  */
 
+/**
+ * Window was resized.
+ *
+ * @event resize
+ * @param {OO.ui.Window} win Window that was resized
+ */
+
 /* Static Properties */
 
 /**
@@ -3000,6 +3029,8 @@ OO.ui.WindowManager.prototype.updateWindowSize = function ( win ) {
        this.$element.toggleClass( 'oo-ui-windowManager-floating', size !== 'full' );
        win.setDimensions( sizes[size] );
 
+       this.emit( 'resize', win );
+
        return this;
 };
 
@@ -3445,6 +3476,55 @@ OO.ui.ToolGroupFactory.static.getDefaultClasses = function () {
        ];
 };
 
+/**
+ * Theme logic.
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.Theme = function OoUiTheme( config ) {
+       // Initialize config
+       config = config || {};
+};
+
+/* Setup */
+
+OO.initClass( OO.ui.Theme );
+
+/* Methods */
+
+/**
+ * Get a list of classes to be applied to a widget.
+ *
+ * @localdoc The 'on' and 'off' lists combined MUST contain keys for all classes the theme adds or
+ *   removes, otherwise state transitions will not work properly.
+ *
+ * @param {OO.ui.Element} element Element for which to get classes
+ * @return {Object.<string,string[]>} Categorized class names with `on` and `off` lists
+ */
+OO.ui.Theme.prototype.getElementClasses = function ( /* element */ ) {
+       return { on: [], off: [] };
+};
+
+/**
+ * Update CSS classes provided by the theme.
+ *
+ * For elements with theme logic hooks, this should be called anytime there's a state change.
+ *
+ * @param {OO.ui.Element} Element for which to update classes
+ * @return {Object.<string,string[]>} Categorized class names with `on` and `off` lists
+ */
+OO.ui.Theme.prototype.updateElementClasses = function ( element ) {
+       var classes = this.getElementClasses( element );
+
+       element.$element
+               .removeClass( classes.off.join( ' ' ) )
+               .addClass( classes.on.join( ' ' ) );
+};
+
 /**
  * Element with a button.
  *
@@ -3557,6 +3637,15 @@ OO.ui.ButtonElement.prototype.onMouseUp = function ( e ) {
        this.getElementDocument().removeEventListener( 'mouseup', this.onMouseUpHandler, true );
 };
 
+/**
+ * Check if button has a frame.
+ *
+ * @return {boolean} Button is framed
+ */
+OO.ui.ButtonElement.prototype.isFramed = function () {
+       return this.framed;
+};
+
 /**
  * Toggle frame.
  *
@@ -3570,6 +3659,7 @@ OO.ui.ButtonElement.prototype.toggleFramed = function ( framed ) {
                this.$element
                        .toggleClass( 'oo-ui-buttonElement-frameless', !framed )
                        .toggleClass( 'oo-ui-buttonElement-framed', framed );
+               this.updateThemeClasses();
        }
 
        return this;
@@ -3979,6 +4069,7 @@ OO.ui.IconElement.prototype.setIcon = function ( icon ) {
        }
 
        this.$element.toggleClass( 'oo-ui-iconElement', !!this.icon );
+       this.updateThemeClasses();
 
        return this;
 };
@@ -4122,6 +4213,7 @@ OO.ui.IndicatorElement.prototype.setIndicator = function ( indicator ) {
        }
 
        this.$element.toggleClass( 'oo-ui-indicatorElement', !!this.indicator );
+       this.updateThemeClasses();
 
        return this;
 };
@@ -4436,6 +4528,7 @@ OO.ui.FlaggedElement.prototype.clearFlags = function () {
                this.$flagged.removeClass( remove.join( ' ' ) );
        }
 
+       this.updateThemeClasses();
        this.emit( 'flag', changes );
 
        return this;
@@ -4501,6 +4594,7 @@ OO.ui.FlaggedElement.prototype.setFlags = function ( flags ) {
                        .removeClass( remove.join( ' ' ) );
        }
 
+       this.updateThemeClasses();
        this.emit( 'flag', changes );
 
        return this;
@@ -4781,14 +4875,14 @@ OO.ui.ClippableElement.prototype.clip = function () {
                clipHeight = desiredHeight < naturalHeight;
 
        if ( clipWidth ) {
-               this.$clippable.css( { overflowX: 'auto', width: desiredWidth } );
+               this.$clippable.css( { overflowX: 'scroll', width: desiredWidth } );
        } else {
                this.$clippable.css( 'width', this.idealWidth || '' );
                this.$clippable.width(); // Force reflow for https://code.google.com/p/chromium/issues/detail?id=387290
                this.$clippable.css( 'overflowX', '' );
        }
        if ( clipHeight ) {
-               this.$clippable.css( { overflowY: 'auto', height: desiredHeight } );
+               this.$clippable.css( { overflowY: 'scroll', height: desiredHeight } );
        } else {
                this.$clippable.css( 'height', this.idealHeight || '' );
                this.$clippable.height(); // Force reflow for https://code.google.com/p/chromium/issues/detail?id=387290
@@ -4808,6 +4902,7 @@ OO.ui.ClippableElement.prototype.clip = function () {
  * @class
  * @extends OO.ui.Widget
  * @mixins OO.ui.IconElement
+ * @mixins OO.ui.FlaggedElement
  *
  * @constructor
  * @param {OO.ui.ToolGroup} toolGroup
@@ -4823,6 +4918,7 @@ OO.ui.Tool = function OoUiTool( toolGroup, config ) {
 
        // Mixin constructors
        OO.ui.IconElement.call( this, config );
+       OO.ui.FlaggedElement.call( this, config );
 
        // Properties
        this.toolGroup = toolGroup;
@@ -4856,6 +4952,7 @@ OO.ui.Tool = function OoUiTool( toolGroup, config ) {
 
 OO.inheritClass( OO.ui.Tool, OO.ui.Widget );
 OO.mixinClass( OO.ui.Tool, OO.ui.IconElement );
+OO.mixinClass( OO.ui.Tool, OO.ui.FlaggedElement );
 
 /* Events */
 
@@ -5188,7 +5285,7 @@ OO.ui.Toolbar.prototype.setup = function ( groups ) {
                                group.type = 'list';
                        }
                        if ( group.label === undefined ) {
-                               group.label = 'ooui-toolbar-more';
+                               group.label = OO.ui.msg( 'ooui-toolbar-more' );
                        }
                }
                // Check type has been registered
@@ -7613,7 +7710,7 @@ OO.ui.ItemWidget.prototype.setElementGroup = function ( group ) {
  * @constructor
  * @param {OO.ui.TextInputWidget} input Input widget
  * @param {Object} [config] Configuration options
- * @cfg {jQuery} [$overlay=this.$( 'body' )] Overlay layer
+ * @cfg {jQuery} [$overlay] Overlay layer; defaults to the current window's overlay.
  */
 OO.ui.LookupInputWidget = function OoUiLookupInputWidget( input, config ) {
        // Config intialization
@@ -7621,7 +7718,10 @@ OO.ui.LookupInputWidget = function OoUiLookupInputWidget( input, config ) {
 
        // Properties
        this.lookupInput = input;
-       this.$overlay = config.$overlay || this.$( 'body,.oo-ui-window-overlay' ).last();
+       this.$overlay = config.$overlay || ( this.$.$iframe || this.$element ).closest( '.oo-ui-window' ).children( '.oo-ui-window-overlay' );
+       if ( this.$overlay.length === 0 ) {
+               this.$overlay = this.$( 'body' );
+       }
        this.lookupMenu = new OO.ui.TextInputMenuWidget( this, {
                $: OO.ui.Element.getJQuery( this.$overlay ),
                input: this.lookupInput,
@@ -7854,7 +7954,7 @@ OO.ui.LookupInputWidget.prototype.getLookupMenuItemsFromData = function () {
  */
 OO.ui.OutlineControlsWidget = function OoUiOutlineControlsWidget( outline, config ) {
        // Configuration initialization
-       config = $.extend( { icon: 'add-item' }, config );
+       config = $.extend( { icon: 'add' }, config );
 
        // Parent constructor
        OO.ui.OutlineControlsWidget.super.call( this, config );
@@ -8551,7 +8651,7 @@ OO.ui.IndicatorWidget.static.tagName = 'span';
  * Inline menus provide a control for accessing a menu and compose a menu within the widget, which
  * can be accessed using the #getMenu method.
  *
- * Use with OO.ui.MenuOptionWidget.
+ * Use with OO.ui.MenuItemWidget.
  *
  * @class
  * @extends OO.ui.Widget
@@ -8970,7 +9070,9 @@ OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) {
        this.multiline = !!config.multiline;
        this.autosize = !!config.autosize;
        this.maxRows = config.maxRows !== undefined ? config.maxRows : 10;
-       this.validate = config.validate || null;
+       this.validate = null;
+
+       this.setValidation( config.validate );
 
        // Events
        this.$input.on( {
@@ -9108,31 +9210,34 @@ OO.ui.TextInputWidget.prototype.setValue = function ( value ) {
  * @chainable
  */
 OO.ui.TextInputWidget.prototype.adjustSize = function () {
-       var $clone, scrollHeight, innerHeight, outerHeight, maxInnerHeight, idealHeight;
+       var $clone, scrollHeight, innerHeight, outerHeight, maxInnerHeight, measurementError, idealHeight;
 
        if ( this.multiline && this.autosize ) {
                $clone = this.$input.clone()
                        .val( this.$input.val() )
+                       // Set inline height property to 0 to measure scroll height
                        .css( { height: 0 } )
                        .insertAfter( this.$input );
-               // Set inline height property to 0 to measure scroll height
                scrollHeight = $clone[0].scrollHeight;
                // Remove inline height property to measure natural heights
                $clone.css( 'height', '' );
                innerHeight = $clone.innerHeight();
                outerHeight = $clone.outerHeight();
                // Measure max rows height
-               $clone.attr( 'rows', this.maxRows ).css( 'height', 'auto' );
+               $clone.attr( 'rows', this.maxRows ).css( 'height', 'auto' ).val( '' );
                maxInnerHeight = $clone.innerHeight();
-               $clone.removeAttr( 'rows' ).css( 'height', '' );
+               // Difference between reported innerHeight and scrollHeight with no scrollbars present
+               // Equals 1 on Blink-based browsers and 0 everywhere else
+               measurementError = maxInnerHeight - $clone[0].scrollHeight;
                $clone.remove();
-               idealHeight = Math.min( maxInnerHeight, scrollHeight );
+               idealHeight = Math.min( maxInnerHeight, scrollHeight + measurementError );
                // Only apply inline height when expansion beyond natural height is needed
-               this.$input.css(
-                       'height',
+               if ( idealHeight > innerHeight ) {
                        // Use the difference between the inner and outer height as a buffer
-                       idealHeight > outerHeight ? idealHeight + ( outerHeight - innerHeight ) : ''
-               );
+                       this.$input.css( 'height', idealHeight + ( outerHeight - innerHeight ) );
+               } else {
+                       this.$input.css( 'height', '' );
+               }
        }
        return this;
 };
@@ -9177,6 +9282,19 @@ OO.ui.TextInputWidget.prototype.select = function () {
        return this;
 };
 
+/**
+ * Sets the validation pattern to use.
+ * @param validate {RegExp|string|null} Regular expression (or symbolic name referencing
+ *  one, see #static-validationPatterns)
+ */
+OO.ui.TextInputWidget.prototype.setValidation = function ( validate ) {
+       if ( validate instanceof RegExp ) {
+               this.validate = validate;
+       } else {
+               this.validate = this.constructor.static.validationPatterns[validate] || /.*/;
+       }
+};
+
 /**
  * Sets the 'invalid' flag appropriately.
  */
@@ -9193,13 +9311,7 @@ OO.ui.TextInputWidget.prototype.setValidityFlag = function () {
  * @return {jQuery.Deferred}
  */
 OO.ui.TextInputWidget.prototype.isValid = function () {
-       var validationRegexp;
-       if ( this.validate instanceof RegExp ) {
-               validationRegexp = this.validate;
-       } else {
-               validationRegexp = this.constructor.static.validationPatterns[this.validate];
-       }
-       return $.Deferred().resolve( !!this.getValue().match( validationRegexp ) ).promise();
+       return $.Deferred().resolve( !!this.getValue().match( this.validate ) ).promise();
 };
 
 /**
@@ -9212,6 +9324,7 @@ OO.ui.TextInputWidget.prototype.isValid = function () {
  * @param {Object} [config] Configuration options
  * @cfg {Object} [menu] Configuration options to pass to menu widget
  * @cfg {Object} [input] Configuration options to pass to input widget
+ * @cfg {jQuery} [$overlay] Overlay layer; defaults to the current window's overlay.
  */
 OO.ui.ComboBoxWidget = function OoUiComboBoxWidget( config ) {
        // Configuration initialization
@@ -9221,11 +9334,15 @@ OO.ui.ComboBoxWidget = function OoUiComboBoxWidget( config ) {
        OO.ui.ComboBoxWidget.super.call( this, config );
 
        // Properties
+       this.$overlay = config.$overlay || ( this.$.$iframe || this.$element ).closest( '.oo-ui-window' ).children( '.oo-ui-window-overlay' );
+       if ( this.$overlay.length === 0 ) {
+               this.$overlay = this.$( 'body' );
+       }
        this.input = new OO.ui.TextInputWidget( $.extend(
                { $: this.$, indicator: 'down', disabled: this.isDisabled() },
                config.input
        ) );
-       this.menu = new OO.ui.MenuWidget( $.extend(
+       this.menu = new OO.ui.TextInputMenuWidget( this.input, $.extend(
                { $: this.$, widget: this, input: this.input, disabled: this.isDisabled() },
                config.menu
        ) );
@@ -9243,10 +9360,8 @@ OO.ui.ComboBoxWidget = function OoUiComboBoxWidget( config ) {
        } );
 
        // Initialization
-       this.$element.addClass( 'oo-ui-comboBoxWidget' ).append(
-               this.input.$element,
-               this.menu.$element
-       );
+       this.$element.addClass( 'oo-ui-comboBoxWidget' ).append( this.input.$element );
+       this.$overlay.append( this.menu.$element );
        this.onMenuItemsChange();
 };
 
@@ -9343,6 +9458,7 @@ OO.ui.LabelWidget = function OoUiLabelWidget( config ) {
 
        // Mixin constructors
        OO.ui.LabelElement.call( this, $.extend( {}, config, { $label: this.$element } ) );
+       OO.ui.TitledElement.call( this, config );
 
        // Properties
        this.input = config.input;
@@ -9360,6 +9476,7 @@ OO.ui.LabelWidget = function OoUiLabelWidget( config ) {
 
 OO.inheritClass( OO.ui.LabelWidget, OO.ui.Widget );
 OO.mixinClass( OO.ui.LabelWidget, OO.ui.LabelElement );
+OO.mixinClass( OO.ui.LabelWidget, OO.ui.TitledElement );
 
 /* Static Properties */
 
@@ -9506,6 +9623,7 @@ OO.ui.OptionWidget.prototype.setSelected = function ( state ) {
                if ( state && this.constructor.static.scrollIntoViewOnSelect ) {
                        this.scrollElementIntoView();
                }
+               this.updateThemeClasses();
        }
        return this;
 };
@@ -9520,6 +9638,7 @@ OO.ui.OptionWidget.prototype.setHighlighted = function ( state ) {
        if ( this.constructor.static.highlightable ) {
                this.highlighted = !!state;
                this.$element.toggleClass( 'oo-ui-optionWidget-highlighted', state );
+               this.updateThemeClasses();
        }
        return this;
 };
@@ -9534,6 +9653,7 @@ OO.ui.OptionWidget.prototype.setPressed = function ( state ) {
        if ( this.constructor.static.pressable ) {
                this.pressed = !!state;
                this.$element.toggleClass( 'oo-ui-optionWidget-pressed', state );
+               this.updateThemeClasses();
        }
        return this;
 };
@@ -9804,6 +9924,7 @@ OO.ui.OutlineItemWidget.prototype.getLevel = function () {
  */
 OO.ui.OutlineItemWidget.prototype.setMovable = function ( movable ) {
        this.movable = !!movable;
+       this.updateThemeClasses();
        return this;
 };
 
@@ -9817,6 +9938,7 @@ OO.ui.OutlineItemWidget.prototype.setMovable = function ( movable ) {
  */
 OO.ui.OutlineItemWidget.prototype.setRemovable = function ( removable ) {
        this.removable = !!removable;
+       this.updateThemeClasses();
        return this;
 };
 
@@ -9839,6 +9961,7 @@ OO.ui.OutlineItemWidget.prototype.setLevel = function ( level ) {
                        this.$element.removeClass( levelClass + i );
                }
        }
+       this.updateThemeClasses();
 
        return this;
 };
@@ -10115,7 +10238,7 @@ OO.ui.PopupWidget.prototype.updateDimensions = function ( transition ) {
        }
 
        // Position body relative to anchor
-       this.$popup.css( 'left', popupOffset );
+       this.$popup.css( 'margin-left', popupOffset );
 
        if ( transition ) {
                // Prevent transitioning after transition is complete
@@ -10291,7 +10414,7 @@ OO.ui.SearchWidget.prototype.getResults = function () {
 /**
  * Generic selection of options.
  *
- * Items can contain any rendering, and are uniquely identified by a has of thier data. Any widget
+ * Items can contain any rendering, and are uniquely identified by a hash of their data. Any widget
  * that provides options, from which the user must choose one, should be built on this class.
  *
  * Use together with OO.ui.OptionWidget.
@@ -11106,7 +11229,7 @@ OO.ui.MenuWidget.prototype.toggle = function ( visible ) {
  * Menu for a text input widget.
  *
  * This menu is specially designed to be positioned beneath the text input widget. Even if the input
- * is in a different frame, the menu's position is automatically calulated and maintained when the
+ * is in a different frame, the menu's position is automatically calculated and maintained when the
  * menu is toggled or the window is resized.
  *
  * @class
@@ -11188,7 +11311,7 @@ OO.ui.TextInputMenuWidget.prototype.position = function () {
        // Position under input
        dimensions.top += $container.height();
 
-       // Compensate for frame position if in a differnt frame
+       // Compensate for frame position if in a different frame
        if ( this.input.$.$iframe && this.input.$.context !== this.$element[0].ownerDocument ) {
                frameOffset = OO.ui.Element.getRelativePosition(
                        this.input.$.$iframe, this.$element.offsetParent()
@@ -11200,7 +11323,7 @@ OO.ui.TextInputMenuWidget.prototype.position = function () {
                if ( this.$element.css( 'direction' ) === 'rtl' ) {
                        dimensions.right = this.$element.parent().position().left -
                                $container.width() - dimensions.left;
-                       // Erase the value for 'left':
+                       // Erase the value for 'left'
                        delete dimensions.left;
                }
        }