X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;ds=sidebyside;f=resources%2Flib%2Fooui%2Foojs-ui-core.js;h=2bb08e0a50dd6c247bbfb924448cd49ef8a70f8e;hb=a38af7ba26579bb3004f673e44d39710887763aa;hp=3ca6632b7907fc95c4b8553e6c084ac9d81745c5;hpb=a34f57387da5a71e7d1fba1939ac600a767b64dc;p=lhc%2Fweb%2Fwiklou.git diff --git a/resources/lib/ooui/oojs-ui-core.js b/resources/lib/ooui/oojs-ui-core.js index 3ca6632b79..2bb08e0a50 100644 --- a/resources/lib/ooui/oojs-ui-core.js +++ b/resources/lib/ooui/oojs-ui-core.js @@ -1,12 +1,12 @@ /*! - * OOUI v0.31.0 + * OOUI v0.31.3 * 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-03-20T23:07:02Z + * Date: 2019-04-04T19:10:48Z */ ( function ( OO ) { @@ -295,7 +295,7 @@ OO.ui.throttle = function ( func, wait ) { previous = 0, run = function () { timeout = null; - previous = OO.ui.now(); + previous = Date.now(); func.apply( context, args ); }; return function () { @@ -304,7 +304,7 @@ OO.ui.throttle = function ( func, wait ) { // 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 - ( OO.ui.now() - previous ); + var remaining = wait - ( Date.now() - previous ); context = this; args = arguments; if ( remaining <= 0 ) { @@ -323,10 +323,12 @@ OO.ui.throttle = function ( func, wait ) { /** * 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 = Date.now || function () { - return new Date().getTime(); +OO.ui.now = function () { + OO.ui.warnDeprecation( 'OO.ui.now() is deprecated, use Date.now() instead' ); + return Date.now(); }; /** @@ -345,124 +347,74 @@ OO.ui.infuse = function ( idOrNode, config ) { return OO.ui.Element.static.infuse( idOrNode, config ); }; -( function () { - /** - * Message store for the default implementation of OO.ui.msg. - * - * Environments that provide a localization system should not use this, but should override - * OO.ui.msg altogether. - * - * @private - */ - var messages = { - // Tool tip for a button that moves items in a list down one place - 'ooui-outline-control-move-down': 'Move item down', - // Tool tip for a button that moves items in a list up one place - 'ooui-outline-control-move-up': 'Move item up', - // Tool tip for a button that removes items from a list - 'ooui-outline-control-remove': 'Remove item', - // Label for the toolbar group that contains a list of all other available tools - 'ooui-toolbar-more': 'More', - // Label for the fake tool that expands the full list of tools in a toolbar group - '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 - 'ooui-dialog-message-reject': 'Cancel', - // Title for process dialog error description - 'ooui-dialog-process-error': 'Something went wrong', - // Label for process dialog dismiss error button, visible when describing errors - 'ooui-dialog-process-dismiss': 'Dismiss', - // Label for process dialog retry action button, visible when describing only recoverable - // errors - 'ooui-dialog-process-retry': 'Try again', - // Label for process dialog retry action button, visible when describing only warnings - 'ooui-dialog-process-continue': 'Continue', - // Label for button in combobox input that triggers its dropdown - 'ooui-combobox-button-label': 'Dropdown for combobox', - // Label for the file selection widget's select file button - 'ooui-selectfile-button-select': 'Select a file', - // Label for the file selection widget if file selection is not supported - 'ooui-selectfile-not-supported': 'File selection is not supported', - // Label for the file selection widget when no file is currently selected - 'ooui-selectfile-placeholder': 'No file is selected', - // Label for the file selection widget's drop target - 'ooui-selectfile-dragdrop-placeholder': 'Drop file here', - // Label for the help icon attached to a form field - 'ooui-field-help': 'Help' - }; - - /** - * Get a localized message. - * - * After the message key, message parameters may optionally be passed. In the default - * implementation, any occurrences of $1 are replaced with the first parameter, $2 with the - * second parameter, etc. - * Alternative implementations of OO.ui.msg may use any substitution system they like, as long - * as they support unnamed, ordered message parameters. - * - * In environments that provide a localization system, this function should be overridden to - * return the message translated in the user's language. The default implementation always - * returns English messages. An example of doing this with - * [jQuery.i18n](https://github.com/wikimedia/jquery.i18n) follows. - * - * @example - * var i, iLen, button, - * messagePath = 'oojs-ui/dist/i18n/', - * languages = [ $.i18n().locale, 'ur', 'en' ], - * languageMap = {}; - * - * for ( i = 0, iLen = languages.length; i < iLen; i++ ) { - * languageMap[ languages[ i ] ] = messagePath + languages[ i ].toLowerCase() + '.json'; - * } - * - * $.i18n().load( languageMap ).done( function() { - * // Replace the built-in `msg` only once we've loaded the internationalization. - * // OOUI uses `OO.ui.deferMsg` for all initially-loaded messages. So long as - * // you put off creating any widgets until this promise is complete, no English - * // will be displayed. - * OO.ui.msg = $.i18n; - * - * // A button displaying "OK" in the default locale - * button = new OO.ui.ButtonWidget( { - * label: OO.ui.msg( 'ooui-dialog-message-accept' ), - * icon: 'check' - * } ); - * $( document.body ).append( button.$element ); - * - * // A button displaying "OK" in Urdu - * $.i18n().locale = 'ur'; - * button = new OO.ui.ButtonWidget( { - * label: OO.ui.msg( 'ooui-dialog-message-accept' ), - * icon: 'check' - * } ); - * $( document.body ).append( button.$element ); - * } ); - * - * @param {string} key Message key - * @param {...Mixed} [params] Message parameters - * @return {string} Translated message with parameters substituted - */ - OO.ui.msg = function ( key ) { - var message = messages[ key ], - params = Array.prototype.slice.call( arguments, 1 ); - if ( typeof message === 'string' ) { - // Perform $1 substitution - message = message.replace( /\$(\d+)/g, function ( unused, n ) { - var i = parseInt( n, 10 ); - return params[ i - 1 ] !== undefined ? params[ i - 1 ] : '$' + n; - } ); - } else { - // Return placeholder if message not found - message = '[' + key + ']'; - } - return message; - }; -}() ); +/** + * Get a localized message. + * + * After the message key, message parameters may optionally be passed. In the default + * implementation, any occurrences of $1 are replaced with the first parameter, $2 with the + * second parameter, etc. + * Alternative implementations of OO.ui.msg may use any substitution system they like, as long + * as they support unnamed, ordered message parameters. + * + * In environments that provide a localization system, this function should be overridden to + * return the message translated in the user's language. The default implementation always + * returns English messages. An example of doing this with + * [jQuery.i18n](https://github.com/wikimedia/jquery.i18n) follows. + * + * @example + * var i, iLen, button, + * messagePath = 'oojs-ui/dist/i18n/', + * languages = [ $.i18n().locale, 'ur', 'en' ], + * languageMap = {}; + * + * for ( i = 0, iLen = languages.length; i < iLen; i++ ) { + * languageMap[ languages[ i ] ] = messagePath + languages[ i ].toLowerCase() + '.json'; + * } + * + * $.i18n().load( languageMap ).done( function() { + * // Replace the built-in `msg` only once we've loaded the internationalization. + * // OOUI uses `OO.ui.deferMsg` for all initially-loaded messages. So long as + * // you put off creating any widgets until this promise is complete, no English + * // will be displayed. + * OO.ui.msg = $.i18n; + * + * // A button displaying "OK" in the default locale + * button = new OO.ui.ButtonWidget( { + * label: OO.ui.msg( 'ooui-dialog-message-accept' ), + * icon: 'check' + * } ); + * $( document.body ).append( button.$element ); + * + * // A button displaying "OK" in Urdu + * $.i18n().locale = 'ur'; + * button = new OO.ui.ButtonWidget( { + * label: OO.ui.msg( 'ooui-dialog-message-accept' ), + * icon: 'check' + * } ); + * $( document.body ).append( button.$element ); + * } ); + * + * @param {string} key Message key + * @param {...Mixed} [params] Message parameters + * @return {string} Translated message with parameters substituted + */ +OO.ui.msg = function ( key ) { + // `OO.ui.msg.messages` is defined in code generated during the build process + var messages = OO.ui.msg.messages, + message = messages[ key ], + params = Array.prototype.slice.call( arguments, 1 ); + if ( typeof message === 'string' ) { + // Perform $1 substitution + message = message.replace( /\$(\d+)/g, function ( unused, n ) { + var i = parseInt( n, 10 ); + return params[ i - 1 ] !== undefined ? params[ i - 1 ] : '$' + n; + } ); + } else { + // Return placeholder if message not found + message = '[' + key + ']'; + } + return message; +}; /** * Package a message and arguments for deferred resolution. @@ -581,6 +533,36 @@ OO.ui.getDefaultOverlay = function () { return OO.ui.$defaultOverlay; }; +/** + * Message store for the default implementation of OO.ui.msg. + * + * Environments that provide a localization system should not use this, but should override + * OO.ui.msg altogether. + * + * @private + */ +OO.ui.msg.messages = { + "ooui-outline-control-move-down": "Move item down", + "ooui-outline-control-move-up": "Move item up", + "ooui-outline-control-remove": "Remove item", + "ooui-toolbar-more": "More", + "ooui-toolgroup-expand": "More", + "ooui-toolgroup-collapse": "Fewer", + "ooui-item-remove": "Remove", + "ooui-dialog-message-accept": "OK", + "ooui-dialog-message-reject": "Cancel", + "ooui-dialog-process-error": "Something went wrong", + "ooui-dialog-process-dismiss": "Dismiss", + "ooui-dialog-process-retry": "Try again", + "ooui-dialog-process-continue": "Continue", + "ooui-combobox-button-label": "Dropdown for combobox", + "ooui-selectfile-button-select": "Select a file", + "ooui-selectfile-not-supported": "File selection is not supported", + "ooui-selectfile-placeholder": "No file is selected", + "ooui-selectfile-dragdrop-placeholder": "Drop file here", + "ooui-field-help": "Help" +}; + /*! * Mixin namespace. */ @@ -2118,7 +2100,7 @@ OO.ui.mixin.TabIndexedElement.prototype.getInputId = function () { OO.ui.mixin.TabIndexedElement.prototype.isLabelableNode = function ( $node ) { var labelableTags = [ 'button', 'meter', 'output', 'progress', 'select', 'textarea' ], - tagName = $node.prop( 'tagName' ).toLowerCase(); + tagName = ( $node.prop( 'tagName' ) || '' ).toLowerCase(); if ( tagName === 'input' && $node.attr( 'type' ) !== 'hidden' ) { return true; @@ -3456,6 +3438,9 @@ OO.initClass( OO.ui.mixin.TitledElement ); * The title text, a function that returns text, or `null` for no title. The value of the static * property is overridden if the #title config option is used. * + * If the element has a default title (e.g. ``), `null` will allow that title to be + * shown. Use empty string to suppress it. + * * @static * @inheritable * @property {string|Function|null} @@ -3480,9 +3465,7 @@ OO.ui.mixin.TitledElement.prototype.setTitledElement = function ( $titled ) { } this.$titled = $titled; - if ( this.title ) { - this.updateTitle(); - } + this.updateTitle(); }; /** @@ -3495,7 +3478,7 @@ OO.ui.mixin.TitledElement.prototype.setTitledElement = function ( $titled ) { */ OO.ui.mixin.TitledElement.prototype.setTitle = function ( title ) { title = typeof title === 'function' ? OO.ui.resolveMsg( title ) : title; - title = ( typeof title === 'string' && title.length ) ? title : null; + title = typeof title === 'string' ? title : null; if ( this.title !== title ) { this.title = title; @@ -6420,6 +6403,7 @@ OO.ui.OptionWidget.prototype.getMatchText = function () { * Options are created with {@link OO.ui.OptionWidget OptionWidget} classes. See * the [OOUI documentation on MediaWiki] [2] for examples. * [2]: https://www.mediawiki.org/wiki/OOUI/Widgets/Selects_and_Options + * @cfg {boolean} [multiselect] Allow for multiple selections */ OO.ui.SelectWidget = function OoUiSelectWidget( config ) { // Configuration initialization @@ -6436,6 +6420,7 @@ OO.ui.SelectWidget = function OoUiSelectWidget( config ) { // Properties this.pressed = false; this.selecting = null; + this.multiselect = !!config.multiselect; this.onDocumentMouseUpHandler = this.onDocumentMouseUp.bind( this ); this.onDocumentMouseMoveHandler = this.onDocumentMouseMove.bind( this ); this.onDocumentKeyDownHandler = this.onDocumentKeyDown.bind( this ); @@ -6496,13 +6481,16 @@ OO.mixinClass( OO.ui.SelectWidget, OO.ui.mixin.GroupWidget ); * A `select` event is emitted when the selection is modified programmatically with the #selectItem * method. * - * @param {OO.ui.OptionWidget|null} item Selected item + * @param {OO.ui.OptionWidget[]|OO.ui.OptionWidget|null} items Currently selected items */ /** * @event choose + * * A `choose` event is emitted when an item is chosen with the #chooseItem method. + * * @param {OO.ui.OptionWidget} item Chosen item + * @param {boolean} selected Item is selected */ /** @@ -6699,12 +6687,13 @@ OO.ui.SelectWidget.prototype.onMouseLeave = function () { OO.ui.SelectWidget.prototype.onDocumentKeyDown = function ( e ) { var nextItem, handled = false, - currentItem = this.findHighlightedItem() || this.findSelectedItem(); + currentItem = this.findHighlightedItem(), + firstItem = this.getItems()[ 0 ]; if ( !this.isDisabled() && this.isVisible() ) { switch ( e.keyCode ) { case OO.ui.Keys.ENTER: - if ( currentItem && currentItem.constructor.static.highlightable ) { + if ( currentItem ) { // Was only highlighted, now let's select it. No-op if already selected. this.chooseItem( currentItem ); handled = true; @@ -6713,18 +6702,18 @@ OO.ui.SelectWidget.prototype.onDocumentKeyDown = function ( e ) { case OO.ui.Keys.UP: case OO.ui.Keys.LEFT: this.clearKeyPressBuffer(); - nextItem = this.findRelativeSelectableItem( currentItem, -1 ); + nextItem = currentItem ? this.findRelativeSelectableItem( currentItem, -1 ) : firstItem; handled = true; break; case OO.ui.Keys.DOWN: case OO.ui.Keys.RIGHT: this.clearKeyPressBuffer(); - nextItem = this.findRelativeSelectableItem( currentItem, 1 ); + nextItem = currentItem ? this.findRelativeSelectableItem( currentItem, 1 ) : firstItem; handled = true; break; case OO.ui.Keys.ESCAPE: case OO.ui.Keys.TAB: - if ( currentItem && currentItem.constructor.static.highlightable ) { + if ( currentItem ) { currentItem.setHighlighted( false ); } this.unbindDocumentKeyDownListener(); @@ -6944,20 +6933,36 @@ OO.ui.SelectWidget.prototype.findTargetItem = function ( e ) { return $option.data( 'oo-ui-optionWidget' ) || null; }; +/** + * Find all selected items, if there are any. If the widget allows for multiselect + * it will return an array of selected options. If the widget doesn't allow for + * multiselect, it will return the selected option or null if no item is selected. + * + * @return {OO.ui.OptionWidget[]|OO.ui.OptionWidget|null} If the widget is multiselect + * then return an array of selected items (or empty array), + * if the widget is not multiselect, return a single selected item, or `null` + * if no item is selected + */ +OO.ui.SelectWidget.prototype.findSelectedItems = function () { + var selected = this.items.filter( function ( item ) { + return item.isSelected(); + } ); + + return this.multiselect ? + selected : + selected[ 0 ] || null; +}; + /** * Find selected item. * - * @return {OO.ui.OptionWidget|null} Selected item, `null` if no item is selected + * @return {OO.ui.OptionWidget[]|OO.ui.OptionWidget|null} If the widget is multiselect + * then return an array of selected items (or empty array), + * if the widget is not multiselect, return a single selected item, or `null` + * if no item is selected */ OO.ui.SelectWidget.prototype.findSelectedItem = function () { - var i, len; - - for ( i = 0, len = this.items.length; i < len; i++ ) { - if ( this.items[ i ].isSelected() ) { - return this.items[ i ]; - } - } - return null; + return this.findSelectedItems(); }; /** @@ -7104,6 +7109,30 @@ OO.ui.SelectWidget.prototype.selectItemByData = function ( data ) { return this.selectItem( itemFromData ); }; +/** + * Programmatically unselect an option by its reference. If the widget + * allows for multiple selections, there may be other items still selected; + * otherwise, no items will be selected. + * If no item is given, all selected items will be unselected. + * + * @param {OO.ui.OptionWidget} [item] Item to unselect + * @fires select + * @chainable + * @return {OO.ui.Widget} The widget, for chaining + */ +OO.ui.SelectWidget.prototype.unselectItem = function ( item ) { + if ( item ) { + item.setSelected( false ); + } else { + this.items.forEach( function ( item ) { + item.setSelected( false ); + } ); + } + + this.emit( 'select', this.findSelectedItems() ); + return this; +}; + /** * Programmatically select an option by its reference. If the `item` parameter is omitted, * all options will be deselected. @@ -7117,14 +7146,20 @@ OO.ui.SelectWidget.prototype.selectItem = function ( item ) { var i, len, selected, changed = false; - for ( i = 0, len = this.items.length; i < len; i++ ) { - selected = this.items[ i ] === item; - if ( this.items[ i ].isSelected() !== selected ) { - this.items[ i ].setSelected( selected ); - changed = true; + if ( this.multiselect && item ) { + // Select the item directly + item.setSelected( true ); + } else { + for ( i = 0, len = this.items.length; i < len; i++ ) { + selected = this.items[ i ] === item; + if ( this.items[ i ].isSelected() !== selected ) { + this.items[ i ].setSelected( selected ); + changed = true; + } } } if ( changed ) { + // TODO: When should a non-highlightable element be selected? if ( item && !item.constructor.static.highlightable ) { if ( item ) { this.$focusOwner.attr( 'aria-activedescendant', item.getElementId() ); @@ -7132,7 +7167,7 @@ OO.ui.SelectWidget.prototype.selectItem = function ( item ) { this.$focusOwner.removeAttr( 'aria-activedescendant' ); } } - this.emit( 'select', item ); + this.emit( 'select', this.findSelectedItems() ); } return this; @@ -7185,8 +7220,13 @@ OO.ui.SelectWidget.prototype.pressItem = function ( item ) { */ OO.ui.SelectWidget.prototype.chooseItem = function ( item ) { if ( item ) { - this.selectItem( item ); - this.emit( 'choose', item ); + if ( this.multiselect && item.isSelected() ) { + this.unselectItem( item ); + } else { + this.selectItem( item ); + } + + this.emit( 'choose', item, item.isSelected() ); } return this; @@ -7655,7 +7695,7 @@ OO.ui.MenuSelectWidget.prototype.onDocumentKeyDown = function ( e ) { break; case OO.ui.Keys.ESCAPE: case OO.ui.Keys.TAB: - if ( currentItem ) { + if ( currentItem && !this.multiselect ) { currentItem.setHighlighted( false ); } this.toggle( false ); @@ -7712,10 +7752,6 @@ OO.ui.MenuSelectWidget.prototype.updateItemVisibility = function () { section.toggle( showAll || !sectionEmpty ); } - if ( anyVisible && this.items.length && !exactMatch ) { - this.scrollItemIntoView( this.items[ 0 ] ); - } - if ( !anyVisible ) { this.highlightItem( null ); } @@ -7872,7 +7908,7 @@ OO.ui.MenuSelectWidget.prototype.clearItems = function () { * @inheritdoc */ OO.ui.MenuSelectWidget.prototype.toggle = function ( visible ) { - var change, originalHeight, flippedHeight; + var change, originalHeight, flippedHeight, selectedItem; visible = ( visible === undefined ? !this.visible : !!visible ) && !!this.items.length; change = visible !== this.isVisible(); @@ -7937,9 +7973,13 @@ OO.ui.MenuSelectWidget.prototype.toggle = function ( visible ) { this.$focusOwner.attr( 'aria-expanded', 'true' ); - if ( this.findSelectedItem() ) { - this.$focusOwner.attr( 'aria-activedescendant', this.findSelectedItem().getElementId() ); - this.findSelectedItem().scrollElementIntoView( { duration: 0 } ); + selectedItem = this.findSelectedItem(); + if ( !this.multiselect && selectedItem ) { + // TODO: Verify if this is even needed; This is already done on highlight changes + // in SelectWidget#highlightItem, so we should just need to highlight the item we need to + // highlight here and not bother with attr or checking selections. + this.$focusOwner.attr( 'aria-activedescendant', selectedItem.getElementId() ); + selectedItem.scrollElementIntoView( { duration: 0 } ); } // Auto-hide @@ -7962,6 +8002,13 @@ OO.ui.MenuSelectWidget.prototype.toggle = function ( visible ) { return this; }; +/** + * Scroll to the top of the menu + */ +OO.ui.MenuSelectWidget.prototype.scrollToTop = function () { + this.$element.scrollTop( 0 ); +}; + /** * DropdownWidgets are not menus themselves, rather they contain a menu of options created with * OO.ui.MenuOptionWidget. The DropdownWidget takes care of opening and displaying the menu so that @@ -9401,6 +9448,7 @@ OO.ui.ButtonInputWidget.prototype.getInputId = function () { * @param {Object} [config] Configuration options * @cfg {boolean} [selected=false] Select the checkbox initially. By default, the checkbox is * not selected. + * @cfg {boolean} [indeterminate=false] Whether the checkbox is in the indeterminate state. */ OO.ui.CheckboxInputWidget = function OoUiCheckboxInputWidget( config ) { // Configuration initialization @@ -9421,12 +9469,24 @@ OO.ui.CheckboxInputWidget = function OoUiCheckboxInputWidget( config ) { // Required for pretty styling in WikimediaUI theme .append( this.checkIcon.$element ); this.setSelected( config.selected !== undefined ? config.selected : false ); + this.setIndeterminate( config.indeterminate !== undefined ? config.indeterminate : false ); }; /* Setup */ OO.inheritClass( OO.ui.CheckboxInputWidget, OO.ui.InputWidget ); +/* Events */ + +/** + * @event change + * + * A change event is emitted when the state of the input changes. + * + * @param {boolean} selected + * @param {boolean} indeterminate + */ + /* Static Properties */ /** @@ -9465,6 +9525,7 @@ OO.ui.CheckboxInputWidget.prototype.onEdit = function () { // Allow the stack to clear so the value will be updated setTimeout( function () { widget.setSelected( widget.$input.prop( 'checked' ) ); + widget.setIndeterminate( widget.$input.prop( 'indeterminate' ) ); } ); } }; @@ -9472,16 +9533,20 @@ OO.ui.CheckboxInputWidget.prototype.onEdit = function () { /** * Set selection state of this checkbox. * - * @param {boolean} state `true` for selected + * @param {boolean} state Selected state + * @param {boolean} internal Used for internal calls to suppress events * @chainable - * @return {OO.ui.Widget} The widget, for chaining + * @return {OO.ui.CheckboxInputWidget} The widget, for chaining */ -OO.ui.CheckboxInputWidget.prototype.setSelected = function ( state ) { +OO.ui.CheckboxInputWidget.prototype.setSelected = function ( state, internal ) { state = !!state; if ( this.selected !== state ) { this.selected = state; this.$input.prop( 'checked', this.selected ); - this.emit( 'change', this.selected ); + if ( !internal ) { + this.setIndeterminate( false, true ); + this.emit( 'change', this.selected, this.indeterminate ); + } } // The first time that the selection state is set (probably while constructing the widget), // remember it in defaultSelected. This property can be later used to check whether @@ -9508,6 +9573,42 @@ OO.ui.CheckboxInputWidget.prototype.isSelected = function () { return this.selected; }; +/** + * Set indeterminate state of this checkbox. + * + * @param {boolean} state Indeterminate state + * @param {boolean} internal Used for internal calls to suppress events + * @chainable + * @return {OO.ui.CheckboxInputWidget} The widget, for chaining + */ +OO.ui.CheckboxInputWidget.prototype.setIndeterminate = function ( state, internal ) { + state = !!state; + if ( this.indeterminate !== state ) { + this.indeterminate = state; + this.$input.prop( 'indeterminate', this.indeterminate ); + if ( !internal ) { + this.setSelected( false, true ); + this.emit( 'change', this.selected, this.indeterminate ); + } + } + return this; +}; + +/** + * Check if this checkbox is selected. + * + * @return {boolean} Checkbox is selected + */ +OO.ui.CheckboxInputWidget.prototype.isIndeterminate = function () { + // Resynchronize our internal data with DOM data. Other scripts executing on the page can modify + // it, and we won't know unless they're kind enough to trigger a 'change' event. + var indeterminate = this.$input.prop( 'indeterminate' ); + if ( this.indeterminate !== indeterminate ) { + this.setIndeterminate( indeterminate ); + } + return this.indeterminate; +}; + /** * @inheritdoc */ @@ -10397,7 +10498,7 @@ OO.ui.CheckboxMultiselectInputWidget.prototype.focus = function () { * // A TextInputWidget. * var textInput = new OO.ui.TextInputWidget( { * value: 'Text input' - * } ) + * } ); * $( document.body ).append( textInput.$element ); * * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs @@ -11117,6 +11218,7 @@ OO.ui.SearchInputWidget = function OoUiSearchInputWidget( config ) { this.connect( this, { change: 'onChange' } ); + this.$indicator.on( 'click', this.onIndicatorClick.bind( this ) ); // Initialization this.updateSearchIndicator(); @@ -11140,9 +11242,12 @@ OO.ui.SearchInputWidget.prototype.getSaneType = function () { }; /** - * @inheritdoc + * Handle click events on the indicator + * + * @param {jQuery.Event} e Click event + * @return {boolean} */ -OO.ui.SearchInputWidget.prototype.onIndicatorMouseDown = function ( e ) { +OO.ui.SearchInputWidget.prototype.onIndicatorClick = function ( e ) { if ( e.which === OO.ui.MouseButtons.LEFT ) { // Clear the text field this.setValue( '' ); @@ -11205,8 +11310,8 @@ OO.ui.SearchInputWidget.prototype.setReadOnly = function ( state ) { * // A MultilineTextInputWidget. * var multilineTextInput = new OO.ui.MultilineTextInputWidget( { * value: 'Text input on multiple lines' - * } ) - * $( 'body' ).append( multilineTextInput.$element ); + * } ); + * $( document.body ).append( multilineTextInput.$element ); * * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs#MultilineTextInputWidget * @@ -12360,21 +12465,21 @@ OO.ui.FieldsetLayout.static.tagName = 'fieldset'; * [2]: https://www.mediawiki.org/wiki/OOUI/Widgets/Inputs * * @example - * // Example of a form layout that wraps a fieldset layout + * // Example of a form layout that wraps a fieldset layout. * var input1 = new OO.ui.TextInputWidget( { - * placeholder: 'Username' - * } ); - * var input2 = new OO.ui.TextInputWidget( { - * placeholder: 'Password', - * type: 'password' - * } ); - * var submit = new OO.ui.ButtonInputWidget( { - * label: 'Submit' - * } ); + * placeholder: 'Username' + * } ), + * input2 = new OO.ui.TextInputWidget( { + * placeholder: 'Password', + * type: 'password' + * } ), + * submit = new OO.ui.ButtonInputWidget( { + * label: 'Submit' + * } ), + * fieldset = new OO.ui.FieldsetLayout( { + * label: 'A form layout' + * } ); * - * var fieldset = new OO.ui.FieldsetLayout( { - * label: 'A form layout' - * } ); * fieldset.addItems( [ * new OO.ui.FieldLayout( input1, { * label: 'Username', @@ -12565,7 +12670,7 @@ OO.ui.PanelLayout.prototype.focus = function () { * Note that inline elements, such as OO.ui.ButtonWidgets, do not need this wrapper. * * @example - * // HorizontalLayout with a text input and a label + * // HorizontalLayout with a text input and a label. * var layout = new OO.ui.HorizontalLayout( { * items: [ * new OO.ui.LabelWidget( { label: 'Label' } ), @@ -12978,6 +13083,269 @@ OO.ui.NumberInputWidget.prototype.setDisabled = function ( disabled ) { return this; }; +/** + * SelectFileInputWidgets allow for selecting files, using . These + * widgets can be configured with {@link OO.ui.mixin.IconElement icons}, {@link + * OO.ui.mixin.IndicatorElement indicators} and {@link OO.ui.mixin.TitledElement titles}. + * Please see the [OOUI documentation on MediaWiki] [1] for more information and examples. + * + * SelectFileInputWidgets must be used in HTML forms, as getValue only returns the filename. + * + * @example + * // A file select input widget. + * var selectFile = new OO.ui.SelectFileInputWidget(); + * $( document.body ).append( selectFile.$element ); + * + * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets + * + * @class + * @extends OO.ui.InputWidget + * + * @constructor + * @param {Object} [config] Configuration options + * @cfg {string[]|null} [accept=null] MIME types to accept. null accepts all types. + * @cfg {boolean} [multiple=false] Allow multiple files to be selected. + * @cfg {string} [placeholder] Text to display when no file is selected. + * @cfg {Object} [button] Config to pass to select file button. + * @cfg {string} [icon] Icon to show next to file info + */ +OO.ui.SelectFileInputWidget = function OoUiSelectFileInputWidget( config ) { + var widget = this; + + config = config || {}; + + // Construct buttons before parent method is called (calling setDisabled) + this.selectButton = new OO.ui.ButtonWidget( $.extend( { + $element: $( '