UI Refactor

Changed:

VisualEditor.i18n.php
* Updated Link inspector i18n messages

ve.ui.MetaDialog.js -> ve.ui.PagedDialog
* Moved paging functionality into Paged dialog

ve.ui.EditorPanelLayout -> ve.ui.PagePanelLayout.js
* Renamed from EditorPanelLayout to work nicely with the concept of
  stacks and pages

ve.ui.GroupElement.js
* Added addItem method and change addItems to use it

ve.ui.Dialog.css
* Updated classname as per refactor of meta dialog

ve.ui.StackPanelLayout.js
* Set currentItem property on showItem
* In addItems method, show currentItem with class method
** rather display block on element

ve.ui.Layout.css
* Make editorPanel layout 100% in width.

ve.ui.Widget.css
* Added CategoryWidget and CategoryPopup styles
* Other adjustments

ve.ui.PopupWidget.js
* Added auto-close on loss of focus
* Made friendly with being initialized inside a frame

ve.ui.MWLinkTargetInputWidget.js
* Mixin ve.ui.PendingInputWidget and remove pending methods
* Prevent querying on spaces
* Reintroduce i18n messages for menu sections

ve.ui.MenuWidget.js
* Update cases of $input config property to input

New:

ve.ui.PagedDialog.js
* Refactored base-class for mwMeta dialog (and probably other dialogs
  too)
* Abstracts adding and accessing pages

ve.ui.PendingInputWidget.js
* Moved pushPending and popPending methods into pending class

Change-Id: I29bcd92b7b5641941a4e98e65b2a56424a5263ff
This commit is contained in:
Trevor Parscal 2013-04-24 12:40:56 -07:00 committed by Jforrester
parent e7ee9564e6
commit 6a068e7d83
16 changed files with 252 additions and 154 deletions

View file

