/*!
- * OOjs UI v0.22.3
+ * OOjs UI v0.23.0
* 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-07-11T22:12:33Z
+ * Date: 2017-09-05T21:23:58Z
*/
( function ( OO ) {
'ooui-toolgroup-expand': 'More',
// Label for the fake tool that collapses the full list of tools in a toolbar group
'ooui-toolgroup-collapse': 'Fewer',
+ // Default label for the tooltip for the button that removes a tag item
+ 'ooui-item-remove': 'Remove',
// Default label for the accept button of a confirmation dialog
'ooui-dialog-message-accept': 'OK',
// Default label for the reject button of a confirmation dialog
OO.ui.Element.static.infuse = function ( idOrNode ) {
var obj = OO.ui.Element.static.unsafeInfuse( idOrNode, false );
// Verify that the type matches up.
- // FIXME: uncomment after T89721 is fixed (see T90929)
+ // FIXME: uncomment after T89721 is fixed, see T90929.
/*
if ( !( obj instanceof this['class'] ) ) {
throw new Error( 'Infusion type mismatch!' );
*/
OO.ui.Element.static.unsafeInfuse = function ( idOrNode, domPromise ) {
// look for a cached result of a previous infusion.
- var id, $elem, data, cls, parts, parent, obj, top, state, infusedChildren;
+ var id, $elem, error, data, cls, parts, parent, obj, top, state, infusedChildren;
if ( typeof idOrNode === 'string' ) {
id = idOrNode;
$elem = $( document.getElementById( id ) );
id = $elem.attr( 'id' );
}
if ( !$elem.length ) {
- throw new Error( 'Widget not found: ' + id );
+ if ( typeof idOrNode === 'string' ) {
+ error = 'Widget not found: ' + idOrNode;
+ } else if ( idOrNode && idOrNode.selector ) {
+ error = 'Widget not found: ' + idOrNode.selector;
+ } else {
+ error = 'Widget not found';
+ }
+ throw new Error( error );
}
if ( $elem[ 0 ].oouiInfused ) {
$elem = $elem[ 0 ].oouiInfused;
parts = data._.split( '.' );
cls = OO.getProp.apply( OO, [ window ].concat( parts ) );
if ( cls === undefined ) {
- // The PHP output might be old and not including the "OO.ui" prefix
- // TODO: Remove this back-compat after next major release
- cls = OO.getProp.apply( OO, [ OO.ui ].concat( parts ) );
- if ( cls === undefined ) {
- throw new Error( 'Unknown widget type: id: ' + id + ', class: ' + data._ );
- }
+ throw new Error( 'Unknown widget type: id: ' + id + ', class: ' + data._ );
}
// Verify that we're creating an OO.ui.Element instance
if ( $elem[ 0 ] !== obj.$element[ 0 ] ) {
$elem.replaceWith( obj.$element );
// This element is now gone from the DOM, but if anyone is holding a reference to it,
- // let's allow them to OO.ui.infuse() it and do what they expect (T105828).
+ // let's allow them to OO.ui.infuse() it and do what they expect, see T105828.
// Do not use jQuery.data(), as using it on detached nodes leaks memory in 1.x line by design.
$elem[ 0 ].oouiInfused = obj.$element;
}
* @chainable
*/
OO.ui.mixin.IconElement.prototype.setIconTitle = function ( iconTitle ) {
- iconTitle = typeof iconTitle === 'function' ||
- ( typeof iconTitle === 'string' && iconTitle.length ) ?
+ iconTitle =
+ ( typeof iconTitle === 'function' || ( typeof iconTitle === 'string' && iconTitle.length ) ) ?
OO.ui.resolveMsg( iconTitle ) : null;
if ( this.iconTitle !== iconTitle ) {
* @chainable
*/
OO.ui.mixin.IndicatorElement.prototype.setIndicatorTitle = function ( indicatorTitle ) {
- indicatorTitle = typeof indicatorTitle === 'function' ||
- ( typeof indicatorTitle === 'string' && indicatorTitle.length ) ?
+ indicatorTitle =
+ ( typeof indicatorTitle === 'function' || ( typeof indicatorTitle === 'string' && indicatorTitle.length ) ) ?
OO.ui.resolveMsg( indicatorTitle ) : null;
if ( this.indicatorTitle !== indicatorTitle ) {
this.$titled = $titled;
if ( this.title ) {
- this.$titled.attr( 'title', this.title );
+ this.updateTitle();
}
};
title = ( typeof title === 'string' && title.length ) ? title : null;
if ( this.title !== title ) {
- if ( this.$titled ) {
- if ( title !== null ) {
- this.$titled.attr( 'title', title );
- } else {
- this.$titled.removeAttr( 'title' );
- }
- }
this.title = title;
+ this.updateTitle();
}
return this;
};
+/**
+ * Update the title attribute, in case of changes to title or accessKey.
+ *
+ * @protected
+ * @chainable
+ */
+OO.ui.mixin.TitledElement.prototype.updateTitle = function () {
+ var title = this.getTitle();
+ if ( this.$titled ) {
+ if ( title !== null ) {
+ // Only if this is an AccessKeyedElement
+ if ( this.formatTitleWithAccessKey ) {
+ title = this.formatTitleWithAccessKey( title );
+ }
+ this.$titled.attr( 'title', title );
+ } else {
+ this.$titled.removeAttr( 'title' );
+ }
+ }
+ return this;
+};
+
/**
* Get title.
*
// Initialization
this.setAccessKey( config.accessKey || null );
this.setAccessKeyedElement( config.$accessKeyed || this.$element );
+
+ // If this is also a TitledElement and it initialized before we did, we may have
+ // to update the title with the access key
+ if ( this.updateTitle ) {
+ this.updateTitle();
+ }
};
/* Setup */
}
}
this.accessKey = accessKey;
+
+ // Only if this is a TitledElement
+ if ( this.updateTitle ) {
+ this.updateTitle();
+ }
}
return this;
return this.accessKey;
};
+/**
+ * Add information about the access key to the element's tooltip label.
+ * (This is only public for hacky usage in FieldLayout.)
+ *
+ * @param {string} title Tooltip label for `title` attribute
+ * @return {string}
+ */
+OO.ui.mixin.AccessKeyedElement.prototype.formatTitleWithAccessKey = function ( title ) {
+ var accessKey;
+
+ if ( !this.$accessKeyed ) {
+ // Not initialized yet; the constructor will call updateTitle() which will rerun this function
+ return title;
+ }
+ // Use jquery.accessKeyLabel if available to show modifiers, otherwise just display the single key
+ if ( $.fn.updateTooltipAccessKeys && $.fn.updateTooltipAccessKeys.getAccessKeyLabel ) {
+ accessKey = $.fn.updateTooltipAccessKeys.getAccessKeyLabel( this.$accessKeyed[ 0 ] );
+ } else {
+ accessKey = this.getAccessKey();
+ }
+ if ( accessKey ) {
+ title += ' [' + accessKey + ']';
+ }
+ return title;
+};
+
/**
* ButtonWidget is a generic widget for buttons. A wide variety of looks,
* feels, and functionality can be customized via the class’s configuration options
clipHeight = allotedHeight < naturalHeight;
if ( clipWidth ) {
- // The order matters here. If overflow is not set first, Chrome displays bogus scrollbars. (T157672)
+ // The order matters here. If overflow is not set first, Chrome displays bogus scrollbars. See T157672.
// Forcing a reflow is a smaller workaround than calling reconsiderScrollbars() for this case.
this.$clippable.css( 'overflowX', 'scroll' );
void this.$clippable[ 0 ].offsetHeight; // Force reflow
} );
}
if ( clipHeight ) {
- // The order matters here. If overflow is not set first, Chrome displays bogus scrollbars. (T157672)
+ // The order matters here. If overflow is not set first, Chrome displays bogus scrollbars. See T157672.
// Forcing a reflow is a smaller workaround than calling reconsiderScrollbars() for this case.
this.$clippable.css( 'overflowY', 'scroll' );
void this.$clippable[ 0 ].offsetHeight; // Force reflow
} else {
// One of the options got focussed (and the event bubbled up here).
// They can't be tabbed to, but they can be activated using accesskeys.
- item = this.getTargetItem( event );
+ item = this.findTargetItem( event );
}
if ( item ) {
if ( !this.isDisabled() && e.which === OO.ui.MouseButtons.LEFT ) {
this.togglePressed( true );
- item = this.getTargetItem( e );
+ item = this.findTargetItem( e );
if ( item && item.isSelectable() ) {
this.pressItem( item );
this.selecting = item;
this.togglePressed( false );
if ( !this.selecting ) {
- item = this.getTargetItem( e );
+ item = this.findTargetItem( e );
if ( item && item.isSelectable() ) {
this.selecting = item;
}
var item;
if ( !this.isDisabled() && this.pressed ) {
- item = this.getTargetItem( e );
+ item = this.findTargetItem( e );
if ( item && item !== this.selecting && item.isSelectable() ) {
this.pressItem( item );
this.selecting = item;
return;
}
if ( !this.isDisabled() ) {
- item = this.getTargetItem( e );
+ item = this.findTargetItem( e );
this.highlightItem( item && item.isHighlightable() ? item : null );
}
return false;
s = s.normalize();
}
s = exact ? s.trim() : s.replace( /^\s+/, '' );
- re = '^\\s*' + s.replace( /([\\{}()|.?*+\-\^$\[\]])/g, '\\$1' ).replace( /\s+/g, '\\s+' );
+ re = '^\\s*' + s.replace( /([\\{}()|.?*+\-^$[\]])/g, '\\$1' ).replace( /\s+/g, '\\s+' );
if ( exact ) {
re += '\\s*$';
}
* @param {jQuery.Event} e
* @return {OO.ui.OptionWidget|null} Outline item widget, `null` if none was found
*/
-OO.ui.SelectWidget.prototype.getTargetItem = function ( e ) {
- return $( e.target ).closest( '.oo-ui-optionWidget' ).data( 'oo-ui-optionWidget' ) || null;
+OO.ui.SelectWidget.prototype.findTargetItem = function ( e ) {
+ var $option = $( e.target ).closest( '.oo-ui-optionWidget' );
+ if ( !$option.closest( '.oo-ui-selectWidget' ).is( this.$element ) ) {
+ return null;
+ }
+ return $option.data( 'oo-ui-optionWidget' ) || null;
};
/**
if (
this.isVisible() &&
!OO.ui.contains(
- this.$element.add( this.$widget ).add( this.$autoCloseIgnore ).get(),
- e.target,
- true
+ this.$element.add( this.$widget ).add( this.$autoCloseIgnore ).get(),
+ e.target,
+ true
)
) {
this.toggle( false );
this.focus();
};
-/**
- * FloatingMenuSelectWidget was a menu that would stick under a specified
- * container, even when it is inserted elsewhere in the document.
- * This functionality is now included in MenuSelectWidget, and FloatingMenuSelectWidget
- * is preserved for backwards-compatibility.
- *
- * @class
- * @extends OO.ui.MenuSelectWidget
- * @deprecated since v0.21.3, use MenuSelectWidget instead.
- *
- * @constructor
- * @param {OO.ui.Widget} [inputWidget] Widget to provide the menu for.
- * Deprecated, omit this parameter and specify `$container` instead.
- * @param {Object} [config] Configuration options
- * @cfg {jQuery} [$container=inputWidget.$element] Element to render menu under
- */
-OO.ui.FloatingMenuSelectWidget = function OoUiFloatingMenuSelectWidget( inputWidget, config ) {
- OO.ui.warnDeprecation( 'FloatingMenuSelectWidget is deprecated. Use the MenuSelectWidget instead.' );
-
- // Allow 'inputWidget' parameter and config for backwards compatibility
- if ( OO.isPlainObject( inputWidget ) && config === undefined ) {
- config = inputWidget;
- inputWidget = config.inputWidget;
- }
-
- // Configuration initialization
- config = config || {};
-
- // Properties
- this.inputWidget = inputWidget; // For backwards compatibility
- this.$container = config.$floatableContainer || config.$container || this.inputWidget.$element;
-
- // Parent constructor
- OO.ui.FloatingMenuSelectWidget.parent.call( this, $.extend( {}, config, { $floatableContainer: this.$container } ) );
-
- // Initialization
- this.$element.addClass( 'oo-ui-floatingMenuSelectWidget' );
- // For backwards compatibility
- this.$element.addClass( 'oo-ui-textInputMenuSelectWidget' );
-};
-
-/* Setup */
-
-OO.inheritClass( OO.ui.FloatingMenuSelectWidget, OO.ui.MenuSelectWidget );
-
/**
* Progress bars visually display the status of an operation, such as a download,
* and can be either determinate or indeterminate:
*/
OO.ui.InputWidget.static.reusePreInfuseDOM = function ( node, config ) {
config = OO.ui.InputWidget.parent.static.reusePreInfuseDOM( node, config );
- // Reusing $input lets browsers preserve inputted values across page reloads (T114134)
+ // Reusing `$input` lets browsers preserve inputted values across page reloads, see T114134.
config.$input = $( node ).find( '.oo-ui-inputWidget-input' );
return config;
};
this.setOptions( config.options || [] );
// Have to repeat this from parent, as we need options to be set up for this to make sense
this.setValue( config.value );
+
+ // setValue when checkboxMultiselectWidget changes
+ this.checkboxMultiselectWidget.on( 'change', function () {
+ this.setValue( this.checkboxMultiselectWidget.getSelectedItemsData() );
+ }.bind( this ) );
};
/* Setup */
}, config );
if ( config.multiline ) {
- OO.ui.warnDeprecation( 'TextInputWidget: config.multiline is deprecated. Use the MultilineTextInputWidget instead. See T130434 for details.' );
+ OO.ui.warnDeprecation( 'TextInputWidget: config.multiline is deprecated. Use the MultilineTextInputWidget instead. See T130434.' );
return new OO.ui.MultilineTextInputWidget( config );
}
* @chainable
*/
OO.ui.TextInputWidget.prototype.positionLabel = function () {
- var after, rtl, property;
+ var after, rtl, property, newCss;
if ( this.isWaitingToBeAttached ) {
// #onElementAttach will be called soon, which calls this method
return this;
}
- // Clear old values
- this.$input
- // Clear old values if present
- .css( {
- 'padding-right': '',
- 'padding-left': ''
- } );
+ newCss = {
+ 'padding-right': '',
+ 'padding-left': ''
+ };
if ( this.label ) {
this.$element.append( this.$label );
} else {
this.$label.detach();
+ // Clear old values if present
+ this.$input.css( newCss );
return;
}
rtl = this.$element.css( 'direction' ) === 'rtl';
property = after === rtl ? 'padding-left' : 'padding-right';
- this.$input.css( property, this.$label.outerWidth( true ) + ( after ? this.scrollWidth : 0 ) );
+ newCss[ property ] = this.$label.outerWidth( true ) + ( after ? this.scrollWidth : 0 );
+ // We have to clear the padding on the other side, in case the element direction changed
+ this.$input.css( newCss );
return this;
};
this.setErrors( config.errors || [] );
this.setNotices( config.notices || [] );
this.setAlignment( config.align );
+ // Call this again to take into account the widget's accessKey
+ this.updateTitle();
};
/* Setup */
}
};
+/**
+ * Include information about the widget's accessKey in our title. TitledElement calls this method.
+ * (This is a bit of a hack.)
+ *
+ * @protected
+ * @param {string} title Tooltip label for 'title' attribute
+ * @return {string}
+ */
+OO.ui.FieldLayout.prototype.formatTitleWithAccessKey = function ( title ) {
+ if ( this.fieldWidget && this.fieldWidget.formatTitleWithAccessKey ) {
+ return this.fieldWidget.formatTitleWithAccessKey( title );
+ }
+ return title;
+};
+
/**
* ActionFieldLayouts are used with OO.ui.FieldsetLayout. The layout consists of a field-widget, a button,
* and an optional label and/or help text. The field-widget (e.g., a {@link OO.ui.TextInputWidget TextInputWidget}),
// Mixin constructors
OO.ui.mixin.IconElement.call( this, config );
- OO.ui.mixin.LabelElement.call( this, $.extend( {}, config, { $label: $( '<div>' ) } ) );
+ OO.ui.mixin.LabelElement.call( this, config );
OO.ui.mixin.GroupElement.call( this, config );
// Properties
- this.$header = $( '<div>' );
+ this.$header = $( '<legend>' );
if ( config.help ) {
this.popupButtonWidget = new OO.ui.PopupButtonWidget( {
$overlay: config.$overlay,