/*!
- * 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
// 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
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 */
* @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()
);
* 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';
// 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();
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 ),
} );
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();
};
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.
*
// 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 */
if ( !this.isDisabled() ) {
this.popup.toggle();
}
- this.setActive( false );
return false;
};
* @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 );
};
/**
// Initialization
this.$element.addClass( 'oo-ui-barToolGroup' );
+ this.$group.addClass( 'oo-ui-barToolGroup-tools' );
};
/* Setup */
* @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
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
this.$element
.addClass( 'oo-ui-popupToolGroup' )
.prepend( this.$handle );
+ this.$group.addClass( 'oo-ui-popupToolGroup-tools' );
+ this.toolbar.$popups.append( this.$group );
};
/* Setup */
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
*/
* @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 );
};
/**
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.
*
* @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;
+ }
}
};
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 ?
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();
// Initialization
this.$element.addClass( 'oo-ui-listToolGroup' );
+ this.$group.addClass( 'oo-ui-listToolGroup-tools' );
};
/* Setup */
// Initialization
this.$element.addClass( 'oo-ui-menuToolGroup' );
+ this.$group.addClass( 'oo-ui-menuToolGroup-tools' );
};
/* Setup */
}( 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