Single-click insertion

Objectives:

* Reduce the number of clicks and mouse maneuvers required to insert
  media, references or template parameters
* Make use of highlighting with mouse movement or arrow key presses,
  similar to menus, to suggest action when clicked
* Improve the way media search results look and feel

Changes:

ve.ui.SelectWidget.js
* Add mouseleave handler to un-highlight when the mouse exits the widget
* Document highlight events (already being emitted)

ve.ui.SearchWidget.js
* Propagate both select and highlight events from results widget
* Make arrow keys change highlight instead of selection
* Get rid of enter event, make enter key select highlighted item instead
* Provide direct access to results widget through getResults method

ve.ui.MenuWidget.js
* Use the selected item as a starting point if nothing is currently
  highlighted when adjusting the highlight position

ve.ui.Dialog.js
* Add footless option to hide the foot element and make the body extend
  all the way down to the bottom
* Remove applyButton, which only some dialogs need, and should be creating
  themselves, along with other buttons as needed

ve.ui.Widget.css
* Change highlight and selected colors of option widgets to match other
  selection colors used elsewhere
* Leave selected and highlighted widget looking selected

ve.ui.Frame.css
* Add background color to combat any color that might have been applied to
  the frame body in the imported CSS from the parent frame

ve.ui.Dialog.css
* Add rules for footless mode

ve.ui.MWReferenceResultWidget.js,
ve.ui.MWParameterResultWidget.js,
ve.ui.MWMediaResultWidget.js
* Allow highlighting

ve.ui.MWParamterSearchWidget.js
* Switch from selecting the first item when filtering to highlighting

ve-mw/ve.ui.Widget.js
* Adjust media result widget styling to better match other elements

ve.ui.MWTransclusionDialog.js,
ve.ui.MWReferenceListDialog.js,
ve.ui.MWReferenceEditDialog.js,
ve.ui.MWMetaDialog.js
ve.ui.MWMediaEditDialog.js
* Add apply button, as per it being removed from parent class

ve.ui.MWTransclusionDialog.js,
ve.ui.MWReferenceInsertDialog.js,
ve.ui.MWMediaInsertDialog.js
* Insert parameter/reference/media on select, instead of clicking an
  insert button
* Use 'insert' instead of 'apply' as argument for close method

Bug: 50774
Bug: 51143
Change-Id: Ia18e79f1f8df2540f465468edb01f5ce989bf843
This commit is contained in:
Trevor Parscal 2013-07-15 14:07:53 -07:00 committed by Trevor Parscal
parent 3d214d97b3
commit 130e446e52
18 changed files with 198 additions and 90 deletions

View file

