/*!
- * OOUI v0.31.6
+ * OOUI v0.33.1
* https://www.mediawiki.org/wiki/OOUI
*
* Copyright 2011–2019 OOUI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2019-05-08T10:08:36Z
+ * Date: 2019-07-03T21:05:08Z
*/
( function ( OO ) {
*/
OO.ui.throttle = function ( func, wait ) {
var context, args, timeout,
- previous = 0,
+ previous = Date.now() - wait,
run = function () {
timeout = null;
previous = Date.now();
// period. If it's less, run the function immediately. If it's more,
// set a timeout for the remaining time -- but don't replace an
// existing timeout, since that'd indefinitely prolong the wait.
- var remaining = wait - ( Date.now() - previous );
+ var remaining = Math.max( wait - ( Date.now() - previous ), 0 );
context = this;
args = arguments;
- if ( remaining <= 0 ) {
- // Note: unless wait was ridiculously large, this means we'll
- // automatically run the first time the function was called in a
- // given period. (If you provide a wait period larger than the
- // current Unix timestamp, you *deserve* unexpected behavior.)
- clearTimeout( timeout );
- run();
- } else if ( !timeout ) {
+ if ( !timeout ) {
+ // If time is up, do setTimeout( run, 0 ) so the function
+ // always runs asynchronously, just like Promise#then .
timeout = setTimeout( run, remaining );
}
};
};
-/**
- * A (possibly faster) way to get the current timestamp as an integer.
- *
- * @deprecated Since 0.31.1; use `Date.now()` instead.
- * @return {number} Current timestamp, in milliseconds since the Unix epoch
- */
-OO.ui.now = function () {
- OO.ui.warnDeprecation( 'OO.ui.now() is deprecated, use Date.now() instead' );
- return Date.now();
-};
-
/**
* Reconstitute a JavaScript object corresponding to a widget created by
* the PHP implementation.
config = config || {};
// Properties
- this.$ = function () {
- OO.ui.warnDeprecation( 'this.$ is deprecated, use global $ instead' );
- return $.apply( this, arguments );
- };
this.elementId = null;
this.visible = true;
this.data = config.data;
return {};
};
-/**
- * Get a jQuery function within a specific document.
- *
- * @static
- * @param {jQuery|HTMLElement|HTMLDocument|Window} context Context to bind the function to
- * @param {jQuery} [$iframe] HTML iframe element that contains the document, omit if document is
- * not in an iframe
- * @return {Function} Bound jQuery function
- */
-OO.ui.Element.static.getJQuery = function ( context, $iframe ) {
- function wrapper( selector ) {
- return $( selector, wrapper.context );
- }
-
- wrapper.context = this.getDocument( context );
-
- if ( $iframe ) {
- wrapper.$iframe = $iframe;
- }
-
- return wrapper;
-};
-
/**
* Get the document of an element.
*
return this.icon;
};
-/**
- * Get the icon title. The title text is displayed when a user moves the mouse over the icon.
- *
- * @return {string} Icon title text
- * @deprecated
- */
-OO.ui.mixin.IconElement.prototype.getIconTitle = function () {
- return this.iconTitle;
-};
-
/**
* IndicatorElement is often mixed into other classes to generate an indicator.
* Indicators are small graphics that are generally used in two ways:
return this.indicator;
};
-/**
- * Get the indicator title.
- *
- * The title is displayed when a user moves the mouse over the indicator.
- *
- * @return {string} Indicator title text
- * @deprecated
- */
-OO.ui.mixin.IndicatorElement.prototype.getIndicatorTitle = function () {
- return this.indicatorTitle;
-};
-
/**
* The FlaggedElement class is an attribute mixin, meaning that it is used to add
* additional functionality to an element created by another class. The class provides
*/
OO.ui.LabelWidget.static.tagName = 'label';
+/**
+ * MessageWidget produces a visual component for sending a notice to the user
+ * with an icon and distinct design noting its purpose. The MessageWidget changes
+ * its visual presentation based on the type chosen, which also denotes its UX
+ * purpose.
+ *
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.mixin.IconElement
+ * @mixins OO.ui.mixin.LabelElement
+ * @mixins OO.ui.mixin.TitledElement
+ * @mixins OO.ui.mixin.FlaggedElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {string} [type='notice'] The type of the notice widget. This will also
+ * impact the flags that the widget receives (and hence its CSS design) as well
+ * as the icon that appears. Available types:
+ * 'notice', 'error', 'warning', 'success'
+ * @cfg {boolean} [inline] Set the notice as an inline notice. The default
+ * is not inline, or 'boxed' style.
+ */
+OO.ui.MessageWidget = function OoUiMessageWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.MessageWidget.parent.call( this, config );
+
+ // Mixin constructors
+ OO.ui.mixin.IconElement.call( this, config );
+ OO.ui.mixin.LabelElement.call( this, config );
+ OO.ui.mixin.TitledElement.call( this, config );
+ OO.ui.mixin.FlaggedElement.call( this, config );
+
+ // Set type
+ this.setType( config.type );
+ this.setInline( config.inline );
+
+ // Build the widget
+ this.$element
+ .append( this.$icon, this.$label )
+ .addClass( 'oo-ui-messageWidget' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.MessageWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.MessageWidget, OO.ui.mixin.IconElement );
+OO.mixinClass( OO.ui.MessageWidget, OO.ui.mixin.LabelElement );
+OO.mixinClass( OO.ui.MessageWidget, OO.ui.mixin.TitledElement );
+OO.mixinClass( OO.ui.MessageWidget, OO.ui.mixin.FlaggedElement );
+
+/* Static Properties */
+
+/**
+ * An object defining the icon name per defined type.
+ *
+ * @static
+ * @property {Object}
+ */
+OO.ui.MessageWidget.static.iconMap = {
+ notice: 'infoFilled',
+ error: 'error',
+ warning: 'alert',
+ success: 'check'
+};
+
+/* Methods */
+
+/**
+ * Set the inline state of the widget.
+ *
+ * @param {boolean} inline Widget is inline
+ */
+OO.ui.MessageWidget.prototype.setInline = function ( inline ) {
+ inline = !!inline;
+
+ if ( this.inline !== inline ) {
+ this.inline = inline;
+ this.$element
+ .toggleClass( 'oo-ui-messageWidget-block', !this.inline );
+ }
+};
+/**
+ * Set the widget type. The given type must belong to the list of
+ * legal types set by OO.ui.MessageWidget.static.iconMap
+ *
+ * @param {string} [type] Given type. Defaults to 'notice'
+ */
+OO.ui.MessageWidget.prototype.setType = function ( type ) {
+ // Validate type
+ if ( Object.keys( this.constructor.static.iconMap ).indexOf( type ) === -1 ) {
+ type = 'notice'; // Default
+ }
+
+ if ( this.type !== type ) {
+
+ // Flags
+ this.clearFlags();
+ this.setFlags( type );
+
+ // Set the icon and its variant
+ this.setIcon( this.constructor.static.iconMap[ type ] );
+ this.$icon.removeClass( 'oo-ui-image-' + this.type );
+ this.$icon.addClass( 'oo-ui-image-' + type );
+
+ if ( type === 'error' ) {
+ this.$element.attr( 'role', 'alert' );
+ this.$element.removeAttr( 'aria-live' );
+ } else {
+ this.$element.removeAttr( 'role' );
+ this.$element.attr( 'aria-live', 'polite' );
+ }
+
+ this.type = type;
+ }
+};
+
/**
* PendingElement is a mixin that is used to create elements that notify users that something is
* happening and that they should wait before proceeding. The pending state is visually represented
// Initialization
this.$element
- // -depressed is a deprecated alias of -unpressed
- .addClass( 'oo-ui-selectWidget oo-ui-selectWidget-unpressed oo-ui-selectWidget-depressed' )
+ .addClass( 'oo-ui-selectWidget oo-ui-selectWidget-unpressed' )
.attr( 'role', 'listbox' );
this.setFocusOwner( this.$element );
if ( Array.isArray( config.items ) ) {
OO.ui.SelectWidget.prototype.onDocumentKeyDown = function ( e ) {
var nextItem,
handled = false,
- currentItem = this.findHighlightedItem(),
+ selected = this.findSelectedItems(),
+ currentItem = this.findHighlightedItem() || (
+ Array.isArray( selected ) ? selected[ 0 ] : selected
+ ),
firstItem = this.getItems()[ 0 ];
if ( !this.isDisabled() && this.isVisible() ) {
* @return {undefined|boolean} False to prevent default if event is handled
*/
OO.ui.SelectWidget.prototype.onDocumentKeyPress = function ( e ) {
- var c, filter, item;
+ var c, filter, item, selected;
if ( !e.charCode ) {
if ( e.keyCode === OO.ui.Keys.BACKSPACE && this.keyPressBuffer !== '' ) {
}
this.keyPressBufferTimer = setTimeout( this.clearKeyPressBuffer.bind( this ), 1500 );
- item = this.findHighlightedItem() || this.findSelectedItem();
+ selected = this.findSelectedItems();
+ item = this.findHighlightedItem() || (
+ Array.isArray( selected ) ? selected[ 0 ] : selected
+ );
if ( this.keyPressBuffer === c ) {
// Common (if weird) special case: typing "xxxx" will cycle through all
if ( pressed !== this.pressed ) {
this.$element
.toggleClass( 'oo-ui-selectWidget-pressed', pressed )
- // -depressed is a deprecated alias of -unpressed
- .toggleClass( 'oo-ui-selectWidget-unpressed oo-ui-selectWidget-depressed', !pressed );
+ .toggleClass( 'oo-ui-selectWidget-unpressed', !pressed );
this.pressed = pressed;
}
};
* @param {Object} [config] Configuration options
* @cfg {Object} [menu] Configuration options to pass to
* {@link OO.ui.MenuSelectWidget menu select widget}.
- * @cfg {jQuery} [$overlay] Render the menu into a separate layer. This configuration is useful
- * in cases where the expanded menu is larger than its containing `<div>`. The specified overlay
- * layer is usually on top of the containing `<div>` and has a larger area. By default, the menu
- * uses relative positioning.
+ * @cfg {jQuery|boolean} [$overlay] Render the menu into a separate layer. This configuration is
+ * useful in cases where the expanded menu is larger than its containing `<div>`. The specified
+ * overlay layer is usually on top of the containing `<div>` and has a larger area. By default,
+ * the menu uses relative positioning. Pass 'true' to use the default overlay.
* See <https://www.mediawiki.org/wiki/OOUI/Concepts#Overlays>.
*/
OO.ui.DropdownWidget = function OoUiDropdownWidget( config ) {
OO.ui.DropdownWidget.parent.call( this, config );
// Properties (must be set before TabIndexedElement constructor call)
- this.$handle = $( '<button>' );
+ this.$handle = $( '<span>' );
this.$overlay = ( config.$overlay === true ?
OO.ui.getDefaultOverlay() : config.$overlay ) || this.$element;
} );
// Initialization
+ this.$label
+ .attr( {
+ role: 'textbox',
+ 'aria-readonly': 'true'
+ } );
this.$handle
.addClass( 'oo-ui-dropdownWidget-handle' )
+ .append( this.$icon, this.$label, this.$indicator )
.attr( {
- type: 'button',
- 'aria-owns': this.menu.getElementId(),
- 'aria-haspopup': 'listbox'
- } )
- .append( this.$icon, this.$label, this.$indicator );
+ role: 'combobox',
+ 'aria-autocomplete': 'list',
+ 'aria-expanded': 'false',
+ 'aria-haspopup': 'true',
+ 'aria-owns': this.menu.getElementId()
+ } );
this.$element
.addClass( 'oo-ui-dropdownWidget' )
.append( this.$handle );
* @param {Object} [config] Configuration options
* @cfg {Object[]} [options=[]] Array of menu options in the format described above.
* @cfg {Object} [dropdown] Configuration options for {@link OO.ui.DropdownWidget DropdownWidget}
- * @cfg {jQuery} [$overlay] Render the menu into a separate layer. This configuration is useful
- * in cases where the expanded menu is larger than its containing `<div>`. The specified overlay
- * layer is usually on top of the containing `<div>` and has a larger area. By default, the menu
- * uses relative positioning.
+ * @cfg {jQuery|boolean} [$overlay] Render the menu into a separate layer. This configuration is
+ * useful in cases where the expanded menu is larger than its containing `<div>`. The specified
+ * overlay layer is usually on top of the containing `<div>` and has a larger area. By default,
+ * the menu uses relative positioning. Pass 'true' to use the default overlay.
* See <https://www.mediawiki.org/wiki/OOUI/Concepts#Overlays>.
*/
OO.ui.DropdownInputWidget = function OoUiDropdownInputWidget( config ) {
this.$element
.addClass( 'oo-ui-dropdownInputWidget' )
.append( this.dropdownWidget.$element );
+ if ( OO.ui.isMobile() ) {
+ this.$element.addClass( 'oo-ui-isMobile' );
+ }
this.setTabIndexedElement( this.dropdownWidget.$tabIndexed );
this.setTitledElement( this.dropdownWidget.$handle );
};
* @protected
*/
OO.ui.DropdownInputWidget.prototype.getInputElement = function () {
- return $( '<select>' );
+ return $( '<select>' ).addClass( 'oo-ui-indicator-down' );
};
/**
this.successMessages = [];
this.notices = [];
this.$field = this.isFieldInline() ? $( '<span>' ) : $( '<div>' );
- this.$messages = $( '<ul>' );
+ this.$messages = $( '<div>' );
this.$header = $( '<span>' );
this.$body = $( '<div>' );
this.align = null;
* @return {jQuery}
*/
OO.ui.FieldLayout.prototype.makeMessage = function ( kind, text ) {
- var $listItem, $icon, message;
- $listItem = $( '<li>' );
- if ( kind === 'error' ) {
- $icon = new OO.ui.IconWidget( { icon: 'error', flags: [ 'error' ] } ).$element;
- $listItem.attr( 'role', 'alert' );
- } else if ( kind === 'warning' ) {
- $icon = new OO.ui.IconWidget( { icon: 'alert', flags: [ 'warning' ] } ).$element;
- $listItem.attr( 'role', 'alert' );
- } else if ( kind === 'success' ) {
- $icon = new OO.ui.IconWidget( { icon: 'check', flags: [ 'success' ] } ).$element;
- } else if ( kind === 'notice' ) {
- $icon = new OO.ui.IconWidget( { icon: 'notice' } ).$element;
- } else {
- $icon = '';
- }
- message = new OO.ui.LabelWidget( { label: text } );
- $listItem
- .append( $icon, message.$element )
- .addClass( 'oo-ui-fieldLayout-messages-' + kind );
- return $listItem;
+ return new OO.ui.MessageWidget( {
+ type: kind,
+ inline: true,
+ label: text
+ } ).$element;
};
/**
* @param {Object} [config] Configuration options
* @cfg {OO.ui.FieldLayout[]} [items] An array of fields to add to the fieldset.
* See OO.ui.FieldLayout for more information about fields.
- * @cfg {string|OO.ui.HtmlSnippet} [help] Help text. When help text is specified, a "help" icon
- * will appear in the upper-right corner of the rendered field; clicking it will display the text
- * in a popup. For important messages, you are advised to use `notices`, as they are always shown.
+ * @cfg {string|OO.ui.HtmlSnippet} [help] Help text. When help text is specified
+ * and `helpInline` is `false`, a "help" icon will appear in the upper-right
+ * corner of the rendered field; clicking it will display the text in a popup.
+ * If `helpInline` is `true`, then a subtle description will be shown after the
+ * label.
+ * For feedback messages, you are advised to use `notices`.
+ * @cfg {boolean} [helpInline=false] Whether or not the help should be inline,
+ * or shown when the "help" icon is clicked.
* @cfg {jQuery} [$overlay] Passed to OO.ui.PopupButtonWidget for help popup, if `help` is given.
* See <https://www.mediawiki.org/wiki/OOUI/Concepts#Overlays>.
*/
OO.ui.FieldsetLayout = function OoUiFieldsetLayout( config ) {
+ var helpWidget;
+
// Configuration initialization
config = config || {};
// Properties
this.$header = $( '<legend>' );
- if ( config.help ) {
- this.popupButtonWidget = new OO.ui.PopupButtonWidget( {
- $overlay: config.$overlay,
- popup: {
- padded: true
- },
- classes: [ 'oo-ui-fieldsetLayout-help' ],
- framed: false,
- icon: 'info',
- label: OO.ui.msg( 'ooui-field-help' ),
- invisibleLabel: true
- } );
- if ( config.help instanceof OO.ui.HtmlSnippet ) {
- this.popupButtonWidget.getPopup().$body.html( config.help.toString() );
- } else {
- this.popupButtonWidget.getPopup().$body.text( config.help );
- }
- this.$help = this.popupButtonWidget.$element;
- } else {
- this.$help = $( [] );
- }
// Initialization
this.$header
.addClass( 'oo-ui-fieldsetLayout-header' )
- .append( this.$icon, this.$label, this.$help );
+ .append( this.$icon, this.$label );
this.$group.addClass( 'oo-ui-fieldsetLayout-group' );
this.$element
.addClass( 'oo-ui-fieldsetLayout' )
.prepend( this.$header, this.$group );
+
+ // Help
+ if ( config.help ) {
+ if ( config.helpInline ) {
+ helpWidget = new OO.ui.LabelWidget( {
+ label: config.help,
+ classes: [ 'oo-ui-inline-help' ]
+ } );
+ this.$element.prepend( this.$header, helpWidget.$element, this.$group );
+ } else {
+ helpWidget = new OO.ui.PopupButtonWidget( {
+ $overlay: config.$overlay,
+ popup: {
+ padded: true
+ },
+ classes: [ 'oo-ui-fieldsetLayout-help' ],
+ framed: false,
+ icon: 'info',
+ label: OO.ui.msg( 'ooui-field-help' ),
+ invisibleLabel: true
+ } );
+ if ( config.help instanceof OO.ui.HtmlSnippet ) {
+ helpWidget.getPopup().$body.html( config.help.toString() );
+ } else {
+ helpWidget.getPopup().$body.text( config.help );
+ }
+ this.$header.append( helpWidget.$element );
+ }
+ }
if ( Array.isArray( config.items ) ) {
this.addItems( config.items );
}