Update OOjs UI to v0.6.2
[lhc/web/wiklou.git] / resources / lib / oojs-ui / oojs-ui.js
index dc92f1f..38aa092 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.4.0
+ * OOjs UI v0.6.2
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
- * Copyright 2011–2014 OOjs Team and other contributors.
+ * Copyright 2011–2015 OOjs Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2014-12-06T00:33:09Z
+ * Date: 2015-01-10T01:25:19Z
  */
 ( function ( OO ) {
 
@@ -171,7 +171,8 @@ OO.ui.contains = function ( containers, contained, matchContainers ) {
         * @return {string} Translated message with parameters substituted
         */
        OO.ui.msg = function ( key ) {
-               var message = messages[key], params = Array.prototype.slice.call( arguments, 1 );
+               var message = messages[key],
+                       params = Array.prototype.slice.call( arguments, 1 );
                if ( typeof message === 'string' ) {
                        // Perform $1 substitution
                        message = message.replace( /\$(\d+)/g, function ( unused, n ) {
@@ -721,6 +722,7 @@ OO.ui.ActionSet.prototype.organize = function () {
  * @param {Object} [config] Configuration options
  * @cfg {Function} [$] jQuery for the frame the widget is in
  * @cfg {string[]} [classes] CSS class names to add
+ * @cfg {string} [id] HTML id attribute
  * @cfg {string} [text] Text to insert
  * @cfg {jQuery} [$content] Content elements to append (after text)
  * @cfg {Mixed} [data] Element data
@@ -741,6 +743,9 @@ OO.ui.Element = function OoUiElement( config ) {
        if ( $.isArray( config.classes ) ) {
                this.$element.addClass( config.classes.join( ' ' ) );
        }
+       if ( config.id ) {
+               this.$element.attr( 'id', config.id );
+       }
        if ( config.text ) {
                this.$element.text( config.text );
        }
@@ -947,10 +952,10 @@ OO.ui.Element.static.getBorders = function ( el ) {
                right = parseFloat( style ? style.borderRightWidth : $el.css( 'borderRightWidth' ) ) || 0;
 
        return {
-               top: Math.round( top ),
-               left: Math.round( left ),
-               bottom: Math.round( bottom ),
-               right: Math.round( right )
+               top: top,
+               left: left,
+               bottom: bottom,
+               right: right
        };
 };
 
@@ -999,6 +1004,38 @@ OO.ui.Element.static.getDimensions = function ( el ) {
        }
 };
 
+/**
+ * Get scrollable object parent
+ *
+ * documentElement can't be used to get or set the scrollTop
+ * property on Blink. Changing and testing its value lets us
+ * use 'body' or 'documentElement' based on what is working.
+ *
+ * https://code.google.com/p/chromium/issues/detail?id=303131
+ *
+ * @static
+ * @param {HTMLElement} el Element to find scrollable parent for
+ * @return {HTMLElement} Scrollable parent
+ */
+OO.ui.Element.static.getRootScrollableElement = function ( el ) {
+       var scrollTop, body;
+
+       if ( OO.ui.scrollableElement === undefined ) {
+               body = el.ownerDocument.body;
+               scrollTop = body.scrollTop;
+               body.scrollTop = 1;
+
+               if ( body.scrollTop === 1 ) {
+                       body.scrollTop = scrollTop;
+                       OO.ui.scrollableElement = 'body';
+               } else {
+                       OO.ui.scrollableElement = 'documentElement';
+               }
+       }
+
+       return el.ownerDocument[ OO.ui.scrollableElement ];
+};
+
 /**
  * Get closest scrollable container.
  *
@@ -1020,7 +1057,7 @@ OO.ui.Element.static.getClosestScrollableContainer = function ( el, dimension )
        }
 
        while ( $parent.length ) {
-               if ( $parent[0] === el.ownerDocument.body ) {
+               if ( $parent[0] === this.getRootScrollableElement( el ) ) {
                        return $parent[0];
                }
                i = props.length;
@@ -1059,8 +1096,8 @@ OO.ui.Element.static.scrollIntoView = function ( el, config ) {
                $win = $( this.getWindow( el ) );
 
        // Compute the distances between the edges of el and the edges of the scroll viewport
-       if ( $sc.is( 'body' ) ) {
-               // If the scrollable container is the <body> this is easy
+       if ( $sc.is( 'html, body' ) ) {
+               // If the scrollable container is the root, this is easy
                rel = {
                        top: eld.rect.top,
                        bottom: $win.innerHeight() - eld.rect.bottom,
@@ -1195,6 +1232,8 @@ OO.ui.Element.prototype.isElementAttached = function () {
  * @return {HTMLDocument} Document object
  */
 OO.ui.Element.prototype.getElementDocument = function () {
+       // Don't use this.$.context because subclasses can rebind this.$
+       // Don't cache this in other ways either because subclasses could can change this.$element
        return OO.ui.Element.static.getDocument( this.$element );
 };
 
@@ -1730,18 +1769,23 @@ OO.ui.Window.prototype.withoutSizeTransitions = function ( callback ) {
 OO.ui.Window.prototype.getContentHeight = function () {
        var bodyHeight,
                win = this,
-               styleObj = this.$frame[0].style;
+               bodyStyleObj = this.$body[0].style,
+               frameStyleObj = this.$frame[0].style;
 
        // Temporarily resize the frame so getBodyHeight() can use scrollHeight measurements.
        // Disable transitions first, otherwise we'll get values from when the window was animating.
        this.withoutSizeTransitions( function () {
-               var oldHeight = styleObj.height;
-               styleObj.height = '1px';
+               var oldHeight = frameStyleObj.height,
+                       oldPosition = bodyStyleObj.position;
+               frameStyleObj.height = '1px';
+               // Force body to resize to new width
+               bodyStyleObj.position = 'relative';
                bodyHeight = win.getBodyHeight();
-               styleObj.height = oldHeight;
+               frameStyleObj.height = oldHeight;
+               bodyStyleObj.position = oldPosition;
        } );
 
-       return Math.round(
+       return (
                // Add buffer for border
                ( this.$frame.outerHeight() - this.$frame.innerHeight() ) +
                // Use combined heights of children
@@ -1897,7 +1941,7 @@ OO.ui.Window.prototype.setManager = function ( manager ) {
        } else {
                this.$content = this.$( '<div>' );
                this.$document = $( this.getElementDocument() );
-               this.$content.addClass( 'oo-ui-window-content' );
+               this.$content.addClass( 'oo-ui-window-content' ).attr( 'tabIndex', 0 );
                this.$frame.append( this.$content );
        }
        this.toggle( false );
@@ -2339,7 +2383,8 @@ OO.ui.Dialog.static.escapable = true;
 OO.ui.Dialog.prototype.onDocumentKeyDown = function ( e ) {
        if ( e.which === OO.ui.Keys.ESCAPE ) {
                this.close();
-               return false;
+               e.preventDefault();
+               e.stopPropagation();
        }
 };
 
@@ -2691,10 +2736,9 @@ OO.ui.WindowManager.prototype.afterWindowResize = function () {
  *
  * @param {jQuery.Event} e Mouse wheel event
  */
-OO.ui.WindowManager.prototype.onWindowMouseWheel = function ( e ) {
-       // Kill all events in the parent window if the child window is isolated,
-       // or if the event didn't come from the child window
-       return !( this.shouldIsolate() || !$.contains( this.getCurrentWindow().$frame[0], e.target ) );
+OO.ui.WindowManager.prototype.onWindowMouseWheel = function () {
+       // Kill all events in the parent window if the child window is isolated
+       return !this.shouldIsolate();
 };
 
 /**
@@ -2712,9 +2756,8 @@ OO.ui.WindowManager.prototype.onDocumentKeyDown = function ( e ) {
                case OO.ui.Keys.UP:
                case OO.ui.Keys.RIGHT:
                case OO.ui.Keys.DOWN:
-                       // Kill all events in the parent window if the child window is isolated,
-                       // or if the event didn't come from the child window
-                       return !( this.shouldIsolate() || !$.contains( this.getCurrentWindow().$frame[0], e.target ) );
+                       // Kill all events in the parent window if the child window is isolated
+                       return !this.shouldIsolate();
        }
 };
 
@@ -3053,7 +3096,7 @@ OO.ui.WindowManager.prototype.addWindows = function ( windows ) {
  *
  * Windows will be closed before they are removed.
  *
- * @param {string} name Symbolic name of window to remove
+ * @param {string[]} names Symbolic names of windows to remove
  * @return {jQuery.Promise} Promise resolved when window is closed and removed
  * @throws {Error} If windows being removed are not being managed
  */
@@ -3144,6 +3187,10 @@ OO.ui.WindowManager.prototype.toggleGlobalEvents = function ( on ) {
                                // Start listening for top-level window dimension changes
                                'orientationchange resize': this.onWindowResizeHandler
                        } );
+                       // Disable window scrolling in isolated windows
+                       if ( !this.shouldIsolate() ) {
+                               $( this.getElementDocument().body ).css( 'overflow', 'hidden' );
+                       }
                        this.globalEvents = true;
                }
        } else if ( this.globalEvents ) {
@@ -3158,6 +3205,9 @@ OO.ui.WindowManager.prototype.toggleGlobalEvents = function ( on ) {
                        // Stop listening for top-level window dimension changes
                        'orientationchange resize': this.onWindowResizeHandler
                } );
+               if ( !this.shouldIsolate() ) {
+                       $( this.getElementDocument().body ).css( 'overflow', '' );
+               }
                this.globalEvents = false;
        }
 
@@ -3192,17 +3242,15 @@ OO.ui.WindowManager.prototype.toggleAriaIsolation = function ( isolate ) {
 
 /**
  * Destroy window manager.
- *
- * Windows will not be closed, only removed from the DOM.
  */
 OO.ui.WindowManager.prototype.destroy = function () {
        this.toggleGlobalEvents( false );
        this.toggleAriaIsolation( false );
+       this.clearWindows();
        this.$element.remove();
 };
 
 /**
- * @abstract
  * @class
  *
  * @constructor
@@ -3700,7 +3748,7 @@ OO.ui.ButtonElement.prototype.setButtonElement = function ( $button ) {
                this.$button
                        .removeClass( 'oo-ui-buttonElement-button' )
                        .removeAttr( 'role accesskey tabindex' )
-                       .off( this.onMouseDownHandler );
+                       .off( 'mousedown', this.onMouseDownHandler );
        }
 
        this.$button = $button
@@ -4715,7 +4763,7 @@ OO.ui.IndicatorElement.prototype.setIndicatorElement = function ( $indicator ) {
                .addClass( 'oo-ui-indicatorElement-indicator' )
                .toggleClass( 'oo-ui-indicator-' + this.indicator, !!this.indicator );
        if ( this.indicatorTitle !== null ) {
-               this.$indicatorTitle.attr( 'title', this.indicatorTitle );
+               this.$indicator.attr( 'title', this.indicatorTitle );
        }
 };
 
@@ -4968,7 +5016,8 @@ OO.ui.PopupElement.prototype.getPopup = function () {
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @cfg {string|string[]} [flags] Styling flags, e.g. 'primary', 'destructive' or 'constructive'
+ * @cfg {string|string[]} [flags] Flags describing importance and functionality, e.g. 'primary',
+ *   'safe', 'progressive', 'destructive' or 'constructive'
  * @cfg {jQuery} [$flagged] Flagged node, assigned to #$flagged, omit to use #$element
  */
 OO.ui.FlaggedElement = function OoUiFlaggedElement( config ) {
@@ -5292,9 +5341,9 @@ OO.ui.ClippableElement.prototype.toggleClipping = function ( clipping ) {
                this.clipping = clipping;
                if ( clipping ) {
                        this.$clippableContainer = this.$( this.getClosestScrollableElementContainer() );
-                       // If the clippable container is the body, we have to listen to scroll events and check
+                       // If the clippable container is the root, we have to listen to scroll events and check
                        // jQuery.scrollTop on the window because of browser inconsistencies
-                       this.$clippableScroller = this.$clippableContainer.is( 'body' ) ?
+                       this.$clippableScroller = this.$clippableContainer.is( 'html, body' ) ?
                                this.$( OO.ui.Element.static.getWindow( this.$clippableContainer ) ) :
                                this.$clippableContainer;
                        this.$clippableScroller.on( 'scroll', this.onClippableContainerScrollHandler );
@@ -5386,17 +5435,23 @@ OO.ui.ClippableElement.prototype.clip = function () {
                return this;
        }
 
-       var buffer = 10,
+       var buffer = 7, // Chosen by fair dice roll
                cOffset = this.$clippable.offset(),
-               $container = this.$clippableContainer.is( 'body' ) ?
+               $container = this.$clippableContainer.is( 'html, body' ) ?
                        this.$clippableWindow : this.$clippableContainer,
                ccOffset = $container.offset() || { top: 0, left: 0 },
                ccHeight = $container.innerHeight() - buffer,
                ccWidth = $container.innerWidth() - buffer,
+               cHeight = this.$clippable.outerHeight() + buffer,
+               cWidth = this.$clippable.outerWidth() + buffer,
                scrollTop = this.$clippableScroller.scrollTop(),
                scrollLeft = this.$clippableScroller.scrollLeft(),
-               desiredWidth = ( ccOffset.left + scrollLeft + ccWidth ) - cOffset.left,
-               desiredHeight = ( ccOffset.top + scrollTop + ccHeight ) - cOffset.top,
+               desiredWidth = cOffset.left < 0 ?
+                       cWidth + cOffset.left :
+                       ( ccOffset.left + scrollLeft + ccWidth ) - cOffset.left,
+               desiredHeight = cOffset.top < 0 ?
+                       cHeight + cOffset.top :
+                       ( ccOffset.top + scrollTop + ccHeight ) - cOffset.top,
                naturalWidth = this.$clippable.prop( 'scrollWidth' ),
                naturalHeight = this.$clippable.prop( 'scrollHeight' ),
                clipWidth = desiredWidth < naturalWidth,
@@ -5453,7 +5508,6 @@ OO.ui.Tool = function OoUiTool( toolGroup, config ) {
        this.toolbar = this.toolGroup.getToolbar();
        this.active = false;
        this.$title = this.$( '<span>' );
-       this.$titleText = this.$( '<span>' );
        this.$accel = this.$( '<span>' );
        this.$link = this.$( '<a>' );
        this.title = null;
@@ -5462,7 +5516,7 @@ OO.ui.Tool = function OoUiTool( toolGroup, config ) {
        this.toolbar.connect( this, { updateState: 'onUpdateState' } );
 
        // Initialization
-       this.$titleText.addClass( 'oo-ui-tool-title-text' );
+       this.$title.addClass( 'oo-ui-tool-title' );
        this.$accel
                .addClass( 'oo-ui-tool-accel' )
                .prop( {
@@ -5471,12 +5525,9 @@ OO.ui.Tool = function OoUiTool( toolGroup, config ) {
                        dir: 'ltr',
                        lang: 'en'
                } );
-       this.$title
-               .addClass( 'oo-ui-tool-title' )
-               .append( this.$titleText, this.$accel );
        this.$link
                .addClass( 'oo-ui-tool-link' )
-               .append( this.$icon, this.$title )
+               .append( this.$icon, this.$title, this.$accel )
                .prop( 'tabIndex', 0 )
                .attr( 'role', 'button' );
        this.$element
@@ -5664,7 +5715,7 @@ OO.ui.Tool.prototype.updateTitle = function () {
                accel = this.toolbar.getToolAccelerator( this.constructor.static.name ),
                tooltipParts = [];
 
-       this.$titleText.text( this.title );
+       this.$title.text( this.title );
        this.$accel.text( accel );
 
        if ( titleTooltips && typeof this.title === 'string' && this.title.length ) {
@@ -6392,7 +6443,7 @@ OO.ui.MessageDialog.prototype.getBodyHeight = function () {
        $scrollable.height();
        $scrollable.contents().show();
 
-       bodyHeight = Math.round( this.text.$element.outerHeight( true ) );
+       bodyHeight = this.text.$element.outerHeight( true );
        $scrollable[0].style.overflow = oldOverflow;
 
        return bodyHeight;
@@ -7200,6 +7251,8 @@ OO.ui.BookletLayout.prototype.selectFirstSelectablePage = function () {
  * @cfg {string} [help] Explanatory text shown as a '?' icon.
  */
 OO.ui.FieldLayout = function OoUiFieldLayout( fieldWidget, config ) {
+       var hasInputWidget = fieldWidget instanceof OO.ui.InputWidget;
+
        // Configuration initialization
        config = $.extend( { align: 'left' }, config );
 
@@ -7214,6 +7267,7 @@ OO.ui.FieldLayout = function OoUiFieldLayout( fieldWidget, config ) {
 
        // Properties
        this.$field = this.$( '<div>' );
+       this.$body = this.$( '<' + ( hasInputWidget ? 'label' : 'div' ) + '>' );
        this.align = null;
        if ( config.help ) {
                this.popupButtonWidget = new OO.ui.PopupButtonWidget( {
@@ -7234,17 +7288,21 @@ OO.ui.FieldLayout = function OoUiFieldLayout( fieldWidget, config ) {
        }
 
        // Events
-       if ( this.fieldWidget instanceof OO.ui.InputWidget ) {
+       if ( hasInputWidget ) {
                this.$label.on( 'click', this.onLabelClick.bind( this ) );
        }
        this.fieldWidget.connect( this, { disable: 'onFieldDisable' } );
 
        // Initialization
-       this.$element.addClass( 'oo-ui-fieldLayout' );
+       this.$element
+               .addClass( 'oo-ui-fieldLayout' )
+               .append( this.$help, this.$body );
+       this.$body.addClass( 'oo-ui-fieldLayout-body' );
        this.$field
                .addClass( 'oo-ui-fieldLayout-field' )
                .toggleClass( 'oo-ui-fieldLayout-disable', this.fieldWidget.isDisabled() )
                .append( this.fieldWidget.$element );
+
        this.setAlignment( config.align );
 };
 
@@ -7255,17 +7313,6 @@ OO.mixinClass( OO.ui.FieldLayout, OO.ui.LabelElement );
 
 /* Methods */
 
-/**
- * @inheritdoc
- */
-OO.ui.FieldLayout.prototype.getTagName = function () {
-       if ( this.fieldWidget instanceof OO.ui.InputWidget ) {
-               return 'label';
-       } else {
-               return 'div';
-       }
-};
-
 /**
  * Handle field disable events.
  *
@@ -7309,9 +7356,9 @@ OO.ui.FieldLayout.prototype.setAlignment = function ( value ) {
                }
                // Reorder elements
                if ( value === 'inline' ) {
-                       this.$element.append( this.$field, this.$label, this.$help );
+                       this.$body.append( this.$field, this.$label );
                } else {
-                       this.$element.append( this.$help, this.$label, this.$field );
+                       this.$body.append( this.$label, this.$field );
                }
                // Set classes. The following classes can be used here:
                // * oo-ui-fieldLayout-align-left
@@ -7547,15 +7594,15 @@ OO.ui.GridLayout.prototype.update = function () {
                        width = this.widths[x];
                        panel = this.panels[i];
                        dimensions = {
-                               width: Math.round( width * 100 ) + '%',
-                               height: Math.round( height * 100 ) + '%',
-                               top: Math.round( top * 100 ) + '%'
+                               width: ( width * 100 ) + '%',
+                               height: ( height * 100 ) + '%',
+                               top: ( top * 100 ) + '%'
                        };
                        // If RTL, reverse:
                        if ( OO.ui.Element.static.getDir( this.$.context ) === 'rtl' ) {
-                               dimensions.right = Math.round( left * 100 ) + '%';
+                               dimensions.right = ( left * 100 ) + '%';
                        } else {
-                               dimensions.left = Math.round( left * 100 ) + '%';
+                               dimensions.left = ( left * 100 ) + '%';
                        }
                        // HACK: Work around IE bug by setting visibility: hidden; if width or height is zero
                        if ( width === 0 || height === 0 ) {
@@ -8188,7 +8235,7 @@ OO.ui.ListToolGroup.prototype.populate = function () {
        // 'display' attribute and restores it, and the tool uses a <span> and can be hidden and re-shown.
        // Is this a jQuery bug? http://jsfiddle.net/gtj4hu3h/
        if ( this.getExpandCollapseTool().$element.css( 'display' ) === 'inline' ) {
-               this.getExpandCollapseTool().$element.css( 'display', 'inline-block' );
+               this.getExpandCollapseTool().$element.css( 'display', 'block' );
        }
 
        this.updateCollapsibleState();
@@ -9933,6 +9980,7 @@ OO.ui.ButtonInputWidget.prototype.onKeyPress = function ( e ) {
  *
  * @constructor
  * @param {Object} [config] Configuration options
+ * @cfg {boolean} [selected=false] Whether the checkbox is initially selected
  */
 OO.ui.CheckboxInputWidget = function OoUiCheckboxInputWidget( config ) {
        // Parent constructor
@@ -9940,6 +9988,7 @@ OO.ui.CheckboxInputWidget = function OoUiCheckboxInputWidget( config ) {
 
        // Initialization
        this.$element.addClass( 'oo-ui-checkboxInputWidget' );
+       this.setSelected( config.selected !== undefined ? config.selected : false );
 };
 
 /* Setup */
@@ -9959,39 +10008,41 @@ OO.ui.CheckboxInputWidget.prototype.getInputElement = function () {
 };
 
 /**
- * Get checked state of the checkbox
- *
- * @return {boolean} If the checkbox is checked
+ * @inheritdoc
  */
-OO.ui.CheckboxInputWidget.prototype.getValue = function () {
-       return this.value;
+OO.ui.CheckboxInputWidget.prototype.onEdit = function () {
+       var widget = this;
+       if ( !this.isDisabled() ) {
+               // Allow the stack to clear so the value will be updated
+               setTimeout( function () {
+                       widget.setSelected( widget.$input.prop( 'checked' ) );
+               } );
+       }
 };
 
 /**
- * Set checked state of the checkbox
+ * Set selection state of this checkbox.
  *
- * @param {boolean} value New value
+ * @param {boolean} state Whether the checkbox is selected
+ * @chainable
  */
-OO.ui.CheckboxInputWidget.prototype.setValue = function ( value ) {
-       value = !!value;
-       if ( this.value !== value ) {
-               this.value = value;
-               this.$input.prop( 'checked', this.value );
-               this.emit( 'change', this.value );
+OO.ui.CheckboxInputWidget.prototype.setSelected = function ( state ) {
+       state = !!state;
+       if ( this.selected !== state ) {
+               this.selected = state;
+               this.$input.prop( 'checked', this.selected );
+               this.emit( 'change', this.selected );
        }
+       return this;
 };
 
 /**
- * @inheritdoc
+ * Check if this checkbox is selected.
+ *
+ * @return {boolean} Checkbox is selected
  */
-OO.ui.CheckboxInputWidget.prototype.onEdit = function () {
-       var widget = this;
-       if ( !this.isDisabled() ) {
-               // Allow the stack to clear so the value will be updated
-               setTimeout( function () {
-                       widget.setValue( widget.$input.prop( 'checked' ) );
-               } );
-       }
+OO.ui.CheckboxInputWidget.prototype.isSelected = function () {
+       return this.selected;
 };
 
 /**
@@ -10000,14 +10051,12 @@ OO.ui.CheckboxInputWidget.prototype.onEdit = function () {
  * Radio buttons only make sense as a set, and you probably want to use the OO.ui.RadioSelectWidget
  * class instead of using this class directly.
  *
- * This class doesn't make it possible to learn whether the radio button is selected ("pressed").
- *
  * @class
  * @extends OO.ui.InputWidget
  *
  * @constructor
  * @param {Object} [config] Configuration options
- * @param {boolean} [config.selected=false] Whether the radio button is initially selected
+ * @cfg {boolean} [selected=false] Whether the radio button is initially selected
  */
 OO.ui.RadioInputWidget = function OoUiRadioInputWidget( config ) {
        // Parent constructor
@@ -10075,6 +10124,8 @@ OO.ui.RadioInputWidget.prototype.isSelected = function () {
  * @param {Object} [config] Configuration options
  * @cfg {string} [type='text'] HTML tag `type` attribute
  * @cfg {string} [placeholder] Placeholder text
+ * @cfg {boolean} [autofocus=false] Ask the browser to focus this widget, using the 'autofocus' HTML
+ *  attribute
  * @cfg {boolean} [readOnly=false] Prevent changes
  * @cfg {boolean} [multiline=false] Allow multiple lines of text
  * @cfg {boolean} [autosize=false] Automatically resize to fit content
@@ -10128,6 +10179,9 @@ OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) {
        if ( config.placeholder ) {
                this.$input.attr( 'placeholder', config.placeholder );
        }
+       if ( config.autofocus ) {
+               this.$input.attr( 'autofocus', 'autofocus' );
+       }
        this.$element.attr( 'role', 'textbox' );
 };
 
@@ -11316,8 +11370,8 @@ OO.ui.PopupWidget.prototype.updateDimensions = function ( transition ) {
        popupOffset = this.width * ( { left: 0, center: -0.5, right: -1 } )[this.align];
 
        // Figure out if this will cause the popup to go beyond the edge of the container
-       originOffset = Math.round( this.$element.offset().left );
-       containerLeft = Math.round( this.$container.offset().left );
+       originOffset = this.$element.offset().left;
+       containerLeft = this.$container.offset().left;
        containerWidth = this.$container.innerWidth();
        containerRight = containerLeft + containerWidth;
        popupLeft = popupOffset - this.containerPadding;
@@ -11391,7 +11445,7 @@ OO.ui.ProgressBarWidget = function OoUiProgressBarWidget( config ) {
 
        // Initialization
        this.setProgress( config.progress !== undefined ? config.progress : false );
-       this.$bar.addClass( 'oo-ui-progressBarWidget-bar');
+       this.$bar.addClass( 'oo-ui-progressBarWidget-bar' );
        this.$element
                .attr( {
                        role: 'progressbar',