X-Git-Url: https://git.cyclocoop.org/%27.WWW_URL.%27admin/?a=blobdiff_plain;f=resources%2Flib%2Foojs-ui%2Foojs-ui-core.js;h=1b90db575ccd4665f28ff40945b58f53e40e9142;hb=da0b4741984825903748e99980d5b86478cd933d;hp=e244037302d7141ce6b365289e71e09742285755;hpb=8515a2e32d0bb168ef1fb0583d388f83217d155a;p=lhc%2Fweb%2Fwiklou.git diff --git a/resources/lib/oojs-ui/oojs-ui-core.js b/resources/lib/oojs-ui/oojs-ui-core.js index e244037302..1b90db575c 100644 --- a/resources/lib/oojs-ui/oojs-ui-core.js +++ b/resources/lib/oojs-ui/oojs-ui-core.js @@ -1,12 +1,12 @@ /*! - * OOjs UI v0.16.0 + * OOjs UI v0.16.3 * https://www.mediawiki.org/wiki/OOjs_UI * * Copyright 2011–2016 OOjs UI Team and other contributors. * Released under the MIT license * http://oojs.mit-license.org * - * Date: 2016-02-22T22:33:33Z + * Date: 2016-03-16T19:20:22Z */ ( function ( OO ) { @@ -56,14 +56,14 @@ OO.ui.MouseButtons = { }; /** - * @property {Number} + * @property {number} */ OO.ui.elementId = 0; /** * Generate a unique ID for element * - * @return {String} [id] + * @return {string} [id] */ OO.ui.generateElementId = function () { OO.ui.elementId += 1; @@ -74,7 +74,7 @@ OO.ui.generateElementId = function () { * Check if an element is focusable. * Inspired from :focusable in jQueryUI v1.11.4 - 2015-04-14 * - * @param {jQuery} element Element to test + * @param {jQuery} $element Element to test * @return {boolean} */ OO.ui.isFocusableElement = function ( $element ) { @@ -260,6 +260,58 @@ OO.ui.debounce = function ( func, wait, immediate ) { }; }; +/** + * Returns a function, that, when invoked, will only be triggered at most once + * during a given window of time. If called again during that window, it will + * wait until the window ends and then trigger itself again. + * + * As it's not knowable to the caller whether the function will actually run + * when the wrapper is called, return values from the function are entirely + * discarded. + * + * @param {Function} func + * @param {number} wait + * @return {Function} + */ +OO.ui.throttle = function ( func, wait ) { + var context, args, timeout, + previous = 0, + run = function () { + timeout = null; + previous = OO.ui.now(); + func.apply( context, args ); + }; + return function () { + // Check how long it's been since the last time the function was + // called, and whether it's more or less than the requested throttle + // 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 ); + context = this; + args = arguments; + if ( remaining <= 0 ) { + // Note: unless wait was ridiculously large, this means we'll + // automatically run the first time the function was called in a + // given period. (If you provide a wait period larger than the + // current Unix timestamp, you *deserve* unexpected behavior.) + clearTimeout( timeout ); + run(); + } else if ( !timeout ) { + timeout = setTimeout( run, remaining ); + } + }; +}; + +/** + * A (possibly faster) way to get the current timestamp as an integer + * + * @return {number} Current timestamp + */ +OO.ui.now = Date.now || function () { + return new Date().getTime(); +}; + /** * Proxy for `node.addEventListener( eventName, handler, true )`. * @@ -356,7 +408,7 @@ OO.ui.infuse = function ( idOrNode ) { * they support unnamed, ordered message parameters. * * @param {string} key Message key - * @param {Mixed...} [params] Message parameters + * @param {...Mixed} [params] Message parameters * @return {string} Translated message with parameters substituted */ OO.ui.msg = function ( key ) { @@ -382,7 +434,7 @@ OO.ui.infuse = function ( idOrNode ) { * Use this when you are statically specifying a message and the message may not yet be present. * * @param {string} key Message key - * @param {Mixed...} [params] Message parameters + * @param {...Mixed} [params] Message parameters * @return {Function} Function that returns the resolved message when executed */ OO.ui.deferMsg = function () { @@ -579,6 +631,7 @@ OO.ui.Element.static.infuse = function ( idOrNode ) { /** * Implementation helper for `infuse`; skips the type check and has an * extra property so that only the top-level invocation touches the DOM. + * * @private * @param {string|HTMLElement|jQuery} idOrNode * @param {jQuery.Promise|boolean} domPromise A promise that will be resolved @@ -610,13 +663,13 @@ OO.ui.Element.static.unsafeInfuse = function ( idOrNode, domPromise ) { } if ( domPromise ) { // pick up dynamic state, like focus, value of form inputs, scroll position, etc. - state = data.gatherPreInfuseState( $elem ); + state = data.constructor.static.gatherPreInfuseState( $elem, data ); // restore dynamic state after the new element is re-inserted into DOM under infused parent domPromise.done( data.restorePreInfuseState.bind( data, state ) ); infusedChildren = $elem.data( 'ooui-infused-children' ); if ( infusedChildren && infusedChildren.length ) { infusedChildren.forEach( function ( data ) { - var state = data.gatherPreInfuseState( $elem ); + var state = data.constructor.static.gatherPreInfuseState( $elem, data ); domPromise.done( data.restorePreInfuseState.bind( data, state ) ); } ); } @@ -684,7 +737,7 @@ OO.ui.Element.static.unsafeInfuse = function ( idOrNode, domPromise ) { infused.$element.removeData( 'ooui-infused-children' ); return infused; } - if ( value.html ) { + if ( value.html !== undefined ) { return new OO.ui.HtmlSnippet( value.html ); } } @@ -1205,7 +1258,7 @@ OO.ui.Element.prototype.getData = function () { /** * Set element data. * - * @param {Mixed} Element data + * @param {Mixed} data Element data * @chainable */ OO.ui.Element.prototype.setData = function ( data ) { @@ -1266,6 +1319,7 @@ OO.ui.Element.prototype.getTagName = function () { /** * Check if the element is attached to the DOM + * * @return {boolean} The element is attached to the DOM */ OO.ui.Element.prototype.isElementAttached = function () { @@ -1726,6 +1780,7 @@ OO.ui.mixin.TabIndexedElement.prototype.getTabIndex = function () { * See the [OOjs UI documentation on MediaWiki] [1] for examples. * * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Buttons_and_Switches#Buttons + * * @abstract * @class * @@ -2577,7 +2632,7 @@ OO.ui.mixin.IndicatorElement.prototype.setIndicator = function ( indicator ) { * * The title is displayed when a user moves the mouse over the indicator. * - * @param {string|Function|null} indicator Indicator title text, a function that returns text, or + * @param {string|Function|null} indicatorTitle Indicator title text, a function that returns text, or * `null` for no indicator title * @chainable */ @@ -3051,7 +3106,7 @@ OO.ui.mixin.TitledElement = function OoUiMixinTitledElement( config ) { this.title = null; // Initialization - this.setTitle( config.title || this.constructor.static.title ); + this.setTitle( config.title !== undefined ? config.title : this.constructor.static.title ); this.setTitledElement( config.$titled || this.$element ); }; @@ -3203,7 +3258,7 @@ OO.ui.mixin.AccessKeyedElement.prototype.setAccessKeyedElement = function ( $acc /** * Set accesskey. * - * @param {string|Function|null} accesskey Key, a function that returns a key, or `null` for no accesskey + * @param {string|Function|null} accessKey Key, a function that returns a key, or `null` for no accesskey * @chainable */ OO.ui.mixin.AccessKeyedElement.prototype.setAccessKey = function ( accessKey ) { @@ -4049,6 +4104,9 @@ OO.ui.mixin.ClippableElement.prototype.clip = function () { ccWidth + ccOffset.left : ( scOffset.left + scrollLeft + scWidth ) - ccOffset.left; desiredHeight = ( scOffset.top + scrollTop + scHeight ) - ccOffset.top; + // It should never be desirable to exceed the dimensions of the browser viewport... right? + desiredWidth = Math.min( desiredWidth, document.documentElement.clientWidth ); + desiredHeight = Math.min( desiredHeight, document.documentElement.clientHeight ); allotedWidth = Math.ceil( desiredWidth - extraWidth ); allotedHeight = Math.ceil( desiredHeight - extraHeight ); naturalWidth = this.$clippable.prop( 'scrollWidth' ); @@ -4466,6 +4524,7 @@ OO.ui.PopupWidget.prototype.updateDimensions = function ( transition ) { /** * Set popup alignment + * * @param {string} align Alignment of the popup, `center`, `force-left`, `force-right`, * `backwards` or `forwards`. */ @@ -4480,6 +4539,7 @@ OO.ui.PopupWidget.prototype.setAlignment = function ( align ) { /** * Get popup alignment + * * @return {string} align Alignment of the popup, `center`, `force-left`, `force-right`, * `backwards` or `forwards`. */ @@ -5188,7 +5248,7 @@ OO.ui.SelectWidget.prototype.unbindKeyDownListener = function () { /** * Scroll item into view, preventing spurious mouse highlight actions from happening. * - * @return {OO.ui.OptionWidget} Item to scroll into view + * @param {OO.ui.OptionWidget} item Item to scroll into view */ OO.ui.SelectWidget.prototype.scrollItemIntoView = function ( item ) { var widget = this; @@ -5977,6 +6037,7 @@ OO.ui.MenuSelectWidget.prototype.onKeyDown = function ( e ) { /** * Update menu item visibility after input changes. + * * @protected */ OO.ui.MenuSelectWidget.prototype.updateItemVisibility = function () { @@ -6052,6 +6113,7 @@ OO.ui.MenuSelectWidget.prototype.unbindKeyPressListener = function () { * * Note that ‘choose’ should never be modified programmatically. A user can choose an option with the keyboard * or mouse and it becomes selected. To select an item programmatically, use the #selectItem method. + * * @param {OO.ui.OptionWidget} item Item to choose * @chainable */ @@ -6556,22 +6618,19 @@ OO.ui.mixin.FloatableElement.prototype.togglePositioning = function ( positionin closestScrollableOfContainer = OO.ui.Element.static.getClosestScrollableContainer( this.$floatableContainer[ 0 ] ); closestScrollableOfFloatable = OO.ui.Element.static.getClosestScrollableContainer( this.$floatable[ 0 ] ); - if ( closestScrollableOfContainer !== closestScrollableOfFloatable ) { - // If the scrollable is the root, we have to listen to scroll events - // on the window because of browser inconsistencies (or do we? someone should verify this) - if ( $( closestScrollableOfContainer ).is( 'html, body' ) ) { - closestScrollableOfContainer = OO.ui.Element.static.getWindow( closestScrollableOfContainer ); - } + this.needsCustomPosition = closestScrollableOfContainer !== closestScrollableOfFloatable; + // If the scrollable is the root, we have to listen to scroll events + // on the window because of browser inconsistencies. + if ( $( closestScrollableOfContainer ).is( 'html, body' ) ) { + closestScrollableOfContainer = OO.ui.Element.static.getWindow( closestScrollableOfContainer ); } if ( positioning ) { this.$floatableWindow = $( this.getElementWindow() ); this.$floatableWindow.on( 'resize', this.onFloatableWindowResizeHandler ); - if ( closestScrollableOfContainer !== closestScrollableOfFloatable ) { - this.$floatableClosestScrollable = $( closestScrollableOfContainer ); - this.$floatableClosestScrollable.on( 'scroll', this.onFloatableScrollHandler ); - } + this.$floatableClosestScrollable = $( closestScrollableOfContainer ); + this.$floatableClosestScrollable.on( 'scroll', this.onFloatableScrollHandler ); // Initial position after visible this.position(); @@ -6593,6 +6652,50 @@ OO.ui.mixin.FloatableElement.prototype.togglePositioning = function ( positionin return this; }; +/** + * Check whether the bottom edge of the given element is within the viewport of the given container. + * + * @private + * @param {jQuery} $element + * @param {jQuery} $container + * @return {boolean} + */ +OO.ui.mixin.FloatableElement.prototype.isElementInViewport = function ( $element, $container ) { + var elemRect, contRect, + topEdgeInBounds = false, + leftEdgeInBounds = false, + bottomEdgeInBounds = false, + rightEdgeInBounds = false; + + elemRect = $element[ 0 ].getBoundingClientRect(); + if ( $container[ 0 ] === window ) { + contRect = { + top: 0, + left: 0, + right: document.documentElement.clientWidth, + bottom: document.documentElement.clientHeight + }; + } else { + contRect = $container[ 0 ].getBoundingClientRect(); + } + + if ( elemRect.top >= contRect.top && elemRect.top <= contRect.bottom ) { + topEdgeInBounds = true; + } + if ( elemRect.left >= contRect.left && elemRect.left <= contRect.right ) { + leftEdgeInBounds = true; + } + if ( elemRect.bottom >= contRect.top && elemRect.bottom <= contRect.bottom ) { + bottomEdgeInBounds = true; + } + if ( elemRect.right >= contRect.left && elemRect.right <= contRect.right ) { + rightEdgeInBounds = true; + } + + // We only care that any part of the bottom edge is visible + return bottomEdgeInBounds && ( leftEdgeInBounds || rightEdgeInBounds ); +}; + /** * Position the floatable below its container. * @@ -6607,6 +6710,17 @@ OO.ui.mixin.FloatableElement.prototype.position = function () { return this; } + if ( !this.isElementInViewport( this.$floatableContainer, this.$floatableClosestScrollable ) ) { + this.$floatable.addClass( 'oo-ui-floatableElement-hidden' ); + return; + } else { + this.$floatable.removeClass( 'oo-ui-floatableElement-hidden' ); + } + + if ( !this.needsCustomPosition ) { + return; + } + pos = OO.ui.Element.static.getRelativePosition( this.$floatableContainer, this.$floatable.offsetParent() ); // Position under container @@ -6738,7 +6852,8 @@ OO.ui.InputWidget = function OoUiInputWidget( config ) { OO.ui.InputWidget.parent.call( this, config ); // Properties - this.$input = this.getInputElement( config ); + // See #reusePreInfuseDOM about config.$input + this.$input = config.$input || this.getInputElement( config ); this.value = ''; this.inputFilter = config.inputFilter; @@ -6822,9 +6937,8 @@ OO.ui.InputWidget.static.gatherPreInfuseState = function ( node, config ) { * @param {Object} config Configuration options * @return {jQuery} Input element */ -OO.ui.InputWidget.prototype.getInputElement = function ( config ) { - // See #reusePreInfuseDOM about config.$input - return config.$input || $( '' ); +OO.ui.InputWidget.prototype.getInputElement = function () { + return $( '' ); }; /** @@ -7013,12 +7127,17 @@ OO.ui.InputWidget.prototype.restorePreInfuseState = function ( state ) { * @cfg {boolean} [useInputTag=false] Use an `` tag instead of a `