aboutsummaryrefslogtreecommitdiffstats
path: root/resources/lib/oojs-ui/oojs-ui-core.js
diff options
context:
space:
mode:
authorVolker E <volker.e@wikimedia.org>2017-03-28 16:25:58 -0700
committerCatrope <roan@wikimedia.org>2017-03-29 19:44:37 +0000
commit5df9a27b27c67e196f5eff48d86615735ec9378c (patch)
tree2e5145c4c05f85624716de858bd7ef81d394d9bd /resources/lib/oojs-ui/oojs-ui-core.js
parentc03f57bc119d404469dfd8046acad7626464d813 (diff)
downloadmediawikicore-5df9a27b27c67e196f5eff48d86615735ec9378c.tar.gz
mediawikicore-5df9a27b27c67e196f5eff48d86615735ec9378c.zip
Update OOjs UI to v0.20.1
Release notes: https://phabricator.wikimedia.org/diffusion/GOJU/browse/master/History.md;v0.20.1 Depends-On: I9b50eee459085eaa00819cbabe340ac442a332bd Change-Id: I9b50eee459085eaa00819cbabe340ac442a332db
Diffstat (limited to 'resources/lib/oojs-ui/oojs-ui-core.js')
-rw-r--r--resources/lib/oojs-ui/oojs-ui-core.js268
1 files changed, 121 insertions, 147 deletions
diff --git a/resources/lib/oojs-ui/oojs-ui-core.js b/resources/lib/oojs-ui/oojs-ui-core.js
index fe11d68f8d62..e6029ddd0894 100644
--- a/resources/lib/oojs-ui/oojs-ui-core.js
+++ b/resources/lib/oojs-ui/oojs-ui-core.js
@@ -1,12 +1,12 @@
/*!
- * OOjs UI v0.20.0
+ * OOjs UI v0.20.1
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2017 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2017-03-15T17:06:24Z
+ * Date: 2017-03-28T22:19:29Z
*/
( function ( OO ) {
@@ -90,7 +90,7 @@ OO.ui.isFocusableElement = function ( $element ) {
// Check if the element is visible
if ( !(
// This is quicker than calling $element.is( ':visible' )
- $.expr.filters.visible( element ) &&
+ $.expr.pseudos.visible( element ) &&
// Check that all parents are visible
!$element.parents().addBack().filter( function () {
return $.css( this, 'visibility' ) === 'hidden';
@@ -883,7 +883,7 @@ OO.ui.Element.static.getDocument = function ( obj ) {
// Window
obj.document ||
// HTMLDocument
- ( obj.nodeType === 9 && obj ) ||
+ ( obj.nodeType === Node.DOCUMENT_NODE && obj ) ||
null;
};
@@ -912,7 +912,7 @@ OO.ui.Element.static.getDir = function ( obj ) {
if ( obj instanceof jQuery ) {
obj = obj[ 0 ];
}
- isDoc = obj.nodeType === 9;
+ isDoc = obj.nodeType === Node.DOCUMENT_NODE;
isWin = obj.document !== undefined;
if ( isDoc || isWin ) {
if ( isWin ) {
@@ -1140,17 +1140,18 @@ OO.ui.Element.static.getScrollLeft = ( function () {
}() );
/**
- * Get scrollable object parent
+ * Get the root scrollable element of given element's document.
*
- * documentElement can't be used to get or set the scrollTop
- * property on Blink. Changing and testing its value lets us
- * use 'body' or 'documentElement' based on what is working.
+ * On Blink-based browsers (Chrome etc.), `document.documentElement` can't be used to get or set
+ * the scrollTop property; instead we have to use `document.body`. Changing and testing the value
+ * lets us use 'body' or 'documentElement' based on what is working.
*
* https://code.google.com/p/chromium/issues/detail?id=303131
*
* @static
- * @param {HTMLElement} el Element to find scrollable parent for
- * @return {HTMLElement} Scrollable parent
+ * @param {HTMLElement} el Element to find root scrollable parent for
+ * @return {HTMLElement} Scrollable parent, `document.body` or `document.documentElement`
+ * depending on browser
*/
OO.ui.Element.static.getRootScrollableElement = function ( el ) {
var scrollTop, body;
@@ -1174,8 +1175,8 @@ OO.ui.Element.static.getRootScrollableElement = function ( el ) {
/**
* Get closest scrollable container.
*
- * Traverses up until either a scrollable element or the root is reached, in which case the window
- * will be returned.
+ * Traverses up until either a scrollable element or the root is reached, in which case the root
+ * scrollable element will be returned (see #getRootScrollableElement).
*
* @static
* @param {HTMLElement} el Element to find scrollable container for
@@ -1193,6 +1194,12 @@ OO.ui.Element.static.getClosestScrollableContainer = function ( el, dimension )
props = [ 'overflow-' + dimension ];
}
+ // Special case for the document root (which doesn't really have any scrollable container, since
+ // it is the ultimate scrollable container, but this is probably saner than null or exception)
+ if ( $( el ).is( 'html, body' ) ) {
+ return this.getRootScrollableElement( el );
+ }
+
while ( $parent.length ) {
if ( $parent[ 0 ] === this.getRootScrollableElement( el ) ) {
return $parent[ 0 ];
@@ -1211,7 +1218,8 @@ OO.ui.Element.static.getClosestScrollableContainer = function ( el, dimension )
}
$parent = $parent.parent();
}
- return this.getDocument( el ).body;
+ // The element is unattached... return something mostly sane
+ return this.getRootScrollableElement( el );
};
/**
@@ -2181,6 +2189,7 @@ OO.ui.mixin.ButtonElement.prototype.isActive = function () {
* [1]: https://www.mediawiki.org/wiki/OOjs_UI/Elements/Groups
*
* @abstract
+ * @mixins OO.EmitterList
* @class
*
* @constructor
@@ -2192,15 +2201,20 @@ OO.ui.mixin.GroupElement = function OoUiMixinGroupElement( config ) {
// Configuration initialization
config = config || {};
+ // Mixin constructors
+ OO.EmitterList.call( this, config );
+
// Properties
this.$group = null;
- this.items = [];
- this.aggregateItemEvents = {};
// Initialization
this.setGroupElement( config.$group || $( '<div>' ) );
};
+/* Setup */
+
+OO.mixinClass( OO.ui.mixin.GroupElement, OO.EmitterList );
+
/* Events */
/**
@@ -2230,28 +2244,6 @@ OO.ui.mixin.GroupElement.prototype.setGroupElement = function ( $group ) {
};
/**
- * Check if a group contains no items.
- *
- * @return {boolean} Group is empty
- */
-OO.ui.mixin.GroupElement.prototype.isEmpty = function () {
- return !this.items.length;
-};
-
-/**
- * Get all items in the group.
- *
- * The method returns an array of item references (e.g., [button1, button2, button3]) and is useful
- * when synchronizing groups of items, or whenever the references are required (e.g., when removing items
- * from a group).
- *
- * @return {OO.ui.Element[]} An array of items.
- */
-OO.ui.mixin.GroupElement.prototype.getItems = function () {
- return this.items.slice( 0 );
-};
-
-/**
* Get an item by its data.
*
* Only the first item with matching data will be returned. To return all matching items,
@@ -2298,62 +2290,6 @@ OO.ui.mixin.GroupElement.prototype.getItemsFromData = function ( data ) {
};
/**
- * Aggregate the events emitted by the group.
- *
- * When events are aggregated, the group will listen to all contained items for the event,
- * and then emit the event under a new name. The new event will contain an additional leading
- * parameter containing the item that emitted the original event. Other arguments emitted from
- * the original event are passed through.
- *
- * @param {Object.<string,string|null>} events An object keyed by the name of the event that should be
- * aggregated (e.g., ‘click’) and the value of the new name to use (e.g., ‘groupClick’).
- * A `null` value will remove aggregated events.
-
- * @throws {Error} An error is thrown if aggregation already exists.
- */
-OO.ui.mixin.GroupElement.prototype.aggregate = function ( events ) {
- var i, len, item, add, remove, itemEvent, groupEvent;
-
- for ( itemEvent in events ) {
- groupEvent = events[ itemEvent ];
-
- // Remove existing aggregated event
- if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) {
- // Don't allow duplicate aggregations
- if ( groupEvent ) {
- throw new Error( 'Duplicate item event aggregation for ' + itemEvent );
- }
- // Remove event aggregation from existing items
- for ( i = 0, len = this.items.length; i < len; i++ ) {
- item = this.items[ i ];
- if ( item.connect && item.disconnect ) {
- remove = {};
- remove[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ];
- item.disconnect( this, remove );
- }
- }
- // Prevent future items from aggregating event
- delete this.aggregateItemEvents[ itemEvent ];
- }
-
- // Add new aggregate event
- if ( groupEvent ) {
- // Make future items aggregate event
- this.aggregateItemEvents[ itemEvent ] = groupEvent;
- // Add event aggregation to existing items
- for ( i = 0, len = this.items.length; i < len; i++ ) {
- item = this.items[ i ];
- if ( item.connect && item.disconnect ) {
- add = {};
- add[ itemEvent ] = [ 'emit', groupEvent, item ];
- item.connect( this, add );
- }
- }
- }
- }
-};
-
-/**
* Add items to the group.
*
* Items will be added to the end of the group array unless the optional `index` parameter specifies
@@ -2364,49 +2300,77 @@ OO.ui.mixin.GroupElement.prototype.aggregate = function ( events ) {
* @chainable
*/
OO.ui.mixin.GroupElement.prototype.addItems = function ( items, index ) {
- var i, len, item, itemEvent, events, currentIndex,
+ var i, len, item,
itemElements = [];
+ // Mixin method
+ OO.EmitterList.prototype.addItems.call( this, items, index );
+
for ( i = 0, len = items.length; i < len; i++ ) {
item = items[ i ];
- // Check if item exists then remove it first, effectively "moving" it
- currentIndex = this.items.indexOf( item );
- if ( currentIndex >= 0 ) {
- this.removeItems( [ item ] );
- // Adjust index to compensate for removal
- if ( currentIndex < index ) {
- index--;
- }
- }
// Add the item
- if ( item.connect && item.disconnect && !$.isEmptyObject( this.aggregateItemEvents ) ) {
- events = {};
- for ( itemEvent in this.aggregateItemEvents ) {
- events[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ];
- }
- item.connect( this, events );
- }
item.setElementGroup( this );
itemElements.push( item.$element.get( 0 ) );
}
- if ( index === undefined || index < 0 || index >= this.items.length ) {
- this.$group.append( itemElements );
- this.items.push.apply( this.items, items );
- } else if ( index === 0 ) {
- this.$group.prepend( itemElements );
- this.items.unshift.apply( this.items, items );
- } else {
- this.items[ index ].$element.before( itemElements );
- this.items.splice.apply( this.items, [ index, 0 ].concat( items ) );
- }
+ this.insertItemElements( items, index );
this.emit( 'change', this.getItems() );
return this;
};
/**
+ * @inheritdoc
+ */
+OO.ui.mixin.GroupElement.prototype.moveItem = function ( items, newIndex ) {
+ // Mixin method
+ newIndex = OO.EmitterList.prototype.moveItem.call( this, items, newIndex );
+
+ this.insertItemElements( items, newIndex );
+
+ return newIndex;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.mixin.GroupElement.prototype.insertItem = function ( item, index ) {
+ // Mixin method
+ index = OO.EmitterList.prototype.insertItem.call( this, item, index );
+
+ this.insertItemElements( item, index );
+
+ return index;
+};
+
+/**
+ * Insert element into the group
+ *
+ * @param {OO.ui.Element|OO.ui.Element[]} itemWidgets Items to insert
+ * @param {number} index Insertion index
+ */
+OO.ui.mixin.GroupElement.prototype.insertItemElements = function ( itemWidgets, index ) {
+ var i, len, item;
+
+ if ( !Array.isArray( itemWidgets ) ) {
+ itemWidgets = [ itemWidgets ];
+ }
+
+ for ( i = 0, len = itemWidgets.length; i < len; i++ ) {
+ item = itemWidgets[ i ];
+
+ if ( index === undefined || index < 0 || index >= this.items.length ) {
+ this.$group.append( item.$element.get( 0 ) );
+ } else if ( index === 0 ) {
+ this.$group.prepend( item.$element.get( 0 ) );
+ } else {
+ this.items[ index ].$element.before( item.$element.get( 0 ) );
+ }
+ }
+};
+
+/**
* Remove the specified items from a group.
*
* Removed items are detached (not removed) from the DOM so that they may be reused.
@@ -2416,26 +2380,21 @@ OO.ui.mixin.GroupElement.prototype.addItems = function ( items, index ) {
* @chainable
*/
OO.ui.mixin.GroupElement.prototype.removeItems = function ( items ) {
- var i, len, item, index, events, itemEvent;
+ var i, len, item, index;
- // Remove specific items
+ // Remove specific items elements
for ( i = 0, len = items.length; i < len; i++ ) {
item = items[ i ];
index = this.items.indexOf( item );
if ( index !== -1 ) {
- if ( item.connect && item.disconnect && !$.isEmptyObject( this.aggregateItemEvents ) ) {
- events = {};
- for ( itemEvent in this.aggregateItemEvents ) {
- events[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ];
- }
- item.disconnect( this, events );
- }
item.setElementGroup( null );
- this.items.splice( index, 1 );
item.$element.detach();
}
}
+ // Mixin method
+ OO.EmitterList.prototype.removeItems.call( this, items );
+
this.emit( 'change', this.getItems() );
return this;
};
@@ -2449,27 +2408,18 @@ OO.ui.mixin.GroupElement.prototype.removeItems = function ( items ) {
* @chainable
*/
OO.ui.mixin.GroupElement.prototype.clearItems = function () {
- var i, len, item, remove, itemEvent;
+ var i, len;
- // Remove all items
+ // Remove all item elements
for ( i = 0, len = this.items.length; i < len; i++ ) {
- item = this.items[ i ];
- if (
- item.connect && item.disconnect &&
- !$.isEmptyObject( this.aggregateItemEvents )
- ) {
- remove = {};
- if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) {
- remove[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ];
- }
- item.disconnect( this, remove );
- }
- item.setElementGroup( null );
- item.$element.detach();
+ this.items[ i ].setElementGroup( null );
+ this.items[ i ].$element.detach();
}
+ // Mixin method
+ OO.EmitterList.prototype.clearItems.call( this );
+
this.emit( 'change', this.getItems() );
- this.items = [];
return this;
};
@@ -4303,6 +4253,20 @@ OO.ui.mixin.FloatableElement.prototype.position = function () {
return this;
}
+ if ( !(
+ // To continue, some things need to be true:
+ // The element must actually be in the DOM
+ this.isElementAttached() && (
+ // The closest scrollable is the current window
+ this.$floatableClosestScrollable[ 0 ] === this.getElementWindow() ||
+ // OR is an element in the element's DOM
+ $.contains( this.getElementDocument(), this.$floatableClosestScrollable[ 0 ] )
+ )
+ ) ) {
+ // Abort early if important parts of the widget are no longer attached to the DOM
+ return this;
+ }
+
if ( this.hideWhenOutOfView && !this.isElementInViewport( this.$floatableContainer, this.$floatableClosestScrollable ) ) {
this.$floatable.addClass( 'oo-ui-element-hidden' );
return this;
@@ -4883,6 +4847,14 @@ OO.mixinClass( OO.ui.PopupWidget, OO.ui.mixin.LabelElement );
OO.mixinClass( OO.ui.PopupWidget, OO.ui.mixin.ClippableElement );
OO.mixinClass( OO.ui.PopupWidget, OO.ui.mixin.FloatableElement );
+/* Events */
+
+/**
+ * @event ready
+ *
+ * The popup is ready: it is visible and has been positioned and clipped.
+ */
+
/* Methods */
/**
@@ -5016,6 +4988,7 @@ OO.ui.PopupWidget.prototype.hasAnchor = function () {
* Side-effects may include broken interface and exceptions being thrown. This wasn't always
* strictly enforced, so currently it only generates a warning in the browser console.
*
+ * @fires ready
* @inheritdoc
*/
OO.ui.PopupWidget.prototype.toggle = function ( show ) {
@@ -5046,6 +5019,7 @@ OO.ui.PopupWidget.prototype.toggle = function ( show ) {
}
this.updateDimensions();
this.toggleClipping( true );
+ this.emit( 'ready' );
} else {
this.toggleClipping( false );
if ( this.autoClose ) {