Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F6038874
oojs-ui-toolbars.js
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
88 KB
Referenced Files
None
Subscribers
None
oojs-ui-toolbars.js
View Options
/*!
* OOUI v0.51.2
* https://www.mediawiki.org/wiki/OOUI
*
* Copyright 2011–2024 OOUI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
* Date: 2024-11-13T16:37:10Z
*/
(
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 OO.ui.Toolbar#event:updateState 'updateState' event}, which calls Tools'
* {@link OO.ui.Tool#onUpdateState onUpdateState method}.
*
* @example <caption>The following is an example of a basic toolbar.</caption>
* // Example of a toolbar
* // Create the toolbar
* const toolFactory = new OO.ui.ToolFactory();
* const toolGroupFactory = new OO.ui.ToolGroupFactory();
* const toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
*
* // We will be placing status text in this element when tools are used
* const $area = $( '<p>' ).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.super.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.super.apply( this, arguments );
* }
* OO.inheritClass( SettingsTool, OO.ui.Tool );
* SettingsTool.static.name = 'settings';
* SettingsTool.static.icon = 'settings';
* 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.super.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( '<p>I am helpful!</p>' );
* }
* 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
* const frame = new OO.ui.PanelLayout( {
* expanded: false,
* framed: true
* } );
* const contentFrame = new OO.ui.PanelLayout( {
* expanded: false,
* padded: true
* } );
* frame.$element.append(
* toolbar.$element,
* contentFrame.$element.append( $area )
* );
* $( document.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' );
*
* @example <caption>The following example extends the previous one to illustrate 'menu' toolgroups and the usage of
* {@link OO.ui.Toolbar#event:updateState 'updateState' event}.</caption>
* // Create the toolbar
* const toolFactory = new OO.ui.ToolFactory();
* const toolGroupFactory = new OO.ui.ToolGroupFactory();
* const toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
*
* // We will be placing status text in this element when tools are used
* const $area = $( '<p>' ).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.super.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.super.apply( this, arguments );
* this.reallyActive = false;
* }
* OO.inheritClass( SettingsTool, OO.ui.Tool );
* SettingsTool.static.name = 'settings';
* SettingsTool.static.icon = 'settings';
* 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.super.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( '<p>I am helpful!</p>' );
* }
* 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
* const frame = new OO.ui.PanelLayout( {
* expanded: false,
* framed: true
* } );
* const contentFrame = new OO.ui.PanelLayout( {
* expanded: false,
* padded: true
* } );
* frame.$element.append(
* toolbar.$element,
* contentFrame.$element.append( $area )
* );
* $( document.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
* @mixes OO.EventEmitter
* @mixes 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
* @param {boolean} [config.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.
* This feature is deprecated. It is suggested to use the ToolGroup 'align' property instead.
* @param {string} [config.position='top'] Whether the toolbar is positioned above ('top') or below
* ('bottom') content.
* @param {jQuery} [config.$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
if
(
OO
.
isPlainObject
(
toolFactory
)
&&
config
===
undefined
)
{
config
=
toolFactory
;
toolFactory
=
config
.
toolFactory
;
toolGroupFactory
=
config
.
toolGroupFactory
;
}
// Configuration initialization
config
=
config
||
{};
// Parent constructor
OO
.
ui
.
Toolbar
.
super
.
call
(
this
,
config
);
// Mixin constructors
OO
.
EventEmitter
.
call
(
this
);
OO
.
ui
.
mixin
.
GroupElement
.
call
(
this
,
config
);
// Properties
this
.
toolFactory
=
toolFactory
;
this
.
toolGroupFactory
=
toolGroupFactory
;
this
.
groupsByName
=
{};
this
.
activeToolGroups
=
0
;
this
.
tools
=
{};
this
.
position
=
config
.
position
||
'top'
;
this
.
$bar
=
$
(
'<div>'
);
this
.
$after
=
$
(
'<div>'
);
this
.
$actions
=
$
(
'<div>'
);
this
.
$popups
=
$
(
'<div>'
);
this
.
initialized
=
false
;
this
.
narrow
=
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
.
add
(
this
.
$bar
).
add
(
this
.
$group
).
add
(
this
.
$after
).
add
(
this
.
$actions
)
.
on
(
'mousedown keydown'
,
this
.
onPointerDown
.
bind
(
this
)
);
// Initialization
this
.
$bar
.
addClass
(
'oo-ui-toolbar-bar'
);
this
.
$group
.
addClass
(
'oo-ui-toolbar-tools'
);
this
.
$after
.
addClass
(
'oo-ui-toolbar-tools oo-ui-toolbar-after'
);
this
.
$popups
.
addClass
(
'oo-ui-toolbar-popups'
);
this
.
$bar
.
append
(
this
.
$group
,
this
.
$after
);
if
(
config
.
actions
)
{
this
.
$bar
.
append
(
this
.
$actions
.
addClass
(
'oo-ui-toolbar-actions'
)
);
}
this
.
$bar
.
append
(
$
(
'<div>'
).
css
(
'clear'
,
'both'
)
);
// 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 */
OO
.
inheritClass
(
OO
.
ui
.
Toolbar
,
OO
.
ui
.
Element
);
OO
.
mixinClass
(
OO
.
ui
.
Toolbar
,
OO
.
EventEmitter
);
OO
.
mixinClass
(
OO
.
ui
.
Toolbar
,
OO
.
ui
.
mixin
.
GroupElement
);
/* Events */
/**
* An 'updateState' event must be emitted on the Toolbar (by calling
* `toolbar.emit( 'updateState' )`) every time the state of the application using the toolbar
* changes, and an update to the state of tools is required.
*
* @event OO.ui.Toolbar#updateState
* @param {...any} data Application-defined parameters
*/
/**
* An 'active' event is emitted when the number of active toolgroups increases from 0, or
* returns to 0.
*
* @event OO.ui.Toolbar#active
* @param {boolean} There are active toolgroups in this toolbar
*/
/**
* Toolbar has resized to a point where narrow mode has changed
*
* @event OO.ui.Toolbar#resize
*/
/* Methods */
/**
* Get the tool factory.
*
* @return {OO.ui.ToolFactory} Tool factory
*/
OO
.
ui
.
Toolbar
.
prototype
.
getToolFactory
=
function
()
{
return
this
.
toolFactory
;
};
/**
* Get the toolgroup factory.
*
* @return {OO.Factory} Toolgroup factory
*/
OO
.
ui
.
Toolbar
.
prototype
.
getToolGroupFactory
=
function
()
{
return
this
.
toolGroupFactory
;
};
/**
* @inheritdoc {OO.ui.mixin.GroupElement}
*/
OO
.
ui
.
Toolbar
.
prototype
.
insertItemElements
=
function
(
item
)
{
// Mixin method
OO
.
ui
.
mixin
.
GroupElement
.
prototype
.
insertItemElements
.
apply
(
this
,
arguments
);
if
(
item
.
align
===
'after'
)
{
// Toolbar only ever appends ToolGroups to the end, so we can ignore 'index'
this
.
$after
.
append
(
item
.
$element
);
}
};
/**
* Handles mouse down events.
*
* @private
* @param {jQuery.Event} e Mouse down event
* @return {undefined|boolean} False to prevent default if event is handled
*/
OO
.
ui
.
Toolbar
.
prototype
.
onPointerDown
=
function
(
e
)
{
const
$closestWidgetToEvent
=
$
(
e
.
target
).
closest
(
'.oo-ui-widget'
),
$closestWidgetToToolbar
=
this
.
$element
.
closest
(
'.oo-ui-widget'
);
if
(
!
$closestWidgetToEvent
.
length
||
$closestWidgetToEvent
[
0
]
===
$closestWidgetToToolbar
[
0
]
)
{
return
false
;
}
};
/**
* Handle window resize event.
*
* @private
* @param {jQuery.Event} e Window resize event
*/
OO
.
ui
.
Toolbar
.
prototype
.
onWindowResize
=
function
()
{
this
.
setNarrow
(
this
.
$bar
[
0
].
clientWidth
<=
this
.
getNarrowThreshold
()
);
};
/**
* Check if the toolbar is in narrow mode
*
* @return {boolean} Toolbar is in narrow mode
*/
OO
.
ui
.
Toolbar
.
prototype
.
isNarrow
=
function
()
{
return
this
.
narrow
;
};
/**
* Set the narrow mode flag
*
* @param {boolean} narrow Toolbar is in narrow mode
*/
OO
.
ui
.
Toolbar
.
prototype
.
setNarrow
=
function
(
narrow
)
{
if
(
narrow
!==
this
.
narrow
)
{
this
.
narrow
=
narrow
;
this
.
$element
.
add
(
this
.
$popups
).
toggleClass
(
'oo-ui-toolbar-narrow'
,
this
.
narrow
);
this
.
emit
(
'resize'
);
}
};
/**
* Get the (lazily-computed) width threshold for applying the oo-ui-toolbar-narrow
* class.
*
* @private
* @return {number} Width threshold in pixels
*/
OO
.
ui
.
Toolbar
.
prototype
.
getNarrowThreshold
=
function
()
{
if
(
this
.
narrowThreshold
===
null
)
{
this
.
narrowThreshold
=
this
.
$group
[
0
].
offsetWidth
+
this
.
$after
[
0
].
offsetWidth
+
this
.
$actions
[
0
].
offsetWidth
;
}
return
this
.
narrowThreshold
;
};
/**
* Sets up handles and preloads required information for the toolbar to work.
* This must be called after it is attached to a visible document and before doing anything else.
*/
OO
.
ui
.
Toolbar
.
prototype
.
initialize
=
function
()
{
if
(
!
this
.
initialized
)
{
this
.
initialized
=
true
;
$
(
this
.
getElementWindow
()
).
on
(
'resize'
,
this
.
onWindowResizeHandler
);
this
.
onWindowResize
();
}
};
/**
* Set up the toolbar.
*
* The toolbar is set up with a list of toolgroup configurations that specify the type of
* toolgroup ({@link OO.ui.BarToolGroup bar}, {@link OO.ui.MenuToolGroup menu}, or
* {@link OO.ui.ListToolGroup list}) to add and which tools to include, exclude, promote, or demote
* within that toolgroup. Please see {@link OO.ui.ToolGroup toolgroups} for more information about
* including tools in toolgroups.
*
* @param {Object[]} groups List of toolgroup configurations
* @param {string} groups.name Symbolic name for this toolgroup
* @param {string} [groups.type] Toolgroup type, e.g. "bar", "list", or "menu". Should exist in the
* {@link OO.ui.ToolGroupFactory} provided via the constructor. Defaults to "list" for catch-all
* groups where `include='*'`, otherwise "bar".
* @param {Array|string} [groups.include] Tools to include in the toolgroup, or "*" for catch-all,
* see {@link OO.ui.ToolFactory#extract}
* @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
)
{
const
defaultType
=
'bar'
;
// Cleanup previous groups
this
.
reset
();
const
items
=
[];
// Build out new groups
for
(
let
i
=
0
,
len
=
groups
.
length
;
i
<
len
;
i
++
)
{
const
groupConfig
=
groups
[
i
];
if
(
groupConfig
.
include
===
'*'
)
{
// Apply defaults to catch-all groups
if
(
groupConfig
.
type
===
undefined
)
{
groupConfig
.
type
=
'list'
;
}
if
(
groupConfig
.
label
===
undefined
)
{
groupConfig
.
label
=
OO
.
ui
.
msg
(
'ooui-toolbar-more'
);
}
}
// Check type has been registered
const
type
=
this
.
getToolGroupFactory
().
lookup
(
groupConfig
.
type
)
?
groupConfig
.
type
:
defaultType
;
const
toolGroup
=
this
.
getToolGroupFactory
().
create
(
type
,
this
,
groupConfig
);
items
.
push
(
toolGroup
);
this
.
groupsByName
[
groupConfig
.
name
]
=
toolGroup
;
toolGroup
.
connect
(
this
,
{
active
:
'onToolGroupActive'
}
);
}
this
.
addItems
(
items
);
};
/**
* Handle active events from tool groups
*
* @param {boolean} active Tool group has become active, inactive if false
* @fires OO.ui.Toolbar#active
*/
OO
.
ui
.
Toolbar
.
prototype
.
onToolGroupActive
=
function
(
active
)
{
if
(
active
)
{
this
.
activeToolGroups
++
;
if
(
this
.
activeToolGroups
===
1
)
{
this
.
emit
(
'active'
,
true
);
}
}
else
{
this
.
activeToolGroups
--
;
if
(
this
.
activeToolGroups
===
0
)
{
this
.
emit
(
'active'
,
false
);
}
}
};
/**
* 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
()
{
this
.
groupsByName
=
{};
this
.
tools
=
{};
for
(
let
i
=
0
,
len
=
this
.
items
.
length
;
i
<
len
;
i
++
)
{
this
.
items
[
i
].
destroy
();
}
this
.
clearItems
();
};
/**
* Destroy the toolbar.
*
* Destroying the toolbar removes all event handlers and DOM elements that constitute the toolbar.
* Call this method whenever you are done using a toolbar.
*/
OO
.
ui
.
Toolbar
.
prototype
.
destroy
=
function
()
{
$
(
this
.
getElementWindow
()
).
off
(
'resize'
,
this
.
onWindowResizeHandler
);
this
.
reset
();
this
.
$element
.
remove
();
};
/**
* Check if the tool is available.
*
* Available tools are ones that have not yet been added to the toolbar.
*
* @param {string} name Symbolic name of tool
* @return {boolean} Tool is available
*/
OO
.
ui
.
Toolbar
.
prototype
.
isToolAvailable
=
function
(
name
)
{
return
!
this
.
tools
[
name
];
};
/**
* Prevent tool from being used again.
*
* @param {OO.ui.Tool} tool Tool to reserve
*/
OO
.
ui
.
Toolbar
.
prototype
.
reserveTool
=
function
(
tool
)
{
this
.
tools
[
tool
.
getName
()
]
=
tool
;
};
/**
* Allow tool to be used again.
*
* @param {OO.ui.Tool} tool Tool to release
*/
OO
.
ui
.
Toolbar
.
prototype
.
releaseTool
=
function
(
tool
)
{
delete
this
.
tools
[
tool
.
getName
()
];
};
/**
* Get accelerator label for tool.
*
* The OOUI library does not contain an accelerator system, but this is the hook for one. To
* use an accelerator system, subclass the toolbar and override this method, which is meant to
* return a label that describes the accelerator keys for the tool passed (by symbolic name) to
* the method.
*
* @param {string} name Symbolic name of tool
* @return {string|undefined} Tool accelerator label if available
*/
OO
.
ui
.
Toolbar
.
prototype
.
getToolAccelerator
=
function
()
{
return
undefined
;
};
/**
* Tools, together with {@link OO.ui.ToolGroup toolgroups}, constitute
* {@link OO.ui.Toolbar toolbars}.
* Each tool is configured with a static name, title, and icon and is customized with the command
* to carry out when the tool is selected. Tools must also be registered with a
* {@link OO.ui.ToolFactory tool factory}, which creates the tools on demand.
*
* Every Tool subclass must implement two methods:
*
* - {@link OO.ui.Tool#onUpdateState onUpdateState}
* - {@link OO.ui.Tool#onSelect onSelect}
*
* Tools are added to toolgroups ({@link OO.ui.ListToolGroup ListToolGroup},
* {@link OO.ui.BarToolGroup BarToolGroup}, or {@link OO.ui.MenuToolGroup MenuToolGroup}), which
* determine how the tool is displayed in the toolbar. See {@link OO.ui.Toolbar toolbars} for an
* example.
*
* For more information, please see the [OOUI documentation on MediaWiki][1].
* [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
*
* @abstract
* @class
* @extends OO.ui.Widget
* @mixes OO.ui.mixin.IconElement
* @mixes OO.ui.mixin.FlaggedElement
* @mixes OO.ui.mixin.TabIndexedElement
*
* @constructor
* @param {OO.ui.ToolGroup} toolGroup
* @param {Object} [config] Configuration options
* @param {string|Function} [config.title] Title text or a function that returns text. If this config is
* omitted, the value of the {@link OO.ui.Tool.static.title static title} property is used.
* @param {boolean} [config.displayBothIconAndLabel] See static.displayBothIconAndLabel
* @param {Object} [config.narrowConfig] See static.narrowConfig
*
* The title is used in different ways depending on the type of toolgroup that contains the tool.
* The title is used as a tooltip if the tool is part of a {@link OO.ui.BarToolGroup bar}
* toolgroup, or as the label text if the tool is part of a {@link OO.ui.ListToolGroup list} or
* {@link OO.ui.MenuToolGroup menu} toolgroup.
*
* For bar toolgroups, a description of the accelerator key is appended to the title if an
* accelerator key is associated with an action by the same name as the tool and accelerator
* functionality has been added to the application.
* To add accelerator key functionality, you must subclass OO.ui.Toolbar and override the
* {@link OO.ui.Toolbar#getToolAccelerator getToolAccelerator} method.
*/
OO
.
ui
.
Tool
=
function
OoUiTool
(
toolGroup
,
config
)
{
// Allow passing positional parameters inside the config object
if
(
OO
.
isPlainObject
(
toolGroup
)
&&
config
===
undefined
)
{
config
=
toolGroup
;
toolGroup
=
config
.
toolGroup
;
}
// Configuration initialization
config
=
config
||
{};
// Parent constructor
OO
.
ui
.
Tool
.
super
.
call
(
this
,
config
);
// Properties
this
.
toolGroup
=
toolGroup
;
this
.
toolbar
=
this
.
toolGroup
.
getToolbar
();
this
.
active
=
false
;
this
.
$title
=
$
(
'<span>'
);
this
.
$accel
=
$
(
'<span>'
);
this
.
$link
=
$
(
'<a>'
);
this
.
title
=
null
;
this
.
checkIcon
=
new
OO
.
ui
.
IconWidget
(
{
icon
:
'check'
,
classes
:
[
'oo-ui-tool-checkIcon'
]
}
);
this
.
displayBothIconAndLabel
=
config
.
displayBothIconAndLabel
!==
undefined
?
config
.
displayBothIconAndLabel
:
this
.
constructor
.
static
.
displayBothIconAndLabel
;
this
.
narrowConfig
=
config
.
narrowConfig
||
this
.
constructor
.
static
.
narrowConfig
;
// Mixin constructors
OO
.
ui
.
mixin
.
IconElement
.
call
(
this
,
config
);
OO
.
ui
.
mixin
.
FlaggedElement
.
call
(
this
,
config
);
OO
.
ui
.
mixin
.
TabIndexedElement
.
call
(
this
,
Object
.
assign
(
{
$tabIndexed
:
this
.
$link
},
config
)
);
// Events
this
.
toolbar
.
connect
(
this
,
{
updateState
:
'onUpdateState'
,
resize
:
'onToolbarResize'
}
);
// Initialization
this
.
$title
.
addClass
(
'oo-ui-tool-title'
);
this
.
$accel
.
addClass
(
'oo-ui-tool-accel'
)
.
prop
(
{
// This may need to be changed if the key names are ever localized,
// but for now they are essentially written in English
dir
:
'ltr'
,
lang
:
'en'
}
);
this
.
$link
.
addClass
(
'oo-ui-tool-link'
)
.
append
(
this
.
checkIcon
.
$element
,
this
.
$icon
,
this
.
$title
,
this
.
$accel
)
.
attr
(
'role'
,
'button'
);
// Don't show keyboard shortcuts on mobile as users are unlikely to have
// a physical keyboard, and likely to have limited screen space.
if
(
!
OO
.
ui
.
isMobile
()
)
{
this
.
$link
.
append
(
this
.
$accel
);
}
this
.
$element
.
data
(
'oo-ui-tool'
,
this
)
.
addClass
(
'oo-ui-tool'
)
.
addClass
(
'oo-ui-tool-name-'
+
this
.
constructor
.
static
.
name
.
replace
(
/^([^/]+)\/([^/]+).*$/
,
'$1-$2'
)
)
.
append
(
this
.
$link
);
this
.
setTitle
(
config
.
title
||
this
.
constructor
.
static
.
title
);
};
/* Setup */
OO
.
inheritClass
(
OO
.
ui
.
Tool
,
OO
.
ui
.
Widget
);
OO
.
mixinClass
(
OO
.
ui
.
Tool
,
OO
.
ui
.
mixin
.
IconElement
);
OO
.
mixinClass
(
OO
.
ui
.
Tool
,
OO
.
ui
.
mixin
.
FlaggedElement
);
OO
.
mixinClass
(
OO
.
ui
.
Tool
,
OO
.
ui
.
mixin
.
TabIndexedElement
);
/* Static Properties */
/**
* @static
* @inheritdoc
*/
OO
.
ui
.
Tool
.
static
.
tagName
=
'span'
;
/**
* Symbolic name of tool.
*
* The symbolic name is used internally to register the tool with a
* {@link OO.ui.ToolFactory ToolFactory}. It can also be used when adding tools to toolgroups.
*
* @abstract
* @static
* @property {string}
*/
OO
.
ui
.
Tool
.
static
.
name
=
''
;
/**
* Symbolic name of the group.
*
* The group name is used to associate tools with each other so that they can be selected later by
* a {@link OO.ui.ToolGroup toolgroup}.
*
* @abstract
* @static
* @property {string}
*/
OO
.
ui
.
Tool
.
static
.
group
=
''
;
/**
* Tool title text or a function that returns title text. The value of the static property is
* overridden if the #title config option is used.
*
* @abstract
* @static
* @property {string|Function}
*/
OO
.
ui
.
Tool
.
static
.
title
=
''
;
/**
* Display both icon and label when the tool is used in a {@link OO.ui.BarToolGroup bar} toolgroup.
* Normally only the icon is displayed, or only the label if no icon is given.
*
* @static
* @property {boolean}
*/
OO
.
ui
.
Tool
.
static
.
displayBothIconAndLabel
=
false
;
/**
* Add tool to catch-all groups automatically.
*
* A catch-all group, which contains all tools that do not currently belong to a toolgroup,
* can be included in a toolgroup using the wildcard selector, an asterisk (*).
*
* @static
* @property {boolean}
*/
OO
.
ui
.
Tool
.
static
.
autoAddToCatchall
=
true
;
/**
* Add tool to named groups automatically.
*
* By default, tools that are configured with a static ‘group’ property are added
* to that group and will be selected when the symbolic name of the group is specified (e.g., when
* toolgroups include tools by group name).
*
* @static
* @property {boolean}
*/
OO
.
ui
.
Tool
.
static
.
autoAddToGroup
=
true
;
/**
* Check if this tool is compatible with given data.
*
* This is a stub that can be overridden to provide support for filtering tools based on an
* arbitrary piece of information (e.g., where the cursor is in a document). The implementation
* must also call this method so that the compatibility check can be performed.
*
* @static
* @param {any} data Data to check
* @return {boolean} Tool can be used with data
*/
OO
.
ui
.
Tool
.
static
.
isCompatibleWith
=
function
()
{
return
false
;
};
/**
* Config options to change when toolbar is in narrow mode
*
* Supports `displayBothIconAndLabel`, `title` and `icon` properties.
*
* @static
* @property {Object|null}
*/
OO
.
ui
.
Tool
.
static
.
narrowConfig
=
null
;
/* Methods */
/**
* Handle the toolbar state being updated. This method is called when the
* {@link OO.ui.Toolbar#event-updateState 'updateState' event} is emitted on the
* {@link OO.ui.Toolbar Toolbar} that uses this tool, and should set the state of this tool
* depending on application state (usually by calling #setDisabled to enable or disable the tool,
* or #setActive to mark is as currently in-use or not).
*
* This is an abstract method that must be overridden in a concrete subclass.
*
* @method
* @protected
* @abstract
*/
OO
.
ui
.
Tool
.
prototype
.
onUpdateState
=
null
;
/**
* Handle the tool being selected. This method is called when the user triggers this tool,
* usually by clicking on its label/icon.
*
* This is an abstract method that must be overridden in a concrete subclass.
*
* @method
* @protected
* @abstract
*/
OO
.
ui
.
Tool
.
prototype
.
onSelect
=
null
;
/**
* Check if the tool is active.
*
* Tools become active when their #onSelect or #onUpdateState handlers change them to appear pressed
* with the #setActive method. Additional CSS is applied to the tool to reflect the active state.
*
* @return {boolean} Tool is active
*/
OO
.
ui
.
Tool
.
prototype
.
isActive
=
function
()
{
return
this
.
active
;
};
/**
* Make the tool appear active or inactive.
*
* This method should be called within #onSelect or #onUpdateState event handlers to make the tool
* appear pressed or not.
*
* @param {boolean} [state=false] Make tool appear active
*/
OO
.
ui
.
Tool
.
prototype
.
setActive
=
function
(
state
)
{
this
.
active
=
!!
state
;
this
.
$element
.
toggleClass
(
'oo-ui-tool-active'
,
this
.
active
);
this
.
updateThemeClasses
();
};
/**
* Set the tool #title.
*
* @param {string|Function} title Title text or a function that returns text
* @chainable
* @return {OO.ui.Tool} The tool, for chaining
*/
OO
.
ui
.
Tool
.
prototype
.
setTitle
=
function
(
title
)
{
this
.
title
=
OO
.
ui
.
resolveMsg
(
title
);
this
.
updateTitle
();
// Update classes
this
.
setDisplayBothIconAndLabel
(
this
.
displayBothIconAndLabel
);
return
this
;
};
/**
* Set the tool's displayBothIconAndLabel state.
*
* Update title classes if necessary
*
* @param {boolean} displayBothIconAndLabel
* @chainable
* @return {OO.ui.Tool} The tool, for chaining
*/
OO
.
ui
.
Tool
.
prototype
.
setDisplayBothIconAndLabel
=
function
(
displayBothIconAndLabel
)
{
this
.
displayBothIconAndLabel
=
displayBothIconAndLabel
;
this
.
$element
.
toggleClass
(
'oo-ui-tool-with-label'
,
!!
this
.
title
&&
this
.
displayBothIconAndLabel
);
return
this
;
};
/**
* Get the tool #title.
*
* @return {string} Title text
*/
OO
.
ui
.
Tool
.
prototype
.
getTitle
=
function
()
{
return
this
.
title
;
};
/**
* Get the tool's symbolic name.
*
* @return {string} Symbolic name of tool
*/
OO
.
ui
.
Tool
.
prototype
.
getName
=
function
()
{
return
this
.
constructor
.
static
.
name
;
};
/**
* Handle resize events from the toolbar
*/
OO
.
ui
.
Tool
.
prototype
.
onToolbarResize
=
function
()
{
if
(
!
this
.
narrowConfig
)
{
return
;
}
if
(
this
.
toolbar
.
isNarrow
()
)
{
if
(
this
.
narrowConfig
.
displayBothIconAndLabel
!==
undefined
)
{
this
.
wideDisplayBothIconAndLabel
=
this
.
displayBothIconAndLabel
;
this
.
setDisplayBothIconAndLabel
(
this
.
narrowConfig
.
displayBothIconAndLabel
);
}
if
(
this
.
narrowConfig
.
title
!==
undefined
)
{
this
.
wideTitle
=
this
.
title
;
this
.
setTitle
(
this
.
narrowConfig
.
title
);
}
if
(
this
.
narrowConfig
.
icon
!==
undefined
)
{
this
.
wideIcon
=
this
.
icon
;
this
.
setIcon
(
this
.
narrowConfig
.
icon
);
}
}
else
{
if
(
this
.
wideDisplayBothIconAndLabel
!==
undefined
)
{
this
.
setDisplayBothIconAndLabel
(
this
.
wideDisplayBothIconAndLabel
);
}
if
(
this
.
wideTitle
!==
undefined
)
{
this
.
setTitle
(
this
.
wideTitle
);
}
if
(
this
.
wideIcon
!==
undefined
)
{
this
.
setIcon
(
this
.
wideIcon
);
}
}
};
/**
* Update the title.
*/
OO
.
ui
.
Tool
.
prototype
.
updateTitle
=
function
()
{
const
titleTooltips
=
this
.
toolGroup
.
constructor
.
static
.
titleTooltips
,
accelTooltips
=
this
.
toolGroup
.
constructor
.
static
.
accelTooltips
,
accel
=
this
.
toolbar
.
getToolAccelerator
(
this
.
constructor
.
static
.
name
),
tooltipParts
=
[];
this
.
$title
.
text
(
this
.
title
);
this
.
$accel
.
text
(
accel
);
if
(
titleTooltips
&&
typeof
this
.
title
===
'string'
&&
this
.
title
.
length
)
{
tooltipParts
.
push
(
this
.
title
);
}
if
(
accelTooltips
&&
typeof
accel
===
'string'
&&
accel
.
length
)
{
tooltipParts
.
push
(
accel
);
}
if
(
tooltipParts
.
length
)
{
this
.
$link
.
attr
(
'title'
,
tooltipParts
.
join
(
' '
)
);
}
else
{
this
.
$link
.
removeAttr
(
'title'
);
}
};
/**
* @inheritdoc OO.ui.mixin.IconElement
*/
OO
.
ui
.
Tool
.
prototype
.
setIcon
=
function
(
icon
)
{
// Mixin method
OO
.
ui
.
mixin
.
IconElement
.
prototype
.
setIcon
.
call
(
this
,
icon
);
this
.
$element
.
toggleClass
(
'oo-ui-tool-with-icon'
,
!!
this
.
icon
);
return
this
;
};
/**
* Destroy tool.
*
* Destroying the tool removes all event handlers and the tool’s DOM elements.
* Call this method whenever you are done using a tool.
*/
OO
.
ui
.
Tool
.
prototype
.
destroy
=
function
()
{
this
.
toolbar
.
disconnect
(
this
);
this
.
$element
.
remove
();
};
/**
* ToolGroups are collections of {@link OO.ui.Tool tools} that are used in a
* {@link OO.ui.Toolbar toolbar}.
* The type of toolgroup ({@link OO.ui.ListToolGroup list}, {@link OO.ui.BarToolGroup bar}, or
* {@link OO.ui.MenuToolGroup menu}) to which a tool belongs determines how the tool is arranged
* and displayed in the toolbar. Toolgroups themselves are created on demand with a
* {@link OO.ui.ToolGroupFactory toolgroup factory}.
*
* Toolgroups can contain individual tools, groups of tools, or all available tools, as specified
* using the `include` config option. See OO.ui.ToolFactory#extract on documentation of the format.
* The options `exclude`, `promote`, and `demote` support the same formats.
*
* See {@link OO.ui.Toolbar toolbars} for a full example. 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.Widget
* @mixes OO.ui.mixin.GroupElement
*
* @constructor
* @param {OO.ui.Toolbar} toolbar
* @param {Object} [config] Configuration options
* @param {Array|string} [config.include=[]] List of tools to include in the toolgroup, see above.
* @param {Array|string} [config.exclude=[]] List of tools to exclude from the toolgroup, see above.
* @param {Array|string} [config.promote=[]] List of tools to promote to the beginning of the toolgroup,
* see above.
* @param {Array|string} [config.demote=[]] List of tools to demote to the end of the toolgroup, see above.
* This setting is particularly useful when tools have been added to the toolgroup
* en masse (e.g., via the catch-all selector).
* @param {string} [config.align='before'] Alignment within the toolbar, either 'before' or 'after'.
*/
OO
.
ui
.
ToolGroup
=
function
OoUiToolGroup
(
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
.
ToolGroup
.
super
.
call
(
this
,
config
);
// Mixin constructors
OO
.
ui
.
mixin
.
GroupElement
.
call
(
this
,
config
);
// Properties
this
.
toolbar
=
toolbar
;
this
.
tools
=
{};
this
.
pressed
=
null
;
this
.
autoDisabled
=
false
;
this
.
include
=
config
.
include
||
[];
this
.
exclude
=
config
.
exclude
||
[];
this
.
promote
=
config
.
promote
||
[];
this
.
demote
=
config
.
demote
||
[];
this
.
align
=
config
.
align
||
'before'
;
this
.
onDocumentMouseKeyUpHandler
=
this
.
onDocumentMouseKeyUp
.
bind
(
this
);
// Events
this
.
$group
.
on
(
{
mousedown
:
this
.
onMouseKeyDown
.
bind
(
this
),
mouseup
:
this
.
onMouseKeyUp
.
bind
(
this
),
keydown
:
this
.
onMouseKeyDown
.
bind
(
this
),
keyup
:
this
.
onMouseKeyUp
.
bind
(
this
),
focus
:
this
.
onMouseOverFocus
.
bind
(
this
),
blur
:
this
.
onMouseOutBlur
.
bind
(
this
),
mouseover
:
this
.
onMouseOverFocus
.
bind
(
this
),
mouseout
:
this
.
onMouseOutBlur
.
bind
(
this
)
}
);
this
.
toolbar
.
getToolFactory
().
connect
(
this
,
{
register
:
'onToolFactoryRegister'
}
);
this
.
aggregate
(
{
disable
:
'itemDisable'
}
);
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
();
};
/* Setup */
OO
.
inheritClass
(
OO
.
ui
.
ToolGroup
,
OO
.
ui
.
Widget
);
OO
.
mixinClass
(
OO
.
ui
.
ToolGroup
,
OO
.
ui
.
mixin
.
GroupElement
);
/* Events */
/**
* @event OO.ui.ToolGroup#update
*/
/**
* An 'active' event is emitted when any popup is shown/hidden.
*
* @event OO.ui.ToolGroup#active
* @param {boolean} The popup is visible
*/
/* Static Properties */
/**
* Show labels in tooltips.
*
* @static
* @property {boolean}
*/
OO
.
ui
.
ToolGroup
.
static
.
titleTooltips
=
false
;
/**
* Show acceleration labels in tooltips.
*
* Note: The OOUI library does not include an accelerator system, but does contain
* a hook for one. To use an accelerator system, subclass the {@link OO.ui.Toolbar toolbar} and
* override the {@link OO.ui.Toolbar#getToolAccelerator getToolAccelerator} method, which is
* meant to return a label that describes the accelerator keys for a given tool (e.g., Control+M
* key combination).
*
* @static
* @property {boolean}
*/
OO
.
ui
.
ToolGroup
.
static
.
accelTooltips
=
false
;
/**
* Automatically disable the toolgroup when all tools are disabled
*
* @static
* @property {boolean}
*/
OO
.
ui
.
ToolGroup
.
static
.
autoDisable
=
true
;
/**
* @abstract
* @static
* @property {string}
*/
OO
.
ui
.
ToolGroup
.
static
.
name
=
null
;
/* Methods */
/**
* @inheritdoc
*/
OO
.
ui
.
ToolGroup
.
prototype
.
isDisabled
=
function
()
{
return
this
.
autoDisabled
||
OO
.
ui
.
ToolGroup
.
super
.
prototype
.
isDisabled
.
apply
(
this
,
arguments
);
};
/**
* @inheritdoc
*/
OO
.
ui
.
ToolGroup
.
prototype
.
updateDisabled
=
function
()
{
let
allDisabled
=
true
;
if
(
this
.
constructor
.
static
.
autoDisable
)
{
for
(
let
i
=
this
.
items
.
length
-
1
;
i
>=
0
;
i
--
)
{
const
item
=
this
.
items
[
i
];
if
(
!
item
.
isDisabled
()
)
{
allDisabled
=
false
;
break
;
}
}
this
.
autoDisabled
=
allDisabled
;
}
OO
.
ui
.
ToolGroup
.
super
.
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.
*
* @protected
* @param {jQuery.Event} e Mouse down or key down event
* @return {undefined|boolean} False to prevent default if event is handled
*/
OO
.
ui
.
ToolGroup
.
prototype
.
onMouseKeyDown
=
function
(
e
)
{
if
(
!
this
.
isDisabled
()
&&
(
e
.
which
===
OO
.
ui
.
MouseButtons
.
LEFT
||
e
.
which
===
OO
.
ui
.
Keys
.
SPACE
||
e
.
which
===
OO
.
ui
.
Keys
.
ENTER
)
)
{
this
.
pressed
=
this
.
findTargetTool
(
e
);
if
(
this
.
pressed
)
{
this
.
pressed
.
setActive
(
true
);
this
.
getElementDocument
().
addEventListener
(
'mouseup'
,
this
.
onDocumentMouseKeyUpHandler
,
true
);
this
.
getElementDocument
().
addEventListener
(
'keyup'
,
this
.
onDocumentMouseKeyUpHandler
,
true
);
return
false
;
}
}
};
/**
* Handle document mouse up and key up events.
*
* @protected
* @param {MouseEvent|KeyboardEvent} e Mouse up or key up event
*/
OO
.
ui
.
ToolGroup
.
prototype
.
onDocumentMouseKeyUp
=
function
(
e
)
{
this
.
getElementDocument
().
removeEventListener
(
'mouseup'
,
this
.
onDocumentMouseKeyUpHandler
,
true
);
this
.
getElementDocument
().
removeEventListener
(
'keyup'
,
this
.
onDocumentMouseKeyUpHandler
,
true
);
// onMouseKeyUp may be called a second time, depending on where the mouse is when the button is
// released, but since `this.pressed` will no longer be true, the second call will be ignored.
this
.
onMouseKeyUp
(
e
);
};
/**
* Handle mouse up and key up events.
*
* @protected
* @param {MouseEvent|KeyboardEvent} e Mouse up or key up event
*/
OO
.
ui
.
ToolGroup
.
prototype
.
onMouseKeyUp
=
function
(
e
)
{
const
tool
=
this
.
findTargetTool
(
e
);
if
(
!
this
.
isDisabled
()
&&
this
.
pressed
&&
this
.
pressed
===
tool
&&
(
e
.
which
===
OO
.
ui
.
MouseButtons
.
LEFT
||
e
.
which
===
OO
.
ui
.
Keys
.
SPACE
||
e
.
which
===
OO
.
ui
.
Keys
.
ENTER
)
)
{
this
.
pressed
.
onSelect
();
this
.
pressed
=
null
;
e
.
preventDefault
();
e
.
stopPropagation
();
}
this
.
pressed
=
null
;
};
/**
* Handle mouse over and focus events.
*
* @protected
* @param {jQuery.Event} e Mouse over or focus event
*/
OO
.
ui
.
ToolGroup
.
prototype
.
onMouseOverFocus
=
function
(
e
)
{
const
tool
=
this
.
findTargetTool
(
e
);
if
(
this
.
pressed
&&
this
.
pressed
===
tool
)
{
this
.
pressed
.
setActive
(
true
);
}
};
/**
* Handle mouse out and blur events.
*
* @protected
* @param {jQuery.Event} e Mouse out or blur event
*/
OO
.
ui
.
ToolGroup
.
prototype
.
onMouseOutBlur
=
function
(
e
)
{
const
tool
=
this
.
findTargetTool
(
e
);
if
(
this
.
pressed
&&
this
.
pressed
===
tool
)
{
this
.
pressed
.
setActive
(
false
);
}
};
/**
* Get the closest tool to a jQuery.Event.
*
* Only tool links are considered, which prevents other elements in the tool such as popups from
* triggering tool group interactions.
*
* @private
* @param {jQuery.Event} e
* @return {OO.ui.Tool|null} Tool, `null` if none was found
*/
OO
.
ui
.
ToolGroup
.
prototype
.
findTargetTool
=
function
(
e
)
{
const
$item
=
$
(
e
.
target
).
closest
(
'.oo-ui-tool-link'
);
let
tool
;
if
(
$item
.
length
)
{
tool
=
$item
.
parent
().
data
(
'oo-ui-tool'
);
}
return
tool
&&
!
tool
.
isDisabled
()
?
tool
:
null
;
};
/**
* Handle tool registry register events.
*
* If a tool is registered after the group is created, we must repopulate the list to account for:
*
* - a tool being added that may be included
* - a tool already included being overridden
*
* @protected
* @param {string} name Symbolic name of tool
*/
OO
.
ui
.
ToolGroup
.
prototype
.
onToolFactoryRegister
=
function
()
{
this
.
populate
();
};
/**
* Get the toolbar that contains the toolgroup.
*
* @return {OO.ui.Toolbar} Toolbar that contains the toolgroup
*/
OO
.
ui
.
ToolGroup
.
prototype
.
getToolbar
=
function
()
{
return
this
.
toolbar
;
};
/**
* Add and remove tools based on configuration.
*/
OO
.
ui
.
ToolGroup
.
prototype
.
populate
=
function
()
{
const
toolFactory
=
this
.
toolbar
.
getToolFactory
(),
names
=
{},
add
=
[],
remove
=
[],
list
=
this
.
toolbar
.
getToolFactory
().
getTools
(
this
.
include
,
this
.
exclude
,
this
.
promote
,
this
.
demote
);
let
name
;
// Build a list of needed tools
for
(
let
i
=
0
,
len
=
list
.
length
;
i
<
len
;
i
++
)
{
name
=
list
[
i
];
if
(
// Tool exists
toolFactory
.
lookup
(
name
)
&&
// Tool is available or is already in this group
(
this
.
toolbar
.
isToolAvailable
(
name
)
||
this
.
tools
[
name
]
)
)
{
// Hack to prevent infinite recursion via ToolGroupTool. We need to reserve the tool
// before creating it, but we can't call reserveTool() yet because we haven't created
// the tool.
this
.
toolbar
.
tools
[
name
]
=
true
;
let
tool
=
this
.
tools
[
name
];
if
(
!
tool
)
{
// Auto-initialize tools on first use
this
.
tools
[
name
]
=
tool
=
toolFactory
.
create
(
name
,
this
);
tool
.
updateTitle
();
}
this
.
toolbar
.
reserveTool
(
tool
);
add
.
push
(
tool
);
names
[
name
]
=
true
;
}
}
// Remove tools that are no longer needed
for
(
name
in
this
.
tools
)
{
if
(
!
names
[
name
]
)
{
this
.
tools
[
name
].
destroy
();
this
.
toolbar
.
releaseTool
(
this
.
tools
[
name
]
);
remove
.
push
(
this
.
tools
[
name
]
);
delete
this
.
tools
[
name
];
}
}
if
(
remove
.
length
)
{
this
.
removeItems
(
remove
);
}
// Update emptiness state
this
.
$element
.
toggleClass
(
'oo-ui-toolGroup-empty'
,
!
add
.
length
);
// Re-add tools (moving existing ones to new locations)
this
.
addItems
(
add
);
// Disabled state may depend on items
this
.
updateDisabled
();
};
/**
* Destroy toolgroup.
*/
OO
.
ui
.
ToolGroup
.
prototype
.
destroy
=
function
()
{
this
.
clearItems
();
this
.
toolbar
.
getToolFactory
().
disconnect
(
this
);
for
(
const
name
in
this
.
tools
)
{
this
.
toolbar
.
releaseTool
(
this
.
tools
[
name
]
);
this
.
tools
[
name
].
disconnect
(
this
).
destroy
();
delete
this
.
tools
[
name
];
}
this
.
$element
.
remove
();
};
/**
* A ToolFactory creates tools on demand. All tools ({@link OO.ui.Tool Tools},
* {@link OO.ui.PopupTool PopupTools}, and {@link OO.ui.ToolGroupTool ToolGroupTools}) must be
* registered with a tool factory. Tools are registered by their symbolic name. See
* {@link OO.ui.Toolbar toolbars} for an example.
*
* 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.Factory
* @constructor
*/
OO
.
ui
.
ToolFactory
=
function
OoUiToolFactory
()
{
// Parent constructor
OO
.
ui
.
ToolFactory
.
super
.
call
(
this
);
};
/* Setup */
OO
.
inheritClass
(
OO
.
ui
.
ToolFactory
,
OO
.
Factory
);
/* Methods */
/**
* Get tools from the factory.
*
* @param {Array|string} include Included tools, see #extract for format
* @param {Array|string} exclude Excluded tools, see #extract for format
* @param {Array|string} promote Promoted tools, see #extract for format
* @param {Array|string} demote Demoted tools, see #extract for format
* @return {string[]} List of tools
*/
OO
.
ui
.
ToolFactory
.
prototype
.
getTools
=
function
(
include
,
exclude
,
promote
,
demote
)
{
const
auto
=
[],
used
=
{};
// Collect included and not excluded tools
const
included
=
OO
.
simpleArrayDifference
(
this
.
extract
(
include
),
this
.
extract
(
exclude
)
);
// Promotion
const
promoted
=
this
.
extract
(
promote
,
used
);
const
demoted
=
this
.
extract
(
demote
,
used
);
// Auto
for
(
let
i
=
0
,
len
=
included
.
length
;
i
<
len
;
i
++
)
{
if
(
!
used
[
included
[
i
]
]
)
{
auto
.
push
(
included
[
i
]
);
}
}
return
promoted
.
concat
(
auto
).
concat
(
demoted
);
};
/**
* Get a flat list of names from a list of names or groups.
*
* Normally, `collection` is an array of tool specifications. Tools can be specified in the
* following ways:
*
* - To include an individual tool, use the symbolic name: `{ name: 'tool-name' }` or `'tool-name'`.
* - To include all tools in a group, use the group name: `{ group: 'group-name' }`. (To assign the
* tool to a group, use OO.ui.Tool.static.group.)
*
* Alternatively, to include all tools that are not yet assigned to any other toolgroup, use the
* catch-all selector `'*'`.
*
* If `used` is passed, tool names that appear as properties in this object will be considered
* already assigned, and will not be returned even if specified otherwise. The tool names extracted
* by this function call will be added as new properties in the object.
*
* @private
* @param {Array|string} collection List of tools, see above
* @param {Object.<string,boolean>} [used] Object containing information about used tools, see above
* @return {string[]} List of extracted tool names
*/
OO
.
ui
.
ToolFactory
.
prototype
.
extract
=
function
(
collection
,
used
)
{
const
names
=
[];
collection
=
!
Array
.
isArray
(
collection
)
?
[
collection
]
:
collection
;
for
(
let
i
=
0
,
len
=
collection
.
length
;
i
<
len
;
i
++
)
{
let
item
=
collection
[
i
],
name
,
tool
;
if
(
item
===
'*'
)
{
for
(
name
in
this
.
registry
)
{
tool
=
this
.
registry
[
name
];
if
(
// Only add tools by group name when auto-add is enabled
tool
.
static
.
autoAddToCatchall
&&
// Exclude already used tools
(
!
used
||
!
used
[
name
]
)
)
{
names
.
push
(
name
);
if
(
used
)
{
used
[
name
]
=
true
;
}
}
}
}
else
{
// Allow plain strings as shorthand for named tools
if
(
typeof
item
===
'string'
)
{
item
=
{
name
:
item
};
}
if
(
OO
.
isPlainObject
(
item
)
)
{
if
(
item
.
group
)
{
for
(
name
in
this
.
registry
)
{
tool
=
this
.
registry
[
name
];
if
(
// Include tools with matching group
tool
.
static
.
group
===
item
.
group
&&
// Only add tools by group name when auto-add is enabled
tool
.
static
.
autoAddToGroup
&&
// Exclude already used tools
(
!
used
||
!
used
[
name
]
)
)
{
names
.
push
(
name
);
if
(
used
)
{
used
[
name
]
=
true
;
}
}
}
// Include tools with matching name and exclude already used tools
}
else
if
(
item
.
name
&&
(
!
used
||
!
used
[
item
.
name
]
)
)
{
names
.
push
(
item
.
name
);
if
(
used
)
{
used
[
item
.
name
]
=
true
;
}
}
}
}
}
return
names
;
};
/**
* ToolGroupFactories create {@link OO.ui.ToolGroup toolgroups} on demand. The toolgroup classes
* must specify a symbolic name and be registered with the factory. The following classes are
* registered by default:
*
* - {@link OO.ui.BarToolGroup BarToolGroups} (‘bar’)
* - {@link OO.ui.MenuToolGroup MenuToolGroups} (‘menu’)
* - {@link OO.ui.ListToolGroup ListToolGroups} (‘list’)
*
* See {@link OO.ui.Toolbar toolbars} for an example.
*
* 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.Factory
* @constructor
*/
OO
.
ui
.
ToolGroupFactory
=
function
OoUiToolGroupFactory
()
{
// Parent constructor
OO
.
Factory
.
call
(
this
);
const
defaultClasses
=
this
.
constructor
.
static
.
getDefaultClasses
();
// Register default toolgroups
for
(
let
i
=
0
,
l
=
defaultClasses
.
length
;
i
<
l
;
i
++
)
{
this
.
register
(
defaultClasses
[
i
]
);
}
};
/* Setup */
OO
.
inheritClass
(
OO
.
ui
.
ToolGroupFactory
,
OO
.
Factory
);
/* Static Methods */
/**
* Get a default set of classes to be registered on construction.
*
* @return {Function[]} Default classes
*/
OO
.
ui
.
ToolGroupFactory
.
static
.
getDefaultClasses
=
function
()
{
return
[
OO
.
ui
.
BarToolGroup
,
OO
.
ui
.
ListToolGroup
,
OO
.
ui
.
MenuToolGroup
];
};
/**
* Popup tools open a popup window when they are selected from the {@link OO.ui.Toolbar toolbar}.
* Each popup tool is configured with a static name, title, and icon, as well with as any popup
* configurations. Unlike other tools, popup tools do not require that developers specify an
* #onSelect or #onUpdateState method, as these methods have been implemented already.
*
* // Example of a popup tool. When selected, a popup tool displays
* // a popup window.
* function HelpTool( toolGroup, config ) {
* OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
* padded: true,
* label: 'Help',
* head: true
* } }, config ) );
* this.popup.$body.append( '<p>I am helpful!</p>' );
* };
* 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
* @mixes 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
.
super
.
call
(
this
,
toolGroup
,
config
);
// Mixin constructors
OO
.
ui
.
mixin
.
PopupElement
.
call
(
this
,
config
);
// Events
this
.
popup
.
connect
(
this
,
{
toggle
:
'onPopupToggle'
}
);
// Initialization
this
.
popup
.
setAutoFlip
(
false
);
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.super.apply( this, arguments );
* };
* OO.inheritClass( SettingsTool, OO.ui.ToolGroupTool );
* SettingsTool.static.name = 'settings';
* SettingsTool.static.title = 'Change settings';
* SettingsTool.static.groupConfig = {
* icon: 'settings',
* 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
.
super
.
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.<string,Array>}
*/
OO
.
ui
.
ToolGroupTool
.
static
.
groupConfig
=
{};
/* Methods */
/**
* Handle the tool being selected.
*
* @inheritdoc
*/
OO
.
ui
.
ToolGroupTool
.
prototype
.
onSelect
=
function
()
{
this
.
innerToolGroup
.
setActive
(
!
this
.
innerToolGroup
.
active
);
return
false
;
};
/**
* Synchronize disabledness state of the tool with the inner toolgroup.
*
* @private
* @param {boolean} disabled Element is disabled
*/
OO
.
ui
.
ToolGroupTool
.
prototype
.
onToolGroupDisable
=
function
(
disabled
)
{
this
.
setDisabled
(
disabled
);
};
/**
* Handle the toolbar state being updated.
*
* @inheritdoc
*/
OO
.
ui
.
ToolGroupTool
.
prototype
.
onUpdateState
=
function
()
{
this
.
setActive
(
false
);
};
/**
* Build a {@link OO.ui.ToolGroup toolgroup} from the specified configuration.
*
* @param {Object.<string,Array>} group Toolgroup configuration. Please see
* {@link OO.ui.ToolGroup toolgroup} for more information.
* @return {OO.ui.ListToolGroup}
*/
OO
.
ui
.
ToolGroupTool
.
prototype
.
createGroup
=
function
(
group
)
{
if
(
group
.
include
===
'*'
)
{
// Apply defaults to catch-all groups
if
(
group
.
label
===
undefined
)
{
group
.
label
=
OO
.
ui
.
msg
(
'ooui-toolbar-more'
);
}
}
return
this
.
toolbar
.
getToolGroupFactory
().
create
(
'list'
,
this
.
toolbar
,
group
);
};
/**
* BarToolGroups 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.ListToolGroup ListToolGroup}).
* The {@link OO.ui.Tool tools} in a BarToolGroup are displayed by icon in a single row. The
* title of the tool is displayed when users move the mouse over the tool.
*
* BarToolGroups are created by a {@link OO.ui.ToolGroupFactory tool group factory} when the toolbar
* is set up.
*
* 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
*
* @example
* // Example of a BarToolGroup with two tools
* const toolFactory = new OO.ui.ToolFactory();
* const toolGroupFactory = new OO.ui.ToolGroupFactory();
* const toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
*
* // We will be placing status text in this element when tools are used
* const $area = $( '<p>' ).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.super.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( '<p>I am helpful!</p>' );
* }
* 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
* const frame = new OO.ui.PanelLayout( {
* expanded: false,
* framed: true
* } );
* const contentFrame = new OO.ui.PanelLayout( {
* expanded: false,
* padded: true
* } );
* frame.$element.append(
* toolbar.$element,
* contentFrame.$element.append( $area )
* );
* $( document.body ).append( frame.$element );
*
* // Here is where the toolbar is actually built. This must be done after inserting it into the
* // document.
* toolbar.initialize();
*
* @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
.
super
.
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
* @mixes OO.ui.mixin.IconElement
* @mixes OO.ui.mixin.IndicatorElement
* @mixes OO.ui.mixin.LabelElement
* @mixes OO.ui.mixin.TitledElement
* @mixes OO.ui.mixin.FlaggedElement
* @mixes OO.ui.mixin.ClippableElement
* @mixes OO.ui.mixin.FloatableElement
* @mixes OO.ui.mixin.TabIndexedElement
*
* @constructor
* @param {OO.ui.Toolbar} toolbar
* @param {Object} [config] Configuration options
* @param {string} [config.header] Text to display at the top of the popup
* @param {Object} [config.narrowConfig] See static.narrowConfig
*/
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
=
Object
.
assign
(
{
indicator
:
config
.
indicator
===
undefined
?
(
toolbar
.
position
===
'bottom'
?
'up'
:
'down'
)
:
config
.
indicator
},
config
);
// Parent constructor
OO
.
ui
.
PopupToolGroup
.
super
.
call
(
this
,
toolbar
,
config
);
// Properties
this
.
active
=
false
;
this
.
dragging
=
false
;
// Don't conflict with parent method of the same name
this
.
onPopupDocumentMouseKeyUpHandler
=
this
.
onPopupDocumentMouseKeyUp
.
bind
(
this
);
this
.
$handle
=
$
(
'<span>'
);
this
.
narrowConfig
=
config
.
narrowConfig
||
this
.
constructor
.
static
.
narrowConfig
;
// 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
,
Object
.
assign
(
{
$clippable
:
this
.
$group
},
config
)
);
OO
.
ui
.
mixin
.
FloatableElement
.
call
(
this
,
Object
.
assign
(
{
$floatable
:
this
.
$group
,
$floatableContainer
:
this
.
$handle
,
hideWhenOutOfView
:
false
,
verticalPosition
:
this
.
toolbar
.
position
===
'bottom'
?
'above'
:
'below'
// horizontalPosition is set in setActive
},
config
)
);
OO
.
ui
.
mixin
.
TabIndexedElement
.
call
(
this
,
Object
.
assign
(
{
$tabIndexed
:
this
.
$handle
},
config
)
);
// Events
this
.
$handle
.
on
(
{
keydown
:
this
.
onHandleMouseKeyDown
.
bind
(
this
),
keyup
:
this
.
onHandleMouseKeyUp
.
bind
(
this
),
mousedown
:
this
.
onHandleMouseKeyDown
.
bind
(
this
),
mouseup
:
this
.
onHandleMouseKeyUp
.
bind
(
this
)
}
);
this
.
toolbar
.
connect
(
this
,
{
resize
:
'onToolbarResize'
}
);
// Initialization
this
.
$handle
.
addClass
(
'oo-ui-popupToolGroup-handle'
)
.
attr
(
{
role
:
'button'
,
'aria-expanded'
:
'false'
}
)
.
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
(
$
(
'<span>'
)
.
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
);
/* Static properties */
/**
* Config options to change when toolbar is in narrow mode
*
* Supports `invisibleLabel`, label` and `icon` properties.
*
* @static
* @property {Object|null}
*/
OO
.
ui
.
PopupToolGroup
.
static
.
narrowConfig
=
null
;
/* 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 resize events from the toolbar
*/
OO
.
ui
.
PopupToolGroup
.
prototype
.
onToolbarResize
=
function
()
{
if
(
!
this
.
narrowConfig
)
{
return
;
}
if
(
this
.
toolbar
.
isNarrow
()
)
{
if
(
this
.
narrowConfig
.
invisibleLabel
!==
undefined
)
{
this
.
wideInvisibleLabel
=
this
.
invisibleLabel
;
this
.
setInvisibleLabel
(
this
.
narrowConfig
.
invisibleLabel
);
}
if
(
this
.
narrowConfig
.
label
!==
undefined
)
{
this
.
wideLabel
=
this
.
label
;
this
.
setLabel
(
this
.
narrowConfig
.
label
);
}
if
(
this
.
narrowConfig
.
icon
!==
undefined
)
{
this
.
wideIcon
=
this
.
icon
;
this
.
setIcon
(
this
.
narrowConfig
.
icon
);
}
}
else
{
if
(
this
.
wideInvisibleLabel
!==
undefined
)
{
this
.
setInvisibleLabel
(
this
.
wideInvisibleLabel
);
}
if
(
this
.
wideLabel
!==
undefined
)
{
this
.
setLabel
(
this
.
wideLabel
);
}
if
(
this
.
wideIcon
!==
undefined
)
{
this
.
setIcon
(
this
.
wideIcon
);
}
}
};
/**
* Handle document mouse up and key up events.
*
* @protected
* @param {MouseEvent|KeyboardEvent} e Mouse up or key up event
*/
OO
.
ui
.
PopupToolGroup
.
prototype
.
onPopupDocumentMouseKeyUp
=
function
(
e
)
{
const
$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
.
super
.
prototype
.
onMouseKeyUp
.
call
(
this
,
e
);
};
/**
* @inheritdoc
*/
OO
.
ui
.
PopupToolGroup
.
prototype
.
onMouseKeyDown
=
function
(
e
)
{
// 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.
const
$focused
=
$
(
document
.
activeElement
);
const
$firstFocusable
=
OO
.
ui
.
findFocusable
(
this
.
$group
);
if
(
$focused
[
0
]
===
$firstFocusable
[
0
]
&&
e
.
shiftKey
)
{
this
.
$handle
.
trigger
(
'focus'
);
return
false
;
}
const
$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
.
trigger
(
'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
.
super
.
prototype
.
onMouseKeyDown
.
call
(
this
,
e
);
};
/**
* Handle mouse up and key up events.
*
* @protected
* @param {jQuery.Event} e Mouse up or key up event
* @return {undefined|boolean} False to prevent default if event is handled
*/
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
* @return {undefined|boolean} False to prevent default if event is handled
*/
OO
.
ui
.
PopupToolGroup
.
prototype
.
onHandleMouseKeyDown
=
function
(
e
)
{
let
$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
.
trigger
(
'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=false] The active state to set
* @fires OO.ui.PopupToolGroup#active
*/
OO
.
ui
.
PopupToolGroup
.
prototype
.
setActive
=
function
(
value
)
{
let
containerWidth
,
containerLeft
;
value
=
!!
value
;
if
(
this
.
active
!==
value
)
{
this
.
active
=
value
;
if
(
value
)
{
this
.
getElementDocument
().
addEventListener
(
'mouseup'
,
this
.
onPopupDocumentMouseKeyUpHandler
,
true
);
this
.
getElementDocument
().
addEventListener
(
'keyup'
,
this
.
onPopupDocumentMouseKeyUpHandler
,
true
);
this
.
$clippable
.
css
(
'left'
,
''
);
this
.
$element
.
addClass
(
'oo-ui-popupToolGroup-active'
);
this
.
$group
.
addClass
(
'oo-ui-popupToolGroup-active-tools'
);
this
.
$handle
.
attr
(
'aria-expanded'
,
true
);
this
.
togglePositioning
(
true
);
this
.
toggleClipping
(
true
);
// Tools on the left of the toolbar will try to align their
// popups with their left side if possible, and vice-versa.
const
preferredSide
=
this
.
align
===
'before'
?
'start'
:
'end'
;
const
otherSide
=
this
.
align
===
'before'
?
'end'
:
'start'
;
// Try anchoring the popup to the preferred side first
this
.
setHorizontalPosition
(
preferredSide
);
if
(
this
.
isClippedHorizontally
()
||
this
.
isFloatableOutOfView
()
)
{
// Anchoring to the preferred side caused the popup to clip, so anchor it
// to the other side instead.
this
.
setHorizontalPosition
(
otherSide
);
}
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
(
preferredSide
);
this
.
$clippable
.
css
(
{
'margin-left'
:
-
(
this
.
$element
.
offset
().
left
-
containerLeft
),
width
:
containerWidth
}
);
}
}
else
{
this
.
getElementDocument
().
removeEventListener
(
'mouseup'
,
this
.
onPopupDocumentMouseKeyUpHandler
,
true
);
this
.
getElementDocument
().
removeEventListener
(
'keyup'
,
this
.
onPopupDocumentMouseKeyUpHandler
,
true
);
this
.
$element
.
removeClass
(
'oo-ui-popupToolGroup-active'
);
this
.
$group
.
removeClass
(
'oo-ui-popupToolGroup-active-tools'
);
this
.
$handle
.
attr
(
'aria-expanded'
,
false
);
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}.
*
* For more information about toolbars in general, please see the
* [OOUI documentation on MediaWiki][1].
*
* [1]: https://www.mediawiki.org/wiki/OOUI/Toolbars
*
* @example
* // Example of a ListToolGroup
* const toolFactory = new OO.ui.ToolFactory();
* const toolGroupFactory = new OO.ui.ToolGroupFactory();
* const toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
*
* // Configure and register two tools
* function SettingsTool() {
* SettingsTool.super.apply( this, arguments );
* }
* OO.inheritClass( SettingsTool, OO.ui.Tool );
* SettingsTool.static.name = 'settings';
* SettingsTool.static.icon = 'settings';
* 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.super.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
* const frame = new OO.ui.PanelLayout( {
* expanded: false,
* framed: true
* } );
* frame.$element.append(
* toolbar.$element
* );
* $( document.body ).append( frame.$element );
* // Build the toolbar. This must be done after the toolbar has been appended to the document.
* toolbar.initialize();
*
* @class
* @extends OO.ui.PopupToolGroup
*
* @constructor
* @param {OO.ui.Toolbar} toolbar
* @param {Object} [config] Configuration options
* @param {Array} [config.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'.
* @param {Array} [config.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.
* @param {boolean} [config.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
;
this
.
collapsibleTools
=
[];
// Parent constructor
OO
.
ui
.
ListToolGroup
.
super
.
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
()
{
OO
.
ui
.
ListToolGroup
.
super
.
prototype
.
populate
.
call
(
this
);
let
allowCollapse
=
[];
// 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
(
let
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
()
{
if
(
this
.
expandCollapseTool
===
undefined
)
{
const
ExpandCollapseTool
=
function
()
{
ExpandCollapseTool
.
super
.
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
.
super
.
super
.
prototype
.
onMouseKeyUp
.
call
(
this
,
e
);
}
else
{
return
OO
.
ui
.
ListToolGroup
.
super
.
prototype
.
onMouseKeyUp
.
call
(
this
,
e
);
}
};
OO
.
ui
.
ListToolGroup
.
prototype
.
updateCollapsibleState
=
function
()
{
const
inverted
=
this
.
toolbar
.
position
===
'bottom'
,
icon
=
this
.
expanded
===
inverted
?
'expand'
:
'collapse'
;
this
.
getExpandCollapseTool
()
.
setIcon
(
icon
)
.
setTitle
(
OO
.
ui
.
msg
(
this
.
expanded
?
'ooui-toolgroup-collapse'
:
'ooui-toolgroup-expand'
)
);
for
(
let
i
=
0
;
i
<
this
.
collapsibleTools
.
length
;
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.
*
* 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
*
* @example
* // Example of a MenuToolGroup
* const toolFactory = new OO.ui.ToolFactory();
* const toolGroupFactory = new OO.ui.ToolGroupFactory();
* const toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
*
* // We will be placing status text in this element when tools are used
* const $area = $( '<p>' ).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.super.apply( this, arguments );
* this.reallyActive = false;
* }
* OO.inheritClass( SettingsTool, OO.ui.Tool );
* SettingsTool.static.name = 'settings';
* SettingsTool.static.icon = 'settings';
* 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.super.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
* const frame = new OO.ui.PanelLayout( {
* expanded: false,
* framed: true
* } );
* const contentFrame = new OO.ui.PanelLayout( {
* expanded: false,
* padded: true
* } );
* frame.$element.append(
* toolbar.$element,
* contentFrame.$element.append( $area )
* );
* $( document.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.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
.
super
.
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
()
{
const
labelTexts
=
[];
for
(
const
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
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Oct 4 2026, 20:06 (5 w, 5 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
21/cd/409aa940847c3b67486b1056f2fc
Default Alt Text
oojs-ui-toolbars.js (88 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment