diff options
Diffstat (limited to 'vendor/oojs/oojs-ui/src/elements/ClippableElement.js')
-rw-r--r-- | vendor/oojs/oojs-ui/src/elements/ClippableElement.js | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/vendor/oojs/oojs-ui/src/elements/ClippableElement.js b/vendor/oojs/oojs-ui/src/elements/ClippableElement.js new file mode 100644 index 00000000..33b0b234 --- /dev/null +++ b/vendor/oojs/oojs-ui/src/elements/ClippableElement.js @@ -0,0 +1,205 @@ +/** + * Element that can be automatically clipped to visible boundaries. + * + * Whenever the element's natural height changes, you have to call + * #clip to make sure it's still clipping correctly. + * + * @abstract + * @class + * + * @constructor + * @param {Object} [config] Configuration options + * @cfg {jQuery} [$clippable] Nodes to clip, assigned to #$clippable, omit to use #$element + */ +OO.ui.ClippableElement = function OoUiClippableElement( config ) { + // Configuration initialization + config = config || {}; + + // Properties + this.$clippable = null; + this.clipping = false; + this.clippedHorizontally = false; + this.clippedVertically = false; + this.$clippableContainer = null; + this.$clippableScroller = null; + this.$clippableWindow = null; + this.idealWidth = null; + this.idealHeight = null; + this.onClippableContainerScrollHandler = this.clip.bind( this ); + this.onClippableWindowResizeHandler = this.clip.bind( this ); + + // Initialization + this.setClippableElement( config.$clippable || this.$element ); +}; + +/* Methods */ + +/** + * Set clippable element. + * + * If an element is already set, it will be cleaned up before setting up the new element. + * + * @param {jQuery} $clippable Element to make clippable + */ +OO.ui.ClippableElement.prototype.setClippableElement = function ( $clippable ) { + if ( this.$clippable ) { + this.$clippable.removeClass( 'oo-ui-clippableElement-clippable' ); + this.$clippable.css( { width: '', height: '', overflowX: '', overflowY: '' } ); + OO.ui.Element.static.reconsiderScrollbars( this.$clippable[ 0 ] ); + } + + this.$clippable = $clippable.addClass( 'oo-ui-clippableElement-clippable' ); + this.clip(); +}; + +/** + * Toggle clipping. + * + * Do not turn clipping on until after the element is attached to the DOM and visible. + * + * @param {boolean} [clipping] Enable clipping, omit to toggle + * @chainable + */ +OO.ui.ClippableElement.prototype.toggleClipping = function ( clipping ) { + clipping = clipping === undefined ? !this.clipping : !!clipping; + + if ( this.clipping !== clipping ) { + this.clipping = clipping; + if ( clipping ) { + this.$clippableContainer = $( this.getClosestScrollableElementContainer() ); + // If the clippable container is the root, we have to listen to scroll events and check + // jQuery.scrollTop on the window because of browser inconsistencies + this.$clippableScroller = this.$clippableContainer.is( 'html, body' ) ? + $( OO.ui.Element.static.getWindow( this.$clippableContainer ) ) : + this.$clippableContainer; + this.$clippableScroller.on( 'scroll', this.onClippableContainerScrollHandler ); + this.$clippableWindow = $( this.getElementWindow() ) + .on( 'resize', this.onClippableWindowResizeHandler ); + // Initial clip after visible + this.clip(); + } else { + this.$clippable.css( { width: '', height: '', overflowX: '', overflowY: '' } ); + OO.ui.Element.static.reconsiderScrollbars( this.$clippable[ 0 ] ); + + this.$clippableContainer = null; + this.$clippableScroller.off( 'scroll', this.onClippableContainerScrollHandler ); + this.$clippableScroller = null; + this.$clippableWindow.off( 'resize', this.onClippableWindowResizeHandler ); + this.$clippableWindow = null; + } + } + + return this; +}; + +/** + * Check if the element will be clipped to fit the visible area of the nearest scrollable container. + * + * @return {boolean} Element will be clipped to the visible area + */ +OO.ui.ClippableElement.prototype.isClipping = function () { + return this.clipping; +}; + +/** + * Check if the bottom or right of the element is being clipped by the nearest scrollable container. + * + * @return {boolean} Part of the element is being clipped + */ +OO.ui.ClippableElement.prototype.isClipped = function () { + return this.clippedHorizontally || this.clippedVertically; +}; + +/** + * Check if the right of the element is being clipped by the nearest scrollable container. + * + * @return {boolean} Part of the element is being clipped + */ +OO.ui.ClippableElement.prototype.isClippedHorizontally = function () { + return this.clippedHorizontally; +}; + +/** + * Check if the bottom of the element is being clipped by the nearest scrollable container. + * + * @return {boolean} Part of the element is being clipped + */ +OO.ui.ClippableElement.prototype.isClippedVertically = function () { + return this.clippedVertically; +}; + +/** + * Set the ideal size. These are the dimensions the element will have when it's not being clipped. + * + * @param {number|string} [width] Width as a number of pixels or CSS string with unit suffix + * @param {number|string} [height] Height as a number of pixels or CSS string with unit suffix + */ +OO.ui.ClippableElement.prototype.setIdealSize = function ( width, height ) { + this.idealWidth = width; + this.idealHeight = height; + + if ( !this.clipping ) { + // Update dimensions + this.$clippable.css( { width: width, height: height } ); + } + // While clipping, idealWidth and idealHeight are not considered +}; + +/** + * Clip element to visible boundaries and allow scrolling when needed. Call this method when + * the element's natural height changes. + * + * Element will be clipped the bottom or right of the element is within 10px of the edge of, or + * overlapped by, the visible area of the nearest scrollable container. + * + * @chainable + */ +OO.ui.ClippableElement.prototype.clip = function () { + if ( !this.clipping ) { + // this.$clippableContainer and this.$clippableWindow are null, so the below will fail + return this; + } + + var buffer = 7, // Chosen by fair dice roll + cOffset = this.$clippable.offset(), + $container = this.$clippableContainer.is( 'html, body' ) ? + this.$clippableWindow : this.$clippableContainer, + ccOffset = $container.offset() || { top: 0, left: 0 }, + ccHeight = $container.innerHeight() - buffer, + ccWidth = $container.innerWidth() - buffer, + cHeight = this.$clippable.outerHeight() + buffer, + cWidth = this.$clippable.outerWidth() + buffer, + scrollTop = this.$clippableScroller.scrollTop(), + scrollLeft = this.$clippableScroller.scrollLeft(), + desiredWidth = cOffset.left < 0 ? + cWidth + cOffset.left : + ( ccOffset.left + scrollLeft + ccWidth ) - cOffset.left, + desiredHeight = cOffset.top < 0 ? + cHeight + cOffset.top : + ( ccOffset.top + scrollTop + ccHeight ) - cOffset.top, + naturalWidth = this.$clippable.prop( 'scrollWidth' ), + naturalHeight = this.$clippable.prop( 'scrollHeight' ), + clipWidth = desiredWidth < naturalWidth, + clipHeight = desiredHeight < naturalHeight; + + if ( clipWidth ) { + this.$clippable.css( { overflowX: 'scroll', width: desiredWidth } ); + } else { + this.$clippable.css( { width: this.idealWidth || '', overflowX: '' } ); + } + if ( clipHeight ) { + this.$clippable.css( { overflowY: 'scroll', height: desiredHeight } ); + } else { + this.$clippable.css( { height: this.idealHeight || '', overflowY: '' } ); + } + + // If we stopped clipping in at least one of the dimensions + if ( !clipWidth || !clipHeight ) { + OO.ui.Element.static.reconsiderScrollbars( this.$clippable[ 0 ] ); + } + + this.clippedHorizontally = clipWidth; + this.clippedVertically = clipHeight; + + return this; +}; |