@ -43,9 +43,9 @@ $messages['en'] = array(
'visualeditor-feedback-tool' => 'Leave feedback',
'visualeditor-window-title' => 'Inspect',
'visualeditor-linkinspector-title' => 'Hyperlink',
'visualeditor-linkinspector-suggest-existing-page' => 'Existing page',
'visualeditor-linkinspector-suggest-matching-page' => 'Matching page',
'visualeditor-linkinspector-suggest-new-page' => 'New page',
'visualeditor-linkinspector-suggest-external-link' => 'Web link',
'visualeditor-linkinspector-suggest-external-link' => 'External link',
'visualeditor-formatdropdown-title' => 'Change format',
'visualeditor-formatdropdown-format-paragraph' => 'Paragraph',
'visualeditor-formatdropdown-format-heading1' => 'Heading 1',
@ -154,7 +154,7 @@ See also:
'visualeditor-window-title' => 'Title of an unnamed inspector',
'visualeditor-linkinspector-title' => 'Title of the link inspector dialog.
{{Identical|Hyperlink}}',
'visualeditor-linkinspector-suggest-existing-page' => 'Label for suggested existing pages in the link inspector',
'visualeditor-linkinspector-suggest-matching-page' => 'Label for suggested matching local wiki pages in the link inspector',
'visualeditor-linkinspector-suggest-new-page' => 'Label for a new page in the link inspector',
'visualeditor-linkinspector-suggest-external-link' => 'Label for an external (Web) link in the link inspector',
'visualeditor-formatdropdown-title' => 'This is a tooltip for the drop-down box for choosing the formatting style of the selected text, such as "Heading 1", "Heading 2" or "Plain text". (This is not related to "file format" or "data format", such as "Wikitext", "HTML", "PDF" etc.)',

View file

@ -373,6 +373,7 @@ $wgResourceModules += array(
've/ui/widgets/ve.ui.MenuItemWidget.js',
've/ui/widgets/ve.ui.MenuSectionItemWidget.js',
've/ui/widgets/ve.ui.MenuWidget.js',
've/ui/widgets/ve.ui.PendingInputWidget.js',
've/ui/widgets/ve.ui.TextInputMenuWidget.js',
've/ui/widgets/ve.ui.LinkTargetInputWidget.js',
've/ui/widgets/ve.ui.MWLinkTargetInputWidget.js',
@ -380,11 +381,11 @@ $wgResourceModules += array(
've/ui/layouts/ve.ui.GridLayout.js',
've/ui/layouts/ve.ui.PanelLayout.js',
've/ui/layouts/panels/ve.ui.StackPanelLayout.js',
've/ui/layouts/panels/ve.ui.EditorPanelLayout.js',
've/ui/layouts/panels/ve.ui.PagePanelLayout.js',
've/ui/dialogs/ve.ui.ContentDialog.js',
've/ui/dialogs/ve.ui.MediaDialog.js',
've/ui/dialogs/ve.ui.MetaDialog.js',
've/ui/dialogs/ve.ui.PagedDialog.js',
've/ui/tools/ve.ui.ButtonTool.js',
've/ui/tools/ve.ui.AnnotationButtonTool.js',
@ -445,7 +446,8 @@ $wgResourceModules += array(
'visualeditor',
'visualeditor-inspector-title',
'visualeditor-linkinspector-title',
'visualeditor-linkinspector-suggest-existing-page',
'visualeditor-linkinspector-label-pagetitle',
'visualeditor-linkinspector-suggest-matching-page',
'visualeditor-linkinspector-suggest-new-page',
'visualeditor-linkinspector-suggest-external-link',
'visualeditor-formatdropdown-title',

View file

@ -254,16 +254,17 @@ $html = file_get_contents( $page );
<script src="../../modules/ve/ui/widgets/ve.ui.MenuItemWidget.js"></script>
<script src="../../modules/ve/ui/widgets/ve.ui.MenuSectionItemWidget.js"></script>
<script src="../../modules/ve/ui/widgets/ve.ui.MenuWidget.js"></script>
<script src="../../modules/ve/ui/widgets/ve.ui.PendingInputWidget.js"></script>
<script src="../../modules/ve/ui/widgets/ve.ui.TextInputMenuWidget.js"></script>
<script src="../../modules/ve/ui/widgets/ve.ui.LinkTargetInputWidget.js"></script>
<script src="../../modules/ve/ui/widgets/ve.ui.MWLinkTargetInputWidget.js"></script>
<script src="../../modules/ve/ui/layouts/ve.ui.GridLayout.js"></script>
<script src="../../modules/ve/ui/layouts/ve.ui.PanelLayout.js"></script>
<script src="../../modules/ve/ui/layouts/panels/ve.ui.StackPanelLayout.js"></script>
<script src="../../modules/ve/ui/layouts/panels/ve.ui.EditorPanelLayout.js"></script>
<script src="../../modules/ve/ui/layouts/panels/ve.ui.PagePanelLayout.js"></script>
<script src="../../modules/ve/ui/dialogs/ve.ui.ContentDialog.js"></script>
<script src="../../modules/ve/ui/dialogs/ve.ui.MediaDialog.js"></script>
<script src="../../modules/ve/ui/dialogs/ve.ui.MetaDialog.js"></script>
<script src="../../modules/ve/ui/dialogs/ve.ui.PagedDialog.js"></script>
<script src="../../modules/ve/ui/tools/ve.ui.ButtonTool.js"></script>
<script src="../../modules/ve/ui/tools/ve.ui.AnnotationButtonTool.js"></script>
<script src="../../modules/ve/ui/tools/ve.ui.DialogButtonTool.js"></script>

View file

@ -197,16 +197,17 @@
<script src="../../ve/ui/widgets/ve.ui.MenuItemWidget.js"></script>
<script src="../../ve/ui/widgets/ve.ui.MenuSectionItemWidget.js"></script>
<script src="../../ve/ui/widgets/ve.ui.MenuWidget.js"></script>
<script src="../../ve/ui/widgets/ve.ui.PendingInputWidget.js"></script>
<script src="../../ve/ui/widgets/ve.ui.TextInputMenuWidget.js"></script>
<script src="../../ve/ui/widgets/ve.ui.LinkTargetInputWidget.js"></script>
<script src="../../ve/ui/widgets/ve.ui.MWLinkTargetInputWidget.js"></script>
<script src="../../ve/ui/layouts/ve.ui.GridLayout.js"></script>
<script src="../../ve/ui/layouts/ve.ui.PanelLayout.js"></script>
<script src="../../ve/ui/layouts/panels/ve.ui.StackPanelLayout.js"></script>
<script src="../../ve/ui/layouts/panels/ve.ui.EditorPanelLayout.js"></script>
<script src="../../ve/ui/layouts/panels/ve.ui.PagePanelLayout.js"></script>
<script src="../../ve/ui/dialogs/ve.ui.ContentDialog.js"></script>
<script src="../../ve/ui/dialogs/ve.ui.MediaDialog.js"></script>
<script src="../../ve/ui/dialogs/ve.ui.MetaDialog.js"></script>
<script src="../../ve/ui/dialogs/ve.ui.PagedDialog.js"></script>
<script src="../../ve/ui/tools/ve.ui.ButtonTool.js"></script>
<script src="../../ve/ui/tools/ve.ui.AnnotationButtonTool.js"></script>
<script src="../../ve/ui/tools/ve.ui.DialogButtonTool.js"></script>

View file

@ -1,87 +0,0 @@
/*!
* VisualEditor user interface MetaDialog class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Document dialog.
*
* @class
* @abstract
* @extends ve.ui.Dialog
*
* @constructor
* @param {ve.Surface} surface
*/
ve.ui.MetaDialog = function VeUiMetaDialog( surface ) {
// Parent constructor
ve.ui.Dialog.call( this, surface );
};
/* Inheritance */
ve.inheritClass( ve.ui.MetaDialog, ve.ui.Dialog );
/* Static Properties */
ve.ui.MetaDialog.static.titleMessage = 'visualeditor-dialog-meta-title';
ve.ui.MetaDialog.static.icon = 'settings';
/* Methods */
/**
* Handle frame ready events.
*
* @method
*/
ve.ui.MetaDialog.prototype.initialize = function () {
// Call parent method
ve.ui.Dialog.prototype.initialize.call( this );
// Properties
this.outlinePanel = new ve.ui.PanelLayout( { '$$': this.$$, 'scroll': true } );
this.editorPanel = new ve.ui.StackPanelLayout( { '$$': this.$$ } );
this.editorPanels = {
'categories': new ve.ui.EditorPanelLayout( {
'$$': this.$$, 'icon': 'tag', 'label': 'Categories'
} ),
'languages': new ve.ui.EditorPanelLayout( {
'$$': this.$$, 'icon': 'language', 'label': 'Languages'
} )
};
this.layout = new ve.ui.GridLayout(
[this.outlinePanel, this.editorPanel],
{ '$$': this.$$, 'widths': [1, 2] }
);
this.editorPanel.addItems( ve.getObjectValues( this.editorPanels ) );
// HACK
this.outlineWidget = new ve.ui.OutlineWidget( { '$$': this.$$ } );
this.outlineWidget.addItems( [
new ve.ui.OutlineItemWidget(
'categories', { '$$': this.$$, 'icon': 'tag', 'label': 'Categories' }
),
new ve.ui.OutlineItemWidget(
'languages', { '$$': this.$$, 'icon': 'language', 'label': 'Languages' }
)
] );
this.outlineWidget
.on( 'select', ve.bind( function ( item ) {
this.editorPanel.showItem( this.editorPanels[item.getData()] );
}, this ) )
.selectItem( this.outlineWidget.getClosestSelectableItem( 0 ) );
// Initialization
this.outlinePanel.$.addClass( 've-ui-metaDialog-outlinePanel' );
this.editorPanel.$.addClass( 've-ui-metaDialog-editorPanel' );
this.$body.append( this.layout.$ );
this.outlinePanel.$.append( this.outlineWidget.$ );
this.layout.update();
};
/* Registration */
ve.ui.dialogFactory.register( 'meta', ve.ui.MetaDialog );

View file

@ -0,0 +1,100 @@
/*!
* VisualEditor user interface PagedDialog class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Paged dialog.
*
* A paged dialog has an outline in the left third, and a series of pages in the right two-thirds.
* Pages can be added using the #addPage method, and later accessed using `this.pages[name]` or
* through the #getPage method.
*
* @class
* @abstract
* @extends ve.ui.Dialog
*
* @constructor
* @param {ve.Surface} surface
*/
ve.ui.PagedDialog = function VeUiPagedDialog( surface ) {
// Parent constructor
ve.ui.Dialog.call( this, surface );
// Properties
this.pages = {};
};
/* Inheritance */
ve.inheritClass( ve.ui.PagedDialog, ve.ui.Dialog );
/* Methods */
/**
* Handle frame ready events.
*
* @method
*/
ve.ui.PagedDialog.prototype.initialize = function () {
// Call parent method
ve.ui.Dialog.prototype.initialize.call( this );
// Properties
this.outlinePanel = new ve.ui.PanelLayout( { '$$': this.$$, 'scroll': true } );
this.pagesPanel = new ve.ui.StackPanelLayout( { '$$': this.$$ } );
this.layout = new ve.ui.GridLayout(
[this.outlinePanel, this.pagesPanel], { '$$': this.$$, 'widths': [1, 2] }
);
this.outlineWidget = new ve.ui.OutlineWidget( { '$$': this.$$ } );
// Events
this.outlineWidget.on( 'select', ve.bind( function ( item ) {
if ( item ) {
this.pagesPanel.showItem( this.pages[item.getData()] );
}
}, this ) );
// Initialization
this.outlinePanel.$.append( this.outlineWidget.$ ).addClass( 've-ui-pagedDialog-outlinePanel' );
this.pagesPanel.$.addClass( 've-ui-pagedDialog-pagesPanel' );
this.$body.append( this.layout.$ );
};
/**
* Add a page to the dialog.
*
* @method
* @param {string} name Symbolic name of page
* @param {jQuery|string} [label] Page label
* @param {string} [icon] Symbolic name of icon
* @chainable
*/
ve.ui.PagedDialog.prototype.addPage = function ( name, label, icon ) {
var config = { '$$': this.$$, 'icon': icon, 'label': label || name };
// Create and add page panel and outline item
this.pages[name] = new ve.ui.PagePanelLayout( config );
this.pagesPanel.addItems( [this.pages[name]] );
this.outlineWidget.addItems( [ new ve.ui.OutlineItemWidget( name, config ) ] );
// Auto-select first item when nothing is selected yet
if ( !this.outlineWidget.getSelectedItem() ) {
this.outlineWidget.selectItem( this.outlineWidget.getClosestSelectableItem( 0 ) );
}
return this;
};
/**
* Get a page by name.
*
* @method
* @param {string} name Symbolic name of page
* @returns {ve.ui.PagePanelLayout|undefined} Page, if found
*/
ve.ui.PagedDialog.prototype.getPage = function ( name ) {
return this.pages[name];
};

View file

@ -41,20 +41,29 @@ ve.ui.GroupElement.prototype.getItems = function () {
* @chainable
*/
ve.ui.GroupElement.prototype.addItems = function ( items ) {
var i, len, item;
var i, len;
for ( i = 0, len = items.length; i < len; i++ ) {
item = items[i];
// Check if item exists then remove it first, effectively "moving" it
if ( this.items.indexOf( item ) !== -1 ) {
this.removeItems( [item] );
}
// Add the item
this.items.push( item );
this.$group.append( item.$ );
this.$items = this.$items.add( item.$ );
this.addItem( items[i] );
}
return this;
};
/**
* Add item.
*
* @method
* @param {ve.ui.Widget} item Item
* @chainable
*/
ve.ui.GroupElement.prototype.addItem = function ( item ) {
// Check if item exists then remove it first, effectively "moving" it
if ( this.items.indexOf( item ) !== -1 ) {
this.removeItems( [item] );
}
// Add the item
this.items.push( item );
this.$.append( item.$ );
this.$items = this.$items.add( item.$ );
return this;
};
@ -70,7 +79,6 @@ ve.ui.GroupElement.prototype.addItems = function ( items ) {
*/
ve.ui.GroupElement.prototype.removeItems = function ( items ) {
var i, len, item, index;
// Remove specific items
for ( i = 0, len = items.length; i < len; i++ ) {
item = items[i];

View file

@ -1,12 +1,12 @@
/*!
* VisualEditor UserInterface EditorPanelLayout class.
* VisualEditor UserInterface PagePanelLayout class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Editor panel layout.
* Page panel layout.
*
* @class
* @extends ve.ui.PanelLayout
@ -16,7 +16,7 @@
* @param {Object} [config] Config options
* @cfg {string} [icon=''] Symbolic icon name
*/
ve.ui.EditorPanelLayout = function VeUiEditorPanelLayout( config ) {
ve.ui.PagePanelLayout = function VeUiPagePanelLayout( config ) {
// Config initialization
config = ve.extendObject( config, { 'scroll': true } );
@ -26,6 +26,9 @@ ve.ui.EditorPanelLayout = function VeUiEditorPanelLayout( config ) {
// Mixin constructors
ve.ui.LabeledElement.call( this, this.$$( '<div>' ), config );
// Properties
this.icon = config.icon;
// Initialization
this.$label.addClass( 've-ui-icon-' + config.icon + '-big' );
this.$.append( this.$label ).addClass( 've-ui-editorPanelLayout' );
@ -33,6 +36,12 @@ ve.ui.EditorPanelLayout = function VeUiEditorPanelLayout( config ) {
/* Inheritance */
ve.inheritClass( ve.ui.EditorPanelLayout, ve.ui.PanelLayout );
ve.inheritClass( ve.ui.PagePanelLayout, ve.ui.PanelLayout );
ve.mixinClass( ve.ui.EditorPanelLayout, ve.ui.LabeledElement );
ve.mixinClass( ve.ui.PagePanelLayout, ve.ui.LabeledElement );
/* Methods */
ve.ui.PagePanelLayout.prototype.getIcon = function () {
return this.icon;
};

View file

@ -55,7 +55,7 @@ ve.ui.StackPanelLayout.prototype.addItems = function ( items ) {
for ( i = 0, len = items.length; i < len; i++ ) {
if ( !this.currentItem ) {
items[i].$.show();
this.showItem( items[i] );
} else {
items[i].$.hide();
}
@ -113,6 +113,7 @@ ve.ui.StackPanelLayout.prototype.clearItems = function () {
ve.ui.StackPanelLayout.prototype.showItem = function ( item ) {
this.$items.hide();
item.$.show();
this.currentItem = item;
return this;
};

View file

@ -117,6 +117,6 @@
margin: 0.25em 0.25em;
}
.ve-ui-metaDialog-outlinePanel {
.ve-ui-pagedDialog-outlinePanel {
border-right: solid 1px #ddd;
}

View file

@ -25,6 +25,7 @@
.ve-ui-editorPanelLayout {
padding: 1.5em;
width: 100%;
-webkit-box-sizing:border-box;
-moz-box-sizing:border-box;
box-sizing:border-box;

View file

@ -236,9 +236,8 @@
.ve-ui-menuWidget {
position: absolute;
background: #FFFFFF;
background: #fff;
margin-top: -1px;
font-size: 0.8em;
z-index: 101;
border: solid 1px #ccc;
border-radius: 0 0 0.25em 0.25em;
@ -268,9 +267,9 @@
cursor: default;
}
/* ve.ui.MWLinkTargetInputWidget */
/* ve.ui.PendingInputWidget */
.ve-ui-mwLinkTargetInputWidget-pending input {
.ve-ui-pendingInputWidget input {
background-image: url(images/pending.gif);
}
@ -279,6 +278,7 @@
.ve-ui-mwLinkTargetInputWidget-menu {
width: 20em;
margin-top: -7px;
font-size: 0.8em;
}
.ve-ui-mwLinkTargetInputWidget-menu .ve-ui-menuWidget-item {
@ -331,3 +331,9 @@
-o-transition-timing-function: ease-in-out;
transition-timing-function: ease-in-out;
}
.ve-ui-popupWidget-body:focus {
/* No outline. The body has a tabindex and is focused on show */
outline: none;
}

View file

@ -56,6 +56,8 @@ ve.ui.MWLinkTargetInputWidget = function VeUiMWLinkTargetInputWidget( config ) {
ve.inheritClass( ve.ui.MWLinkTargetInputWidget, ve.ui.LinkTargetInputWidget );
ve.mixinClass( ve.ui.MWLinkTargetInputWidget, ve.ui.PendingInputWidget );
/* Methods */
/**
@ -138,7 +140,7 @@ ve.ui.MWLinkTargetInputWidget.prototype.openMenu = function () {
this.populateMenu();
this.queryPageExistence();
this.queryMatchingPages();
if ( this.value.length && !this.menu.isVisible() ) {
if ( this.value.length && $.trim( this.value ) !== '' && !this.menu.isVisible() ) {
this.menu.show();
}
return this;
@ -169,7 +171,7 @@ ve.ui.MWLinkTargetInputWidget.prototype.populateMenu = function () {
// External link
if ( ve.init.platform.getExternalLinkUrlProtocolsRegExp().test( this.value ) ) {
items.push( new ve.ui.MenuSectionItemWidget(
'externalLink', { '$$': menu$$, 'label': 'External link' }
'externalLink', { '$$': menu$$, 'label': ve.msg( 'visualeditor-linkinspector-suggest-external-link' ) }
) );
items.push( new ve.ui.MenuItemWidget(
this.getExternalLinkAnnotationFromUrl( this.value ),
@ -180,7 +182,7 @@ ve.ui.MWLinkTargetInputWidget.prototype.populateMenu = function () {
// Internal link
if ( !pageExists && ( !matchingPages || matchingPages.indexOf( this.value ) === -1 ) ) {
items.push( new ve.ui.MenuSectionItemWidget(
'newPage', { '$$': menu$$, 'label': 'New page' }
'newPage', { '$$': menu$$, 'label': ve.msg( 'visualeditor-linkinspector-suggest-new-page' ) }
) );
items.push( new ve.ui.MenuItemWidget(
this.getInternalLinkAnnotationFromTitle( this.value ),
@ -191,7 +193,7 @@ ve.ui.MWLinkTargetInputWidget.prototype.populateMenu = function () {
// Matching pages
if ( matchingPages && matchingPages.length ) {
items.push( new ve.ui.MenuSectionItemWidget(
'matchingPages', { '$$': menu$$, 'label': 'Matching page' }
'matchingPages', { '$$': menu$$, 'label': ve.msg( 'visualeditor-linkinspector-suggest-matching-page' ) }
) );
for ( i = 0, len = matchingPages.length; i < len; i++ ) {
items.push( new ve.ui.MenuItemWidget(
@ -215,30 +217,6 @@ ve.ui.MWLinkTargetInputWidget.prototype.populateMenu = function () {
return this;
};
/**
* Signals that an response is pending.
*
* @method
* @chainable
*/
ve.ui.MWLinkTargetInputWidget.prototype.pushPending = function () {
this.pending++;
this.$.addClass( 've-ui-mwLinkTargetInputWidget-pending' );
return this;
};
/**
* Signals that an response is complete.
*
* @method
* @chainable
*/
ve.ui.MWLinkTargetInputWidget.prototype.popPending = function () {
this.pending--;
this.$.removeClass( 've-ui-mwLinkTargetInputWidget-pending' );
return this;
};
/**
* Gets an internal link annotation.
*

View file

@ -34,7 +34,7 @@ ve.ui.MenuWidget = function VeUiMenuWidget( config ) {
// Initialization
this.$.hide().addClass( 've-ui-menuWidget' );
if ( !config.$input ) {
if ( !config.input ) {
this.$.append( this.$input );
}
};

View file

@ -0,0 +1,38 @@
/*!
* VisualEditor UserInterface PendingInputWidget class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Creates an ve.ui.PendingInputWidget object.
*
* @class
* @abstract
*
* @constructor
*/
ve.ui.PendingInputWidget = function VeUiPendingInputWidget () {
this.pending = 0;
};
/* Methods */
/**
* Adds a pending marker
*/
ve.ui.PendingInputWidget.prototype.pushPending = function () {
this.pending++;
this.$.addClass( 've-ui-pendingInputWidget' );
return this;
};
/**
* Removes a pending marker
*/
ve.ui.PendingInputWidget.prototype.popPending = function () {
this.pending--;
this.$.removeClass( 've-ui-pendingInputWidget' );
return this;
};

View file

@ -23,8 +23,9 @@ ve.ui.PopupWidget = function VeUiPopupWidget( config ) {
// Properties
this.visible = false;
this.$callout = $( '<div>' );
this.$body = $( '<div>' );
this.$callout = this.$$( '<div>' );
// Tab index on body so that it may blur
this.$body = this.$$( '<div>' ).attr( 'tabindex', 1 );
this.transitionTimeout = null;
this.align = config.align || 'center';
@ -35,12 +36,21 @@ ve.ui.PopupWidget = function VeUiPopupWidget( config ) {
this.$callout.addClass( 've-ui-popupWidget-callout' ),
this.$body.addClass( 've-ui-popupWidget-body' )
);
// Auto hide popup
this.$body.on( 'blur', ve.bind( this.onPopupBlur, this ) );
};
/* Inheritance */
ve.inheritClass( ve.ui.PopupWidget, ve.ui.Widget );
/* Events */
/**
* @event hide
*/
/* Methods */
/**
@ -52,7 +62,8 @@ ve.inheritClass( ve.ui.PopupWidget, ve.ui.Widget );
ve.ui.PopupWidget.prototype.show = function () {
this.$.show();
this.visible = true;
// Focus body so that it may blur.
this.$body.focus();
return this;
};
@ -65,10 +76,39 @@ ve.ui.PopupWidget.prototype.show = function () {
ve.ui.PopupWidget.prototype.hide = function () {
this.$.hide();
this.visible = false;
this.emit( 'hide' );
return this;
};
ve.ui.PopupWidget.prototype.getFocusedChild = function () {
return this.$body.find( ':focus' );
};
ve.ui.PopupWidget.prototype.onPopupBlur = function () {
// Find out what is focused after blur
setTimeout( ve.bind( function () {
var $focused = this.getFocusedChild();
// Is there a focused child element?
if ( $focused.length > 0 ) {
// Bind a one off blur event to that focused child element
$focused.one( 'blur', ve.bind( function () {
setTimeout( ve.bind( function () {
if ( this.getFocusedChild().length === 0 ) {
// Be sure focus is not the popup itself.
if ( this.$.find( ':focus' ).is( this.$body ) ){
return;
}
// Not a child and not the popup itself, so hide.
this.hide();
}
}, this ), 0 );
}, this ) );
} else {
this.hide();
}
}, this ), 0 );
};
/**
* Updates the position and size.
*