X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=resources%2Flib%2Fooui%2Foojs-ui-widgets.js;h=da0ddb6e825b21262d8dfb26b1598cde4c125004;hb=4732f11a5d4b75616150e36a2c24ccaffc89c265;hp=c59ca5da83433c8047b61c6d674c9fd08d27b907;hpb=216c7d838012c52961c955f40a93af8a4087e68f;p=lhc%2Fweb%2Fwiklou.git diff --git a/resources/lib/ooui/oojs-ui-widgets.js b/resources/lib/ooui/oojs-ui-widgets.js index c59ca5da83..da0ddb6e82 100644 --- a/resources/lib/ooui/oojs-ui-widgets.js +++ b/resources/lib/ooui/oojs-ui-widgets.js @@ -1,12 +1,12 @@ /*! - * OOUI v0.28.2 + * OOUI v0.29.3 * https://www.mediawiki.org/wiki/OOUI * * Copyright 2011–2018 OOUI Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2018-09-11T23:05:15Z + * Date: 2018-11-01T02:03:33Z */ ( function ( OO ) { @@ -1263,7 +1263,7 @@ OO.ui.PageLayout.prototype.setActive = function ( active ) { * by setting the #continuous option to 'true'. * * @example - * // A stack layout with two panels, configured to be displayed continously + * // A stack layout with two panels, configured to be displayed continuously * var myStack = new OO.ui.StackLayout( { * items: [ * new OO.ui.PanelLayout( { @@ -1506,6 +1506,30 @@ OO.ui.StackLayout.prototype.setItem = function ( item ) { return this; }; +/** + * Reset the scroll offset of all panels, or the container if continuous + * + * @inheritdoc + */ +OO.ui.StackLayout.prototype.resetScroll = function () { + if ( this.continuous ) { + // Parent method + return OO.ui.StackLayout.parent.prototype.resetScroll.call( this ); + } + // Reset each panel + this.getItems().forEach( function ( panel ) { + var hidden = panel.$element.hasClass( 'oo-ui-element-hidden' ); + // Scroll can only be reset when panel is visible + panel.$element.removeClass( 'oo-ui-element-hidden' ); + panel.resetScroll(); + if ( hidden ) { + panel.$element.addClass( 'oo-ui-element-hidden' ); + } + } ); + + return this; +}; + /** * Update the visibility of all items in case of non-continuous view. * @@ -1538,34 +1562,37 @@ OO.ui.StackLayout.prototype.updateHiddenState = function ( items, selectedItem ) * and its size is customized with the #menuSize config. The content area will fill all remaining space. * * @example - * var menuLayout = new OO.ui.MenuLayout( { - * position: 'top' - * } ), + * var menuLayout, * menuPanel = new OO.ui.PanelLayout( { padded: true, expanded: true, scrollable: true } ), * contentPanel = new OO.ui.PanelLayout( { padded: true, expanded: true, scrollable: true } ), * select = new OO.ui.SelectWidget( { * items: [ * new OO.ui.OptionWidget( { * data: 'before', - * label: 'Before', + * label: 'Before' * } ), * new OO.ui.OptionWidget( { * data: 'after', - * label: 'After', + * label: 'After' * } ), * new OO.ui.OptionWidget( { * data: 'top', - * label: 'Top', + * label: 'Top' * } ), * new OO.ui.OptionWidget( { * data: 'bottom', - * label: 'Bottom', + * label: 'Bottom' * } ) * ] * } ).on( 'select', function ( item ) { * menuLayout.setMenuPosition( item.getData() ); * } ); * + * menuLayout = new OO.ui.MenuLayout( { + * position: 'top', + * menuPanel: menuPanel, + * contentPanel: contentPanel + * } ) * menuLayout.$menu.append( * menuPanel.$element.append( 'Menu panel', select.$element ) * ); @@ -1580,9 +1607,10 @@ OO.ui.StackLayout.prototype.updateHiddenState = function ( items, selectedItem ) * may be omitted. * * .oo-ui-menuLayout-menu { - * height: 200px; * width: 200px; + * height: 200px; * } + * * .oo-ui-menuLayout-content { * top: 200px; * left: 200px; @@ -1595,6 +1623,8 @@ OO.ui.StackLayout.prototype.updateHiddenState = function ( items, selectedItem ) * * @constructor * @param {Object} [config] Configuration options + * @cfg {OO.ui.PanelLayout} [menuPanel] Menu panel + * @cfg {OO.ui.PanelLayout} [contentPanel] Content panel * @cfg {boolean} [expanded=true] Expand the layout to fill the entire parent element. * @cfg {boolean} [showMenu=true] Show menu * @cfg {string} [menuPosition='before'] Position of menu: `top`, `after`, `bottom` or `before` @@ -1610,6 +1640,8 @@ OO.ui.MenuLayout = function OoUiMenuLayout( config ) { // Parent constructor OO.ui.MenuLayout.parent.call( this, config ); + this.menuPanel = null; + this.contentPanel = null; this.expanded = !!config.expanded; /** * Menu DOM node @@ -1635,6 +1667,12 @@ OO.ui.MenuLayout = function OoUiMenuLayout( config ) { } else { this.$element.addClass( 'oo-ui-menuLayout-static' ); } + if ( config.menuPanel ) { + this.setMenuPanel( config.menuPanel ); + } + if ( config.contentPanel ) { + this.setContentPanel( config.contentPanel ); + } this.setMenuPosition( config.menuPosition ); this.toggleMenu( config.showMenu ); }; @@ -1703,6 +1741,58 @@ OO.ui.MenuLayout.prototype.getMenuPosition = function () { return this.menuPosition; }; +/** + * Set the menu panel. + * + * @param {OO.ui.PanelLayout} menuPanel Menu panel + */ +OO.ui.MenuLayout.prototype.setMenuPanel = function ( menuPanel ) { + this.menuPanel = menuPanel; + this.$menu.append( this.menuPanel.$element ); +}; + +/** + * Set the content panel. + * + * @param {OO.ui.PanelLayout} menuPanel Content panel + */ +OO.ui.MenuLayout.prototype.setContentPanel = function ( contentPanel ) { + this.contentPanel = contentPanel; + this.$content.append( this.contentPanel.$element ); +}; + +/** + * Clear the menu panel. + */ +OO.ui.MenuLayout.prototype.clearMenuPanel = function () { + this.menuPanel = null; + this.$menu.empty(); +}; + +/** + * Clear the content panel. + */ +OO.ui.MenuLayout.prototype.clearContentPanel = function () { + this.contentPanel = null; + this.$content.empty(); +}; + +/** + * Reset the scroll offset of all panels and the tab select widget + * + * @inheritdoc + */ +OO.ui.MenuLayout.prototype.resetScroll = function () { + if ( this.menuPanel ) { + this.menuPanel.resetScroll(); + } + if ( this.contentPanel ) { + this.contentPanel.resetScroll(); + } + + return this; +}; + /** * BookletLayouts contain {@link OO.ui.PageLayout page layouts} as well as * an {@link OO.ui.OutlineSelectWidget outline} that allows users to easily navigate @@ -1740,7 +1830,7 @@ OO.ui.MenuLayout.prototype.getMenuPosition = function () { * outlined: true * } ); * - * booklet.addPages ( [ page1, page2 ] ); + * booklet.addPages( [ page1, page2 ] ); * $( 'body' ).append( booklet.$element ); * * @class @@ -1768,7 +1858,7 @@ OO.ui.BookletLayout = function OoUiBookletLayout( config ) { continuous: !!config.continuous, expanded: this.expanded } ); - this.$content.append( this.stackLayout.$element ); + this.setContentPanel( this.stackLayout ); this.autoFocus = config.autoFocus === undefined || !!config.autoFocus; this.outlineVisible = false; this.outlined = !!config.outlined; @@ -1780,7 +1870,7 @@ OO.ui.BookletLayout = function OoUiBookletLayout( config ) { expanded: this.expanded, scrollable: true } ); - this.$menu.append( this.outlinePanel.$element ); + this.setMenuPanel( this.outlinePanel ); this.outlineVisible = true; if ( this.editable ) { this.outlineControlsWidget = new OO.ui.OutlineControlsWidget( @@ -2268,6 +2358,23 @@ OO.ui.BookletLayout.prototype.setPage = function ( name ) { } }; +/** + * For outlined-continuous booklets, also reset the outlineSelectWidget to the first item. + * + * @inheritdoc + */ +OO.ui.BookletLayout.prototype.resetScroll = function () { + // Parent method + OO.ui.BookletLayout.parent.prototype.resetScroll.call( this ); + + if ( this.outlined && this.stackLayout.continuous && this.outlineSelectWidget.findFirstSelectableItem() ) { + this.scrolling = true; + this.outlineSelectWidget.selectItem( this.outlineSelectWidget.findFirstSelectableItem() ); + this.scrolling = false; + } + return this; +}; + /** * Select the first selectable page. * @@ -2309,7 +2416,7 @@ OO.ui.BookletLayout.prototype.selectFirstSelectablePage = function () { * * var index = new OO.ui.IndexLayout(); * - * index.addTabPanels ( [ tabPanel1, tabPanel2 ] ); + * index.addTabPanels( [ tabPanel1, tabPanel2 ] ); * $( 'body' ).append( index.$element ); * * @class @@ -2336,14 +2443,14 @@ OO.ui.IndexLayout = function OoUiIndexLayout( config ) { continuous: !!config.continuous, expanded: this.expanded } ); - this.$content.append( this.stackLayout.$element ); + this.setContentPanel( this.stackLayout ); this.autoFocus = config.autoFocus === undefined || !!config.autoFocus; this.tabSelectWidget = new OO.ui.TabSelectWidget(); this.tabPanel = new OO.ui.PanelLayout( { expanded: this.expanded } ); - this.$menu.append( this.tabPanel.$element ); + this.setMenuPanel( this.tabPanel ); this.toggleMenu( true ); @@ -2670,10 +2777,11 @@ OO.ui.IndexLayout.prototype.clearTabPanels = function () { OO.ui.IndexLayout.prototype.setTabPanel = function ( name ) { var selectedItem, $focused, - tabPanel = this.tabPanels[ name ], - previousTabPanel = this.currentTabPanelName && this.tabPanels[ this.currentTabPanelName ]; + previousTabPanel, + tabPanel = this.tabPanels[ name ]; if ( name !== this.currentTabPanelName ) { + previousTabPanel = this.getCurrentTabPanel(); selectedItem = this.tabSelectWidget.findSelectedItem(); if ( selectedItem && selectedItem.getData() !== name ) { this.tabSelectWidget.selectItemByData( name ); @@ -3363,8 +3471,8 @@ OO.ui.OutlineSelectWidget = function OoUiOutlineSelectWidget( config ) { // Events this.$element.on( { - focus: this.bindKeyDownListener.bind( this ), - blur: this.unbindKeyDownListener.bind( this ) + focus: this.bindDocumentKeyDownListener.bind( this ), + blur: this.unbindDocumentKeyDownListener.bind( this ) } ); // Initialization @@ -3502,8 +3610,8 @@ OO.ui.ButtonSelectWidget = function OoUiButtonSelectWidget( config ) { // Events this.$element.on( { - focus: this.bindKeyDownListener.bind( this ), - blur: this.unbindKeyDownListener.bind( this ) + focus: this.bindDocumentKeyDownListener.bind( this ), + blur: this.unbindDocumentKeyDownListener.bind( this ) } ); // Initialization @@ -3574,8 +3682,8 @@ OO.ui.TabSelectWidget = function OoUiTabSelectWidget( config ) { // Events this.$element.on( { - focus: this.bindKeyDownListener.bind( this ), - blur: this.unbindKeyDownListener.bind( this ) + focus: this.bindDocumentKeyDownListener.bind( this ), + blur: this.unbindDocumentKeyDownListener.bind( this ) } ); // Initialization @@ -3589,930 +3697,6 @@ OO.ui.TabSelectWidget = function OoUiTabSelectWidget( config ) { OO.inheritClass( OO.ui.TabSelectWidget, OO.ui.SelectWidget ); OO.mixinClass( OO.ui.TabSelectWidget, OO.ui.mixin.TabIndexedElement ); -/** - * CapsuleItemWidgets are used within a {@link OO.ui.CapsuleMultiselectWidget - * CapsuleMultiselectWidget} to display the selected items. - * - * @class - * @extends OO.ui.Widget - * @mixins OO.ui.mixin.ItemWidget - * @mixins OO.ui.mixin.LabelElement - * @mixins OO.ui.mixin.FlaggedElement - * @mixins OO.ui.mixin.TabIndexedElement - * - * @constructor - * @param {Object} [config] Configuration options - * @deprecated - */ -OO.ui.CapsuleItemWidget = function OoUiCapsuleItemWidget( config ) { - // Configuration initialization - config = config || {}; - - // Parent constructor - OO.ui.CapsuleItemWidget.parent.call( this, config ); - - // Mixin constructors - OO.ui.mixin.ItemWidget.call( this ); - OO.ui.mixin.LabelElement.call( this, config ); - OO.ui.mixin.FlaggedElement.call( this, config ); - OO.ui.mixin.TabIndexedElement.call( this, config ); - - // Events - this.closeButton = new OO.ui.ButtonWidget( { - framed: false, - icon: 'close', - tabIndex: -1, - title: OO.ui.msg( 'ooui-item-remove' ) - } ).on( 'click', this.onCloseClick.bind( this ) ); - - this.on( 'disable', function ( disabled ) { - this.closeButton.setDisabled( disabled ); - }.bind( this ) ); - - // Initialization - this.$element - .on( { - click: this.onClick.bind( this ), - keydown: this.onKeyDown.bind( this ) - } ) - .addClass( 'oo-ui-capsuleItemWidget' ) - .append( this.$label, this.closeButton.$element ); -}; - -/* Setup */ - -OO.inheritClass( OO.ui.CapsuleItemWidget, OO.ui.Widget ); -OO.mixinClass( OO.ui.CapsuleItemWidget, OO.ui.mixin.ItemWidget ); -OO.mixinClass( OO.ui.CapsuleItemWidget, OO.ui.mixin.LabelElement ); -OO.mixinClass( OO.ui.CapsuleItemWidget, OO.ui.mixin.FlaggedElement ); -OO.mixinClass( OO.ui.CapsuleItemWidget, OO.ui.mixin.TabIndexedElement ); - -/* Methods */ - -/** - * Handle close icon clicks - */ -OO.ui.CapsuleItemWidget.prototype.onCloseClick = function () { - var element = this.getElementGroup(); - - if ( element && $.isFunction( element.removeItems ) ) { - element.removeItems( [ this ] ); - element.focus(); - } -}; - -/** - * Handle click event for the entire capsule - */ -OO.ui.CapsuleItemWidget.prototype.onClick = function () { - var element = this.getElementGroup(); - - if ( !this.isDisabled() && element && $.isFunction( element.editItem ) ) { - element.editItem( this ); - } -}; - -/** - * Handle keyDown event for the entire capsule - * - * @param {jQuery.Event} e Key down event - */ -OO.ui.CapsuleItemWidget.prototype.onKeyDown = function ( e ) { - var element = this.getElementGroup(); - - if ( e.keyCode === OO.ui.Keys.BACKSPACE || e.keyCode === OO.ui.Keys.DELETE ) { - element.removeItems( [ this ] ); - element.focus(); - return false; - } else if ( e.keyCode === OO.ui.Keys.ENTER ) { - element.editItem( this ); - return false; - } else if ( e.keyCode === OO.ui.Keys.LEFT ) { - element.getPreviousItem( this ).focus(); - } else if ( e.keyCode === OO.ui.Keys.RIGHT ) { - element.getNextItem( this ).focus(); - } -}; - -/** - * CapsuleMultiselectWidgets are something like a {@link OO.ui.ComboBoxInputWidget combo box widget} - * that allows for selecting multiple values. - * - * For more information about menus and options, please see the [OOUI documentation on MediaWiki][1]. - * - * @example - * // Example: A CapsuleMultiselectWidget. - * var capsule = new OO.ui.CapsuleMultiselectWidget( { - * label: 'CapsuleMultiselectWidget', - * selected: [ 'Option 1', 'Option 3' ], - * menu: { - * items: [ - * new OO.ui.MenuOptionWidget( { - * data: 'Option 1', - * label: 'Option One' - * } ), - * new OO.ui.MenuOptionWidget( { - * data: 'Option 2', - * label: 'Option Two' - * } ), - * new OO.ui.MenuOptionWidget( { - * data: 'Option 3', - * label: 'Option Three' - * } ), - * new OO.ui.MenuOptionWidget( { - * data: 'Option 4', - * label: 'Option Four' - * } ), - * new OO.ui.MenuOptionWidget( { - * data: 'Option 5', - * label: 'Option Five' - * } ) - * ] - * } - * } ); - * $( 'body' ).append( capsule.$element ); - * - * [1]: https://www.mediawiki.org/wiki/OOUI/Widgets/Selects_and_Options#Menu_selects_and_options - * - * @class - * @extends OO.ui.Widget - * @mixins OO.ui.mixin.GroupElement - * @mixins OO.ui.mixin.PopupElement - * @mixins OO.ui.mixin.TabIndexedElement - * @mixins OO.ui.mixin.IndicatorElement - * @mixins OO.ui.mixin.IconElement - * @uses OO.ui.CapsuleItemWidget - * @uses OO.ui.MenuSelectWidget - * - * @constructor - * @param {Object} [config] Configuration options - * @cfg {string} [placeholder] Placeholder text - * @cfg {boolean} [allowArbitrary=false] Allow data items to be added even if not present in the menu. - * @cfg {boolean} [allowDuplicates=false] Allow duplicate items to be added. - * @cfg {Object} [menu] (required) Configuration options to pass to the - * {@link OO.ui.MenuSelectWidget menu select widget}. - * @cfg {Object} [popup] Configuration options to pass to the {@link OO.ui.PopupWidget popup widget}. - * If specified, this popup will be shown instead of the menu (but the menu - * will still be used for item labels and allowArbitrary=false). The widgets - * in the popup should use {@link #addItemsFromData} or {@link #addItems} as necessary. - * @cfg {jQuery} [$overlay=this.$element] Render the menu or popup into a separate layer. - * This configuration is useful in cases where the expanded menu is larger than - * its containing `
`. The specified overlay layer is usually on top of - * the containing `
` and has a larger area. By default, the menu uses - * relative positioning. - * See . - * @deprecated - */ -OO.ui.CapsuleMultiselectWidget = function OoUiCapsuleMultiselectWidget( config ) { - var $tabFocus; - - // Parent constructor - OO.ui.CapsuleMultiselectWidget.parent.call( this, config ); - - // Configuration initialization - config = $.extend( { - allowArbitrary: false, - allowDuplicates: false - }, config ); - - // Properties (must be set before mixin constructor calls) - this.$handle = $( '
' ); - this.$input = config.popup ? null : $( '' ); - if ( config.placeholder !== undefined && config.placeholder !== '' ) { - this.$input.attr( 'placeholder', config.placeholder ); - } - - // Mixin constructors - OO.ui.mixin.GroupElement.call( this, config ); - if ( config.popup ) { - config.popup = $.extend( {}, config.popup, { - align: 'forwards', - anchor: false - } ); - OO.ui.mixin.PopupElement.call( this, config ); - $tabFocus = $( '' ); - OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: $tabFocus } ) ); - } else { - this.popup = null; - $tabFocus = null; - OO.ui.mixin.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$input } ) ); - } - OO.ui.mixin.IndicatorElement.call( this, config ); - OO.ui.mixin.IconElement.call( this, config ); - - // Properties - this.$content = $( '
' ); - this.allowArbitrary = config.allowArbitrary; - this.allowDuplicates = config.allowDuplicates; - this.$overlay = ( config.$overlay === true ? OO.ui.getDefaultOverlay() : config.$overlay ) || this.$element; - this.menu = new OO.ui.MenuSelectWidget( $.extend( - { - widget: this, - $input: this.$input, - $floatableContainer: this.$element, - filterFromInput: true, - disabled: this.isDisabled() - }, - config.menu - ) ); - - // Events - if ( this.popup ) { - $tabFocus.on( { - focus: this.focus.bind( this ) - } ); - this.popup.$element.on( 'focusout', this.onPopupFocusOut.bind( this ) ); - if ( this.popup.$autoCloseIgnore ) { - this.popup.$autoCloseIgnore.on( 'focusout', this.onPopupFocusOut.bind( this ) ); - } - this.popup.connect( this, { - toggle: function ( visible ) { - $tabFocus.toggle( !visible ); - } - } ); - } else { - this.$input.on( { - focus: this.onInputFocus.bind( this ), - blur: this.onInputBlur.bind( this ), - 'propertychange change click mouseup keydown keyup input cut paste select focus': - OO.ui.debounce( this.updateInputSize.bind( this ) ), - keydown: this.onKeyDown.bind( this ), - keypress: this.onKeyPress.bind( this ) - } ); - } - this.menu.connect( this, { - choose: 'onMenuChoose', - toggle: 'onMenuToggle', - add: 'onMenuItemsChange', - remove: 'onMenuItemsChange' - } ); - this.$handle.on( { - mousedown: this.onMouseDown.bind( this ) - } ); - - // Initialization - if ( this.$input ) { - this.$input.prop( 'disabled', this.isDisabled() ); - this.$input.attr( { - role: 'combobox', - 'aria-owns': this.menu.getElementId(), - 'aria-autocomplete': 'list' - } ); - } - if ( config.data ) { - this.setItemsFromData( config.data ); - } - this.$content.addClass( 'oo-ui-capsuleMultiselectWidget-content' ) - .append( this.$group ); - this.$group.addClass( 'oo-ui-capsuleMultiselectWidget-group' ); - this.$handle.addClass( 'oo-ui-capsuleMultiselectWidget-handle' ) - .append( this.$indicator, this.$icon, this.$content ); - this.$element.addClass( 'oo-ui-capsuleMultiselectWidget' ) - .append( this.$handle ); - if ( this.popup ) { - this.popup.$element.addClass( 'oo-ui-capsuleMultiselectWidget-popup' ); - this.$content.append( $tabFocus ); - this.$overlay.append( this.popup.$element ); - } else { - this.$content.append( this.$input ); - this.$overlay.append( this.menu.$element ); - } - if ( $tabFocus ) { - $tabFocus.addClass( 'oo-ui-capsuleMultiselectWidget-focusTrap' ); - } - - // Input size needs to be calculated after everything else is rendered - setTimeout( function () { - if ( this.$input ) { - this.updateInputSize(); - } - }.bind( this ) ); - - this.onMenuItemsChange(); - - // Deprecation warning - OO.ui.warnDeprecation( 'CapsuleMultiselectWidget: Deprecated widget. Use TagMultiselectWidget instead. See T183299.' ); -}; - -/* Setup */ - -OO.inheritClass( OO.ui.CapsuleMultiselectWidget, OO.ui.Widget ); -OO.mixinClass( OO.ui.CapsuleMultiselectWidget, OO.ui.mixin.GroupElement ); -OO.mixinClass( OO.ui.CapsuleMultiselectWidget, OO.ui.mixin.PopupElement ); -OO.mixinClass( OO.ui.CapsuleMultiselectWidget, OO.ui.mixin.TabIndexedElement ); -OO.mixinClass( OO.ui.CapsuleMultiselectWidget, OO.ui.mixin.IndicatorElement ); -OO.mixinClass( OO.ui.CapsuleMultiselectWidget, OO.ui.mixin.IconElement ); - -/* Events */ - -/** - * @event change - * - * A change event is emitted when the set of selected items changes. - * - * @param {Mixed[]} datas Data of the now-selected items - */ - -/** - * @event resize - * - * A resize event is emitted when the widget's dimensions change to accomodate newly added items or - * current user input. - */ - -/* Methods */ - -/** - * Construct a OO.ui.CapsuleItemWidget (or a subclass thereof) from given label and data. - * May return `null` if the given label and data are not valid. - * - * @protected - * @param {Mixed} data Custom data of any type. - * @param {string} label The label text. - * @return {OO.ui.CapsuleItemWidget|null} - */ -OO.ui.CapsuleMultiselectWidget.prototype.createItemWidget = function ( data, label ) { - if ( label === '' ) { - return null; - } - return new OO.ui.CapsuleItemWidget( { data: data, label: label } ); -}; - -/** - * @inheritdoc - */ -OO.ui.CapsuleMultiselectWidget.prototype.getInputId = function () { - if ( !this.$input ) { - return null; - } - return OO.ui.mixin.TabIndexedElement.prototype.getInputId.call( this ); -}; - -/** - * Get the data of the items in the capsule - * - * @return {Mixed[]} - */ -OO.ui.CapsuleMultiselectWidget.prototype.getItemsData = function () { - return this.getItems().map( function ( item ) { - return item.data; - } ); -}; - -/** - * Set the items in the capsule by providing data - * - * @chainable - * @param {Mixed[]} datas - * @return {OO.ui.CapsuleMultiselectWidget} - */ -OO.ui.CapsuleMultiselectWidget.prototype.setItemsFromData = function ( datas ) { - var widget = this, - menu = this.menu, - items = this.getItems(); - - $.each( datas, function ( i, data ) { - var j, label, - item = menu.findItemFromData( data ); - - if ( item ) { - label = item.label; - } else if ( widget.allowArbitrary ) { - label = String( data ); - } else { - return; - } - - item = null; - for ( j = 0; j < items.length; j++ ) { - if ( items[ j ].data === data && items[ j ].label === label ) { - item = items[ j ]; - items.splice( j, 1 ); - break; - } - } - if ( !item ) { - item = widget.createItemWidget( data, label ); - } - if ( item ) { - widget.addItems( [ item ], i ); - } - } ); - - if ( items.length ) { - widget.removeItems( items ); - } - - return this; -}; - -/** - * Add items to the capsule by providing their data - * - * @chainable - * @param {Mixed[]} datas - * @return {OO.ui.CapsuleMultiselectWidget} - */ -OO.ui.CapsuleMultiselectWidget.prototype.addItemsFromData = function ( datas ) { - var widget = this, - menu = this.menu, - items = []; - - $.each( datas, function ( i, data ) { - var item; - - if ( !widget.findItemFromData( data ) || widget.allowDuplicates ) { - item = menu.findItemFromData( data ); - if ( item ) { - item = widget.createItemWidget( data, item.label ); - } else if ( widget.allowArbitrary ) { - item = widget.createItemWidget( data, String( data ) ); - } - if ( item ) { - items.push( item ); - } - } - } ); - - if ( items.length ) { - this.addItems( items ); - } - - return this; -}; - -/** - * Add items to the capsule by providing a label - * - * @param {string} label - * @return {boolean} Whether the item was added or not - */ -OO.ui.CapsuleMultiselectWidget.prototype.addItemFromLabel = function ( label ) { - var item, items; - item = this.menu.getItemFromLabel( label, true ); - if ( item ) { - this.addItemsFromData( [ item.data ] ); - return true; - } else if ( this.allowArbitrary ) { - items = this.getItems(); - this.addItemsFromData( [ label ] ); - return !OO.compare( this.getItems(), items ); - } - return false; -}; - -/** - * Remove items by data - * - * @chainable - * @param {Mixed[]} datas - * @return {OO.ui.CapsuleMultiselectWidget} - */ -OO.ui.CapsuleMultiselectWidget.prototype.removeItemsFromData = function ( datas ) { - var widget = this, - items = []; - - $.each( datas, function ( i, data ) { - var item = widget.findItemFromData( data ); - if ( item ) { - items.push( item ); - } - } ); - - if ( items.length ) { - this.removeItems( items ); - } - - return this; -}; - -/** - * @inheritdoc - */ -OO.ui.CapsuleMultiselectWidget.prototype.addItems = function ( items ) { - var same, i, l, - oldItems = this.items.slice(); - - OO.ui.mixin.GroupElement.prototype.addItems.call( this, items ); - - if ( this.items.length !== oldItems.length ) { - same = false; - } else { - same = true; - for ( i = 0, l = oldItems.length; same && i < l; i++ ) { - same = same && this.items[ i ] === oldItems[ i ]; - } - } - if ( !same ) { - this.emit( 'change', this.getItemsData() ); - this.updateInputSize(); - } - - return this; -}; - -/** - * Removes the item from the list and copies its label to `this.$input`. - * - * @param {Object} item - */ -OO.ui.CapsuleMultiselectWidget.prototype.editItem = function ( item ) { - this.addItemFromLabel( this.$input.val() ); - this.clearInput(); - this.$input.val( item.label ); - this.updateInputSize(); - this.focus(); - this.menu.updateItemVisibility(); // Hack, we shouldn't be calling this method directly - this.removeItems( [ item ] ); -}; - -/** - * @inheritdoc - */ -OO.ui.CapsuleMultiselectWidget.prototype.removeItems = function ( items ) { - var same, i, l, - oldItems = this.items.slice(); - - OO.ui.mixin.GroupElement.prototype.removeItems.call( this, items ); - - if ( this.items.length !== oldItems.length ) { - same = false; - } else { - same = true; - for ( i = 0, l = oldItems.length; same && i < l; i++ ) { - same = same && this.items[ i ] === oldItems[ i ]; - } - } - if ( !same ) { - this.emit( 'change', this.getItemsData() ); - this.updateInputSize(); - } - - return this; -}; - -/** - * @inheritdoc - */ -OO.ui.CapsuleMultiselectWidget.prototype.clearItems = function () { - if ( this.items.length ) { - OO.ui.mixin.GroupElement.prototype.clearItems.call( this ); - this.emit( 'change', this.getItemsData() ); - this.updateInputSize(); - } - return this; -}; - -/** - * Given an item, returns the item after it. If its the last item, - * returns `this.$input`. If no item is passed, returns the very first - * item. - * - * @param {OO.ui.CapsuleItemWidget} [item] - * @return {OO.ui.CapsuleItemWidget|jQuery|boolean} - */ -OO.ui.CapsuleMultiselectWidget.prototype.getNextItem = function ( item ) { - var itemIndex; - - if ( item === undefined ) { - return this.items[ 0 ]; - } - - itemIndex = this.items.indexOf( item ); - if ( itemIndex < 0 ) { // Item not in list - return false; - } else if ( itemIndex === this.items.length - 1 ) { // Last item - return this.$input; - } else { - return this.items[ itemIndex + 1 ]; - } -}; - -/** - * Given an item, returns the item before it. If its the first item, - * returns `this.$input`. If no item is passed, returns the very last - * item. - * - * @param {OO.ui.CapsuleItemWidget} [item] - * @return {OO.ui.CapsuleItemWidget|jQuery|boolean} - */ -OO.ui.CapsuleMultiselectWidget.prototype.getPreviousItem = function ( item ) { - var itemIndex; - - if ( item === undefined ) { - return this.items[ this.items.length - 1 ]; - } - - itemIndex = this.items.indexOf( item ); - if ( itemIndex < 0 ) { // Item not in list - return false; - } else if ( itemIndex === 0 ) { // First item - return this.$input; - } else { - return this.items[ itemIndex - 1 ]; - } -}; - -/** - * Get the capsule widget's menu. - * - * @return {OO.ui.MenuSelectWidget} Menu widget - */ -OO.ui.CapsuleMultiselectWidget.prototype.getMenu = function () { - return this.menu; -}; - -/** - * Handle focus events - * - * @private - * @param {jQuery.Event} event - */ -OO.ui.CapsuleMultiselectWidget.prototype.onInputFocus = function () { - if ( !this.isDisabled() ) { - this.updateInputSize(); - this.menu.toggle( true ); - } -}; - -/** - * Handle blur events - * - * @private - * @param {jQuery.Event} event - */ -OO.ui.CapsuleMultiselectWidget.prototype.onInputBlur = function () { - this.addItemFromLabel( this.$input.val() ); - this.clearInput(); -}; - -/** - * Handles popup focus out events. - * - * @private - * @param {jQuery.Event} e Focus out event - */ -OO.ui.CapsuleMultiselectWidget.prototype.onPopupFocusOut = function () { - var widget = this.popup; - - setTimeout( function () { - if ( - widget.isVisible() && - !OO.ui.contains( widget.$element.add( widget.$autoCloseIgnore ).get(), document.activeElement, true ) - ) { - widget.toggle( false ); - } - } ); -}; - -/** - * Handle mouse down events. - * - * @private - * @param {jQuery.Event} e Mouse down event - */ -OO.ui.CapsuleMultiselectWidget.prototype.onMouseDown = function ( e ) { - if ( e.which === OO.ui.MouseButtons.LEFT ) { - this.focus(); - return false; - } else { - this.updateInputSize(); - } -}; - -/** - * Handle key press events. - * - * @private - * @param {jQuery.Event} e Key press event - */ -OO.ui.CapsuleMultiselectWidget.prototype.onKeyPress = function ( e ) { - if ( !this.isDisabled() ) { - if ( e.which === OO.ui.Keys.ESCAPE ) { - this.clearInput(); - return false; - } - - if ( !this.popup ) { - this.menu.toggle( true ); - if ( e.which === OO.ui.Keys.ENTER ) { - if ( this.addItemFromLabel( this.$input.val() ) ) { - this.clearInput(); - } - return false; - } - - // Make sure the input gets resized. - setTimeout( this.updateInputSize.bind( this ), 0 ); - } - } -}; - -/** - * Handle key down events. - * - * @private - * @param {jQuery.Event} e Key down event - */ -OO.ui.CapsuleMultiselectWidget.prototype.onKeyDown = function ( e ) { - if ( - !this.isDisabled() && - this.$input.val() === '' && - this.items.length - ) { - // 'keypress' event is not triggered for Backspace - if ( e.keyCode === OO.ui.Keys.BACKSPACE ) { - if ( e.metaKey || e.ctrlKey ) { - this.removeItems( this.items.slice( -1 ) ); - } else { - this.editItem( this.items[ this.items.length - 1 ] ); - } - return false; - } else if ( e.keyCode === OO.ui.Keys.LEFT ) { - this.getPreviousItem().focus(); - } else if ( e.keyCode === OO.ui.Keys.RIGHT ) { - this.getNextItem().focus(); - } - } -}; - -/** - * Update the dimensions of the text input field to encompass all available area. - * - * @private - * @param {jQuery.Event} e Event of some sort - */ -OO.ui.CapsuleMultiselectWidget.prototype.updateInputSize = function () { - var $lastItem, direction, contentWidth, currentWidth, bestWidth; - if ( this.$input && !this.isDisabled() ) { - this.$input.css( 'width', '1em' ); - $lastItem = this.$group.children().last(); - direction = OO.ui.Element.static.getDir( this.$handle ); - - // Get the width of the input with the placeholder text as - // the value and save it so that we don't keep recalculating - if ( - this.contentWidthWithPlaceholder === undefined && - this.$input.val() === '' && - this.$input.attr( 'placeholder' ) !== undefined - ) { - this.$input.val( this.$input.attr( 'placeholder' ) ); - this.contentWidthWithPlaceholder = this.$input[ 0 ].scrollWidth; - this.$input.val( '' ); - - } - - // Always keep the input wide enough for the placeholder text - contentWidth = Math.max( - this.$input[ 0 ].scrollWidth, - // undefined arguments in Math.max lead to NaN - ( this.contentWidthWithPlaceholder === undefined ) ? - 0 : this.contentWidthWithPlaceholder - ); - currentWidth = this.$input.width(); - - if ( contentWidth < currentWidth ) { - this.updateIfHeightChanged(); - // All is fine, don't perform expensive calculations - return; - } - - if ( $lastItem.length === 0 ) { - bestWidth = this.$content.innerWidth(); - } else { - bestWidth = direction === 'ltr' ? - this.$content.innerWidth() - $lastItem.position().left - $lastItem.outerWidth() : - $lastItem.position().left; - } - - // Some safety margin for sanity, because I *really* don't feel like finding out where the few - // pixels this is off by are coming from. - bestWidth -= 10; - if ( contentWidth > bestWidth ) { - // This will result in the input getting shifted to the next line - bestWidth = this.$content.innerWidth() - 10; - } - this.$input.width( Math.floor( bestWidth ) ); - this.updateIfHeightChanged(); - } else { - this.updateIfHeightChanged(); - } -}; - -/** - * Determine if widget height changed, and if so, update menu position and emit 'resize' event. - * - * @private - */ -OO.ui.CapsuleMultiselectWidget.prototype.updateIfHeightChanged = function () { - var height = this.$element.height(); - if ( height !== this.height ) { - this.height = height; - this.menu.position(); - if ( this.popup ) { - this.popup.updateDimensions(); - } - this.emit( 'resize' ); - } -}; - -/** - * Handle menu choose events. - * - * @private - * @param {OO.ui.OptionWidget} item Chosen item - */ -OO.ui.CapsuleMultiselectWidget.prototype.onMenuChoose = function ( item ) { - if ( item && item.isVisible() ) { - this.addItemsFromData( [ item.getData() ] ); - this.clearInput(); - } -}; - -/** - * Handle menu toggle events. - * - * @private - * @param {boolean} isVisible Open state of the menu - */ -OO.ui.CapsuleMultiselectWidget.prototype.onMenuToggle = function ( isVisible ) { - this.$element.toggleClass( 'oo-ui-capsuleMultiselectWidget-open', isVisible ); -}; - -/** - * Handle menu item change events. - * - * @private - */ -OO.ui.CapsuleMultiselectWidget.prototype.onMenuItemsChange = function () { - this.setItemsFromData( this.getItemsData() ); - this.$element.toggleClass( 'oo-ui-capsuleMultiselectWidget-empty', this.menu.isEmpty() ); -}; - -/** - * Clear the input field - * - * @private - */ -OO.ui.CapsuleMultiselectWidget.prototype.clearInput = function () { - if ( this.$input ) { - this.$input.val( '' ); - this.updateInputSize(); - } - if ( this.popup ) { - this.popup.toggle( false ); - } - this.menu.toggle( false ); - this.menu.selectItem(); - this.menu.highlightItem(); -}; - -/** - * @inheritdoc - */ -OO.ui.CapsuleMultiselectWidget.prototype.setDisabled = function ( disabled ) { - var i, len; - - // Parent method - OO.ui.CapsuleMultiselectWidget.parent.prototype.setDisabled.call( this, disabled ); - - if ( this.$input ) { - this.$input.prop( 'disabled', this.isDisabled() ); - } - if ( this.menu ) { - this.menu.setDisabled( this.isDisabled() ); - } - if ( this.popup ) { - this.popup.setDisabled( this.isDisabled() ); - } - - if ( this.items ) { - for ( i = 0, len = this.items.length; i < len; i++ ) { - this.items[ i ].updateDisabled(); - } - } - - return this; -}; - -/** - * Focus the widget - * - * @chainable - */ -OO.ui.CapsuleMultiselectWidget.prototype.focus = function () { - if ( !this.isDisabled() ) { - if ( this.popup ) { - this.popup.setSize( this.$handle.outerWidth() ); - this.popup.toggle( true ); - OO.ui.findFocusable( this.popup.$element ).focus(); - } else { - OO.ui.mixin.TabIndexedElement.prototype.focus.call( this ); - } - } - return this; -}; - /** * TagItemWidgets are used within a {@link OO.ui.TagMultiselectWidget * TagMultiselectWidget} to display the selected items. @@ -4933,7 +4117,7 @@ OO.ui.TagMultiselectWidget = function OoUiTagMultiselectWidget( config ) { if ( this.inputPosition === 'outline' ) { // Override max-height for the input widget // in the case the widget is outline so it can - // stretch all the way if the widet is wide + // stretch all the way if the widget is wide this.input.$element.css( 'max-width', 'inherit' ); this.$element .addClass( 'oo-ui-tagMultiselectWidget-outlined' ) @@ -5568,10 +4752,10 @@ OO.ui.TagMultiselectWidget.prototype.updateInputSize = function () { // Some safety margin for sanity, because I *really* don't feel like finding out where the few // pixels this is off by are coming from. - bestWidth -= 10; + bestWidth -= 13; if ( contentWidth > bestWidth ) { // This will result in the input getting shifted to the next line - bestWidth = this.$content.innerWidth() - 10; + bestWidth = this.$content.innerWidth() - 13; } this.input.$input.width( Math.floor( bestWidth ) ); this.updateIfHeightChanged();