mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-29 00:30:44 +00:00
61c708ef1c
* ve.ui.MenuWidget.js MenuWidget no longer creates an embeded input element by default. In the case of no configured input element, we bind the keydown handler to window with addEventListner while using the useCapture flag. This nicely prevents elements lower in the dom from triggering ( document node ) Supported in IE9 and above and all modern browsers. * ve.ui.ListAction.js Since MenuWidget is no longer stealing focus from the surface, we no longer need to restore focus after a list item conversion. This is the end goal, as browsers like Chrome like to scroll to the top of elements that gain focus. Bug: 50792 Change-Id: I5b6969bca1a58b040708f8ac9d3dc8b07ddf9e6b
225 lines
4.6 KiB
JavaScript
225 lines
4.6 KiB
JavaScript
/*!
|
|
* VisualEditor UserInterface MenuWidget class.
|
|
*
|
|
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
*/
|
|
|
|
/**
|
|
* Create an ve.ui.MenuWidget object.
|
|
*
|
|
* @class
|
|
* @extends ve.ui.SelectWidget
|
|
*
|
|
* @constructor
|
|
* @param {Object} [config] Config options
|
|
* @cfg {ve.ui.InputWidget} [input] Input to bind keyboard handlers to
|
|
*/
|
|
ve.ui.MenuWidget = function VeUiMenuWidget( config ) {
|
|
// Config intialization
|
|
config = config || {};
|
|
|
|
// Parent constructor
|
|
ve.ui.SelectWidget.call( this, config );
|
|
|
|
// Properties
|
|
this.newItems = [];
|
|
this.$input = config.input ? config.input.$input : null;
|
|
this.$previousFocus = null;
|
|
this.isolated = !config.input;
|
|
this.visible = false;
|
|
this.keydownHandler = ve.bind( this.onKeyDown, this );
|
|
|
|
// Initialization
|
|
this.$.hide().addClass( 've-ui-menuWidget' );
|
|
};
|
|
|
|
/* Inheritance */
|
|
|
|
ve.inheritClass( ve.ui.MenuWidget, ve.ui.SelectWidget );
|
|
|
|
/* Methods */
|
|
|
|
/**
|
|
* Handles key down events.
|
|
*
|
|
* @method
|
|
* @param {jQuery.Event} e Key down event
|
|
*/
|
|
ve.ui.MenuWidget.prototype.onKeyDown = function ( e ) {
|
|
var handled = false,
|
|
highlightItem = this.getHighlightedItem();
|
|
|
|
if ( !this.disabled && this.visible ) {
|
|
switch ( e.keyCode ) {
|
|
case ve.Keys.ENTER:
|
|
this.selectItem( highlightItem );
|
|
handled = true;
|
|
break;
|
|
case ve.Keys.UP:
|
|
this.highlightItem( this.getRelativeSelectableItem( highlightItem, -1 ) );
|
|
handled = true;
|
|
break;
|
|
case ve.Keys.DOWN:
|
|
this.highlightItem( this.getRelativeSelectableItem( highlightItem, 1 ) );
|
|
handled = true;
|
|
break;
|
|
case ve.Keys.ESCAPE:
|
|
if ( highlightItem ) {
|
|
highlightItem.setHighlighted( false );
|
|
}
|
|
this.hide();
|
|
handled = true;
|
|
break;
|
|
}
|
|
if ( handled ) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
return false;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Check if the menu is visible.
|
|
*
|
|
* @method
|
|
* @returns {boolean} Menu is visible
|
|
*/
|
|
ve.ui.MenuWidget.prototype.isVisible = function () {
|
|
return this.visible;
|
|
};
|
|
|
|
|
|
/**
|
|
* Bind keydown listener
|
|
*
|
|
* @method
|
|
*/
|
|
ve.ui.MenuWidget.prototype.bindKeydownListener = function () {
|
|
if ( this.$input ) {
|
|
this.$input.on( 'keydown', this.keydownHandler );
|
|
} else {
|
|
// Capture menu navigation keys
|
|
window.addEventListener( 'keydown', this.keydownHandler, true );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Unbind keydown listener
|
|
*
|
|
* @method
|
|
*/
|
|
ve.ui.MenuWidget.prototype.unbindKeydownListener = function () {
|
|
if ( this.$input ) {
|
|
this.$input.off( 'keydown' );
|
|
} else {
|
|
window.removeEventListener( 'keydown', this.keydownHandler, true );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Select an item.
|
|
*
|
|
* The menu will stay open if an item is silently selected.
|
|
*
|
|
* @method
|
|
* @param {ve.ui.OptionWidget} [item] Item to select, omit to deselect all
|
|
* @param {boolean} [silent=false] Update UI only, do not emit `select` event
|
|
* @chainable
|
|
*/
|
|
ve.ui.MenuWidget.prototype.selectItem = function ( item, silent ) {
|
|
if ( !this.disabled && !silent ) {
|
|
if ( item ) {
|
|
this.disabled = true;
|
|
item.flash( ve.bind( function () {
|
|
this.hide();
|
|
this.disabled = false;
|
|
}, this ) );
|
|
} else {
|
|
this.hide();
|
|
}
|
|
}
|
|
|
|
ve.ui.SelectWidget.prototype.selectItem.call( this, item, silent );
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Add items.
|
|
*
|
|
* Adding an existing item (by value) will move it.
|
|
*
|
|
* @method
|
|
* @param {ve.ui.MenuItemWidget[]} items Items to add
|
|
* @param {number} [index] Index to insert items after
|
|
* @chainable
|
|
*/
|
|
ve.ui.MenuWidget.prototype.addItems = function ( items, index ) {
|
|
var i, len, item;
|
|
|
|
ve.ui.SelectWidget.prototype.addItems.call( this, items, index );
|
|
|
|
for ( i = 0, len = items.length; i < len; i++ ) {
|
|
item = items[i];
|
|
if ( this.visible ) {
|
|
// Defer fitting label until
|
|
item.fitLabel();
|
|
} else {
|
|
this.newItems.push( item );
|
|
}
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Show the menu.
|
|
*
|
|
* @method
|
|
* @chainable
|
|
*/
|
|
ve.ui.MenuWidget.prototype.show = function () {
|
|
var i, len;
|
|
|
|
if ( this.items.length ) {
|
|
this.$.show();
|
|
this.visible = true;
|
|
this.bindKeydownListener();
|
|
|
|
// Change focus to enable keyboard navigation
|
|
if ( this.isolated && this.$input && !this.$input.is( ':focus' ) ) {
|
|
this.$previousFocus = this.$$( ':focus' );
|
|
this.$input.focus();
|
|
}
|
|
if ( this.newItems.length ) {
|
|
for ( i = 0, len = this.newItems.length; i < len; i++ ) {
|
|
this.newItems[i].fitLabel();
|
|
}
|
|
this.newItems = [];
|
|
}
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Hide the menu.
|
|
*
|
|
* @method
|
|
* @chainable
|
|
*/
|
|
ve.ui.MenuWidget.prototype.hide = function () {
|
|
this.$.hide();
|
|
this.visible = false;
|
|
this.unbindKeydownListener();
|
|
|
|
if ( this.isolated && this.$previousFocus ) {
|
|
this.$previousFocus.focus();
|
|
this.$previousFocus = null;
|
|
}
|
|
|
|
return this;
|
|
};
|