/** * Popup list of tools with an icon and optional label. * * @abstract * @class * @extends OO.ui.ToolGroup * @mixins OO.ui.IconElement * @mixins OO.ui.IndicatorElement * @mixins OO.ui.LabelElement * @mixins OO.ui.TitledElement * @mixins OO.ui.ClippableElement * @mixins OO.ui.TabIndexedElement * * @constructor * @param {OO.ui.Toolbar} toolbar * @param {Object} [config] Configuration options * @cfg {string} [header] Text to display at the top of the pop-up */ OO.ui.PopupToolGroup = function OoUiPopupToolGroup( toolbar, config ) { // Allow passing positional parameters inside the config object if ( OO.isPlainObject( toolbar ) && config === undefined ) { config = toolbar; toolbar = config.toolbar; } // Configuration initialization config = config || {}; // Parent constructor OO.ui.PopupToolGroup.super.call( this, toolbar, config ); // Properties this.active = false; this.dragging = false; this.onBlurHandler = this.onBlur.bind( this ); this.$handle = $( '' ); // Mixin constructors OO.ui.IconElement.call( this, config ); OO.ui.IndicatorElement.call( this, config ); OO.ui.LabelElement.call( this, config ); OO.ui.TitledElement.call( this, config ); OO.ui.ClippableElement.call( this, $.extend( {}, config, { $clippable: this.$group } ) ); OO.ui.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$handle } ) ); // Events this.$handle.on( { keydown: this.onHandleMouseKeyDown.bind( this ), keyup: this.onHandleMouseKeyUp.bind( this ), mousedown: this.onHandleMouseKeyDown.bind( this ), mouseup: this.onHandleMouseKeyUp.bind( this ) } ); // Initialization this.$handle .addClass( 'oo-ui-popupToolGroup-handle' ) .append( this.$icon, this.$label, this.$indicator ); // If the pop-up should have a header, add it to the top of the toolGroup. // Note: If this feature is useful for other widgets, we could abstract it into an // OO.ui.HeaderedElement mixin constructor. if ( config.header !== undefined ) { this.$group .prepend( $( '' ) .addClass( 'oo-ui-popupToolGroup-header' ) .text( config.header ) ); } this.$element .addClass( 'oo-ui-popupToolGroup' ) .prepend( this.$handle ); }; /* Setup */ OO.inheritClass( OO.ui.PopupToolGroup, OO.ui.ToolGroup ); OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.IconElement ); OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.IndicatorElement ); OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.LabelElement ); OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.TitledElement ); OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.ClippableElement ); OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.TabIndexedElement ); /* Methods */ /** * @inheritdoc */ OO.ui.PopupToolGroup.prototype.setDisabled = function () { // Parent method OO.ui.PopupToolGroup.super.prototype.setDisabled.apply( this, arguments ); if ( this.isDisabled() && this.isElementAttached() ) { this.setActive( false ); } }; /** * Handle focus being lost. * * The event is actually generated from a mouseup/keyup, so it is not a normal blur event object. * * @param {jQuery.Event} e Mouse up or key up event */ OO.ui.PopupToolGroup.prototype.onBlur = function ( e ) { // Only deactivate when clicking outside the dropdown element if ( $( e.target ).closest( '.oo-ui-popupToolGroup' )[ 0 ] !== this.$element[ 0 ] ) { this.setActive( false ); } }; /** * @inheritdoc */ OO.ui.PopupToolGroup.prototype.onMouseKeyUp = function ( e ) { // Only close toolgroup when a tool was actually selected if ( !this.isDisabled() && this.pressed && this.pressed === this.getTargetTool( e ) && ( e.which === 1 || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) { this.setActive( false ); } return OO.ui.PopupToolGroup.super.prototype.onMouseKeyUp.call( this, e ); }; /** * Handle mouse up and key up events. * * @param {jQuery.Event} e Mouse up or key up event */ OO.ui.PopupToolGroup.prototype.onHandleMouseKeyUp = function ( e ) { if ( !this.isDisabled() && ( e.which === 1 || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) { return false; } }; /** * Handle mouse down and key down events. * * @param {jQuery.Event} e Mouse down or key down event */ OO.ui.PopupToolGroup.prototype.onHandleMouseKeyDown = function ( e ) { if ( !this.isDisabled() && ( e.which === 1 || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) { this.setActive( !this.active ); return false; } }; /** * Switch into active mode. * * When active, mouseup events anywhere in the document will trigger deactivation. */ OO.ui.PopupToolGroup.prototype.setActive = function ( value ) { value = !!value; if ( this.active !== value ) { this.active = value; if ( value ) { this.getElementDocument().addEventListener( 'mouseup', this.onBlurHandler, true ); this.getElementDocument().addEventListener( 'keyup', this.onBlurHandler, true ); // Try anchoring the popup to the left first this.$element.addClass( 'oo-ui-popupToolGroup-active oo-ui-popupToolGroup-left' ); this.toggleClipping( true ); if ( this.isClippedHorizontally() ) { // 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 ); } } 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.toggleClipping( false ); } } };