From c4b9f22b65f2112bcc76d9545a4a5b211a3b973a Mon Sep 17 00:00:00 2001 From: "James D. Forrester" Date: Fri, 5 Dec 2014 17:37:28 -0800 Subject: [PATCH] Update OOjs UI to v0.4.0 Release notes: https://git.wikimedia.org/blob/oojs%2Fui.git/v0.4.0/History.md Change-Id: I8c81656d4f95138d7c187e71e52ee60b669a46a5 --- resources/lib/oojs-ui/images/grab.cur | Bin 0 -> 326 bytes resources/lib/oojs-ui/images/grabbing.cur | Bin 0 -> 326 bytes resources/lib/oojs-ui/oojs-ui-apex.css | 25 +- resources/lib/oojs-ui/oojs-ui-apex.js | 4 +- resources/lib/oojs-ui/oojs-ui-apex.svg.css | 25 +- resources/lib/oojs-ui/oojs-ui-mediawiki.css | 29 +- resources/lib/oojs-ui/oojs-ui-mediawiki.js | 4 +- .../lib/oojs-ui/oojs-ui-mediawiki.svg.css | 29 +- resources/lib/oojs-ui/oojs-ui.js | 584 ++++++++++++++---- 9 files changed, 560 insertions(+), 140 deletions(-) create mode 100644 resources/lib/oojs-ui/images/grab.cur create mode 100644 resources/lib/oojs-ui/images/grabbing.cur diff --git a/resources/lib/oojs-ui/images/grab.cur b/resources/lib/oojs-ui/images/grab.cur new file mode 100644 index 0000000000000000000000000000000000000000..fba3ddc807fd2e29b41d09af0b14d6db6bdb879c GIT binary patch literal 326 zcmbu(u?@m75QX92i6U1h9TF8ODcJzFVFXGt0;3=b>Wq?t0aBq9D39kj1vQ-Y*=OHL zXDA3XO+ln$A7Bmatg)j7uQ`?@la<+x_h6sG+m?*Z%Tfs literal 0 HcmV?d00001 diff --git a/resources/lib/oojs-ui/images/grabbing.cur b/resources/lib/oojs-ui/images/grabbing.cur new file mode 100644 index 0000000000000000000000000000000000000000..41aaa62a596f9e973f333e084e004da35bfdd9bf GIT binary patch literal 326 zcmbu(y$ypf5QgE`Mv<$CE-fxoW&}3Ks*J!W8Nosn6iAovz(opZ!dZ{+r#KrTft(|W z8?OKl;3-*p2|8QU` + * @cfg {string} [orientation] Item orientation, 'horizontal' or 'vertical'. Defaults to 'vertical' + */ +OO.ui.DraggableGroupElement = function OoUiDraggableGroupElement( config ) { + // Configuration initialization + config = config || {}; + + // Parent constructor + OO.ui.GroupElement.call( this, config ); + + // Properties + this.orientation = config.orientation || 'vertical'; + this.dragItem = null; + this.itemDragOver = null; + this.itemKeys = {}; + this.sideInsertion = ''; + + // Events + this.aggregate( { + dragstart: 'itemDragStart', + dragend: 'itemDragEnd', + drop: 'itemDrop' + } ); + this.connect( this, { + itemDragStart: 'onItemDragStart', + itemDrop: 'onItemDrop', + itemDragEnd: 'onItemDragEnd' + } ); + this.$element.on( { + dragover: $.proxy( this.onDragOver, this ), + dragleave: $.proxy( this.onDragLeave, this ) + } ); + + // Initialize + if ( $.isArray( config.items ) ) { + this.addItems( config.items ); + } + this.$placeholder = $( '
' ) + .addClass( 'oo-ui-draggableGroupElement-placeholder' ); + this.$element + .addClass( 'oo-ui-draggableGroupElement' ) + .append( this.$status ) + .toggleClass( 'oo-ui-draggableGroupElement-horizontal', this.orientation === 'horizontal' ) + .prepend( this.$placeholder ); +}; + +/* Setup */ +OO.mixinClass( OO.ui.DraggableGroupElement, OO.ui.GroupElement ); + +/* Events */ + +/** + * @event reorder + * @param {OO.ui.DraggableElement} item Reordered item + * @param {number} [newIndex] New index for the item + */ + +/* Methods */ + +/** + * Respond to item drag start event + * @param {OO.ui.DraggableElement} item Dragged item + */ +OO.ui.DraggableGroupElement.prototype.onItemDragStart = function ( item ) { + var i, len; + + // Map the index of each object + for ( i = 0, len = this.items.length; i < len; i++ ) { + this.items[i].setIndex( i ); + } + + if ( this.orientation === 'horizontal' ) { + // Set the height of the indicator + this.$placeholder.css( { + height: item.$element.outerHeight(), + width: 2 + } ); + } else { + // Set the width of the indicator + this.$placeholder.css( { + height: 2, + width: item.$element.outerWidth() + } ); + } + this.setDragItem( item ); +}; + +/** + * Respond to item drag end event + */ +OO.ui.DraggableGroupElement.prototype.onItemDragEnd = function () { + this.unsetDragItem(); + return false; +}; + +/** + * Handle drop event and switch the order of the items accordingly + * @param {OO.ui.DraggableElement} item Dropped item + * @fires reorder + */ +OO.ui.DraggableGroupElement.prototype.onItemDrop = function ( item ) { + var toIndex = item.getIndex(); + // Check if the dropped item is from the current group + // TODO: Figure out a way to configure a list of legally droppable + // elements even if they are not yet in the list + if ( this.getDragItem() ) { + // If the insertion point is 'after', the insertion index + // is shifted to the right (or to the left in RTL, hence 'after') + if ( this.sideInsertion === 'after' ) { + toIndex++; + } + // Emit change event + this.emit( 'reorder', this.getDragItem(), toIndex ); + } + // Return false to prevent propogation + return false; +}; + +/** + * Handle dragleave event. + */ +OO.ui.DraggableGroupElement.prototype.onDragLeave = function () { + // This means the item was dragged outside the widget + this.$placeholder + .css( 'left', 0 ) + .hide(); +}; + +/** + * Respond to dragover event + * @param {jQuery.Event} event Event details + */ +OO.ui.DraggableGroupElement.prototype.onDragOver = function ( e ) { + var dragOverObj, $optionWidget, itemOffset, itemMidpoint, itemBoundingRect, + itemSize, cssOutput, dragPosition, itemIndex, itemPosition, + clientX = e.originalEvent.clientX, + clientY = e.originalEvent.clientY; + + // Get the OptionWidget item we are dragging over + dragOverObj = this.getElementDocument().elementFromPoint( clientX, clientY ); + $optionWidget = $( dragOverObj ).closest( '.oo-ui-draggableElement' ); + if ( $optionWidget[0] ) { + itemOffset = $optionWidget.offset(); + itemBoundingRect = $optionWidget[0].getBoundingClientRect(); + itemPosition = $optionWidget.position(); + itemIndex = $optionWidget.data( 'index' ); + } + + if ( + itemOffset && + this.isDragging() && + itemIndex !== this.getDragItem().getIndex() + ) { + if ( this.orientation === 'horizontal' ) { + // Calculate where the mouse is relative to the item width + itemSize = itemBoundingRect.width; + itemMidpoint = itemBoundingRect.left + itemSize / 2; + dragPosition = clientX; + // Which side of the item we hover over will dictate + // where the placeholder will appear, on the left or + // on the right + cssOutput = { + left: dragPosition < itemMidpoint ? itemPosition.left : itemPosition.left + itemSize, + top: itemPosition.top + }; + } else { + // Calculate where the mouse is relative to the item height + itemSize = itemBoundingRect.height; + itemMidpoint = itemBoundingRect.top + itemSize / 2; + dragPosition = clientY; + // Which side of the item we hover over will dictate + // where the placeholder will appear, on the top or + // on the bottom + cssOutput = { + top: dragPosition < itemMidpoint ? itemPosition.top : itemPosition.top + itemSize, + left: itemPosition.left + }; + } + // Store whether we are before or after an item to rearrange + // For horizontal layout, we need to account for RTL, as this is flipped + if ( this.orientation === 'horizontal' && this.$element.css( 'direction' ) === 'rtl' ) { + this.sideInsertion = dragPosition < itemMidpoint ? 'after' : 'before'; + } else { + this.sideInsertion = dragPosition < itemMidpoint ? 'before' : 'after'; + } + // Add drop indicator between objects + if ( this.sideInsertion ) { + this.$placeholder + .css( cssOutput ) + .show(); + } else { + this.$placeholder + .css( { + left: 0, + top: 0 + } ) + .hide(); + } + } else { + // This means the item was dragged outside the widget + this.$placeholder + .css( 'left', 0 ) + .hide(); + } + // Prevent default + e.preventDefault(); +}; + +/** + * Set a dragged item + * @param {OO.ui.DraggableElement} item Dragged item + */ +OO.ui.DraggableGroupElement.prototype.setDragItem = function ( item ) { + this.dragItem = item; +}; + +/** + * Unset the current dragged item + */ +OO.ui.DraggableGroupElement.prototype.unsetDragItem = function () { + this.dragItem = null; + this.itemDragOver = null; + this.$placeholder.hide(); + this.sideInsertion = ''; +}; + +/** + * Get the current dragged item + * @return {OO.ui.DraggableElement|null} item Dragged item or null if no item is dragged + */ +OO.ui.DraggableGroupElement.prototype.getDragItem = function () { + return this.dragItem; +}; + +/** + * Check if there's an item being dragged. + * @return {Boolean} Item is being dragged + */ +OO.ui.DraggableGroupElement.prototype.isDragging = function () { + return this.getDragItem() !== null; +}; + /** * Element containing an icon. * @@ -4313,6 +4628,15 @@ OO.ui.IconElement.prototype.getIcon = function () { return this.icon; }; +/** + * Get icon title. + * + * @return {string} Icon title text + */ +OO.ui.IconElement.prototype.getIconTitle = function () { + return this.iconTitle; +}; + /** * Element containing an indicator. * @@ -4971,7 +5295,7 @@ OO.ui.ClippableElement.prototype.toggleClipping = function ( clipping ) { // If the clippable container is the body, we have to listen to scroll events and check // jQuery.scrollTop on the window because of browser inconsistencies this.$clippableScroller = this.$clippableContainer.is( 'body' ) ? - this.$( OO.ui.Element.getWindow( this.$clippableContainer ) ) : + this.$( OO.ui.Element.static.getWindow( this.$clippableContainer ) ) : this.$clippableContainer; this.$clippableScroller.on( 'scroll', this.onClippableContainerScrollHandler ); this.$clippableWindow = this.$( this.getElementWindow() ) @@ -6460,7 +6784,7 @@ OO.ui.BookletLayout = function OoUiBookletLayout( config ) { } if ( this.autoFocus ) { // Event 'focus' does not bubble, but 'focusin' does - this.stackLayout.onDOMEvent( 'focusin', this.onStackLayoutFocus.bind( this ) ); + this.stackLayout.$element.on( 'focusin', this.onStackLayoutFocus.bind( this ) ); } // Initialization @@ -6685,7 +7009,7 @@ OO.ui.BookletLayout.prototype.getPage = function ( name ) { * * @return {string|null} Current page name */ -OO.ui.BookletLayout.prototype.getPageName = function () { +OO.ui.BookletLayout.prototype.getCurrentPageName = function () { return this.currentPageName; }; @@ -7228,7 +7552,7 @@ OO.ui.GridLayout.prototype.update = function () { top: Math.round( top * 100 ) + '%' }; // If RTL, reverse: - if ( OO.ui.Element.getDir( this.$.context ) === 'rtl' ) { + if ( OO.ui.Element.static.getDir( this.$.context ) === 'rtl' ) { dimensions.right = Math.round( left * 100 ) + '%'; } else { dimensions.left = Math.round( left * 100 ) + '%'; @@ -7312,7 +7636,6 @@ OO.inheritClass( OO.ui.PanelLayout, OO.ui.Layout ); * @constructor * @param {string} name Unique symbolic name of page * @param {Object} [config] Configuration options - * @param {string} [outlineItem] Outline item widget */ OO.ui.PageLayout = function OoUiPageLayout( name, config ) { // Configuration initialization @@ -7323,7 +7646,7 @@ OO.ui.PageLayout = function OoUiPageLayout( name, config ) { // Properties this.name = name; - this.outlineItem = config.outlineItem || null; + this.outlineItem = null; this.active = false; // Initialization @@ -7427,7 +7750,6 @@ OO.ui.PageLayout.prototype.setActive = function ( active ) { * @constructor * @param {Object} [config] Configuration options * @cfg {boolean} [continuous=false] Show all pages, one after another - * @cfg {string} [icon=''] Symbolic icon name * @cfg {OO.ui.Layout[]} [items] Layouts to add */ OO.ui.StackLayout = function OoUiStackLayout( config ) { @@ -8154,7 +8476,7 @@ OO.ui.LookupInputWidget = function OoUiLookupInputWidget( input, config ) { this.lookupInput = input; this.$overlay = config.$overlay || this.$element; this.lookupMenu = new OO.ui.TextInputMenuSelectWidget( this, { - $: OO.ui.Element.getJQuery( this.$overlay ), + $: OO.ui.Element.static.getJQuery( this.$overlay ), input: this.lookupInput, $container: config.$container } ); @@ -8669,7 +8991,7 @@ OO.mixinClass( OO.ui.ButtonGroupWidget, OO.ui.GroupElement ); */ OO.ui.ButtonWidget = function OoUiButtonWidget( config ) { // Configuration initialization - config = $.extend( { target: '_blank' }, config ); + config = config || {}; // Parent constructor OO.ui.ButtonWidget.super.call( this, config ); @@ -9374,15 +9696,15 @@ OO.ui.InputWidget.prototype.setRTL = function ( isRTL ) { */ OO.ui.InputWidget.prototype.setValue = function ( value ) { value = this.cleanUpValue( value ); + // Update the DOM if it has changed. Note that with cleanUpValue, it + // is possible for the DOM value to change without this.value changing. + if ( this.$input.val() !== value ) { + this.$input.val( value ); + } if ( this.value !== value ) { this.value = value; this.emit( 'change', this.value ); } - // Update the DOM if it has changed. Note that with cleanUpValue, it - // is possible for the DOM value to change without this.value changing. - if ( this.$input.val() !== this.value ) { - this.$input.val( this.value ); - } return this; }; @@ -9779,6 +10101,14 @@ OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) { this.maxRows = config.maxRows !== undefined ? config.maxRows : 10; this.validate = null; + // Clone for resizing + if ( this.autosize ) { + this.$clone = this.$input + .clone() + .insertAfter( this.$input ) + .hide(); + } + this.setValidation( config.validate ); // Events @@ -9941,28 +10271,40 @@ OO.ui.TextInputWidget.prototype.setReadOnly = function ( state ) { * @chainable */ OO.ui.TextInputWidget.prototype.adjustSize = function () { - var $clone, scrollHeight, innerHeight, outerHeight, maxInnerHeight, measurementError, idealHeight; + var scrollHeight, innerHeight, outerHeight, maxInnerHeight, measurementError, idealHeight; if ( this.multiline && this.autosize && this.$input.val() !== this.valCache ) { - $clone = this.$input.clone() + this.$clone .val( this.$input.val() ) + .attr( 'rows', '' ) // Set inline height property to 0 to measure scroll height - .css( 'height', 0 ) - .insertAfter( this.$input ); + .css( 'height', 0 ); + + this.$clone[0].style.display = 'block'; + this.valCache = this.$input.val(); - scrollHeight = $clone[0].scrollHeight; + + scrollHeight = this.$clone[0].scrollHeight; + // Remove inline height property to measure natural heights - $clone.css( 'height', '' ); - innerHeight = $clone.innerHeight(); - outerHeight = $clone.outerHeight(); + this.$clone.css( 'height', '' ); + innerHeight = this.$clone.innerHeight(); + outerHeight = this.$clone.outerHeight(); + // Measure max rows height - $clone.attr( 'rows', this.maxRows ).css( 'height', 'auto' ).val( '' ); - maxInnerHeight = $clone.innerHeight(); + this.$clone + .attr( 'rows', this.maxRows ) + .css( 'height', 'auto' ) + .val( '' ); + maxInnerHeight = this.$clone.innerHeight(); + // Difference between reported innerHeight and scrollHeight with no scrollbars present // Equals 1 on Blink-based browsers and 0 everywhere else - measurementError = maxInnerHeight - $clone[0].scrollHeight; - $clone.remove(); + measurementError = maxInnerHeight - this.$clone[0].scrollHeight; idealHeight = Math.min( maxInnerHeight, scrollHeight + measurementError ); + + this.$clone[0].style.display = 'none'; + // Only apply inline height when expansion beyond natural height is needed if ( idealHeight > innerHeight ) { // Use the difference between the inner and outer height as a buffer @@ -10078,7 +10420,7 @@ OO.ui.ComboBoxWidget = function OoUiComboBoxWidget( config ) { ) ); this.menu = new OO.ui.TextInputMenuSelectWidget( this.input, $.extend( { - $: OO.ui.Element.getJQuery( this.$overlay ), + $: OO.ui.Element.static.getJQuery( this.$overlay ), widget: this, input: this.input, disabled: this.isDisabled() @@ -11616,32 +11958,30 @@ OO.ui.SelectWidget.prototype.chooseItem = function ( item ) { /** * Get an item relative to another one. * - * @param {OO.ui.OptionWidget} item Item to start at - * @param {number} direction Direction to move in, -1 to look backward, 1 to move forward + * @param {OO.ui.OptionWidget|null} item Item to start at, null to get relative to list start + * @param {number} direction Direction to move in, -1 to move backward, 1 to move forward * @return {OO.ui.OptionWidget|null} Item at position, `null` if there are no items in the menu */ OO.ui.SelectWidget.prototype.getRelativeSelectableItem = function ( item, direction ) { - var inc = direction > 0 ? 1 : -1, - len = this.items.length, - index = item instanceof OO.ui.OptionWidget ? - $.inArray( item, this.items ) : ( inc > 0 ? -1 : 0 ), - stopAt = Math.max( Math.min( index, len - 1 ), 0 ), - i = inc > 0 ? - // Default to 0 instead of -1, if nothing is selected let's start at the beginning - Math.max( index, -1 ) : - // Default to n-1 instead of -1, if nothing is selected let's start at the end - Math.min( index, len ); - - while ( len !== 0 ) { - i = ( i + inc + len ) % len; - item = this.items[i]; + var currentIndex, nextIndex, i, + increase = direction > 0 ? 1 : -1, + len = this.items.length; + + if ( item instanceof OO.ui.OptionWidget ) { + currentIndex = $.inArray( item, this.items ); + nextIndex = ( currentIndex + increase + len ) % len; + } else { + // If no item is selected and moving forward, start at the beginning. + // If moving backward, start at the end. + nextIndex = direction > 0 ? 0 : len - 1; + } + + for ( i = 0; i < len; i++ ) { + item = this.items[nextIndex]; if ( item instanceof OO.ui.OptionWidget && item.isSelectable() ) { return item; } - // Stop iterating when we've looped all the way around - if ( i === stopAt ) { - break; - } + nextIndex = ( nextIndex + increase + len ) % len; } return null; }; @@ -12149,7 +12489,7 @@ OO.ui.TextInputMenuSelectWidget.prototype.toggle = function ( visible ) { */ OO.ui.TextInputMenuSelectWidget.prototype.position = function () { var $container = this.$container, - pos = OO.ui.Element.getRelativePosition( $container, this.$element.offsetParent() ); + pos = OO.ui.Element.static.getRelativePosition( $container, this.$element.offsetParent() ); // Position under input pos.top += $container.height(); -- 2.20.1