summaryrefslogtreecommitdiff
path: root/vendor/oojs/oojs-ui/src/Element.js
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/oojs/oojs-ui/src/Element.js')
-rw-r--r--vendor/oojs/oojs-ui/src/Element.js748
1 files changed, 0 insertions, 748 deletions
diff --git a/vendor/oojs/oojs-ui/src/Element.js b/vendor/oojs/oojs-ui/src/Element.js
deleted file mode 100644
index 127eb503..00000000
--- a/vendor/oojs/oojs-ui/src/Element.js
+++ /dev/null
@@ -1,748 +0,0 @@
-/**
- * Each Element represents a rendering in the DOM—a button or an icon, for example, or anything
- * that is visible to a user. Unlike {@link OO.ui.Widget widgets}, plain elements usually do not have events
- * connected to them and can't be interacted with.
- *
- * @abstract
- * @class
- *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {string[]} [classes] The names of the CSS classes to apply to the element. CSS styles are added
- * to the top level (e.g., the outermost div) of the element. See the [OOjs UI documentation on MediaWiki][2]
- * for an example.
- * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Buttons_and_Switches#cssExample
- * @cfg {string} [id] The HTML id attribute used in the rendered tag.
- * @cfg {string} [text] Text to insert
- * @cfg {Array} [content] An array of content elements to append (after #text).
- * Strings will be html-escaped; use an OO.ui.HtmlSnippet to append raw HTML.
- * Instances of OO.ui.Element will have their $element appended.
- * @cfg {jQuery} [$content] Content elements to append (after #text)
- * @cfg {Mixed} [data] Custom data of any type or combination of types (e.g., string, number, array, object).
- * Data can also be specified with the #setData method.
- */
-OO.ui.Element = function OoUiElement( config ) {
- // Configuration initialization
- config = config || {};
-
- // Properties
- this.$ = $;
- this.visible = true;
- this.data = config.data;
- this.$element = config.$element ||
- $( document.createElement( this.getTagName() ) );
- this.elementGroup = null;
- this.debouncedUpdateThemeClassesHandler = this.debouncedUpdateThemeClasses.bind( this );
- this.updateThemeClassesPending = false;
-
- // Initialization
- if ( Array.isArray( config.classes ) ) {
- this.$element.addClass( config.classes.join( ' ' ) );
- }
- if ( config.id ) {
- this.$element.attr( 'id', config.id );
- }
- if ( config.text ) {
- this.$element.text( config.text );
- }
- if ( config.content ) {
- // The `content` property treats plain strings as text; use an
- // HtmlSnippet to append HTML content. `OO.ui.Element`s get their
- // appropriate $element appended.
- this.$element.append( config.content.map( function ( v ) {
- if ( typeof v === 'string' ) {
- // Escape string so it is properly represented in HTML.
- return document.createTextNode( v );
- } else if ( v instanceof OO.ui.HtmlSnippet ) {
- // Bypass escaping.
- return v.toString();
- } else if ( v instanceof OO.ui.Element ) {
- return v.$element;
- }
- return v;
- } ) );
- }
- if ( config.$content ) {
- // The `$content` property treats plain strings as HTML.
- this.$element.append( config.$content );
- }
-};
-
-/* Setup */
-
-OO.initClass( OO.ui.Element );
-
-/* Static Properties */
-
-/**
- * The name of the HTML tag used by the element.
- *
- * The static value may be ignored if the #getTagName method is overridden.
- *
- * @static
- * @inheritable
- * @property {string}
- */
-OO.ui.Element.static.tagName = 'div';
-
-/* Static Methods */
-
-/**
- * Reconstitute a JavaScript object corresponding to a widget created
- * by the PHP implementation.
- *
- * @param {string|HTMLElement|jQuery} idOrNode
- * A DOM id (if a string) or node for the widget to infuse.
- * @return {OO.ui.Element}
- * The `OO.ui.Element` corresponding to this (infusable) document node.
- * For `Tag` objects emitted on the HTML side (used occasionally for content)
- * the value returned is a newly-created Element wrapping around the existing
- * DOM node.
- */
-OO.ui.Element.static.infuse = function ( idOrNode ) {
- var obj = OO.ui.Element.static.unsafeInfuse( idOrNode, true );
- // Verify that the type matches up.
- // FIXME: uncomment after T89721 is fixed (see T90929)
- /*
- if ( !( obj instanceof this['class'] ) ) {
- throw new Error( 'Infusion type mismatch!' );
- }
- */
- return obj;
-};
-
-/**
- * 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 {boolean} top True only for top-level invocation.
- * @return {OO.ui.Element}
- */
-OO.ui.Element.static.unsafeInfuse = function ( idOrNode, top ) {
- // look for a cached result of a previous infusion.
- var id, $elem, data, cls, obj;
- if ( typeof idOrNode === 'string' ) {
- id = idOrNode;
- $elem = $( document.getElementById( id ) );
- } else {
- $elem = $( idOrNode );
- id = $elem.attr( 'id' );
- }
- data = $elem.data( 'ooui-infused' );
- if ( data ) {
- // cached!
- if ( data === true ) {
- throw new Error( 'Circular dependency! ' + id );
- }
- return data;
- }
- if ( !$elem.length ) {
- throw new Error( 'Widget not found: ' + id );
- }
- data = $elem.attr( 'data-ooui' );
- if ( !data ) {
- throw new Error( 'No infusion data found: ' + id );
- }
- try {
- data = $.parseJSON( data );
- } catch ( _ ) {
- data = null;
- }
- if ( !( data && data._ ) ) {
- throw new Error( 'No valid infusion data found: ' + id );
- }
- if ( data._ === 'Tag' ) {
- // Special case: this is a raw Tag; wrap existing node, don't rebuild.
- return new OO.ui.Element( { $element: $elem } );
- }
- cls = OO.ui[data._];
- if ( !cls ) {
- throw new Error( 'Unknown widget type: ' + id );
- }
- $elem.data( 'ooui-infused', true ); // prevent loops
- data.id = id; // implicit
- data = OO.copy( data, null, function deserialize( value ) {
- if ( OO.isPlainObject( value ) ) {
- if ( value.tag ) {
- return OO.ui.Element.static.unsafeInfuse( value.tag, false );
- }
- if ( value.html ) {
- return new OO.ui.HtmlSnippet( value.html );
- }
- }
- } );
- // jscs:disable requireCapitalizedConstructors
- obj = new cls( data ); // rebuild widget
- // now replace old DOM with this new DOM.
- if ( top ) {
- $elem.replaceWith( obj.$element );
- }
- obj.$element.data( 'ooui-infused', obj );
- // set the 'data-ooui' attribute so we can identify infused widgets
- obj.$element.attr( 'data-ooui', '' );
- return obj;
-};
-
-/**
- * Get a jQuery function within a specific document.
- *
- * @static
- * @param {jQuery|HTMLElement|HTMLDocument|Window} context Context to bind the function to
- * @param {jQuery} [$iframe] HTML iframe element that contains the document, omit if document is
- * not in an iframe
- * @return {Function} Bound jQuery function
- */
-OO.ui.Element.static.getJQuery = function ( context, $iframe ) {
- function wrapper( selector ) {
- return $( selector, wrapper.context );
- }
-
- wrapper.context = this.getDocument( context );
-
- if ( $iframe ) {
- wrapper.$iframe = $iframe;
- }
-
- return wrapper;
-};
-
-/**
- * Get the document of an element.
- *
- * @static
- * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Object to get the document for
- * @return {HTMLDocument|null} Document object
- */
-OO.ui.Element.static.getDocument = function ( obj ) {
- // jQuery - selections created "offscreen" won't have a context, so .context isn't reliable
- return ( obj[ 0 ] && obj[ 0 ].ownerDocument ) ||
- // Empty jQuery selections might have a context
- obj.context ||
- // HTMLElement
- obj.ownerDocument ||
- // Window
- obj.document ||
- // HTMLDocument
- ( obj.nodeType === 9 && obj ) ||
- null;
-};
-
-/**
- * Get the window of an element or document.
- *
- * @static
- * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Context to get the window for
- * @return {Window} Window object
- */
-OO.ui.Element.static.getWindow = function ( obj ) {
- var doc = this.getDocument( obj );
- return doc.parentWindow || doc.defaultView;
-};
-
-/**
- * Get the direction of an element or document.
- *
- * @static
- * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Context to get the direction for
- * @return {string} Text direction, either 'ltr' or 'rtl'
- */
-OO.ui.Element.static.getDir = function ( obj ) {
- var isDoc, isWin;
-
- if ( obj instanceof jQuery ) {
- obj = obj[ 0 ];
- }
- isDoc = obj.nodeType === 9;
- isWin = obj.document !== undefined;
- if ( isDoc || isWin ) {
- if ( isWin ) {
- obj = obj.document;
- }
- obj = obj.body;
- }
- return $( obj ).css( 'direction' );
-};
-
-/**
- * Get the offset between two frames.
- *
- * TODO: Make this function not use recursion.
- *
- * @static
- * @param {Window} from Window of the child frame
- * @param {Window} [to=window] Window of the parent frame
- * @param {Object} [offset] Offset to start with, used internally
- * @return {Object} Offset object, containing left and top properties
- */
-OO.ui.Element.static.getFrameOffset = function ( from, to, offset ) {
- var i, len, frames, frame, rect;
-
- if ( !to ) {
- to = window;
- }
- if ( !offset ) {
- offset = { top: 0, left: 0 };
- }
- if ( from.parent === from ) {
- return offset;
- }
-
- // Get iframe element
- frames = from.parent.document.getElementsByTagName( 'iframe' );
- for ( i = 0, len = frames.length; i < len; i++ ) {
- if ( frames[ i ].contentWindow === from ) {
- frame = frames[ i ];
- break;
- }
- }
-
- // Recursively accumulate offset values
- if ( frame ) {
- rect = frame.getBoundingClientRect();
- offset.left += rect.left;
- offset.top += rect.top;
- if ( from !== to ) {
- this.getFrameOffset( from.parent, offset );
- }
- }
- return offset;
-};
-
-/**
- * Get the offset between two elements.
- *
- * The two elements may be in a different frame, but in that case the frame $element is in must
- * be contained in the frame $anchor is in.
- *
- * @static
- * @param {jQuery} $element Element whose position to get
- * @param {jQuery} $anchor Element to get $element's position relative to
- * @return {Object} Translated position coordinates, containing top and left properties
- */
-OO.ui.Element.static.getRelativePosition = function ( $element, $anchor ) {
- var iframe, iframePos,
- pos = $element.offset(),
- anchorPos = $anchor.offset(),
- elementDocument = this.getDocument( $element ),
- anchorDocument = this.getDocument( $anchor );
-
- // If $element isn't in the same document as $anchor, traverse up
- while ( elementDocument !== anchorDocument ) {
- iframe = elementDocument.defaultView.frameElement;
- if ( !iframe ) {
- throw new Error( '$element frame is not contained in $anchor frame' );
- }
- iframePos = $( iframe ).offset();
- pos.left += iframePos.left;
- pos.top += iframePos.top;
- elementDocument = iframe.ownerDocument;
- }
- pos.left -= anchorPos.left;
- pos.top -= anchorPos.top;
- return pos;
-};
-
-/**
- * Get element border sizes.
- *
- * @static
- * @param {HTMLElement} el Element to measure
- * @return {Object} Dimensions object with `top`, `left`, `bottom` and `right` properties
- */
-OO.ui.Element.static.getBorders = function ( el ) {
- var doc = el.ownerDocument,
- win = doc.parentWindow || doc.defaultView,
- style = win && win.getComputedStyle ?
- win.getComputedStyle( el, null ) :
- el.currentStyle,
- $el = $( el ),
- top = parseFloat( style ? style.borderTopWidth : $el.css( 'borderTopWidth' ) ) || 0,
- left = parseFloat( style ? style.borderLeftWidth : $el.css( 'borderLeftWidth' ) ) || 0,
- bottom = parseFloat( style ? style.borderBottomWidth : $el.css( 'borderBottomWidth' ) ) || 0,
- right = parseFloat( style ? style.borderRightWidth : $el.css( 'borderRightWidth' ) ) || 0;
-
- return {
- top: top,
- left: left,
- bottom: bottom,
- right: right
- };
-};
-
-/**
- * Get dimensions of an element or window.
- *
- * @static
- * @param {HTMLElement|Window} el Element to measure
- * @return {Object} Dimensions object with `borders`, `scroll`, `scrollbar` and `rect` properties
- */
-OO.ui.Element.static.getDimensions = function ( el ) {
- var $el, $win,
- doc = el.ownerDocument || el.document,
- win = doc.parentWindow || doc.defaultView;
-
- if ( win === el || el === doc.documentElement ) {
- $win = $( win );
- return {
- borders: { top: 0, left: 0, bottom: 0, right: 0 },
- scroll: {
- top: $win.scrollTop(),
- left: $win.scrollLeft()
- },
- scrollbar: { right: 0, bottom: 0 },
- rect: {
- top: 0,
- left: 0,
- bottom: $win.innerHeight(),
- right: $win.innerWidth()
- }
- };
- } else {
- $el = $( el );
- return {
- borders: this.getBorders( el ),
- scroll: {
- top: $el.scrollTop(),
- left: $el.scrollLeft()
- },
- scrollbar: {
- right: $el.innerWidth() - el.clientWidth,
- bottom: $el.innerHeight() - el.clientHeight
- },
- rect: el.getBoundingClientRect()
- };
- }
-};
-
-/**
- * Get scrollable object parent
- *
- * documentElement can't be used to get or set the scrollTop
- * property on Blink. Changing and testing its value lets us
- * use 'body' or 'documentElement' based on what is working.
- *
- * https://code.google.com/p/chromium/issues/detail?id=303131
- *
- * @static
- * @param {HTMLElement} el Element to find scrollable parent for
- * @return {HTMLElement} Scrollable parent
- */
-OO.ui.Element.static.getRootScrollableElement = function ( el ) {
- var scrollTop, body;
-
- if ( OO.ui.scrollableElement === undefined ) {
- body = el.ownerDocument.body;
- scrollTop = body.scrollTop;
- body.scrollTop = 1;
-
- if ( body.scrollTop === 1 ) {
- body.scrollTop = scrollTop;
- OO.ui.scrollableElement = 'body';
- } else {
- OO.ui.scrollableElement = 'documentElement';
- }
- }
-
- return el.ownerDocument[ OO.ui.scrollableElement ];
-};
-
-/**
- * Get closest scrollable container.
- *
- * Traverses up until either a scrollable element or the root is reached, in which case the window
- * will be returned.
- *
- * @static
- * @param {HTMLElement} el Element to find scrollable container for
- * @param {string} [dimension] Dimension of scrolling to look for; `x`, `y` or omit for either
- * @return {HTMLElement} Closest scrollable container
- */
-OO.ui.Element.static.getClosestScrollableContainer = function ( el, dimension ) {
- var i, val,
- props = [ 'overflow' ],
- $parent = $( el ).parent();
-
- if ( dimension === 'x' || dimension === 'y' ) {
- props.push( 'overflow-' + dimension );
- }
-
- while ( $parent.length ) {
- if ( $parent[ 0 ] === this.getRootScrollableElement( el ) ) {
- return $parent[ 0 ];
- }
- i = props.length;
- while ( i-- ) {
- val = $parent.css( props[ i ] );
- if ( val === 'auto' || val === 'scroll' ) {
- return $parent[ 0 ];
- }
- }
- $parent = $parent.parent();
- }
- return this.getDocument( el ).body;
-};
-
-/**
- * Scroll element into view.
- *
- * @static
- * @param {HTMLElement} el Element to scroll into view
- * @param {Object} [config] Configuration options
- * @param {string} [config.duration] jQuery animation duration value
- * @param {string} [config.direction] Scroll in only one direction, e.g. 'x' or 'y', omit
- * to scroll in both directions
- * @param {Function} [config.complete] Function to call when scrolling completes
- */
-OO.ui.Element.static.scrollIntoView = function ( el, config ) {
- // Configuration initialization
- config = config || {};
-
- var rel, anim = {},
- callback = typeof config.complete === 'function' && config.complete,
- sc = this.getClosestScrollableContainer( el, config.direction ),
- $sc = $( sc ),
- eld = this.getDimensions( el ),
- scd = this.getDimensions( sc ),
- $win = $( this.getWindow( el ) );
-
- // Compute the distances between the edges of el and the edges of the scroll viewport
- if ( $sc.is( 'html, body' ) ) {
- // If the scrollable container is the root, this is easy
- rel = {
- top: eld.rect.top,
- bottom: $win.innerHeight() - eld.rect.bottom,
- left: eld.rect.left,
- right: $win.innerWidth() - eld.rect.right
- };
- } else {
- // Otherwise, we have to subtract el's coordinates from sc's coordinates
- rel = {
- top: eld.rect.top - ( scd.rect.top + scd.borders.top ),
- bottom: scd.rect.bottom - scd.borders.bottom - scd.scrollbar.bottom - eld.rect.bottom,
- left: eld.rect.left - ( scd.rect.left + scd.borders.left ),
- right: scd.rect.right - scd.borders.right - scd.scrollbar.right - eld.rect.right
- };
- }
-
- if ( !config.direction || config.direction === 'y' ) {
- if ( rel.top < 0 ) {
- anim.scrollTop = scd.scroll.top + rel.top;
- } else if ( rel.top > 0 && rel.bottom < 0 ) {
- anim.scrollTop = scd.scroll.top + Math.min( rel.top, -rel.bottom );
- }
- }
- if ( !config.direction || config.direction === 'x' ) {
- if ( rel.left < 0 ) {
- anim.scrollLeft = scd.scroll.left + rel.left;
- } else if ( rel.left > 0 && rel.right < 0 ) {
- anim.scrollLeft = scd.scroll.left + Math.min( rel.left, -rel.right );
- }
- }
- if ( !$.isEmptyObject( anim ) ) {
- $sc.stop( true ).animate( anim, config.duration || 'fast' );
- if ( callback ) {
- $sc.queue( function ( next ) {
- callback();
- next();
- } );
- }
- } else {
- if ( callback ) {
- callback();
- }
- }
-};
-
-/**
- * Force the browser to reconsider whether it really needs to render scrollbars inside the element
- * and reserve space for them, because it probably doesn't.
- *
- * Workaround primarily for <https://code.google.com/p/chromium/issues/detail?id=387290>, but also
- * similar bugs in other browsers. "Just" forcing a reflow is not sufficient in all cases, we need
- * to first actually detach (or hide, but detaching is simpler) all children, *then* force a reflow,
- * and then reattach (or show) them back.
- *
- * @static
- * @param {HTMLElement} el Element to reconsider the scrollbars on
- */
-OO.ui.Element.static.reconsiderScrollbars = function ( el ) {
- var i, len, nodes = [];
- // Detach all children
- while ( el.firstChild ) {
- nodes.push( el.firstChild );
- el.removeChild( el.firstChild );
- }
- // Force reflow
- void el.offsetHeight;
- // Reattach all children
- for ( i = 0, len = nodes.length; i < len; i++ ) {
- el.appendChild( nodes[ i ] );
- }
-};
-
-/* Methods */
-
-/**
- * Toggle visibility of an element.
- *
- * @param {boolean} [show] Make element visible, omit to toggle visibility
- * @fires visible
- * @chainable
- */
-OO.ui.Element.prototype.toggle = function ( show ) {
- show = show === undefined ? !this.visible : !!show;
-
- if ( show !== this.isVisible() ) {
- this.visible = show;
- this.$element.toggleClass( 'oo-ui-element-hidden', !this.visible );
- this.emit( 'toggle', show );
- }
-
- return this;
-};
-
-/**
- * Check if element is visible.
- *
- * @return {boolean} element is visible
- */
-OO.ui.Element.prototype.isVisible = function () {
- return this.visible;
-};
-
-/**
- * Get element data.
- *
- * @return {Mixed} Element data
- */
-OO.ui.Element.prototype.getData = function () {
- return this.data;
-};
-
-/**
- * Set element data.
- *
- * @param {Mixed} Element data
- * @chainable
- */
-OO.ui.Element.prototype.setData = function ( data ) {
- this.data = data;
- return this;
-};
-
-/**
- * Check if element supports one or more methods.
- *
- * @param {string|string[]} methods Method or list of methods to check
- * @return {boolean} All methods are supported
- */
-OO.ui.Element.prototype.supports = function ( methods ) {
- var i, len,
- support = 0;
-
- methods = Array.isArray( methods ) ? methods : [ methods ];
- for ( i = 0, len = methods.length; i < len; i++ ) {
- if ( $.isFunction( this[ methods[ i ] ] ) ) {
- support++;
- }
- }
-
- return methods.length === support;
-};
-
-/**
- * Update the theme-provided classes.
- *
- * @localdoc This is called in element mixins and widget classes any time state changes.
- * Updating is debounced, minimizing overhead of changing multiple attributes and
- * guaranteeing that theme updates do not occur within an element's constructor
- */
-OO.ui.Element.prototype.updateThemeClasses = function () {
- if ( !this.updateThemeClassesPending ) {
- this.updateThemeClassesPending = true;
- setTimeout( this.debouncedUpdateThemeClassesHandler );
- }
-};
-
-/**
- * @private
- */
-OO.ui.Element.prototype.debouncedUpdateThemeClasses = function () {
- OO.ui.theme.updateElementClasses( this );
- this.updateThemeClassesPending = false;
-};
-
-/**
- * Get the HTML tag name.
- *
- * Override this method to base the result on instance information.
- *
- * @return {string} HTML tag name
- */
-OO.ui.Element.prototype.getTagName = function () {
- return this.constructor.static.tagName;
-};
-
-/**
- * Check if the element is attached to the DOM
- * @return {boolean} The element is attached to the DOM
- */
-OO.ui.Element.prototype.isElementAttached = function () {
- return $.contains( this.getElementDocument(), this.$element[ 0 ] );
-};
-
-/**
- * Get the DOM document.
- *
- * @return {HTMLDocument} Document object
- */
-OO.ui.Element.prototype.getElementDocument = function () {
- // Don't cache this in other ways either because subclasses could can change this.$element
- return OO.ui.Element.static.getDocument( this.$element );
-};
-
-/**
- * Get the DOM window.
- *
- * @return {Window} Window object
- */
-OO.ui.Element.prototype.getElementWindow = function () {
- return OO.ui.Element.static.getWindow( this.$element );
-};
-
-/**
- * Get closest scrollable container.
- */
-OO.ui.Element.prototype.getClosestScrollableElementContainer = function () {
- return OO.ui.Element.static.getClosestScrollableContainer( this.$element[ 0 ] );
-};
-
-/**
- * Get group element is in.
- *
- * @return {OO.ui.GroupElement|null} Group element, null if none
- */
-OO.ui.Element.prototype.getElementGroup = function () {
- return this.elementGroup;
-};
-
-/**
- * Set group element is in.
- *
- * @param {OO.ui.GroupElement|null} group Group element, null if none
- * @chainable
- */
-OO.ui.Element.prototype.setElementGroup = function ( group ) {
- this.elementGroup = group;
- return this;
-};
-
-/**
- * Scroll element into view.
- *
- * @param {Object} [config] Configuration options
- */
-OO.ui.Element.prototype.scrollElementIntoView = function ( config ) {
- return OO.ui.Element.static.scrollIntoView( this.$element[ 0 ], config );
-};