Merge "Title: Title::getSubpage should not lose the interwiki prefix"
[lhc/web/wiklou.git] / resources / lib / ooui / oojs-ui-core.js
index f78f4e7..97c7d9a 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOUI v0.32.1
+ * OOUI v0.33.2
  * https://www.mediawiki.org/wiki/OOUI
  *
  * Copyright 2011–2019 OOUI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2019-06-05T16:24:08Z
+ * Date: 2019-07-10T12:25:07Z
  */
 ( function ( OO ) {
 
@@ -292,7 +292,7 @@ OO.ui.warnDeprecation = function ( message ) {
  */
 OO.ui.throttle = function ( func, wait ) {
        var context, args, timeout,
-               previous = 0,
+               previous = Date.now() - wait,
                run = function () {
                        timeout = null;
                        previous = Date.now();
@@ -304,33 +304,17 @@ OO.ui.throttle = function ( func, wait ) {
                // period. If it's less, run the function immediately. If it's more,
                // set a timeout for the remaining time -- but don't replace an
                // existing timeout, since that'd indefinitely prolong the wait.
-               var remaining = wait - ( Date.now() - previous );
+               var remaining = Math.max( wait - ( Date.now() - previous ), 0 );
                context = this;
                args = arguments;
-               if ( remaining <= 0 ) {
-                       // Note: unless wait was ridiculously large, this means we'll
-                       // automatically run the first time the function was called in a
-                       // given period. (If you provide a wait period larger than the
-                       // current Unix timestamp, you *deserve* unexpected behavior.)
-                       clearTimeout( timeout );
-                       run();
-               } else if ( !timeout ) {
+               if ( !timeout ) {
+                       // If time is up, do setTimeout( run, 0 ) so the function
+                       // always runs asynchronously, just like Promise#then .
                        timeout = setTimeout( run, remaining );
                }
        };
 };
 
-/**
- * A (possibly faster) way to get the current timestamp as an integer.
- *
- * @deprecated Since 0.31.1; use `Date.now()` instead.
- * @return {number} Current timestamp, in milliseconds since the Unix epoch
- */
-OO.ui.now = function () {
-       OO.ui.warnDeprecation( 'OO.ui.now() is deprecated, use Date.now() instead' );
-       return Date.now();
-};
-
 /**
  * Reconstitute a JavaScript object corresponding to a widget created by
  * the PHP implementation.
@@ -613,10 +597,6 @@ OO.ui.Element = function OoUiElement( config ) {
        config = config || {};
 
        // Properties
-       this.$ = function () {
-               OO.ui.warnDeprecation( 'this.$ is deprecated, use global $ instead' );
-               return $.apply( this, arguments );
-       };
        this.elementId = null;
        this.visible = true;
        this.data = config.data;
@@ -893,29 +873,6 @@ OO.ui.Element.static.gatherPreInfuseState = function () {
        return {};
 };
 
-/**
- * Get a jQuery function within a specific document.
- *
- * @static
- * @param {jQuery|HTMLElement|HTMLDocument|Window} context Context to bind the function to
- * @param {jQuery} [$iframe] HTML iframe element that contains the document, omit if document is
- *   not in an iframe
- * @return {Function} Bound jQuery function
- */
-OO.ui.Element.static.getJQuery = function ( context, $iframe ) {
-       function wrapper( selector ) {
-               return $( selector, wrapper.context );
-       }
-
-       wrapper.context = this.getDocument( context );
-
-       if ( $iframe ) {
-               wrapper.$iframe = $iframe;
-       }
-
-       return wrapper;
-};
-
 /**
  * Get the document of an element.
  *
@@ -1095,7 +1052,7 @@ OO.ui.Element.static.getDimensions = function ( el ) {
                        borders: { top: 0, left: 0, bottom: 0, right: 0 },
                        scroll: {
                                top: $win.scrollTop(),
-                               left: $win.scrollLeft()
+                               left: OO.ui.Element.static.getScrollLeft( win )
                        },
                        scrollbar: { right: 0, bottom: 0 },
                        rect: {
@@ -1111,7 +1068,7 @@ OO.ui.Element.static.getDimensions = function ( el ) {
                        borders: this.getBorders( el ),
                        scroll: {
                                top: $el.scrollTop(),
-                               left: $el.scrollLeft()
+                               left: OO.ui.Element.static.getScrollLeft( el )
                        },
                        scrollbar: {
                                right: $el.innerWidth() - el.clientWidth,
@@ -1122,29 +1079,12 @@ OO.ui.Element.static.getDimensions = function ( el ) {
        }
 };
 
-/**
- * Get the number of pixels that an element's content is scrolled to the left.
- *
- * Adapted from <https://github.com/othree/jquery.rtl-scroll-type>.
- * Original code copyright 2012 Wei-Ko Kao, licensed under the MIT License.
- *
- * This function smooths out browser inconsistencies (nicely described in the README at
- * <https://github.com/othree/jquery.rtl-scroll-type>) and produces a result consistent
- * with Firefox's 'scrollLeft', which seems the sanest.
- *
- * @static
- * @method
- * @param {HTMLElement|Window} el Element to measure
- * @return {number} Scroll position from the left.
- *  If the element's direction is LTR, this is a positive number between `0` (initial scroll
- *  position) and `el.scrollWidth - el.clientWidth` (furthest possible scroll position).
- *  If the element's direction is RTL, this is a negative number between `0` (initial scroll
- *  position) and `-el.scrollWidth + el.clientWidth` (furthest possible scroll position).
- */
-OO.ui.Element.static.getScrollLeft = ( function () {
+( function () {
        var rtlScrollType = null;
 
-       function test() {
+       // Adapted from <https://github.com/othree/jquery.rtl-scroll-type>.
+       // Original code copyright 2012 Wei-Ko Kao, licensed under the MIT License.
+       function rtlScrollTypeTest() {
                var $definer = $( '<div>' ).attr( {
                                dir: 'rtl',
                                style: 'font-size: 14px; width: 4px; height: 1px; position: absolute; top: -1000px; overflow: scroll;'
@@ -1168,28 +1108,113 @@ OO.ui.Element.static.getScrollLeft = ( function () {
                $definer.remove();
        }
 
-       return function getScrollLeft( el ) {
-               var isRoot = el.window === el ||
-                               el === el.ownerDocument.body ||
-                               el === el.ownerDocument.documentElement,
-                       scrollLeft = isRoot ? $( window ).scrollLeft() : el.scrollLeft,
-                       // All browsers use the correct scroll type ('negative') on the root, so don't
-                       // do any fixups when looking at the root element
-                       direction = isRoot ? 'ltr' : $( el ).css( 'direction' );
+       function isRoot( el ) {
+               return el.window === el ||
+                       el === el.ownerDocument.body ||
+                       el === el.ownerDocument.documentElement;
+       }
+
+       /**
+        * Convert native `scrollLeft` value to a value consistent between browsers. See #getScrollLeft.
+        * @param {number} nativeOffset Native `scrollLeft` value
+        * @param {HTMLElement|Window} el Element from which the value was obtained
+        * @return {number}
+        */
+       OO.ui.Element.static.computeNormalizedScrollLeft = function ( nativeOffset, el ) {
+               // All browsers use the correct scroll type ('negative') on the root, so don't
+               // do any fixups when looking at the root element
+               var direction = isRoot( el ) ? 'ltr' : $( el ).css( 'direction' );
+
+               if ( direction === 'rtl' ) {
+                       if ( rtlScrollType === null ) {
+                               rtlScrollTypeTest();
+                       }
+                       if ( rtlScrollType === 'reverse' ) {
+                               return -nativeOffset;
+                       } else if ( rtlScrollType === 'default' ) {
+                               return nativeOffset - el.scrollWidth + el.clientWidth;
+                       }
+               }
+
+               return nativeOffset;
+       };
+
+       /**
+        * Convert our normalized `scrollLeft` value to a value for current browser. See #getScrollLeft.
+        * @param {number} normalizedOffset Normalized `scrollLeft` value
+        * @param {HTMLElement|Window} el Element on which the value will be set
+        * @return {number}
+        */
+       OO.ui.Element.static.computeNativeScrollLeft = function ( normalizedOffset, el ) {
+               // All browsers use the correct scroll type ('negative') on the root, so don't
+               // do any fixups when looking at the root element
+               var direction = isRoot( el ) ? 'ltr' : $( el ).css( 'direction' );
 
                if ( direction === 'rtl' ) {
                        if ( rtlScrollType === null ) {
-                               test();
+                               rtlScrollTypeTest();
                        }
                        if ( rtlScrollType === 'reverse' ) {
-                               scrollLeft = -scrollLeft;
+                               return -normalizedOffset;
                        } else if ( rtlScrollType === 'default' ) {
-                               scrollLeft = scrollLeft - el.scrollWidth + el.clientWidth;
+                               return normalizedOffset + el.scrollWidth - el.clientWidth;
                        }
                }
 
+               return normalizedOffset;
+       };
+
+       /**
+        * Get the number of pixels that an element's content is scrolled to the left.
+        *
+        * This function smooths out browser inconsistencies (nicely described in the README at
+        * <https://github.com/othree/jquery.rtl-scroll-type>) and produces a result consistent
+        * with Firefox's 'scrollLeft', which seems the sanest.
+        *
+        * (Firefox's scrollLeft handling is nice because it increases from left to right, consistently
+        * with `getBoundingClientRect().left` and related APIs; because initial value is zero, so
+        * resetting it is easy; because adapting a hardcoded scroll position to a symmetrical RTL
+        * interface requires just negating it, rather than involving `clientWidth` and `scrollWidth`;
+        * and because if you mess up and don't adapt your code to RTL, it will scroll to the beginning
+        * rather than somewhere randomly in the middle but not where you wanted.)
+        *
+        * @static
+        * @method
+        * @param {HTMLElement|Window} el Element to measure
+        * @return {number} Scroll position from the left.
+        *  If the element's direction is LTR, this is a positive number between `0` (initial scroll
+        *  position) and `el.scrollWidth - el.clientWidth` (furthest possible scroll position).
+        *  If the element's direction is RTL, this is a negative number between `0` (initial scroll
+        *  position) and `-el.scrollWidth + el.clientWidth` (furthest possible scroll position).
+        */
+       OO.ui.Element.static.getScrollLeft = function ( el ) {
+               var scrollLeft = isRoot( el ) ? $( window ).scrollLeft() : el.scrollLeft;
+               scrollLeft = OO.ui.Element.static.computeNormalizedScrollLeft( scrollLeft, el );
                return scrollLeft;
        };
+
+       /**
+        * Set the number of pixels that an element's content is scrolled to the left.
+        *
+        * See #getScrollLeft.
+        *
+        * @static
+        * @method
+        * @param {HTMLElement|Window} el Element to scroll (and to use in calculations)
+        * @param {number} scrollLeft Scroll position from the left.
+        *  If the element's direction is LTR, this must be a positive number between `0` (initial scroll
+        *  position) and `el.scrollWidth - el.clientWidth` (furthest possible scroll position).
+        *  If the element's direction is RTL, this must be a negative number between `0` (initial scroll
+        *  position) and `-el.scrollWidth + el.clientWidth` (furthest possible scroll position).
+        */
+       OO.ui.Element.static.setScrollLeft = function ( el, scrollLeft ) {
+               scrollLeft = OO.ui.Element.static.computeNativeScrollLeft( scrollLeft, el );
+               if ( isRoot( el ) ) {
+                       $( window ).scrollLeft( scrollLeft );
+               } else {
+                       el.scrollLeft = scrollLeft;
+               }
+       };
 }() );
 
 /**
@@ -1368,6 +1393,9 @@ OO.ui.Element.static.scrollIntoView = function ( elOrPosition, config ) {
                                // of scrolling the left out of view
                                Math.min( position.left - padding.left, -position.right + padding.right );
                }
+               if ( animations.scrollLeft !== undefined ) {
+                       animations.scrollLeft = OO.ui.Element.static.computeNativeScrollLeft( animations.scrollLeft, container );
+               }
        }
        if ( !$.isEmptyObject( animations ) ) {
                if ( animate ) {
@@ -1708,7 +1736,7 @@ OO.mixinClass( OO.ui.Layout, OO.EventEmitter );
  */
 OO.ui.Layout.prototype.resetScroll = function () {
        this.$element[ 0 ].scrollTop = 0;
-       // TODO: Reset scrollLeft in an RTL-aware manner, see OO.ui.Element.static.getScrollLeft.
+       OO.ui.Element.static.setScrollLeft( this.$element[ 0 ], 0 );
 
        return this;
 };
@@ -3052,16 +3080,6 @@ OO.ui.mixin.IconElement.prototype.getIcon = function () {
        return this.icon;
 };
 
-/**
- * Get the icon title. The title text is displayed when a user moves the mouse over the icon.
- *
- * @return {string} Icon title text
- * @deprecated
- */
-OO.ui.mixin.IconElement.prototype.getIconTitle = function () {
-       return this.iconTitle;
-};
-
 /**
  * IndicatorElement is often mixed into other classes to generate an indicator.
  * Indicators are small graphics that are generally used in two ways:
@@ -3196,18 +3214,6 @@ OO.ui.mixin.IndicatorElement.prototype.getIndicator = function () {
        return this.indicator;
 };
 
-/**
- * Get the indicator title.
- *
- * The title is displayed when a user moves the mouse over the indicator.
- *
- * @return {string} Indicator title text
- * @deprecated
- */
-OO.ui.mixin.IndicatorElement.prototype.getIndicatorTitle = function () {
-       return this.indicatorTitle;
-};
-
 /**
  * The FlaggedElement class is an attribute mixin, meaning that it is used to add
  * additional functionality to an element created by another class. The class provides
@@ -4274,6 +4280,125 @@ OO.mixinClass( OO.ui.LabelWidget, OO.ui.mixin.TitledElement );
  */
 OO.ui.LabelWidget.static.tagName = 'label';
 
+/**
+ * MessageWidget produces a visual component for sending a notice to the user
+ * with an icon and distinct design noting its purpose. The MessageWidget changes
+ * its visual presentation based on the type chosen, which also denotes its UX
+ * purpose.
+ *
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.mixin.IconElement
+ * @mixins OO.ui.mixin.LabelElement
+ * @mixins OO.ui.mixin.TitledElement
+ * @mixins OO.ui.mixin.FlaggedElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {string} [type='notice'] The type of the notice widget. This will also
+ *  impact the flags that the widget receives (and hence its CSS design) as well
+ *  as the icon that appears. Available types:
+ *  'notice', 'error', 'warning', 'success'
+ * @cfg {boolean} [inline] Set the notice as an inline notice. The default
+ *  is not inline, or 'boxed' style.
+ */
+OO.ui.MessageWidget = function OoUiMessageWidget( config ) {
+       // Configuration initialization
+       config = config || {};
+
+       // Parent constructor
+       OO.ui.MessageWidget.parent.call( this, config );
+
+       // Mixin constructors
+       OO.ui.mixin.IconElement.call( this, config );
+       OO.ui.mixin.LabelElement.call( this, config );
+       OO.ui.mixin.TitledElement.call( this, config );
+       OO.ui.mixin.FlaggedElement.call( this, config );
+
+       // Set type
+       this.setType( config.type );
+       this.setInline( config.inline );
+
+       // Build the widget
+       this.$element
+               .append( this.$icon, this.$label )
+               .addClass( 'oo-ui-messageWidget' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.MessageWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.MessageWidget, OO.ui.mixin.IconElement );
+OO.mixinClass( OO.ui.MessageWidget, OO.ui.mixin.LabelElement );
+OO.mixinClass( OO.ui.MessageWidget, OO.ui.mixin.TitledElement );
+OO.mixinClass( OO.ui.MessageWidget, OO.ui.mixin.FlaggedElement );
+
+/* Static Properties */
+
+/**
+ * An object defining the icon name per defined type.
+ *
+ * @static
+ * @property {Object}
+ */
+OO.ui.MessageWidget.static.iconMap = {
+       notice: 'infoFilled',
+       error: 'error',
+       warning: 'alert',
+       success: 'check'
+};
+
+/* Methods */
+
+/**
+ * Set the inline state of the widget.
+ *
+ * @param {boolean} inline Widget is inline
+ */
+OO.ui.MessageWidget.prototype.setInline = function ( inline ) {
+       inline = !!inline;
+
+       if ( this.inline !== inline ) {
+               this.inline = inline;
+               this.$element
+                       .toggleClass( 'oo-ui-messageWidget-block', !this.inline );
+       }
+};
+/**
+ * Set the widget type. The given type must belong to the list of
+ * legal types set by OO.ui.MessageWidget.static.iconMap
+ *
+ * @param  {string} [type] Given type. Defaults to 'notice'
+ */
+OO.ui.MessageWidget.prototype.setType = function ( type ) {
+       // Validate type
+       if ( Object.keys( this.constructor.static.iconMap ).indexOf( type ) === -1 ) {
+               type = 'notice'; // Default
+       }
+
+       if ( this.type !== type ) {
+
+               // Flags
+               this.clearFlags();
+               this.setFlags( type );
+
+               // Set the icon and its variant
+               this.setIcon( this.constructor.static.iconMap[ type ] );
+               this.$icon.removeClass( 'oo-ui-image-' + this.type );
+               this.$icon.addClass( 'oo-ui-image-' + type );
+
+               if ( type === 'error' ) {
+                       this.$element.attr( 'role', 'alert' );
+                       this.$element.removeAttr( 'aria-live' );
+               } else {
+                       this.$element.removeAttr( 'role' );
+                       this.$element.attr( 'aria-live', 'polite' );
+               }
+
+               this.type = type;
+       }
+};
+
 /**
  * PendingElement is a mixin that is used to create elements that notify users that something is
  * happening and that they should wait before proceeding. The pending state is visually represented
@@ -7634,7 +7759,7 @@ OO.ui.MenuSectionOptionWidget.static.highlightable = false;
  * @cfg {boolean} [highlightOnFilter] Highlight the first result when filtering
  * @cfg {string} [filterMode='prefix'] The mode by which the menu filters the results.
  *  Options are 'exact', 'prefix' or 'substring'. See `OO.ui.SelectWidget#getItemMatcher`
- * @param {number|string} [width] Width of the menu as a number of pixels or CSS string with unit
+ * @cfg {number|string} [width] Width of the menu as a number of pixels or CSS string with unit
  *  suffix, used by {@link OO.ui.mixin.ClippableElement ClippableElement}
  */
 OO.ui.MenuSelectWidget = function OoUiMenuSelectWidget( config ) {
@@ -11986,7 +12111,7 @@ OO.ui.FieldLayout = function OoUiFieldLayout( fieldWidget, config ) {
        this.successMessages = [];
        this.notices = [];
        this.$field = this.isFieldInline() ? $( '<span>' ) : $( '<div>' );
-       this.$messages = $( '<ul>' );
+       this.$messages = $( '<div>' );
        this.$header = $( '<span>' );
        this.$body = $( '<div>' );
        this.align = null;
@@ -12081,26 +12206,11 @@ OO.ui.FieldLayout.prototype.isFieldInline = function () {
  * @return {jQuery}
  */
 OO.ui.FieldLayout.prototype.makeMessage = function ( kind, text ) {
-       var $listItem, $icon, message;
-       $listItem = $( '<li>' );
-       if ( kind === 'error' ) {
-               $icon = new OO.ui.IconWidget( { icon: 'error', flags: [ 'error' ] } ).$element;
-               $listItem.attr( 'role', 'alert' );
-       } else if ( kind === 'warning' ) {
-               $icon = new OO.ui.IconWidget( { icon: 'alert', flags: [ 'warning' ] } ).$element;
-               $listItem.attr( 'role', 'alert' );
-       } else if ( kind === 'success' ) {
-               $icon = new OO.ui.IconWidget( { icon: 'check', flags: [ 'success' ] } ).$element;
-       } else if ( kind === 'notice' ) {
-               $icon = new OO.ui.IconWidget( { icon: 'notice' } ).$element;
-       } else {
-               $icon = '';
-       }
-       message = new OO.ui.LabelWidget( { label: text } );
-       $listItem
-               .append( $icon, message.$element )
-               .addClass( 'oo-ui-fieldLayout-messages-' + kind );
-       return $listItem;
+       return new OO.ui.MessageWidget( {
+               type: kind,
+               inline: true,
+               label: text
+       } ).$element;
 };
 
 /**