Merge "Remove two deprecated functions and one depreciated variable in a function...
[lhc/web/wiklou.git] / resources / lib / oojs-ui / oojs-ui-core.js
index 2c9731d..fd238ff 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOjs UI v0.22.3
+ * OOjs UI v0.22.5
  * https://www.mediawiki.org/wiki/OOjs_UI
  *
  * Copyright 2011–2017 OOjs UI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2017-07-11T22:12:33Z
+ * Date: 2017-08-22T21:37:37Z
  */
 ( function ( OO ) {
 
@@ -362,6 +362,8 @@ OO.ui.infuse = function ( idOrNode ) {
                'ooui-toolgroup-expand': 'More',
                // Label for the fake tool that collapses the full list of tools in a toolbar group
                'ooui-toolgroup-collapse': 'Fewer',
+               // Default label for the tooltip for the button that removes a tag item
+               'ooui-item-remove': 'Remove',
                // Default label for the accept button of a confirmation dialog
                'ooui-dialog-message-accept': 'OK',
                // Default label for the reject button of a confirmation dialog
@@ -659,7 +661,7 @@ OO.ui.Element.static.tagName = 'div';
 OO.ui.Element.static.infuse = function ( idOrNode ) {
        var obj = OO.ui.Element.static.unsafeInfuse( idOrNode, false );
        // Verify that the type matches up.
-       // FIXME: uncomment after T89721 is fixed (see T90929)
+       // FIXME: uncomment after T89721 is fixed, see T90929.
        /*
        if ( !( obj instanceof this['class'] ) ) {
                throw new Error( 'Infusion type mismatch!' );
@@ -796,7 +798,7 @@ OO.ui.Element.static.unsafeInfuse = function ( idOrNode, domPromise ) {
                if ( $elem[ 0 ] !== obj.$element[ 0 ] ) {
                        $elem.replaceWith( obj.$element );
                        // This element is now gone from the DOM, but if anyone is holding a reference to it,
-                       // let's allow them to OO.ui.infuse() it and do what they expect (T105828).
+                       // let's allow them to OO.ui.infuse() it and do what they expect, see T105828.
                        // Do not use jQuery.data(), as using it on detached nodes leaks memory in 1.x line by design.
                        $elem[ 0 ].oouiInfused = obj.$element;
                }
@@ -2679,8 +2681,8 @@ OO.ui.mixin.IconElement.prototype.setIcon = function ( icon ) {
  * @chainable
  */
 OO.ui.mixin.IconElement.prototype.setIconTitle = function ( iconTitle ) {
-       iconTitle = typeof iconTitle === 'function' ||
-               ( typeof iconTitle === 'string' && iconTitle.length ) ?
+       iconTitle =
+               ( typeof iconTitle === 'function' || ( typeof iconTitle === 'string' && iconTitle.length ) ) ?
                        OO.ui.resolveMsg( iconTitle ) : null;
 
        if ( this.iconTitle !== iconTitle ) {
@@ -2848,8 +2850,8 @@ OO.ui.mixin.IndicatorElement.prototype.setIndicator = function ( indicator ) {
  * @chainable
  */
 OO.ui.mixin.IndicatorElement.prototype.setIndicatorTitle = function ( indicatorTitle ) {
-       indicatorTitle = typeof indicatorTitle === 'function' ||
-               ( typeof indicatorTitle === 'string' && indicatorTitle.length ) ?
+       indicatorTitle =
+               ( typeof indicatorTitle === 'function' || ( typeof indicatorTitle === 'string' && indicatorTitle.length ) ) ?
                        OO.ui.resolveMsg( indicatorTitle ) : null;
 
        if ( this.indicatorTitle !== indicatorTitle ) {
@@ -3344,7 +3346,7 @@ OO.ui.mixin.TitledElement.prototype.setTitledElement = function ( $titled ) {
 
        this.$titled = $titled;
        if ( this.title ) {
-               this.$titled.attr( 'title', this.title );
+               this.updateTitle();
        }
 };
 
@@ -3359,19 +3361,35 @@ OO.ui.mixin.TitledElement.prototype.setTitle = function ( title ) {
        title = ( typeof title === 'string' && title.length ) ? title : null;
 
        if ( this.title !== title ) {
-               if ( this.$titled ) {
-                       if ( title !== null ) {
-                               this.$titled.attr( 'title', title );
-                       } else {
-                               this.$titled.removeAttr( 'title' );
-                       }
-               }
                this.title = title;
+               this.updateTitle();
        }
 
        return this;
 };
 
+/**
+ * Update the title attribute, in case of changes to title or accessKey.
+ *
+ * @protected
+ * @chainable
+ */
+OO.ui.mixin.TitledElement.prototype.updateTitle = function () {
+       var title = this.getTitle();
+       if ( this.$titled ) {
+               if ( title !== null ) {
+                       // Only if this is an AccessKeyedElement
+                       if ( this.formatTitleWithAccessKey ) {
+                               title = this.formatTitleWithAccessKey( title );
+                       }
+                       this.$titled.attr( 'title', title );
+               } else {
+                       this.$titled.removeAttr( 'title' );
+               }
+       }
+       return this;
+};
+
 /**
  * Get title.
  *
@@ -3418,6 +3436,12 @@ OO.ui.mixin.AccessKeyedElement = function OoUiMixinAccessKeyedElement( config )
        // Initialization
        this.setAccessKey( config.accessKey || null );
        this.setAccessKeyedElement( config.$accessKeyed || this.$element );
+
+       // If this is also a TitledElement and it initialized before we did, we may have
+       // to update the title with the access key
+       if ( this.updateTitle ) {
+               this.updateTitle();
+       }
 };
 
 /* Setup */
@@ -3474,6 +3498,11 @@ OO.ui.mixin.AccessKeyedElement.prototype.setAccessKey = function ( accessKey ) {
                        }
                }
                this.accessKey = accessKey;
+
+               // Only if this is a TitledElement
+               if ( this.updateTitle ) {
+                       this.updateTitle();
+               }
        }
 
        return this;
@@ -3488,6 +3517,32 @@ OO.ui.mixin.AccessKeyedElement.prototype.getAccessKey = function () {
        return this.accessKey;
 };
 
+/**
+ * Add information about the access key to the element's tooltip label.
+ * (This is only public for hacky usage in FieldLayout.)
+ *
+ * @param {string} title Tooltip label for `title` attribute
+ * @return {string}
+ */
+OO.ui.mixin.AccessKeyedElement.prototype.formatTitleWithAccessKey = function ( title ) {
+       var accessKey;
+
+       if ( !this.$accessKeyed ) {
+               // Not initialized yet; the constructor will call updateTitle() which will rerun this function
+               return title;
+       }
+       // Use jquery.accessKeyLabel if available to show modifiers, otherwise just display the single key
+       if ( $.fn.updateTooltipAccessKeys && $.fn.updateTooltipAccessKeys.getAccessKeyLabel ) {
+               accessKey = $.fn.updateTooltipAccessKeys.getAccessKeyLabel( this.$accessKeyed[ 0 ] );
+       } else {
+               accessKey = this.getAccessKey();
+       }
+       if ( accessKey ) {
+               title += ' [' + accessKey + ']';
+       }
+       return title;
+};
+
 /**
  * ButtonWidget is a generic widget for buttons. A wide variety of looks,
  * feels, and functionality can be customized via the class’s configuration options
@@ -4781,7 +4836,7 @@ OO.ui.mixin.ClippableElement.prototype.clip = function () {
        clipHeight = allotedHeight < naturalHeight;
 
        if ( clipWidth ) {
-               // The order matters here. If overflow is not set first, Chrome displays bogus scrollbars. (T157672)
+               // 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' );
                void this.$clippable[ 0 ].offsetHeight; // Force reflow
@@ -4797,7 +4852,7 @@ OO.ui.mixin.ClippableElement.prototype.clip = function () {
                } );
        }
        if ( clipHeight ) {
-               // The order matters here. If overflow is not set first, Chrome displays bogus scrollbars. (T157672)
+               // 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' );
                void this.$clippable[ 0 ].offsetHeight; // Force reflow
@@ -6310,7 +6365,7 @@ OO.ui.SelectWidget.prototype.getItemMatcher = function ( s, exact ) {
                s = s.normalize();
        }
        s = exact ? s.trim() : s.replace( /^\s+/, '' );
-       re = '^\\s*' + s.replace( /([\\{}()|.?*+\-\^$\[\]])/g, '\\$1' ).replace( /\s+/g, '\\s+' );
+       re = '^\\s*' + s.replace( /([\\{}()|.?*+\-^$[\]])/g, '\\$1' ).replace( /\s+/g, '\\s+' );
        if ( exact ) {
                re += '\\s*$';
        }
@@ -7001,9 +7056,9 @@ OO.ui.MenuSelectWidget.prototype.onDocumentMouseDown = function ( e ) {
        if (
                this.isVisible() &&
                !OO.ui.contains(
-                               this.$element.add( this.$widget ).add( this.$autoCloseIgnore ).get(),
-                               e.target,
-                               true
+                       this.$element.add( this.$widget ).add( this.$autoCloseIgnore ).get(),
+                       e.target,
+                       true
                )
        ) {
                this.toggle( false );
@@ -8319,7 +8374,7 @@ OO.mixinClass( OO.ui.InputWidget, OO.ui.mixin.AccessKeyedElement );
  */
 OO.ui.InputWidget.static.reusePreInfuseDOM = function ( node, config ) {
        config = OO.ui.InputWidget.parent.static.reusePreInfuseDOM( node, config );
-       // Reusing $input lets browsers preserve inputted values across page reloads (T114134)
+       // Reusing `$input` lets browsers preserve inputted values across page reloads, see T114134.
        config.$input = $( node ).find( '.oo-ui-inputWidget-input' );
        return config;
 };
@@ -9340,6 +9395,11 @@ OO.ui.CheckboxMultiselectInputWidget = function OoUiCheckboxMultiselectInputWidg
        this.setOptions( config.options || [] );
        // Have to repeat this from parent, as we need options to be set up for this to make sense
        this.setValue( config.value );
+
+       // setValue when checkboxMultiselectWidget changes
+       this.checkboxMultiselectWidget.on( 'change', function () {
+               this.setValue( this.checkboxMultiselectWidget.getSelectedItemsData() );
+       }.bind( this ) );
 };
 
 /* Setup */
@@ -9530,7 +9590,7 @@ OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) {
        }, config );
 
        if ( config.multiline ) {
-               OO.ui.warnDeprecation( 'TextInputWidget: config.multiline is deprecated. Use the MultilineTextInputWidget instead. See T130434 for details.' );
+               OO.ui.warnDeprecation( 'TextInputWidget: config.multiline is deprecated. Use the MultilineTextInputWidget instead. See T130434.' );
                return new OO.ui.MultilineTextInputWidget( config );
        }
 
@@ -10141,25 +10201,24 @@ OO.ui.TextInputWidget.prototype.updatePosition = function () {
  * @chainable
  */
 OO.ui.TextInputWidget.prototype.positionLabel = function () {
-       var after, rtl, property;
+       var after, rtl, property, newCss;
 
        if ( this.isWaitingToBeAttached ) {
                // #onElementAttach will be called soon, which calls this method
                return this;
        }
 
-       // Clear old values
-       this.$input
-               // Clear old values if present
-               .css( {
-                       'padding-right': '',
-                       'padding-left': ''
-               } );
+       newCss = {
+               'padding-right': '',
+               'padding-left': ''
+       };
 
        if ( this.label ) {
                this.$element.append( this.$label );
        } else {
                this.$label.detach();
+               // Clear old values if present
+               this.$input.css( newCss );
                return;
        }
 
@@ -10167,7 +10226,9 @@ OO.ui.TextInputWidget.prototype.positionLabel = function () {
        rtl = this.$element.css( 'direction' ) === 'rtl';
        property = after === rtl ? 'padding-left' : 'padding-right';
 
-       this.$input.css( property, this.$label.outerWidth( true ) + ( after ? this.scrollWidth : 0 ) );
+       newCss[ property ] = this.$label.outerWidth( true ) + ( after ? this.scrollWidth : 0 );
+       // We have to clear the padding on the other side, in case the element direction changed
+       this.$input.css( newCss );
 
        return this;
 };
@@ -10876,6 +10937,8 @@ OO.ui.FieldLayout = function OoUiFieldLayout( fieldWidget, config ) {
        this.setErrors( config.errors || [] );
        this.setNotices( config.notices || [] );
        this.setAlignment( config.align );
+       // Call this again to take into account the widget's accessKey
+       this.updateTitle();
 };
 
 /* Setup */
@@ -11033,6 +11096,21 @@ OO.ui.FieldLayout.prototype.updateMessages = function () {
        }
 };
 
+/**
+ * Include information about the widget's accessKey in our title. TitledElement calls this method.
+ * (This is a bit of a hack.)
+ *
+ * @protected
+ * @param {string} title Tooltip label for 'title' attribute
+ * @return {string}
+ */
+OO.ui.FieldLayout.prototype.formatTitleWithAccessKey = function ( title ) {
+       if ( this.fieldWidget && this.fieldWidget.formatTitleWithAccessKey ) {
+               return this.fieldWidget.formatTitleWithAccessKey( title );
+       }
+       return title;
+};
+
 /**
  * ActionFieldLayouts are used with OO.ui.FieldsetLayout. The layout consists of a field-widget, a button,
  * and an optional label and/or help text. The field-widget (e.g., a {@link OO.ui.TextInputWidget TextInputWidget}),
@@ -11168,11 +11246,11 @@ OO.ui.FieldsetLayout = function OoUiFieldsetLayout( config ) {
 
        // Mixin constructors
        OO.ui.mixin.IconElement.call( this, config );
-       OO.ui.mixin.LabelElement.call( this, $.extend( {}, config, { $label: $( '<div>' ) } ) );
+       OO.ui.mixin.LabelElement.call( this, config );
        OO.ui.mixin.GroupElement.call( this, config );
 
        // Properties
-       this.$header = $( '<div>' );
+       this.$header = $( '<legend>' );
        if ( config.help ) {
                this.popupButtonWidget = new OO.ui.PopupButtonWidget( {
                        $overlay: config.$overlay,