/*! * OOUI v0.28.1 * 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-09-05T00:41:49Z */ ( function ( OO ) { 'use strict'; /** * Toolbars are complex interface components that permit users to easily access a variety * of {@link OO.ui.Tool tools} (e.g., formatting commands) and actions, which are additional commands that are * part of the toolbar, but not configured as tools. * * Individual tools are customized and then registered with a {@link OO.ui.ToolFactory tool factory}, which creates * the tools on demand. Each tool has a symbolic name (used when registering the tool), a title (e.g., ‘Insert * image’), and an icon. * * Individual tools are organized in {@link OO.ui.ToolGroup toolgroups}, which can be {@link OO.ui.MenuToolGroup menus} * of tools, {@link OO.ui.ListToolGroup lists} of tools, or a single {@link OO.ui.BarToolGroup bar} of tools. * The arrangement and order of the toolgroups is customized when the toolbar is set up. Tools can be presented in * any order, but each can only appear once in the toolbar. * * The toolbar can be synchronized with the state of the external "application", like a text * editor's editing area, marking tools as active/inactive (e.g. a 'bold' tool would be shown as * active when the text cursor was inside bolded text) or enabled/disabled (e.g. a table caption * tool would be disabled while the user is not editing a table). A state change is signalled by * emitting the {@link #event-updateState 'updateState' event}, which calls Tools' * {@link OO.ui.Tool#onUpdateState onUpdateState method}. * * The following is an example of a basic toolbar. * * @example * // Example of a toolbar * // Create the toolbar * var toolFactory = new OO.ui.ToolFactory(); * var toolGroupFactory = new OO.ui.ToolGroupFactory(); * var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory ); * * // We will be placing status text in this element when tools are used * var $area = $( '
' ).text( 'Toolbar example' ); * * // Define the tools that we're going to place in our toolbar * * // Create a class inheriting from OO.ui.Tool * function SearchTool() { * SearchTool.parent.apply( this, arguments ); * } * OO.inheritClass( SearchTool, OO.ui.Tool ); * // Each tool must have a 'name' (used as an internal identifier, see later) and at least one * // of 'icon' and 'title' (displayed icon and text). * SearchTool.static.name = 'search'; * SearchTool.static.icon = 'search'; * SearchTool.static.title = 'Search...'; * // Defines the action that will happen when this tool is selected (clicked). * SearchTool.prototype.onSelect = function () { * $area.text( 'Search tool clicked!' ); * // Never display this tool as "active" (selected). * this.setActive( false ); * }; * SearchTool.prototype.onUpdateState = function () {}; * // Make this tool available in our toolFactory and thus our toolbar * toolFactory.register( SearchTool ); * * // Register two more tools, nothing interesting here * function SettingsTool() { * SettingsTool.parent.apply( this, arguments ); * } * OO.inheritClass( SettingsTool, OO.ui.Tool ); * SettingsTool.static.name = 'settings'; * SettingsTool.static.icon = 'advanced'; * SettingsTool.static.title = 'Change settings'; * SettingsTool.prototype.onSelect = function () { * $area.text( 'Settings tool clicked!' ); * this.setActive( false ); * }; * SettingsTool.prototype.onUpdateState = function () {}; * toolFactory.register( SettingsTool ); * * // Register two more tools, nothing interesting here * function StuffTool() { * StuffTool.parent.apply( this, arguments ); * } * OO.inheritClass( StuffTool, OO.ui.Tool ); * StuffTool.static.name = 'stuff'; * StuffTool.static.icon = 'ellipsis'; * StuffTool.static.title = 'More stuff'; * StuffTool.prototype.onSelect = function () { * $area.text( 'More stuff tool clicked!' ); * this.setActive( false ); * }; * StuffTool.prototype.onUpdateState = function () {}; * toolFactory.register( StuffTool ); * * // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a * // little popup window (a PopupWidget). * function HelpTool( toolGroup, config ) { * OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: { * padded: true, * label: 'Help', * head: true * } }, config ) ); * this.popup.$body.append( '
I am helpful!
' ); * } * OO.inheritClass( HelpTool, OO.ui.PopupTool ); * HelpTool.static.name = 'help'; * HelpTool.static.icon = 'help'; * HelpTool.static.title = 'Help'; * toolFactory.register( HelpTool ); * * // Finally define which tools and in what order appear in the toolbar. Each tool may only be * // used once (but not all defined tools must be used). * toolbar.setup( [ * { * // 'bar' tool groups display tools' icons only, side-by-side. * type: 'bar', * include: [ 'search', 'help' ] * }, * { * // 'list' tool groups display both the titles and icons, in a dropdown list. * type: 'list', * indicator: 'down', * label: 'More', * include: [ 'settings', 'stuff' ] * } * // Note how the tools themselves are toolgroup-agnostic - the same tool can be displayed * // either in a 'list' or a 'bar'. There is a 'menu' tool group too, not showcased here, * // since it's more complicated to use. (See the next example snippet on this page.) * ] ); * * // Create some UI around the toolbar and place it in the document * var frame = new OO.ui.PanelLayout( { * expanded: false, * framed: true * } ); * var contentFrame = new OO.ui.PanelLayout( { * expanded: false, * padded: true * } ); * frame.$element.append( * toolbar.$element, * contentFrame.$element.append( $area ) * ); * $( 'body' ).append( frame.$element ); * * // Here is where the toolbar is actually built. This must be done after inserting it into the * // document. * toolbar.initialize(); * toolbar.emit( 'updateState' ); * * The following example extends the previous one to illustrate 'menu' toolgroups and the usage of * {@link #event-updateState 'updateState' event}. * * @example * // Create the toolbar * var toolFactory = new OO.ui.ToolFactory(); * var toolGroupFactory = new OO.ui.ToolGroupFactory(); * var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory ); * * // We will be placing status text in this element when tools are used * var $area = $( '' ).text( 'Toolbar example' ); * * // Define the tools that we're going to place in our toolbar * * // Create a class inheriting from OO.ui.Tool * function SearchTool() { * SearchTool.parent.apply( this, arguments ); * } * OO.inheritClass( SearchTool, OO.ui.Tool ); * // Each tool must have a 'name' (used as an internal identifier, see later) and at least one * // of 'icon' and 'title' (displayed icon and text). * SearchTool.static.name = 'search'; * SearchTool.static.icon = 'search'; * SearchTool.static.title = 'Search...'; * // Defines the action that will happen when this tool is selected (clicked). * SearchTool.prototype.onSelect = function () { * $area.text( 'Search tool clicked!' ); * // Never display this tool as "active" (selected). * this.setActive( false ); * }; * SearchTool.prototype.onUpdateState = function () {}; * // Make this tool available in our toolFactory and thus our toolbar * toolFactory.register( SearchTool ); * * // Register two more tools, nothing interesting here * function SettingsTool() { * SettingsTool.parent.apply( this, arguments ); * this.reallyActive = false; * } * OO.inheritClass( SettingsTool, OO.ui.Tool ); * SettingsTool.static.name = 'settings'; * SettingsTool.static.icon = 'advanced'; * SettingsTool.static.title = 'Change settings'; * SettingsTool.prototype.onSelect = function () { * $area.text( 'Settings tool clicked!' ); * // Toggle the active state on each click * this.reallyActive = !this.reallyActive; * this.setActive( this.reallyActive ); * // To update the menu label * this.toolbar.emit( 'updateState' ); * }; * SettingsTool.prototype.onUpdateState = function () {}; * toolFactory.register( SettingsTool ); * * // Register two more tools, nothing interesting here * function StuffTool() { * StuffTool.parent.apply( this, arguments ); * this.reallyActive = false; * } * OO.inheritClass( StuffTool, OO.ui.Tool ); * StuffTool.static.name = 'stuff'; * StuffTool.static.icon = 'ellipsis'; * StuffTool.static.title = 'More stuff'; * StuffTool.prototype.onSelect = function () { * $area.text( 'More stuff tool clicked!' ); * // Toggle the active state on each click * this.reallyActive = !this.reallyActive; * this.setActive( this.reallyActive ); * // To update the menu label * this.toolbar.emit( 'updateState' ); * }; * StuffTool.prototype.onUpdateState = function () {}; * toolFactory.register( StuffTool ); * * // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a * // little popup window (a PopupWidget). 'onUpdateState' is also already implemented. * function HelpTool( toolGroup, config ) { * OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: { * padded: true, * label: 'Help', * head: true * } }, config ) ); * this.popup.$body.append( '
I am helpful!
' ); * } * OO.inheritClass( HelpTool, OO.ui.PopupTool ); * HelpTool.static.name = 'help'; * HelpTool.static.icon = 'help'; * HelpTool.static.title = 'Help'; * toolFactory.register( HelpTool ); * * // Finally define which tools and in what order appear in the toolbar. Each tool may only be * // used once (but not all defined tools must be used). * toolbar.setup( [ * { * // 'bar' tool groups display tools' icons only, side-by-side. * type: 'bar', * include: [ 'search', 'help' ] * }, * { * // 'menu' tool groups display both the titles and icons, in a dropdown menu. * // Menu label indicates which items are selected. * type: 'menu', * indicator: 'down', * include: [ 'settings', 'stuff' ] * } * ] ); * * // Create some UI around the toolbar and place it in the document * var frame = new OO.ui.PanelLayout( { * expanded: false, * framed: true * } ); * var contentFrame = new OO.ui.PanelLayout( { * expanded: false, * padded: true * } ); * frame.$element.append( * toolbar.$element, * contentFrame.$element.append( $area ) * ); * $( 'body' ).append( frame.$element ); * * // Here is where the toolbar is actually built. This must be done after inserting it into the * // document. * toolbar.initialize(); * toolbar.emit( 'updateState' ); * * @class * @extends OO.ui.Element * @mixins OO.EventEmitter * @mixins OO.ui.mixin.GroupElement * * @constructor * @param {OO.ui.ToolFactory} toolFactory Factory for creating tools * @param {OO.ui.ToolGroupFactory} toolGroupFactory Factory for creating toolgroups * @param {Object} [config] Configuration options * @cfg {boolean} [actions] Add an actions section to the toolbar. Actions are commands that are included * 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. * SeeI am helpful!
' ); * }; * OO.inheritClass( HelpTool, OO.ui.PopupTool ); * HelpTool.static.name = 'help'; * HelpTool.static.icon = 'help'; * HelpTool.static.title = 'Help'; * toolFactory.register( HelpTool ); * * For an example of a toolbar that contains a popup tool, see {@link OO.ui.Toolbar toolbars}. For more information about * toolbars in general, please see the [OOUI documentation on MediaWiki][1]. * * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars * * @abstract * @class * @extends OO.ui.Tool * @mixins OO.ui.mixin.PopupElement * * @constructor * @param {OO.ui.ToolGroup} toolGroup * @param {Object} [config] Configuration options */ OO.ui.PopupTool = function OoUiPopupTool( toolGroup, config ) { // Allow passing positional parameters inside the config object if ( OO.isPlainObject( toolGroup ) && config === undefined ) { config = toolGroup; toolGroup = config.toolGroup; } // Parent constructor OO.ui.PopupTool.parent.call( this, 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' ); this.popup.$element.addClass( 'oo-ui-popupTool-popup' ); this.toolbar.$popups.append( this.popup.$element ); }; /* Setup */ OO.inheritClass( OO.ui.PopupTool, OO.ui.Tool ); OO.mixinClass( OO.ui.PopupTool, OO.ui.mixin.PopupElement ); /* Methods */ /** * Handle the tool being selected. * * @inheritdoc */ OO.ui.PopupTool.prototype.onSelect = function () { if ( !this.isDisabled() ) { this.popup.toggle(); } return false; }; /** * Handle the toolbar state being updated. * * @inheritdoc */ OO.ui.PopupTool.prototype.onUpdateState = function () { }; /** * Handle popup visibility being toggled. * * @param {boolean} isVisible */ OO.ui.PopupTool.prototype.onPopupToggle = function ( isVisible ) { this.setActive( isVisible ); this.toolGroup.emit( 'active', isVisible ); }; /** * A ToolGroupTool is a special sort of tool that can contain other {@link OO.ui.Tool tools} * and {@link OO.ui.ToolGroup toolgroups}. The ToolGroupTool was specifically designed to be used * inside a {@link OO.ui.BarToolGroup bar} toolgroup to provide access to additional tools from * the bar item. Included tools will be displayed in a dropdown {@link OO.ui.ListToolGroup list} * when the ToolGroupTool is selected. * * // Example: ToolGroupTool with two nested tools, 'setting1' and 'setting2', defined elsewhere. * * function SettingsTool() { * SettingsTool.parent.apply( this, arguments ); * }; * OO.inheritClass( SettingsTool, OO.ui.ToolGroupTool ); * SettingsTool.static.name = 'settings'; * SettingsTool.static.title = 'Change settings'; * SettingsTool.static.groupConfig = { * icon: 'advanced', * label: 'ToolGroupTool', * include: [ 'setting1', 'setting2' ] * }; * toolFactory.register( SettingsTool ); * * For more information, please see the [OOUI documentation on MediaWiki][1]. * * Please note that this implementation is subject to change per [T74159] [2]. * * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars#ToolGroupTool * [2]: https://phabricator.wikimedia.org/T74159 * * @abstract * @class * @extends OO.ui.Tool * * @constructor * @param {OO.ui.ToolGroup} toolGroup * @param {Object} [config] Configuration options */ OO.ui.ToolGroupTool = function OoUiToolGroupTool( toolGroup, config ) { // Allow passing positional parameters inside the config object if ( OO.isPlainObject( toolGroup ) && config === undefined ) { config = toolGroup; toolGroup = config.toolGroup; } // Parent constructor OO.ui.ToolGroupTool.parent.call( this, toolGroup, config ); // Properties this.innerToolGroup = this.createGroup( this.constructor.static.groupConfig ); // Events this.innerToolGroup.connect( this, { disable: 'onToolGroupDisable', // Re-emit active events from the innerToolGroup on the parent toolGroup active: this.toolGroup.emit.bind( this.toolGroup, 'active' ) } ); // Initialization this.$link.remove(); this.$element .addClass( 'oo-ui-toolGroupTool' ) .append( this.innerToolGroup.$element ); }; /* Setup */ OO.inheritClass( OO.ui.ToolGroupTool, OO.ui.Tool ); /* Static Properties */ /** * Toolgroup configuration. * * The toolgroup configuration consists of the tools to include, as well as an icon and label * to use for the bar item. Tools can be included by symbolic name, group, or with the * wildcard selector. Please see {@link OO.ui.ToolGroup toolgroup} for more information. * * @property {Object.' ).text( 'Example of a BarToolGroup with two tools.' ); * * // Define the tools that we're going to place in our toolbar * * // Create a class inheriting from OO.ui.Tool * function SearchTool() { * SearchTool.parent.apply( this, arguments ); * } * OO.inheritClass( SearchTool, OO.ui.Tool ); * // Each tool must have a 'name' (used as an internal identifier, see later) and at least one * // of 'icon' and 'title' (displayed icon and text). * SearchTool.static.name = 'search'; * SearchTool.static.icon = 'search'; * SearchTool.static.title = 'Search...'; * // Defines the action that will happen when this tool is selected (clicked). * SearchTool.prototype.onSelect = function () { * $area.text( 'Search tool clicked!' ); * // Never display this tool as "active" (selected). * this.setActive( false ); * }; * SearchTool.prototype.onUpdateState = function () {}; * // Make this tool available in our toolFactory and thus our toolbar * toolFactory.register( SearchTool ); * * // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a * // little popup window (a PopupWidget). * function HelpTool( toolGroup, config ) { * OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: { * padded: true, * label: 'Help', * head: true * } }, config ) ); * this.popup.$body.append( '
I am helpful!
' ); * } * OO.inheritClass( HelpTool, OO.ui.PopupTool ); * HelpTool.static.name = 'help'; * HelpTool.static.icon = 'help'; * HelpTool.static.title = 'Help'; * toolFactory.register( HelpTool ); * * // Finally define which tools and in what order appear in the toolbar. Each tool may only be * // used once (but not all defined tools must be used). * toolbar.setup( [ * { * // 'bar' tool groups display tools by icon only * type: 'bar', * include: [ 'search', 'help' ] * } * ] ); * * // Create some UI around the toolbar and place it in the document * var frame = new OO.ui.PanelLayout( { * expanded: false, * framed: true * } ); * var contentFrame = new OO.ui.PanelLayout( { * expanded: false, * padded: true * } ); * frame.$element.append( * toolbar.$element, * contentFrame.$element.append( $area ) * ); * $( 'body' ).append( frame.$element ); * * // Here is where the toolbar is actually built. This must be done after inserting it into the * // document. * toolbar.initialize(); * * For more information about how to add tools to a bar tool group, please see {@link OO.ui.ToolGroup toolgroup}. * For more information about toolbars in general, please see the [OOUI documentation on MediaWiki][1]. * * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars * * @class * @extends OO.ui.ToolGroup * * @constructor * @param {OO.ui.Toolbar} toolbar * @param {Object} [config] Configuration options */ OO.ui.BarToolGroup = function OoUiBarToolGroup( toolbar, config ) { // Allow passing positional parameters inside the config object if ( OO.isPlainObject( toolbar ) && config === undefined ) { config = toolbar; toolbar = config.toolbar; } // Parent constructor OO.ui.BarToolGroup.parent.call( this, toolbar, config ); // Initialization this.$element.addClass( 'oo-ui-barToolGroup' ); this.$group.addClass( 'oo-ui-barToolGroup-tools' ); }; /* Setup */ OO.inheritClass( OO.ui.BarToolGroup, OO.ui.ToolGroup ); /* Static Properties */ /** * @static * @inheritdoc */ OO.ui.BarToolGroup.static.titleTooltips = true; /** * @static * @inheritdoc */ OO.ui.BarToolGroup.static.accelTooltips = true; /** * @static * @inheritdoc */ OO.ui.BarToolGroup.static.name = 'bar'; /** * PopupToolGroup is an abstract base class used by both {@link OO.ui.MenuToolGroup MenuToolGroup} * and {@link OO.ui.ListToolGroup ListToolGroup} to provide a popup--an overlaid menu or list of tools with an * optional icon and label. This class can be used for other base classes that also use this functionality. * * @abstract * @class * @extends OO.ui.ToolGroup * @mixins OO.ui.mixin.IconElement * @mixins OO.ui.mixin.IndicatorElement * @mixins OO.ui.mixin.LabelElement * @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 * @param {OO.ui.Toolbar} toolbar * @param {Object} [config] Configuration options * @cfg {string} [header] Text to display at the top of the popup */ 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 = $.extend( { indicator: config.indicator === undefined ? ( toolbar.position === 'bottom' ? 'up' : 'down' ) : config.indicator }, config ); // Parent constructor OO.ui.PopupToolGroup.parent.call( this, toolbar, config ); // Properties this.active = false; this.dragging = false; this.onBlurHandler = this.onBlur.bind( this ); this.$handle = $( '' ); // Mixin constructors OO.ui.mixin.IconElement.call( this, config ); OO.ui.mixin.IndicatorElement.call( this, config ); OO.ui.mixin.LabelElement.call( this, 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 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' ) .attr( 'role', 'button' ) .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 ); this.$group.addClass( 'oo-ui-popupToolGroup-tools' ); this.toolbar.$popups.append( this.$group ); }; /* Setup */ OO.inheritClass( OO.ui.PopupToolGroup, OO.ui.ToolGroup ); OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.IconElement ); OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.mixin.IndicatorElement ); 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.PopupToolGroup.prototype.setDisabled = function () { // Parent method OO.ui.PopupToolGroup.parent.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. * * @protected * @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 ( $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 ); }; /** * @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.findTargetTool( e ) && ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) { 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. * * @protected * @param {jQuery.Event} e Mouse up or key up event */ OO.ui.PopupToolGroup.prototype.onHandleMouseKeyUp = function ( e ) { if ( !this.isDisabled() && ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) { return false; } }; /** * Handle mouse down and key down events. * * @protected * @param {jQuery.Event} e Mouse down or key down event */ OO.ui.PopupToolGroup.prototype.onHandleMouseKeyDown = function ( e ) { 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; } } }; /** * Check if the tool group is active. * * @return {boolean} Tool group is active */ OO.ui.PopupToolGroup.prototype.isActive = function () { return this.active; }; /** * Switch into 'active' mode. * * When active, the popup is visible. A mouseup event anywhere in the document will trigger * deactivation. * * @param {boolean} value The active state to set * @fires active */ OO.ui.PopupToolGroup.prototype.setActive = function ( value ) { var containerWidth, containerLeft; 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 ); this.$clippable.css( 'left', '' ); this.$element.addClass( 'oo-ui-popupToolGroup-active' ); this.$group.addClass( 'oo-ui-popupToolGroup-active-tools' ); this.togglePositioning( true ); this.toggleClipping( true ); // 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.setHorizontalPosition( 'end' ); } 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 ? 0 : this.$clippableScrollableContainer.offset().left; this.toggleClipping( false ); this.setHorizontalPosition( 'start' ); this.$clippable.css( { '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' ); this.$group.removeClass( 'oo-ui-popupToolGroup-active-tools' ); this.togglePositioning( false ); this.toggleClipping( false ); } this.emit( 'active', this.active ); this.updateThemeClasses(); } }; /** * ListToolGroups are one of three types of {@link OO.ui.ToolGroup toolgroups} that are used to * create {@link OO.ui.Toolbar toolbars} (the other types of groups are {@link OO.ui.MenuToolGroup MenuToolGroup} * and {@link OO.ui.BarToolGroup BarToolGroup}). The {@link OO.ui.Tool tools} in a ListToolGroup are displayed * by label in a dropdown menu. The title of the tool is used as the label text. The menu itself can be configured * with a label, icon, indicator, header, and title. * * ListToolGroups can be configured to be expanded and collapsed. Collapsed lists will have a ‘More’ option that * users can select to see the full list of tools. If a collapsed toolgroup is expanded, a ‘Fewer’ option permits * users to collapse the list again. * * ListToolGroups are created by a {@link OO.ui.ToolGroupFactory toolgroup factory} when the toolbar is set up. The factory * requires the ListToolGroup's symbolic name, 'list', which is specified along with the other configurations. For more * information about how to add tools to a ListToolGroup, please see {@link OO.ui.ToolGroup toolgroup}. * * @example * // Example of a ListToolGroup * var toolFactory = new OO.ui.ToolFactory(); * var toolGroupFactory = new OO.ui.ToolGroupFactory(); * var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory ); * * // Configure and register two tools * function SettingsTool() { * SettingsTool.parent.apply( this, arguments ); * } * OO.inheritClass( SettingsTool, OO.ui.Tool ); * SettingsTool.static.name = 'settings'; * SettingsTool.static.icon = 'advanced'; * SettingsTool.static.title = 'Change settings'; * SettingsTool.prototype.onSelect = function () { * this.setActive( false ); * }; * SettingsTool.prototype.onUpdateState = function () {}; * toolFactory.register( SettingsTool ); * // Register two more tools, nothing interesting here * function StuffTool() { * StuffTool.parent.apply( this, arguments ); * } * OO.inheritClass( StuffTool, OO.ui.Tool ); * StuffTool.static.name = 'stuff'; * StuffTool.static.icon = 'search'; * StuffTool.static.title = 'Change the world'; * StuffTool.prototype.onSelect = function () { * this.setActive( false ); * }; * StuffTool.prototype.onUpdateState = function () {}; * toolFactory.register( StuffTool ); * toolbar.setup( [ * { * // Configurations for list toolgroup. * type: 'list', * label: 'ListToolGroup', * icon: 'ellipsis', * title: 'This is the title, displayed when user moves the mouse over the list toolgroup', * header: 'This is the header', * include: [ 'settings', 'stuff' ], * allowCollapse: ['stuff'] * } * ] ); * * // Create some UI around the toolbar and place it in the document * var frame = new OO.ui.PanelLayout( { * expanded: false, * framed: true * } ); * frame.$element.append( * toolbar.$element * ); * $( 'body' ).append( frame.$element ); * // Build the toolbar. This must be done after the toolbar has been appended to the document. * toolbar.initialize(); * * For more information about toolbars in general, please see the [OOUI documentation on MediaWiki][1]. * * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars * * @class * @extends OO.ui.PopupToolGroup * * @constructor * @param {OO.ui.Toolbar} toolbar * @param {Object} [config] Configuration options * @cfg {Array} [allowCollapse] Allow the specified tools to be collapsed. By default, collapsible tools * will only be displayed if users click the ‘More’ option displayed at the bottom of the list. If * the list is expanded, a ‘Fewer’ option permits users to collapse the list again. Any tools that * are included in the toolgroup, but are not designated as collapsible, will always be displayed. * To open a collapsible list in its expanded state, set #expanded to 'true'. * @cfg {Array} [forceExpand] Expand the specified tools. All other tools will be designated as collapsible. * Unless #expanded is set to true, the collapsible tools will be collapsed when the list is first opened. * @cfg {boolean} [expanded=false] Expand collapsible tools. This config is only relevant if tools have * been designated as collapsible. When expanded is set to true, all tools in the group will be displayed * when the list is first opened. Users can collapse the list with a ‘Fewer’ option at the bottom. */ OO.ui.ListToolGroup = function OoUiListToolGroup( 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 || {}; // Properties (must be set before parent constructor, which calls #populate) this.allowCollapse = config.allowCollapse; this.forceExpand = config.forceExpand; this.expanded = config.expanded !== undefined ? config.expanded : false; this.collapsibleTools = []; // Parent constructor OO.ui.ListToolGroup.parent.call( this, toolbar, config ); // Initialization this.$element.addClass( 'oo-ui-listToolGroup' ); this.$group.addClass( 'oo-ui-listToolGroup-tools' ); }; /* Setup */ OO.inheritClass( OO.ui.ListToolGroup, OO.ui.PopupToolGroup ); /* Static Properties */ /** * @static * @inheritdoc */ OO.ui.ListToolGroup.static.name = 'list'; /* Methods */ /** * @inheritdoc */ OO.ui.ListToolGroup.prototype.populate = function () { var i, len, allowCollapse = []; OO.ui.ListToolGroup.parent.prototype.populate.call( this ); // Update the list of collapsible tools if ( this.allowCollapse !== undefined ) { allowCollapse = this.allowCollapse; } else if ( this.forceExpand !== undefined ) { allowCollapse = OO.simpleArrayDifference( Object.keys( this.tools ), this.forceExpand ); } this.collapsibleTools = []; for ( i = 0, len = allowCollapse.length; i < len; i++ ) { if ( this.tools[ allowCollapse[ i ] ] !== undefined ) { this.collapsibleTools.push( this.tools[ allowCollapse[ i ] ] ); } } // Keep at the end, even when tools are added this.$group.append( this.getExpandCollapseTool().$element ); this.getExpandCollapseTool().toggle( this.collapsibleTools.length !== 0 ); this.updateCollapsibleState(); }; /** * Get the expand/collapse tool for this group * * @return {OO.ui.Tool} Expand collapse tool */ OO.ui.ListToolGroup.prototype.getExpandCollapseTool = function () { var ExpandCollapseTool; if ( this.expandCollapseTool === undefined ) { ExpandCollapseTool = function () { ExpandCollapseTool.parent.apply( this, arguments ); }; OO.inheritClass( ExpandCollapseTool, OO.ui.Tool ); ExpandCollapseTool.prototype.onSelect = function () { this.toolGroup.expanded = !this.toolGroup.expanded; this.toolGroup.updateCollapsibleState(); this.setActive( false ); }; ExpandCollapseTool.prototype.onUpdateState = function () { // Do nothing. Tool interface requires an implementation of this function. }; ExpandCollapseTool.static.name = 'more-fewer'; this.expandCollapseTool = new ExpandCollapseTool( this ); } return this.expandCollapseTool; }; /** * @inheritdoc */ OO.ui.ListToolGroup.prototype.onMouseKeyUp = function ( e ) { // Do not close the popup when the user wants to show more/fewer tools if ( $( e.target ).closest( '.oo-ui-tool-name-more-fewer' ).length && ( e.which === OO.ui.MouseButtons.LEFT || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) { // HACK: Prevent the popup list from being hidden. Skip the PopupToolGroup implementation (which // hides the popup list when a tool is selected) and call ToolGroup's implementation directly. return OO.ui.ListToolGroup.parent.parent.prototype.onMouseKeyUp.call( this, e ); } else { return OO.ui.ListToolGroup.parent.prototype.onMouseKeyUp.call( this, e ); } }; OO.ui.ListToolGroup.prototype.updateCollapsibleState = function () { var i, icon, len; if ( this.toolbar.position !== 'bottom' ) { icon = this.expanded ? 'collapse' : 'expand'; } else { icon = this.expanded ? 'expand' : 'collapse'; } this.getExpandCollapseTool() .setIcon( icon ) .setTitle( OO.ui.msg( this.expanded ? 'ooui-toolgroup-collapse' : 'ooui-toolgroup-expand' ) ); for ( i = 0, len = this.collapsibleTools.length; i < len; i++ ) { this.collapsibleTools[ i ].toggle( this.expanded ); } // Re-evaluate clipping, because our height has changed this.clip(); }; /** * MenuToolGroups are one of three types of {@link OO.ui.ToolGroup toolgroups} that are used to * create {@link OO.ui.Toolbar toolbars} (the other types of groups are {@link OO.ui.BarToolGroup BarToolGroup} * and {@link OO.ui.ListToolGroup ListToolGroup}). MenuToolGroups contain selectable {@link OO.ui.Tool tools}, * which are displayed by label in a dropdown menu. The tool's title is used as the label text, and the * menu label is updated to reflect which tool or tools are currently selected. If no tools are selected, * the menu label is empty. The menu can be configured with an indicator, icon, title, and/or header. * * MenuToolGroups are created by a {@link OO.ui.ToolGroupFactory tool group factory} when the toolbar * is set up. * * @example * // Example of a MenuToolGroup * var toolFactory = new OO.ui.ToolFactory(); * var toolGroupFactory = new OO.ui.ToolGroupFactory(); * var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory ); * * // We will be placing status text in this element when tools are used * var $area = $( '' ).text( 'An example of a MenuToolGroup. Select a tool from the dropdown menu.' ); * * // Define the tools that we're going to place in our toolbar * * function SettingsTool() { * SettingsTool.parent.apply( this, arguments ); * this.reallyActive = false; * } * OO.inheritClass( SettingsTool, OO.ui.Tool ); * SettingsTool.static.name = 'settings'; * SettingsTool.static.icon = 'advanced'; * SettingsTool.static.title = 'Change settings'; * SettingsTool.prototype.onSelect = function () { * $area.text( 'Settings tool clicked!' ); * // Toggle the active state on each click * this.reallyActive = !this.reallyActive; * this.setActive( this.reallyActive ); * // To update the menu label * this.toolbar.emit( 'updateState' ); * }; * SettingsTool.prototype.onUpdateState = function () {}; * toolFactory.register( SettingsTool ); * * function StuffTool() { * StuffTool.parent.apply( this, arguments ); * this.reallyActive = false; * } * OO.inheritClass( StuffTool, OO.ui.Tool ); * StuffTool.static.name = 'stuff'; * StuffTool.static.icon = 'ellipsis'; * StuffTool.static.title = 'More stuff'; * StuffTool.prototype.onSelect = function () { * $area.text( 'More stuff tool clicked!' ); * // Toggle the active state on each click * this.reallyActive = !this.reallyActive; * this.setActive( this.reallyActive ); * // To update the menu label * this.toolbar.emit( 'updateState' ); * }; * StuffTool.prototype.onUpdateState = function () {}; * toolFactory.register( StuffTool ); * * // Finally define which tools and in what order appear in the toolbar. Each tool may only be * // used once (but not all defined tools must be used). * toolbar.setup( [ * { * type: 'menu', * header: 'This is the (optional) header', * title: 'This is the (optional) title', * include: [ 'settings', 'stuff' ] * } * ] ); * * // Create some UI around the toolbar and place it in the document * var frame = new OO.ui.PanelLayout( { * expanded: false, * framed: true * } ); * var contentFrame = new OO.ui.PanelLayout( { * expanded: false, * padded: true * } ); * frame.$element.append( * toolbar.$element, * contentFrame.$element.append( $area ) * ); * $( 'body' ).append( frame.$element ); * * // Here is where the toolbar is actually built. This must be done after inserting it into the * // document. * toolbar.initialize(); * toolbar.emit( 'updateState' ); * * For more information about how to add tools to a MenuToolGroup, please see {@link OO.ui.ToolGroup toolgroup}. * For more information about toolbars in general, please see the [OOUI documentation on MediaWiki] [1]. * * [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars * * @class * @extends OO.ui.PopupToolGroup * * @constructor * @param {OO.ui.Toolbar} toolbar * @param {Object} [config] Configuration options */ OO.ui.MenuToolGroup = function OoUiMenuToolGroup( 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.MenuToolGroup.parent.call( this, toolbar, config ); // Events this.toolbar.connect( this, { updateState: 'onUpdateState' } ); // Initialization this.$element.addClass( 'oo-ui-menuToolGroup' ); this.$group.addClass( 'oo-ui-menuToolGroup-tools' ); }; /* Setup */ OO.inheritClass( OO.ui.MenuToolGroup, OO.ui.PopupToolGroup ); /* Static Properties */ /** * @static * @inheritdoc */ OO.ui.MenuToolGroup.static.name = 'menu'; /* Methods */ /** * Handle the toolbar state being updated. * * When the state changes, the title of each active item in the menu will be joined together and * used as a label for the group. The label will be empty if none of the items are active. * * @private */ OO.ui.MenuToolGroup.prototype.onUpdateState = function () { var name, labelTexts = []; for ( name in this.tools ) { if ( this.tools[ name ].isActive() ) { labelTexts.push( this.tools[ name ].getTitle() ); } } this.setLabel( labelTexts.join( ', ' ) || ' ' ); }; }( OO ) ); //# sourceMappingURL=oojs-ui-toolbars.js.map.json