@ -6,7 +6,7 @@
*/
/**
* Document dialog.
* Dialog for editing MediaWiki media objects.
*
* @class
* @extends ve.ui.MWDialog
@ -54,10 +54,17 @@ ve.ui.MWMediaEditDialog.prototype.initialize = function () {
'label': ve.msg( 'visualeditor-dialog-media-content-section' ),
'icon': 'parameter'
} );
this.applyButton = new ve.ui.ButtonWidget( {
'$$': this.$$, 'label': ve.msg( 'visualeditor-dialog-action-apply' ), 'flags': ['primary']
} );
// Events
this.applyButton.connect( this, { 'click': [ 'close', 'apply' ] } );
// Initialization
this.$body.addClass( 've-ui-mwMediaEditDialog-body' );
this.$body.append( this.contentFieldset.$ );
this.$foot.append( this.applyButton.$ );
};
ve.ui.MWMediaEditDialog.prototype.onOpen = function () {

View file

@ -5,11 +5,10 @@
* @license The MIT License (MIT); see LICENSE.txt
*/
/*global mw */
/**
* Dialog for inserting MediaWiki media objects.
*
* @class
* @abstract
* @extends ve.ui.MWDialog
*
* @constructor
@ -17,6 +16,9 @@
* @param {Object} [config] Config options
*/
ve.ui.MWMediaInsertDialog = function VeUiMWMediaInsertDialog( surface, config ) {
// Configuration initialization
config = ve.extendObject( {}, config, { 'footless': true } );
// Parent constructor
ve.ui.MWDialog.call( this, surface, config );
@ -36,9 +38,11 @@ ve.ui.MWMediaInsertDialog.static.icon = 'picture';
/* Methods */
ve.ui.MWMediaInsertDialog.prototype.onSelect = function ( item ) {
ve.ui.MWMediaInsertDialog.prototype.onSearchSelect = function ( item ) {
this.item = item;
this.applyButton.setDisabled( item === null );
if ( item ) {
this.close( 'insert' );
}
};
ve.ui.MWMediaInsertDialog.prototype.onOpen = function () {
@ -47,6 +51,8 @@ ve.ui.MWMediaInsertDialog.prototype.onOpen = function () {
// Initialization
this.search.getQuery().$input.focus().select();
this.search.getResults().selectItem();
this.search.getResults().highlightItem();
};
ve.ui.MWMediaInsertDialog.prototype.onClose = function ( action ) {
@ -55,7 +61,7 @@ ve.ui.MWMediaInsertDialog.prototype.onClose = function ( action ) {
// Parent method
ve.ui.MWDialog.prototype.onClose.call( this );
if ( action === 'apply' ) {
if ( action === 'insert' ) {
info = this.item.imageinfo[0];
this.surface.getModel().getFragment().insertContent( [
{
@ -86,12 +92,9 @@ ve.ui.MWMediaInsertDialog.prototype.initialize = function () {
this.search = new ve.ui.MWMediaSearchWidget( { '$$': this.frame.$$ } );
// Events
this.search.connect( this, { 'select': 'onSelect' } );
this.search.connect( this, { 'select': 'onSearchSelect' } );
// Initialization
this.applyButton.setDisabled( true ).setLabel(
mw.msg( 'visualeditor-dialog-media-insert-button' )
);
this.search.$.addClass( 've-ui-mwMediaInsertDialog-select' );
this.$body.append( this.search.$ );
};

View file

@ -8,6 +8,8 @@
/*global mw*/
/**
* Dialog for editing MediaWiki page meta information.
*
* @class
* @extends ve.ui.MWDialog
* @mixins ve.ui.PagedDialog
@ -85,6 +87,9 @@ ve.ui.MWMetaDialog.prototype.initialize = function () {
'label': ve.msg( 'visualeditor-dialog-meta-languages-label' ),
'icon': 'language'
} );
this.applyButton = new ve.ui.ButtonWidget( {
'$$': this.$$, 'label': ve.msg( 'visualeditor-dialog-action-apply' ), 'flags': ['primary']
} );
// Events
this.categoryWidget.connect( this, {
@ -94,6 +99,7 @@ ve.ui.MWMetaDialog.prototype.initialize = function () {
this.defaultSortInput.connect( this, {
'change': 'onDefaultSortChange'
} );
this.applyButton.connect( this, { 'click': [ 'close', 'apply' ] } );
// Initialization
this.categoryWidget.addItems( this.getCategoryItems() );
@ -108,6 +114,7 @@ ve.ui.MWMetaDialog.prototype.initialize = function () {
this.pages.categories.$.append( this.categoriesFieldset.$, this.categoryOptionsFieldset.$ );
this.categoriesFieldset.$.append( this.categoryWidget.$ );
this.categoryOptionsFieldset.$.append( this.defaultSortLabel.$, this.defaultSortInput.$ );
this.$foot.append( this.applyButton.$ );
this.pages.languages.$.append( this.languagesFieldset.$ );

View file

@ -6,7 +6,7 @@
*/
/**
* Dialog for a MediaWiki reference.
* Dialog for editing MediaWiki references.
*
* @class
* @extends ve.ui.MWDialog
@ -68,6 +68,13 @@ ve.ui.MWReferenceEditDialog.prototype.initialize = function () {
'label': ve.msg( 'visualeditor-dialog-reference-options-group-label' )
} );
this.applyButton = new ve.ui.ButtonWidget( {
'$$': this.$$, 'label': ve.msg( 'visualeditor-dialog-action-apply' ), 'flags': ['primary']
} );
// Events
this.applyButton.connect( this, { 'click': [ 'close', 'apply' ] } );
// Initialization
this.optionsFieldset.$.append(
this.groupLabel.$,
@ -76,6 +83,7 @@ ve.ui.MWReferenceEditDialog.prototype.initialize = function () {
this.$body
.append( this.contentFieldset.$, this.optionsFieldset.$ )
.addClass( 've-ui-mwReferenceEditDialog-body' );
this.$foot.append( this.applyButton.$ );
};
ve.ui.MWReferenceEditDialog.prototype.onOpen = function () {

View file

@ -5,11 +5,10 @@
* @license The MIT License (MIT); see LICENSE.txt
*/
/*global mw */
/**
* Dialog for inserting MediaWiki references.
*
* @class
* @abstract
* @extends ve.ui.MWDialog
*
* @constructor
@ -17,6 +16,9 @@
* @param {Object} [config] Config options
*/
ve.ui.MWReferenceInsertDialog = function VeUiMWReferenceInsertDialog( surface, config ) {
// Configuration initialization
config = ve.extendObject( {}, config, { 'footless': true } );
// Parent constructor
ve.ui.MWDialog.call( this, surface, config );
@ -43,9 +45,11 @@ ve.ui.MWReferenceInsertDialog.static.icon = 'reference';
* @param {string|Object|null} result Command string, reference attributes object, or null if
* nothing is selected
*/
ve.ui.MWReferenceInsertDialog.prototype.onSelect = function ( result ) {
ve.ui.MWReferenceInsertDialog.prototype.onSearchSelect = function ( result ) {
this.result = result;
this.applyButton.setDisabled( result === null );
if ( result ) {
this.close( 'insert' );
}
};
ve.ui.MWReferenceInsertDialog.prototype.onOpen = function () {
@ -66,7 +70,7 @@ ve.ui.MWReferenceInsertDialog.prototype.onClose = function ( action ) {
// Parent method
ve.ui.MWDialog.prototype.onClose.call( this );
if ( action === 'apply' ) {
if ( action === 'insert' ) {
doc = surfaceModel.getDocument(),
internalList = doc.getInternalList();
if ( create ) {
@ -132,12 +136,9 @@ ve.ui.MWReferenceInsertDialog.prototype.initialize = function () {
this.search = new ve.ui.MWReferenceSearchWidget( this.surface, { '$$': this.frame.$$ } );
// Events
this.search.connect( this, { 'select': 'onSelect' } );
this.search.connect( this, { 'select': 'onSearchSelect' } );
// Initialization
this.applyButton.setDisabled( true ).setLabel(
mw.msg( 'visualeditor-dialog-reference-insert-button' )
);
this.search.$.addClass( 've-ui-mwReferenceInsertDialog-select' );
this.$body.append( this.search.$ );
};

View file

@ -6,7 +6,7 @@
*/
/**
* Dialog for a MediaWiki references list.
* Dialog for inserting and editing MediaWiki reference lists.
*
* @class
* @extends ve.ui.MWDialog
@ -50,11 +50,19 @@ ve.ui.MWReferenceListDialog.prototype.initialize = function () {
'label': ve.msg( 'visualeditor-dialog-reference-options-group-label' )
} );
this.applyButton = new ve.ui.ButtonWidget( {
'$$': this.$$, 'label': ve.msg( 'visualeditor-dialog-action-apply' ), 'flags': ['primary']
} );
// Events
this.applyButton.connect( this, { 'click': [ 'close', 'apply' ] } );
// Initialization
this.optionsFieldset.$.append( this.groupLabel.$, this.groupInput.$ );
this.$body
.append( this.optionsFieldset.$ )
.addClass( 've-ui-mwReferenceListDialog-body' );
this.$foot.append( this.applyButton.$ );
};
ve.ui.MWReferenceListDialog.prototype.onOpen = function () {

View file

@ -6,7 +6,7 @@
*/
/**
* Dialog for editing a MediaWiki transclusion.
* Dialog for inserting and editing MediaWiki transclusions.
*
* See https://raw.github.com/wikimedia/mediawiki-extensions-TemplateData/master/spec.templatedata.json
* for the latest version of the TemplateData specification.
@ -70,11 +70,20 @@ ve.ui.MWTransclusionDialog.prototype.initialize = function () {
// Setup for PagedDialog
this.initializePages();
// Properties
this.applyButton = new ve.ui.ButtonWidget( {
'$$': this.$$, 'label': ve.msg( 'visualeditor-dialog-action-apply' ), 'flags': ['primary']
} );
// Events
this.outlineControlsWidget.connect( this, {
'move': 'onOutlineControlsMove',
'add': 'onOutlineControlsAdd'
} );
this.applyButton.connect( this, { 'click': [ 'close', 'apply' ] } );
// Initialization
this.$foot.append( this.applyButton.$ );
};
ve.ui.MWTransclusionDialog.prototype.onOpen = function () {
@ -394,18 +403,15 @@ ve.ui.MWTransclusionDialog.prototype.getContentPage = function ( content ) {
* @param {ve.dm.MWTemplateModel} template Template model
*/
ve.ui.MWTransclusionDialog.prototype.getTemplatePage = function ( template ) {
var infoFieldset, addParameterFieldset, addParameterSearch, addParameterButton, optionsFieldset,
var infoFieldset, addParameterFieldset, addParameterSearch, optionsFieldset,
removeButton,
spec = template.getSpec(),
label = spec.getLabel(),
description = spec.getDescription();
function addParameter() {
var data, name, param,
item = addParameterSearch.results.getSelectedItem();
function addParameter( name ) {
var param;
data = item && item.getData();
name = data && data.name;
if ( name ) {
param = new ve.dm.MWTemplateParameterModel( template, name );
template.addParameter( param );
@ -431,20 +437,8 @@ ve.ui.MWTransclusionDialog.prototype.getTemplatePage = function ( template ) {
} );
addParameterFieldset.$.addClass( 've-ui-mwTransclusionDialog-addParameterFieldset' );
addParameterSearch = new ve.ui.MWParameterSearchWidget( template, { '$$': this.frame.$$ } );
addParameterButton = new ve.ui.ButtonWidget( {
'$$': this.frame.$$,
'label': ve.msg( 'visualeditor-dialog-transclusion-add-param' ),
'disabled': true
} );
addParameterButton.connect( this, { 'click': addParameter } );
addParameterSearch.connect( this, {
'enter': addParameter,
'select': function ( name ) {
var names = template.getParameterNames();
addParameterButton.setDisabled( !name || names.indexOf( name ) !== -1 );
}
} );
addParameterFieldset.$.append( addParameterSearch.$, addParameterButton.$ );
addParameterSearch.connect( this, { 'select': addParameter } );
addParameterFieldset.$.append( addParameterSearch.$ );
optionsFieldset = new ve.ui.FieldsetLayout( {
'$$': this.frame.$$,

View file

@ -221,10 +221,20 @@
bottom: 0;
left: 0;
right: 0;
box-shadow: inset 0 0 0 1px #ccc;
}
.ve-ui-optionWidget-highlighted .ve-ui-mwMediaResultWidget-overlay,
.ve-ui-optionWidget-selected .ve-ui-mwMediaResultWidget-overlay {
box-shadow: inset 0 0 0 1px rgba(0,0,0,0.5), inset 0 0 0 2px rgba(255,255,255,0.5);
box-shadow: inset 0 0 0 1px #a7dcff;
}
.ve-ui-mwMediaResultWidget.ve-ui-optionWidget-highlighted {
box-shadow: 0 0 0.3em #a7dcff, 0 0 0 white;
}
.ve-ui-mwMediaResultWidget.ve-ui-optionWidget-selected {
box-shadow: 0 0 0.3em #a7dcff, 0 0 0 white;
}
.ve-ui-mwMediaResultWidget-error .ve-ui-mwMediaResultWidget-thumbnail {
@ -249,6 +259,14 @@
text-overflow: ellipsis;
}
.ve-ui-mwMediaResultWidget.ve-ui-optionWidget-highlighted .ve-ui-labeledElement-label {
background-color: rgba(0,0,0,0.75);
}
.ve-ui-mwMediaResultWidget.ve-ui-optionWidget-selected .ve-ui-labeledElement-label {
background-color: #000;
}
/* ve.ui.MWReferenceSearchWidget */
.ve-ui-mwReferenceSearchWidget-citation {

View file

@ -43,10 +43,6 @@ ve.ui.MWMediaResultWidget = function VeUiMWMediaResultWidget( data, config ) {
ve.inheritClass( ve.ui.MWMediaResultWidget, ve.ui.OptionWidget );
/* Static Properties */
ve.ui.MWMediaResultWidget.static.highlightable = false;
/* Methods */
ve.ui.MWMediaResultWidget.prototype.onThumbnailLoad = function () {

View file

@ -36,10 +36,6 @@ ve.ui.MWParameterResultWidget = function VeUiMWParameterResultWidget( data, conf
ve.inheritClass( ve.ui.MWParameterResultWidget, ve.ui.OptionWidget );
/* Static Properties */
ve.ui.MWParameterResultWidget.static.highlightable = false;
/* Methods */
ve.ui.MWParameterResultWidget.prototype.buildLabel = function () {

View file

@ -153,6 +153,6 @@ ve.ui.MWParameterSearchWidget.prototype.addResults = function () {
this.results.addItems( items );
if ( query.length ) {
this.results.selectItem( this.results.getFirstSelectableItem() );
this.results.highlightItem( this.results.getFirstSelectableItem() );
}
};

View file

@ -40,7 +40,3 @@ ve.ui.MWReferenceResultWidget = function VeUiMWReferenceResultWidget( data, conf
/* Inheritance */
ve.inheritClass( ve.ui.MWReferenceResultWidget, ve.ui.OptionWidget );
/* Static Properties */
ve.ui.MWReferenceResultWidget.static.highlightable = false;

View file

@ -95,6 +95,14 @@
bottom: 4.8em;
}
.ve-ui-dialog-content-footless .ve-ui-window-body {
bottom: 0;
}
.ve-ui-dialog-content-footless .ve-ui-window-foot {
display: none;
}
.ve-ui-dialog-content .ve-ui-window-icon {
width: 2.4em;
height: 2.8em;

View file

@ -176,9 +176,12 @@
overflow: hidden;
}
.ve-ui-optionWidget-highlighted,
.ve-ui-optionWidget-highlighted {
background-color: #e1f3ff;
}
.ve-ui-optionWidget-selected {
background-color: #b3d6f6;
background-color: #a7dcff;
}
.ve-ui-optionWidget.ve-ui-widget-disabled {
@ -386,10 +389,6 @@
position: relative;
}
.ve-ui-menuItemWidget.ve-ui-optionWidget-selected:not(.ve-ui-optionWidget-highlighted) {
background-color: transparent;
}
.ve-ui-menuItemWidget .ve-ui-optionWidget-icon {
display: none;
}

View file

@ -15,19 +15,26 @@
* @constructor
* @param {ve.ui.Surface} surface
* @param {Object} [config] Config options
* @cfg {boolean} [footless] Hide foot
*/
ve.ui.Dialog = function VeUiDialog( surface, config ) {
// Configuration initialization
config = config || {};
// Parent constructor
ve.ui.Window.call( this, surface, config );
// Properties
this.visible = false;
this.footless = !!config.footless;
this.onWindowMouseWheelHandler = ve.bind( this.onWindowMouseWheel, this );
this.onDocumentKeyDownHandler = ve.bind( this.onDocumentKeyDown, this );
// Events
this.$.on( 'mousedown', false );
// Initialization
this.$.addClass( 've-ui-dialog' );
this.$.on( 'mousedown', false );
};
/* Inheritance */
@ -45,15 +52,6 @@ ve.ui.Dialog.prototype.onCloseButtonClick = function () {
this.close( 'cancel' );
};
/**
* Handle apply button click events.
*
* @method
*/
ve.ui.Dialog.prototype.onApplyButtonClick = function () {
this.close( 'apply' );
};
/**
* Handle window mouse wheel events.
*
@ -142,22 +140,19 @@ ve.ui.Dialog.prototype.initialize = function () {
ve.ui.Window.prototype.initialize.call( this );
// Properties
this.applyButton = new ve.ui.ButtonWidget( {
'$$': this.$$, 'label': ve.msg( 'visualeditor-dialog-action-apply' ), 'flags': ['primary']
} );
this.closeButton = new ve.ui.IconButtonWidget( {
'$$': this.$$, 'title': ve.msg( 'visualeditor-dialog-action-close' ), 'icon': 'close'
} );
// Events
this.closeButton.connect( this, { 'click': 'onCloseButtonClick' } );
this.applyButton.connect( this, { 'click': 'onApplyButtonClick' } );
this.frame.$document.on( 'keydown', ve.bind( this.onFrameDocumentKeyDown, this ) );
// Initialization
this.frame.$content.addClass( 've-ui-dialog-content' );
if ( this.footless ) {
this.frame.$content.addClass( 've-ui-dialog-content-footless' );
}
this.closeButton.$.addClass( 've-ui-window-closeButton' );
this.applyButton.$.addClass( 've-ui-window-applyButton' );
this.$head.append( this.closeButton.$ );
this.$foot.append( this.applyButton.$ );
};

View file

@ -51,6 +51,9 @@ ve.ui.MenuWidget.prototype.onKeyDown = function ( e ) {
highlightItem = this.getHighlightedItem();
if ( !this.disabled && this.visible ) {
if ( !highlightItem ) {
highlightItem = this.getSelectedItem();
}
switch ( e.keyCode ) {
case ve.Keys.ENTER:
this.selectItem( highlightItem );

View file

@ -35,9 +35,15 @@ ve.ui.SearchWidget = function VeUiSearchWidget( config ) {
this.$results = this.$$( '<div>' );
// Events
this.query.connect( this, { 'change': 'onQueryChange', 'enter': [ 'emit', 'enter' ] } );
this.results.connect( this, { 'select': 'onResultsSelect' } );
this.query.$.on( 'keydown', ve.bind( this.onQueryKeydown, this ) );
this.query.connect( this, {
'change': 'onQueryChange',
'enter': 'onQueryEnter'
} );
this.results.connect( this, {
'highlight': 'onResultsHighlight',
'select': 'onResultsSelect'
} );
this.query.$input.on( 'keydown', ve.bind( this.onQueryKeydown, this ) );
// Initialization
this.$query
@ -58,12 +64,13 @@ ve.inheritClass( ve.ui.SearchWidget, ve.ui.Widget );
/* Events */
/**
* @event select
* @param {Object|null} item Item data or null if no item is selected
* @event highlight
* @param {Object|null} item Item data or null if no item is highlighted
*/
/**
* @event enter
* @event select
* @param {Object|null} item Item data or null if no item is selected
*/
/* Methods */
@ -75,12 +82,15 @@ ve.inheritClass( ve.ui.SearchWidget, ve.ui.Widget );
* @param {jQuery.Event} e Key down event
*/
ve.ui.SearchWidget.prototype.onQueryKeydown = function ( e ) {
var selectedItem,
var highlightedItem,
dir = e.which === ve.Keys.DOWN ? 1 : ( e.which === ve.Keys.UP ? -1 : 0 );
if ( dir ) {
selectedItem = this.results.getSelectedItem();
this.results.selectItem( this.results.getRelativeSelectableItem( selectedItem, dir ) );
highlightedItem = this.results.getHighlightedItem();
if ( !highlightedItem ) {
highlightedItem = this.results.getSelectedItem();
}
this.results.highlightItem( this.results.getRelativeSelectableItem( highlightedItem, dir ) );
}
};
@ -97,6 +107,30 @@ ve.ui.SearchWidget.prototype.onQueryChange = function () {
this.results.clearItems();
};
/**
* Handle select widget enter key events.
*
* Selects highlighted item.
*
* @method
* @param {string} value New value
*/
ve.ui.SearchWidget.prototype.onQueryEnter = function () {
// Reset
this.results.selectItem( this.results.getHighlightedItem() );
};
/**
* Handle select widget highlight events.
*
* @method
* @param {ve.ui.OptionWidget} item Highlighted item
* @emits highlight
*/
ve.ui.SearchWidget.prototype.onResultsHighlight = function ( item ) {
this.emit( 'highlight', item ? item.getData() : null );
};
/**
* Handle select widget select events.
*
@ -117,3 +151,13 @@ ve.ui.SearchWidget.prototype.onResultsSelect = function ( item ) {
ve.ui.SearchWidget.prototype.getQuery = function () {
return this.query;
};
/**
* Get the results list.
*
* @method
* @returns {ve.ui.SelectWidget} Select list
*/
ve.ui.SearchWidget.prototype.getResults = function () {
return this.results;
};

View file

@ -36,7 +36,8 @@ ve.ui.SelectWidget = function VeUiSelectWidget( config ) {
'mousedown': ve.bind( this.onMouseDown, this ),
'mouseup': ve.bind( this.onMouseUp, this ),
'mousemove': ve.bind( this.onMouseMove, this ),
'mouseover': ve.bind( this.onMouseOver, this )
'mouseover': ve.bind( this.onMouseOver, this ),
'mouseleave': ve.bind( this.onMouseLeave, this )
} );
// Initialization
@ -51,6 +52,11 @@ ve.mixinClass( ve.ui.SelectWidget, ve.ui.GroupElement );
/* Events */
/**
* @event highlight
* @param {ve.ui.OptionWidget|null} item Highlighted item or null if no item is highlighted
*/
/**
* @event select
* @param {ve.ui.OptionWidget|null} item Selected item or null if no item is selected
@ -150,6 +156,20 @@ ve.ui.SelectWidget.prototype.onMouseOver = function ( e ) {
return false;
};
/**
* Handle mouse leave events.
*
* @method
* @private
* @param {jQuery.Event} e Mouse over event
*/
ve.ui.SelectWidget.prototype.onMouseLeave = function () {
if ( !this.disabled ) {
this.highlightItem();
}
return false;
};
/**
* Get the closest item to a jQuery.Event.
*
@ -225,6 +245,7 @@ ve.ui.SelectWidget.prototype.getItemFromData = function ( data ) {
* @method
* @param {ve.ui.OptionWidget} [item] Item to highlight, omit to deselect all
* @param {boolean} [silent=false] Update UI only, do not emit `highlight` event
* @emits highlight
* @chainable
*/
ve.ui.SelectWidget.prototype.highlightItem = function ( item, silent ) {
@ -252,6 +273,7 @@ ve.ui.SelectWidget.prototype.highlightItem = function ( item, silent ) {
* @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
* @emits select
* @chainable
*/
ve.ui.SelectWidget.prototype.selectItem = function ( item, silent ) {
@ -334,6 +356,7 @@ ve.ui.SelectWidget.prototype.getFirstSelectableItem = function () {
* @method
* @param {ve.ui.OptionWidget[]} items Items to add
* @param {number} [index] Index to insert items after
* @emits add
* @chainable
*/
ve.ui.SelectWidget.prototype.addItems = function ( items, index ) {
@ -365,6 +388,7 @@ ve.ui.SelectWidget.prototype.addItems = function ( items, index ) {
*
* @method
* @param {ve.ui.OptionWidget[]} items Items to remove
* @emits remove
* @chainable
*/
ve.ui.SelectWidget.prototype.removeItems = function ( items ) {
@ -394,6 +418,7 @@ ve.ui.SelectWidget.prototype.removeItems = function ( items ) {
* Items will be detached, not removed, so they can be used later.
*
* @method
* @emits remove
* @chainable
*/
ve.ui.SelectWidget.prototype.clearItems = function () {