Update OOUI to v0.27.3
[lhc/web/wiklou.git] / resources / lib / oojs-ui / oojs-ui-toolbars.js
index fb76013..f5c6912 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * OOUI v0.26.4
+ * OOUI v0.27.3
  * https://www.mediawiki.org/wiki/OOUI
  *
  * Copyright 2011–2018 OOUI Team and other contributors.
  * Released under the MIT license
  * http://oojs.mit-license.org
  *
- * Date: 2018-04-17T22:23:58Z
+ * Date: 2018-06-07T21:36:30Z
  */
 ( function ( OO ) {
 
  *  in the toolbar, but are not configured as tools. By default, actions are displayed on the right side of
  *  the toolbar.
  * @cfg {string} [position='top'] Whether the toolbar is positioned above ('top') or below ('bottom') content.
+ * @cfg {jQuery} [$overlay] An overlay for the popup.
+ *  See <https://www.mediawiki.org/wiki/OOUI/Concepts#Overlays>.
  */
 OO.ui.Toolbar = function OoUiToolbar( toolFactory, toolGroupFactory, config ) {
        // Allow passing positional parameters inside the config object
@@ -315,14 +317,16 @@ OO.ui.Toolbar = function OoUiToolbar( toolFactory, toolGroupFactory, config ) {
        // Properties
        this.toolFactory = toolFactory;
        this.toolGroupFactory = toolGroupFactory;
-       this.groups = [];
+       this.groupsByName = {};
        this.tools = {};
        this.position = config.position || 'top';
        this.$bar = $( '<div>' );
        this.$actions = $( '<div>' );
+       this.$popups = $( '<div>' );
        this.initialized = false;
        this.narrowThreshold = null;
        this.onWindowResizeHandler = this.onWindowResize.bind( this );
+       this.$overlay = ( config.$overlay === true ? OO.ui.getDefaultOverlay() : config.$overlay ) || this.$element;
 
        // Events
        this.$element
@@ -334,11 +338,13 @@ OO.ui.Toolbar = function OoUiToolbar( toolFactory, toolGroupFactory, config ) {
        if ( config.actions ) {
                this.$bar.append( this.$actions.addClass( 'oo-ui-toolbar-actions' ) );
        }
+       this.$popups.addClass( 'oo-ui-toolbar-popups' );
        this.$bar
                .addClass( 'oo-ui-toolbar-bar' )
                .append( this.$group, '<div style="clear:both"></div>' );
        // Possible classes: oo-ui-toolbar-position-top, oo-ui-toolbar-position-bottom
        this.$element.addClass( 'oo-ui-toolbar oo-ui-toolbar-position-' + this.position ).append( this.$bar );
+       this.$overlay.append( this.$popups );
 };
 
 /* Setup */
@@ -400,7 +406,7 @@ OO.ui.Toolbar.prototype.onPointerDown = function ( e ) {
  * @param {jQuery.Event} e Window resize event
  */
 OO.ui.Toolbar.prototype.onWindowResize = function () {
-       this.$element.toggleClass(
+       this.$element.add( this.$popups ).toggleClass(
                'oo-ui-toolbar-narrow',
                this.$bar[ 0 ].clientWidth <= this.getNarrowThreshold()
        );
@@ -441,13 +447,15 @@ OO.ui.Toolbar.prototype.initialize = function () {
  * see {@link OO.ui.ToolGroup toolgroups} for more information about including tools in toolgroups.
  *
  * @param {Object.<string,Array>} groups List of toolgroup configurations
+ * @param {string} [groups.name] Symbolic name for this toolgroup
+ * @param {string} [groups.type] Toolgroup type, should exist in the toolgroup factory
  * @param {Array|string} [groups.include] Tools to include in the toolgroup
  * @param {Array|string} [groups.exclude] Tools to exclude from the toolgroup
  * @param {Array|string} [groups.promote] Tools to promote to the beginning of the toolgroup
  * @param {Array|string} [groups.demote] Tools to demote to the end of the toolgroup
  */
 OO.ui.Toolbar.prototype.setup = function ( groups ) {
-       var i, len, type, group,
+       var i, len, type, toolGroup, groupConfig,
                items = [],
                defaultType = 'bar';
 
@@ -456,32 +464,47 @@ OO.ui.Toolbar.prototype.setup = function ( groups ) {
 
        // Build out new groups
        for ( i = 0, len = groups.length; i < len; i++ ) {
-               group = groups[ i ];
-               if ( group.include === '*' ) {
+               groupConfig = groups[ i ];
+               if ( groupConfig.include === '*' ) {
                        // Apply defaults to catch-all groups
-                       if ( group.type === undefined ) {
-                               group.type = 'list';
+                       if ( groupConfig.type === undefined ) {
+                               groupConfig.type = 'list';
                        }
-                       if ( group.label === undefined ) {
-                               group.label = OO.ui.msg( 'ooui-toolbar-more' );
+                       if ( groupConfig.label === undefined ) {
+                               groupConfig.label = OO.ui.msg( 'ooui-toolbar-more' );
                        }
                }
                // Check type has been registered
-               type = this.getToolGroupFactory().lookup( group.type ) ? group.type : defaultType;
-               items.push(
-                       this.getToolGroupFactory().create( type, this, group )
-               );
+               type = this.getToolGroupFactory().lookup( groupConfig.type ) ? groupConfig.type : defaultType;
+               toolGroup = this.getToolGroupFactory().create( type, this, groupConfig );
+               items.push( toolGroup );
+               if ( groupConfig.name ) {
+                       this.groupsByName[ groupConfig.name ] = toolGroup;
+               } else {
+                       // Groups without name are deprecated
+                       OO.ui.warnDeprecation( 'Toolgroups must have a \'name\' property' );
+               }
        }
        this.addItems( items );
 };
 
+/**
+ * Get a toolgroup by name
+ *
+ * @param {string} name Group name
+ * @return {OO.ui.ToolGroup|null} Tool group, or null if none found by that name
+ */
+OO.ui.Toolbar.prototype.getToolGroupByName = function ( name ) {
+       return this.groupsByName[ name ] || null;
+};
+
 /**
  * Remove all tools and toolgroups from the toolbar.
  */
 OO.ui.Toolbar.prototype.reset = function () {
        var i, len;
 
-       this.groups = [];
+       this.groupsByName = {};
        this.tools = {};
        for ( i = 0, len = this.items.length; i < len; i++ ) {
                this.items[ i ].destroy();
@@ -922,7 +945,7 @@ OO.ui.ToolGroup = function OoUiToolGroup( toolbar, config ) {
        this.onCapturedMouseKeyUpHandler = this.onCapturedMouseKeyUp.bind( this );
 
        // Events
-       this.$element.on( {
+       this.$group.on( {
                mousedown: this.onMouseKeyDown.bind( this ),
                mouseup: this.onMouseKeyUp.bind( this ),
                keydown: this.onMouseKeyDown.bind( this ),
@@ -934,13 +957,17 @@ OO.ui.ToolGroup = function OoUiToolGroup( toolbar, config ) {
        } );
        this.toolbar.getToolFactory().connect( this, { register: 'onToolFactoryRegister' } );
        this.aggregate( { disable: 'itemDisable' } );
-       this.connect( this, { itemDisable: 'updateDisabled' } );
+       this.connect( this, {
+               itemDisable: 'updateDisabled',
+               disable: 'onDisable'
+       } );
 
        // Initialization
        this.$group.addClass( 'oo-ui-toolGroup-tools' );
        this.$element
                .addClass( 'oo-ui-toolGroup' )
                .append( this.$group );
+       this.onDisable( this.isDisabled() );
        this.populate();
 };
 
@@ -1025,6 +1052,17 @@ OO.ui.ToolGroup.prototype.updateDisabled = function () {
        OO.ui.ToolGroup.parent.prototype.updateDisabled.apply( this, arguments );
 };
 
+/**
+ * Handle disable events.
+ *
+ * @protected
+ * @param {boolean} isDisabled
+ */
+OO.ui.ToolGroup.prototype.onDisable = function ( isDisabled ) {
+       this.$group.toggleClass( 'oo-ui-toolGroup-disabled-tools', isDisabled );
+       this.$group.toggleClass( 'oo-ui-toolGroup-enabled-tools', !isDisabled );
+};
+
 /**
  * Handle mouse down and key down events.
  *
@@ -1466,11 +1504,14 @@ OO.ui.PopupTool = function OoUiPopupTool( toolGroup, config ) {
        // Mixin constructors
        OO.ui.mixin.PopupElement.call( this, config );
 
+       // Events
+       this.popup.connect( this, { toggle: 'onPopupToggle' } );
+
        // Initialization
        this.popup.setPosition( toolGroup.getToolbar().position === 'bottom' ? 'above' : 'below' );
-       this.$element
-               .addClass( 'oo-ui-popupTool' )
-               .append( this.popup.$element );
+       this.$element.addClass( 'oo-ui-popupTool' );
+       this.popup.$element.addClass( 'oo-ui-popupTool-popup' );
+       this.toolbar.$popups.append( this.popup.$element );
 };
 
 /* Setup */
@@ -1489,7 +1530,6 @@ OO.ui.PopupTool.prototype.onSelect = function () {
        if ( !this.isDisabled() ) {
                this.popup.toggle();
        }
-       this.setActive( false );
        return false;
 };
 
@@ -1499,7 +1539,15 @@ OO.ui.PopupTool.prototype.onSelect = function () {
  * @inheritdoc
  */
 OO.ui.PopupTool.prototype.onUpdateState = function () {
-       this.setActive( false );
+};
+
+/**
+ * Handle popup visibility being toggled.
+ *
+ * @param {boolean} isVisible
+ */
+OO.ui.PopupTool.prototype.onPopupToggle = function ( isVisible ) {
+       this.setActive( isVisible );
 };
 
 /**
@@ -1738,6 +1786,7 @@ OO.ui.BarToolGroup = function OoUiBarToolGroup( toolbar, config ) {
 
        // Initialization
        this.$element.addClass( 'oo-ui-barToolGroup' );
+       this.$group.addClass( 'oo-ui-barToolGroup-tools' );
 };
 
 /* Setup */
@@ -1778,6 +1827,7 @@ OO.ui.BarToolGroup.static.name = 'bar';
  * @mixins OO.ui.mixin.TitledElement
  * @mixins OO.ui.mixin.FlaggedElement
  * @mixins OO.ui.mixin.ClippableElement
+ * @mixins OO.ui.mixin.FloatableElement
  * @mixins OO.ui.mixin.TabIndexedElement
  *
  * @constructor
@@ -1813,6 +1863,12 @@ OO.ui.PopupToolGroup = function OoUiPopupToolGroup( toolbar, config ) {
        OO.ui.mixin.TitledElement.call( this, config );
        OO.ui.mixin.FlaggedElement.call( this, config );
        OO.ui.mixin.ClippableElement.call( this, $.extend( {}, config, { $clippable: this.$group } ) );
+       OO.ui.mixin.FloatableElement.call( this, $.extend( {}, config, {
+               $floatable: this.$group,
+               $floatableContainer: this.$handle,
+               hideWhenOutOfView: false,
+               verticalPosition: this.toolbar.position === 'bottom' ? 'above' : 'below'
+       } ) );
        OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$handle } ) );
 
        // Events
@@ -1841,6 +1897,8 @@ OO.ui.PopupToolGroup = function OoUiPopupToolGroup( toolbar, config ) {
        this.$element
                .addClass( 'oo-ui-popupToolGroup' )
                .prepend( this.$handle );
+       this.$group.addClass( 'oo-ui-popupToolGroup-tools' );
+       this.toolbar.$popups.append( this.$group );
 };
 
 /* Setup */
@@ -1852,37 +1910,11 @@ OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.LabelElement );
 OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.TitledElement );
 OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.FlaggedElement );
 OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.ClippableElement );
+OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.FloatableElement );
 OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.TabIndexedElement );
 
 /* Methods */
 
-/**
- * @inheritdoc OO.ui.mixin.ClippableElement
- */
-OO.ui.PopupToolGroup.prototype.getHorizontalAnchorEdge = function () {
-       var out;
-       if ( this.$element.hasClass( 'oo-ui-popupToolGroup-right' ) ) {
-               out = 'right';
-       } else {
-               out = 'left';
-       }
-       // Flip for RTL
-       if ( this.$element.css( 'direction' ) === 'rtl' ) {
-               out = ( out === 'left' ) ? 'right' : 'left';
-       }
-       return out;
-};
-
-/**
- * @inheritdoc OO.ui.mixin.ClippableElement
- */
-OO.ui.PopupToolGroup.prototype.getVerticalAnchorEdge = function () {
-       if ( this.toolbar.position === 'bottom' ) {
-               return 'bottom';
-       }
-       return 'top';
-};
-
 /**
  * @inheritdoc
  */
@@ -1904,10 +1936,15 @@ OO.ui.PopupToolGroup.prototype.setDisabled = function () {
  * @param {MouseEvent|KeyboardEvent} e Mouse up or key up event
  */
 OO.ui.PopupToolGroup.prototype.onBlur = function ( e ) {
+       var $target = $( e.target );
        // Only deactivate when clicking outside the dropdown element
-       if ( $( e.target ).closest( '.oo-ui-popupToolGroup' )[ 0 ] !== this.$element[ 0 ] ) {
-               this.setActive( false );
+       if ( $target.closest( '.oo-ui-popupToolGroup' )[ 0 ] === this.$element[ 0 ] ) {
+               return;
        }
+       if ( $target.closest( '.oo-ui-popupToolGroup-tools' )[ 0 ] === this.$group[ 0 ] ) {
+               return;
+       }
+       this.setActive( false );
 };
 
 /**
@@ -1924,6 +1961,34 @@ OO.ui.PopupToolGroup.prototype.onMouseKeyUp = function ( e ) {
        return OO.ui.PopupToolGroup.parent.prototype.onMouseKeyUp.call( this, e );
 };
 
+/**
+ * @inheritdoc
+ */
+OO.ui.PopupToolGroup.prototype.onMouseKeyDown = function ( e ) {
+       var $focused, $firstFocusable, $lastFocusable;
+       // Shift-Tab on the first tool in the group jumps to the handle.
+       // Tab on the last tool in the group jumps to the next group.
+       if ( !this.isDisabled() && e.which === OO.ui.Keys.TAB ) {
+               // (We can't use this.items because ListToolGroup inserts the extra fake expand/collapse tool.)
+               $focused = $( document.activeElement );
+               $firstFocusable = OO.ui.findFocusable( this.$group );
+               if ( $focused[ 0 ] === $firstFocusable[ 0 ] && e.shiftKey ) {
+                       this.$handle.focus();
+                       return false;
+               }
+               $lastFocusable = OO.ui.findFocusable( this.$group, true );
+               if ( $focused[ 0 ] === $lastFocusable[ 0 ] && !e.shiftKey ) {
+                       // Focus this group's handle and let the browser's tab handling happen (no 'return false').
+                       // This way we don't have to fiddle with other ToolGroups' business, or worry what to do
+                       // if the next group is not a PopupToolGroup or doesn't exist at all.
+                       this.$handle.focus();
+                       // Close the popup so that we don't move back inside it (if this is the last group).
+                       this.setActive( false );
+               }
+       }
+       return OO.ui.PopupToolGroup.parent.prototype.onMouseKeyDown.call( this, e );
+};
+
 /**
  * Handle mouse up and key up events.
  *
@@ -1946,12 +2011,20 @@ OO.ui.PopupToolGroup.prototype.onHandleMouseKeyUp = function ( e ) {
  * @param {jQuery.Event} e Mouse down or key down event
  */
 OO.ui.PopupToolGroup.prototype.onHandleMouseKeyDown = function ( e ) {
-       if (
-               !this.isDisabled() &&
-               ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
-       ) {
-               this.setActive( !this.active );
-               return false;
+       var $focusable;
+       if ( !this.isDisabled() ) {
+               // Tab on the handle jumps to the first tool in the group (if the popup is open).
+               if ( e.which === OO.ui.Keys.TAB && !e.shiftKey ) {
+                       $focusable = OO.ui.findFocusable( this.$group );
+                       if ( $focusable.length ) {
+                               $focusable.focus();
+                               return false;
+                       }
+               }
+               if ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) {
+                       this.setActive( !this.active );
+                       return false;
+               }
        }
 };
 
@@ -1982,18 +2055,19 @@ OO.ui.PopupToolGroup.prototype.setActive = function ( value ) {
                        this.getElementDocument().addEventListener( 'keyup', this.onBlurHandler, true );
 
                        this.$clippable.css( 'left', '' );
-                       // Try anchoring the popup to the left first
-                       this.$element.addClass( 'oo-ui-popupToolGroup-active oo-ui-popupToolGroup-left' );
+                       this.$element.addClass( 'oo-ui-popupToolGroup-active' );
+                       this.$group.addClass( 'oo-ui-popupToolGroup-active-tools' );
+                       this.togglePositioning( true );
                        this.toggleClipping( true );
-                       if ( this.isClippedHorizontally() ) {
+
+                       // Try anchoring the popup to the left first
+                       this.setHorizontalPosition( 'start' );
+
+                       if ( this.isClippedHorizontally() || this.isFloatableOutOfView() ) {
                                // Anchoring to the left caused the popup to clip, so anchor it to the right instead
-                               this.toggleClipping( false );
-                               this.$element
-                                       .removeClass( 'oo-ui-popupToolGroup-left' )
-                                       .addClass( 'oo-ui-popupToolGroup-right' );
-                               this.toggleClipping( true );
+                               this.setHorizontalPosition( 'end' );
                        }
-                       if ( this.isClippedHorizontally() ) {
+                       if ( this.isClippedHorizontally() || this.isFloatableOutOfView() ) {
                                // Anchoring to the right also caused the popup to clip, so just make it fill the container
                                containerWidth = this.$clippableScrollableContainer.width();
                                containerLeft = this.$clippableScrollableContainer[ 0 ] === document.documentElement ?
@@ -2001,19 +2075,19 @@ OO.ui.PopupToolGroup.prototype.setActive = function ( value ) {
                                        this.$clippableScrollableContainer.offset().left;
 
                                this.toggleClipping( false );
-                               this.$element.removeClass( 'oo-ui-popupToolGroup-right' );
+                               this.setHorizontalPosition( 'start' );
 
                                this.$clippable.css( {
-                                       left: -( this.$element.offset().left - containerLeft ),
+                                       'margin-left': -( this.$element.offset().left - containerLeft ),
                                        width: containerWidth
                                } );
                        }
                } else {
                        this.getElementDocument().removeEventListener( 'mouseup', this.onBlurHandler, true );
                        this.getElementDocument().removeEventListener( 'keyup', this.onBlurHandler, true );
-                       this.$element.removeClass(
-                               'oo-ui-popupToolGroup-active oo-ui-popupToolGroup-left oo-ui-popupToolGroup-right'
-                       );
+                       this.$element.removeClass( 'oo-ui-popupToolGroup-active' );
+                       this.$group.removeClass( 'oo-ui-popupToolGroup-active-tools' );
+                       this.togglePositioning( false );
                        this.toggleClipping( false );
                }
                this.updateThemeClasses();
@@ -2134,6 +2208,7 @@ OO.ui.ListToolGroup = function OoUiListToolGroup( toolbar, config ) {
 
        // Initialization
        this.$element.addClass( 'oo-ui-listToolGroup' );
+       this.$group.addClass( 'oo-ui-listToolGroup-tools' );
 };
 
 /* Setup */
@@ -2368,6 +2443,7 @@ OO.ui.MenuToolGroup = function OoUiMenuToolGroup( toolbar, config ) {
 
        // Initialization
        this.$element.addClass( 'oo-ui-menuToolGroup' );
+       this.$group.addClass( 'oo-ui-menuToolGroup-tools' );
 };
 
 /* Setup */
@@ -2407,4 +2483,4 @@ OO.ui.MenuToolGroup.prototype.onUpdateState = function () {
 
 }( OO ) );
 
-//# sourceMappingURL=oojs-ui-toolbars.js.map
\ No newline at end of file
+//# sourceMappingURL=oojs-ui-toolbars.js.map.json
\ No newline at end of file