/** * This mixin provides a common interface for objects that can be positioned, e.g. * {@link Ext.Component Components} and {@link Ext.dom.Element Elements} * @private */Ext.define('Ext.util.Positionable', { mixinId: 'positionable', _positionTopLeft: ['position', 'top', 'left'], // Stub implementation called after positioning. // May be implemented in subclasses. Component has an implementation. // Hardware acceleration due to the transform:translateZ(0) flickering // when painting clipped elements. This class allows that to be turned off // while elements are in a clipped state. clippedCls: Ext.baseCSSPrefix + 'clipped', afterSetPosition: Ext.emptyFn, //<debug> // *********************** // Begin Abstract Methods // *********************** /** * Gets the x,y coordinates of an element specified by the anchor position on the * element. * @param {Ext.dom.Element} el The element * @param {String} [anchor='tl'] The specified anchor position. * See {@link #alignTo} for details on supported anchor positions. * @param {Boolean} [local] True to get the local (element top/left-relative) anchor * position instead of page coordinates * @param {Object} [size] An object containing the size to use for calculating anchor * position {width: (target width), height: (target height)} (defaults to the * element's current size) * @return {Number[]} [x, y] An array containing the element's x and y coordinates * @private */ getAnchorToXY: function() { Ext.raise("getAnchorToXY is not implemented in " + this.$className); }, /** * Returns the size of the element's borders and padding. * @return {Object} an object with the following numeric properties * - beforeX * - afterX * - beforeY * - afterY * @private */ getBorderPadding: function() { Ext.raise("getBorderPadding is not implemented in " + this.$className); }, /** * Returns the x coordinate of this element reletive to its `offsetParent`. * @return {Number} The local x coordinate */ getLocalX: function() { Ext.raise("getLocalX is not implemented in " + this.$className); }, /** * Returns the x and y coordinates of this element relative to its `offsetParent`. * @return {Number[]} The local XY position of the element */ getLocalXY: function() { Ext.raise("getLocalXY is not implemented in " + this.$className); }, /** * Returns the y coordinate of this element reletive to its `offsetParent`. * @return {Number} The local y coordinate */ getLocalY: function() { Ext.raise("getLocalY is not implemented in " + this.$className); }, /** * Gets the current X position of the DOM element based on page coordinates. * @return {Number} The X position of the element */ getX: function() { Ext.raise("getX is not implemented in " + this.$className); }, /** * Gets the current position of the DOM element based on page coordinates. * @return {Number[]} The XY position of the element */ getXY: function() { Ext.raise("getXY is not implemented in " + this.$className); }, /** * Gets the current Y position of the DOM element based on page coordinates. * @return {Number} The Y position of the element */ getY: function() { Ext.raise("getY is not implemented in " + this.$className); }, /** * Sets the local x coordinate of this element using CSS style. When used on an * absolute positioned element this method is symmetrical with {@link #getLocalX}, but * may not be symmetrical when used on a relatively positioned element. * @param {Number} x The x coordinate. A value of `null` sets the left style to 'auto'. * @return {Ext.util.Positionable} this */ setLocalX: function() { Ext.raise("setLocalX is not implemented in " + this.$className); }, /** * Sets the local x and y coordinates of this element using CSS style. When used on an * absolute positioned element this method is symmetrical with {@link #getLocalXY}, but * may not be symmetrical when used on a relatively positioned element. * @param {Number/Array} x The x coordinate or an array containing [x, y]. A value of * `null` sets the left style to 'auto' * @param {Number} [y] The y coordinate, required if x is not an array. A value of * `null` sets the top style to 'auto' * @return {Ext.util.Positionable} this */ setLocalXY: function() { Ext.raise("setLocalXY is not implemented in " + this.$className); }, /** * Sets the local y coordinate of this element using CSS style. When used on an * absolute positioned element this method is symmetrical with {@link #getLocalY}, but * may not be symmetrical when used on a relatively positioned element. * @param {Number} y The y coordinate. A value of `null` sets the top style to 'auto'. * @return {Ext.util.Positionable} this */ setLocalY: function() { Ext.raise("setLocalY is not implemented in " + this.$className); }, /** * Sets the X position of the DOM element based on page coordinates. * @param {Number} x The X position * @return {Ext.util.Positionable} this */ setX: function() { Ext.raise("setX is not implemented in " + this.$className); }, /** * Sets the position of the DOM element in page coordinates. * @param {Number[]} pos Contains X & Y [x, y] values for new position (coordinates * are page-based) * @return {Ext.util.Positionable} this */ setXY: function() { Ext.raise("setXY is not implemented in " + this.$className); }, /** * Sets the Y position of the DOM element based on page coordinates. * @param {Number} y The Y position * @return {Ext.util.Positionable} this */ setY: function() { Ext.raise("setY is not implemented in " + this.$className); }, // *********************** // End Abstract Methods // *********************** //</debug> // TODO: currently only used by ToolTip. does this method belong here? /** * @private */ adjustForConstraints: function(xy, parent) { var vector = this.getConstrainVector(parent, xy); if (vector) { xy[0] += vector[0]; xy[1] += vector[1]; } return xy; }, /** * Aligns the element with another element relative to the specified anchor points. If * the other element is the document it aligns it to the viewport. The position * parameter is optional, and can be specified in any one of the following formats: * * - **Blank**: Defaults to aligning the element's top-left corner to the target's * bottom-left corner ("tl-bl"). * - **Two anchors**: If two values from the table below are passed separated by a dash, * the first value is used as the element's anchor point, and the second value is * used as the target's anchor point. * - **Two edge/offset descriptors:** An edge/offset descriptor is an edge initial * (`t`/`r`/`b`/`l`) followed by a percentage along that side. This describes a * point to align with a similar point in the target. So `'t0-b0'` would be * the same as `'tl-bl'`, `'l0-r50'` would place the top left corner of this item * halfway down the right edge of the target item. This allows more flexibility * and also describes which two edges are considered adjacent when positioning a tip pointer. * * Following are all of the supported predefined anchor positions: * * Value Description * ----- ----------------------------- * tl The top left corner * t The center of the top edge * tr The top right corner * l The center of the left edge * c The center * r The center of the right edge * bl The bottom left corner * b The center of the bottom edge * br The bottom right corner * * You can put a '?' at the end of the alignment string to constrain the positioned element * to the {@link Ext.Viewport Viewport}. The element will attempt to align as specified, but * the position will be adjusted to constrain to the viewport if necessary. Note that * the element being aligned might be swapped to align to a different position than that * specified in order to enforce the viewport constraints. * * Example Usage: * * // align el to other-el using the default positioning * // ("tl-bl", non-constrained) * el.alignTo("other-el"); * * // align the top left corner of el with the top right corner of other-el * // (constrained to viewport) * el.alignTo("other-el", "tl-tr?"); * * // align the bottom right corner of el with the center left edge of other-el * el.alignTo("other-el", "br-l?"); * * // align the center of el with the bottom left corner of other-el and * // adjust the x position by -6 pixels (and the y position by 0) * el.alignTo("other-el", "c-bl", [-6, 0]); * * // align the 25% point on the bottom edge of this el * // with the 75% point on the top edge of other-el. * el.alignTo("other-el", 'b25-t75'); * * @param {Ext.util.Positionable/HTMLElement/String} element The Positionable, * HTMLElement, or id of the element to align to. * @param {String} [position="tl-bl?"] The position to align to * @param {Number[]} [offsets] Offset the positioning by [x, y] * Element animation config object * @param {Boolean} animate (private) * @return {Ext.util.Positionable} this */ alignTo: function(element, position, offsets, animate) { var me = this, el = me.el; return me.setXY(me.getAlignToXY(element, position, offsets), el.anim && !!animate ? el.anim(animate) : false); }, /** * Calculates x,y coordinates specified by the anchor position on the element, adding * extraX and extraY values. * @param {String} [anchor='tl'] The specified anchor position. * See {@link #alignTo} for details on supported anchor positions. * @param {Number} [extraX] value to be added to the x coordinate * @param {Number} [extraY] value to be added to the y coordinate * @param {Object} [size] An object containing the size to use for calculating anchor * position {width: (target width), height: (target height)} (defaults to the * element's current size) * @return {Number[]} [x, y] An array containing the element's x and y coordinates * @private */ calculateAnchorXY: function(anchor, extraX, extraY, size) { var region = this.getRegion(); region.setPosition(0, 0); region.translateBy(extraX || 0, extraY || 0); if (size) { region.setWidth(size.width); region.setHeight(size.height); } return region.getAnchorPoint(anchor); }, /** * This function converts a legacy alignment string such as 't-b' into a * pair of edge, offset objects which describe the alignment points of * the two regions. * * So tl-br becomes {myEdge:'t', offset:0}, {otherEdge:'b', offset:100} * * This not only allows more flexibility in the alignment possibilities, * but it also resolves any ambiguity as to chich two edges are desired * to be adjacent if an anchor pointer is required. * @private */ convertPositionSpec: function(posSpec) { return Ext.util.Region.getAlignInfo(posSpec); }, /** * Gets the x,y coordinates to align this element with another element. See * {@link #alignTo} for more info on the supported position values. * @param {Ext.util.Positionable/HTMLElement/String} alignToEl The Positionable, * HTMLElement, or id of the element to align to. * @param {String} [position="tl-bl?"] The position to align to * @param {Number[]} [offsets] Offset the positioning by [x, y] * @return {Number[]} [x, y] */ getAlignToXY: function(alignToEl, position, offsets) { var newRegion = this.getAlignToRegion(alignToEl, position, offsets); return [newRegion.x, newRegion.y]; }, getAlignToRegion: function(alignToEl, posSpec, offset, minHeight) { var me = this, inside, newRegion, bodyScroll; alignToEl = Ext.fly(alignToEl.el || alignToEl); if (!alignToEl || !alignToEl.dom) { //<debug> Ext.raise({ sourceClass: 'Ext.util.Positionable', sourceMethod: 'getAlignToXY', msg: 'Attempted to align an element that doesn\'t exist' }); //</debug> } posSpec = me.convertPositionSpec(posSpec); // If position spec ended with a "?" or "!", then constraining is necessary if (posSpec.constrain) { // Constrain to the correct enclosing object: // If the assertive form was used (like "tl-bl!"), constrain to the alignToEl. if (posSpec.constrain === '!') { inside = alignToEl; } else { // Otherwise, attempt to use the constrainTo property. // Otherwise, if we are a Component, there will be a container property. // Otherwise, use this Positionable's element's parent node. inside = me.constrainTo || me.container || me.el.parent(); } inside = Ext.fly(inside.el || inside).getConstrainRegion(); } if (alignToEl === Ext.getBody()) { bodyScroll = alignToEl.getScroll(); offset = [bodyScroll.left, bodyScroll.top]; } newRegion = me.getRegion().alignTo({ target: alignToEl.getRegion(), inside: inside, minHeight: minHeight, offset: offset, align: posSpec, axisLock: true }); return newRegion; }, /** * Gets the x,y coordinates specified by the anchor position on the element. * @param {String} [anchor='tl'] The specified anchor position. * See {@link #alignTo} for details on supported anchor positions. * @param {Boolean} [local] True to get the local (element top/left-relative) anchor * position instead of page coordinates * @param {Object} [size] An object containing the size to use for calculating anchor * position {width: (target width), height: (target height)} (defaults to the * element's current size) * @return {Number[]} [x, y] An array containing the element's x and y coordinates */ getAnchorXY: function(anchor, local, size) { var me = this, region = me.getRegion(), el = me.el, isViewport = el.dom.nodeName === 'BODY' || el.dom.nodeType === 9, scroll = el.getScroll(); if (local) { region.setPosition(0, 0); } else if (isViewport) { region.setPosition(scroll.left, scroll.top); } if (size) { region.setWidth(size.width); region.setHeight(size.height); } return region.getAnchorPoint(anchor); }, /** * Return an object defining the area of this Element which can be passed to * {@link #setBox} to set another Element's size/location to match this element. * * @param {Boolean} [contentBox] If true a box for the content of the element is * returned. * @param {Boolean} [local] If true the element's left and top relative to its * `offsetParent` are returned instead of page x/y. * @return {Object} An object in the format * @return {Number} return.x The element's X position. * @return {Number} return.y The element's Y position. * @return {Number} return.width The element's width. * @return {Number} return.height The element's height. * @return {Number} return.bottom The element's lower bound. * @return {Number} return.right The element's rightmost bound. * * The returned object may also be addressed as an Array where index 0 contains the X * position and index 1 contains the Y position. The result may also be used for * {@link #setXY} */ getBox: function(contentBox, local) { var me = this, xy = local ? me.getLocalXY() : me.getXY(), x = xy[0], y = xy[1], w, h, borderPadding, beforeX, beforeY; // Document body or document is special case if (me.el.dom.nodeName === 'BODY' || me.el.dom.nodeType === 9) { w = Ext.Element.getViewportWidth(); h = Ext.Element.getViewportHeight(); } else { w = me.getWidth(); h = me.getHeight(); } if (contentBox) { borderPadding = me.getBorderPadding(); beforeX = borderPadding.beforeX; beforeY = borderPadding.beforeY; x += beforeX; y += beforeY; w -= (beforeX + borderPadding.afterX); h -= (beforeY + borderPadding.afterY); } return { x: x, left: x, 0: x, y: y, top: y, 1: y, width: w, height: h, right: x + w, bottom: y + h }; }, /** * Calculates the new [x,y] position to move this Positionable into a constrain region. * * By default, this Positionable is constrained to be within the container it was added to, * or the element it was rendered to. * * Priority is given to constraining the top and left within the constraint. * * An alternative constraint may be passed. * @param {String/HTMLElement/Ext.dom.Element/Ext.util.Region} [constrainTo] The Element * or {@link Ext.util.Region Region} into which this Component is to be constrained. * Defaults to the element into which this Positionable was rendered, or this Component's * {@link Ext.Component#constrainTo}. * @param {Number[]} [proposedPosition] A proposed `[X, Y]` position to test for validity * and to coerce into constraints instead of using this Positionable's current position. * @param {Boolean} [local] The proposedPosition is local *(relative to floatParent * if a floating Component)* * @param {Number[]} [proposedSize] A proposed `[width, height]` size to use when calculating * constraints instead of using this Positionable's current size. * @return {Number[]} **If** the element *needs* to be translated, the new `[X, Y]` position * within constraints if possible, giving priority to keeping the top and left edge * in the constrain region. Otherwise, `false`. * @private */ calculateConstrainedPosition: function(constrainTo, proposedPosition, local, proposedSize) { var me = this, vector, fp = me.floatParent, parentNode = fp ? fp.getTargetEl() : null, parentOffset, borderPadding, proposedConstrainPosition, xy = false; if (local && fp) { parentOffset = parentNode.getXY(); borderPadding = parentNode.getBorderPadding(); parentOffset[0] += borderPadding.beforeX; parentOffset[1] += borderPadding.beforeY; if (proposedPosition) { proposedConstrainPosition = [proposedPosition[0] + parentOffset[0], proposedPosition[1] + parentOffset[1]]; } } else { proposedConstrainPosition = proposedPosition; } // Calculate the constrain vector to coerce our position to within our // constrainTo setting. getConstrainVector will provide a default constraint // region if there is no explicit constrainTo, *and* there is no floatParent // owner Component. constrainTo = constrainTo || me.constrainTo || parentNode || me.container || me.el.parent(); if (local && proposedConstrainPosition) { proposedConstrainPosition = me.reverseTranslateXY(proposedConstrainPosition); } vector = ((me.constrainHeader && me.header.rendered) ? me.header : me).getConstrainVector( constrainTo, proposedConstrainPosition, proposedSize ); // false is returned if no movement is needed if (vector) { xy = proposedPosition || me.getPosition(local); xy[0] += vector[0]; xy[1] += vector[1]; } return xy; }, /** * Returns the content region of this element for purposes of constraining or clipping floating * children. That is the region within the borders and scrollbars, but not within the padding. * * @return {Ext.util.Region} A Region containing "top, left, bottom, right" properties. */ getConstrainRegion: function() { var me = this, el = me.el, isBody = el.dom.nodeName === 'BODY', dom = el.dom, borders = el.getBorders(), pos = el.getXY(), left = pos[0] + borders.beforeX, top = pos[1] + borders.beforeY, scroll, width, height; // For the body we want to do some special logic. if (isBody) { scroll = el.getScroll(); left = scroll.left; top = scroll.top; width = Ext.Element.getViewportWidth(); height = Ext.Element.getViewportHeight(); } else { width = dom.clientWidth; height = dom.clientHeight; } return new Ext.util.Region(top, left + width, top + height, left); }, /** * Returns the `[X, Y]` vector by which this Positionable's element must be translated to make * a best attempt to constrain within the passed constraint. Returns `false` if the element * does not need to be moved. * * Priority is given to constraining the top and left within the constraint. * * The constraint may either be an existing element into which the element is to be * constrained, or a {@link Ext.util.Region Region} into which this element is to be * constrained. * * By default, any extra shadow around the element is **not** included in the constrain * calculations - the edges of the element are used as the element bounds. To constrain * the shadow within the constrain region, set the `constrainShadow` property on this element * to `true`. * * @param {Ext.util.Positionable/HTMLElement/String/Ext.util.Region} [constrainTo] The * Positionable, HTMLElement, element id, or Region into which the element is to be * constrained. * @param {Number[]} [proposedPosition] A proposed `[X, Y]` position to test for validity * and to produce a vector for instead of using the element's current position * @param {Number[]} [proposedSize] A proposed `[width, height]` size to constrain * instead of using the element's current size * @return {Number[]/Boolean} **If** the element *needs* to be translated, an `[X, Y]` * vector by which this element must be translated. Otherwise, `false`. */ getConstrainVector: function(constrainTo, proposedPosition, proposedSize) { var me = this, thisRegion = me.getRegion(), vector = [0, 0], shadowSize = (me.shadow && me.constrainShadow && !me.shadowDisabled) ? me.el.shadow.getShadowSize() : undefined, overflowed = false, constraintInsets = me.constraintInsets; if (!(constrainTo instanceof Ext.util.Region)) { constrainTo = Ext.get(constrainTo.el || constrainTo); // getConstrainRegion uses clientWidth and clientHeight. // so it will clear any scrollbars. constrainTo = constrainTo.getConstrainRegion(); } // Apply constraintInsets if (constraintInsets) { constraintInsets = Ext.isObject(constraintInsets) ? constraintInsets : Ext.Element.parseBox(constraintInsets); constrainTo.adjust(constraintInsets.top, constraintInsets.right, constraintInsets.bottom, constraintInsets.left); } // Shift this region to occupy the proposed position if (proposedPosition) { thisRegion.translateBy(proposedPosition[0] - thisRegion.x, proposedPosition[1] - thisRegion.y); } // Set the size of this region to the proposed size if (proposedSize) { thisRegion.right = thisRegion.left + proposedSize[0]; thisRegion.bottom = thisRegion.top + proposedSize[1]; } // Reduce the constrain region to allow for shadow if (shadowSize) { constrainTo.adjust(shadowSize[0], -shadowSize[1], -shadowSize[2], shadowSize[3]); } // Constrain the X coordinate by however much this Element overflows if (thisRegion.right > constrainTo.right) { overflowed = true; vector[0] = (constrainTo.right - thisRegion.right); // overflowed the right } if (thisRegion.left + vector[0] < constrainTo.left) { overflowed = true; vector[0] = (constrainTo.left - thisRegion.left); // overflowed the left } // Constrain the Y coordinate by however much this Element overflows if (thisRegion.bottom > constrainTo.bottom) { overflowed = true; vector[1] = (constrainTo.bottom - thisRegion.bottom); // overflowed the bottom } if (thisRegion.top + vector[1] < constrainTo.top) { overflowed = true; vector[1] = (constrainTo.top - thisRegion.top); // overflowed the top } return overflowed ? vector : false; }, /** * Returns the offsets of this element from the passed element. The element must both * be part of the DOM tree and not have display:none to have page coordinates. * @param {Ext.util.Positionable/HTMLElement/String} offsetsTo The Positionable, * HTMLElement, or element id to get get the offsets from. * @return {Number[]} The XY page offsets (e.g. `[100, -200]`) */ getOffsetsTo: function(offsetsTo) { var o = this.getXY(), e = offsetsTo.isRegion ? [offsetsTo.x, offsetsTo.y] : Ext.fly(offsetsTo.el || offsetsTo).getXY(); return [o[0] - e[0], o[1] - e[1]]; }, /** * Returns a region object that defines the area of this element. * @param {Boolean} [contentBox] If true a box for the content of the element is * returned. * @param {Boolean} [local] If true the element's left and top relative to its * `offsetParent` are returned instead of page x/y. * @return {Ext.util.Region} A Region containing "top, left, bottom, right" properties. */ getRegion: function(contentBox, local) { var box = this.getBox(contentBox, local); return new Ext.util.Region(box.top, box.right, box.bottom, box.left); }, /** * Returns a region object that defines the client area of this element. * * That is, the area *within* any scrollbars. * @return {Ext.util.Region} A Region containing "top, left, bottom, right" properties. */ getClientRegion: function() { var me = this, el = me.el, dom = el.dom, viewContentBox = me.getBox(true), scrollbarHeight = dom.offsetHeight > dom.clientHeight, scrollbarWidth = dom.offsetWidth > dom.clientWidth, padding, scrollSize, isRTL; if (scrollbarHeight || scrollbarWidth) { scrollSize = Ext.getScrollbarSize(); // Capture width taken by any vertical scrollbar. // If there is a vertical scrollbar, shrink the box. if (scrollbarWidth) { scrollbarWidth = scrollSize.width; isRTL = el.getStyle('direction') === 'rtl' && !Ext.supports.rtlVertScrollbarOnRight; if (isRTL) { padding = el.getPadding('l'); viewContentBox.left -= padding + Math.max(padding, scrollbarWidth); } else { padding = el.getPadding('r'); viewContentBox.right += padding - Math.max(padding, scrollbarWidth); } } // Capture height taken by any horizontal scrollbar. // If there is a horizontal scrollbar, shrink the box. if (scrollbarHeight) { scrollbarHeight = scrollSize.height; padding = el.getPadding('b'); viewContentBox.bottom += padding - Math.max(padding, scrollbarHeight); } } // The client region excluding any scrollbars. return new Ext.util.Region(viewContentBox.top, viewContentBox.right, viewContentBox.bottom, viewContentBox.left); }, /** * Returns the **content** region of this element. That is the region within the borders * and padding. * @return {Ext.util.Region} A Region containing "top, left, bottom, right" member data. */ getViewRegion: function() { var me = this, el = me.el, isBody = el.dom.nodeName === 'BODY', borderPadding, scroll, pos, top, left, width, height; // For the body we want to do some special logic if (isBody) { scroll = el.getScroll(); left = scroll.left; top = scroll.top; width = Ext.Element.getViewportWidth(); height = Ext.Element.getViewportHeight(); } else { borderPadding = me.getBorderPadding(); pos = me.getXY(); left = pos[0] + borderPadding.beforeX; top = pos[1] + borderPadding.beforeY; width = me.getWidth(true); height = me.getHeight(true); } return new Ext.util.Region(top, left + width, top + height, left); }, /** * Move the element relative to its current position. * @param {String} direction Possible values are: * * - `"l"` (or `"left"`) * - `"r"` (or `"right"`) * - `"t"` (or `"top"`, or `"up"`) * - `"b"` (or `"bottom"`, or `"down"`) * * @param {Number} distance How far to move the element in pixels * @param {Boolean} animate (private) */ move: function(direction, distance, animate) { var me = this, xy = me.getXY(), x = xy[0], y = xy[1], left = [x - distance, y], right = [x + distance, y], top = [x, y - distance], bottom = [x, y + distance], hash = { l: left, left: left, r: right, right: right, t: top, top: top, up: top, b: bottom, bottom: bottom, down: bottom }; direction = direction.toLowerCase(); me.setXY([hash[direction][0], hash[direction][1]], animate); }, /** * Sets the element's box. * @param {Object} box The box to fill {x, y, width, height} * @return {Ext.util.Positionable} this */ setBox: function(box) { var me = this, x, y; if (box.isRegion) { box = { x: box.left, y: box.top, width: box.right - box.left, height: box.bottom - box.top }; } me.constrainBox(box); x = box.x; y = box.y; // Position to the contrained position // Call setSize *last* so that any possible layout has the last word on position. me.setXY([x, y]); me.setSize(box.width, box.height); me.afterSetPosition(x, y); return me; }, /** * @private */ constrainBox: function(box) { var me = this, constrainedPos, x, y; if (me.constrain || me.constrainHeader) { x = ('x' in box) ? box.x : box.left; y = ('y' in box) ? box.y : box.top; constrainedPos = me.calculateConstrainedPosition( null, [x, y], false, [box.width, box.height] ); // If it *needs* constraining, change the position if (constrainedPos) { box.x = constrainedPos[0]; box.y = constrainedPos[1]; } } }, /** * Translates the passed page coordinates into left/top css values for the element * @param {Number/Array} x The page x or an array containing [x, y] * @param {Number} [y] The page y, required if x is not an array * @return {Object} An object with left and top properties. e.g. * {left: (value), top: (value)} */ translatePoints: function(x, y) { var pos = this.translateXY(x, y); return { left: pos.x, top: pos.y }; }, /** * Translates the passed page coordinates into x and y css values for the element * @param {Number/Array} x The page x or an array containing [x, y] * @param {Number} [y] The page y, required if x is not an array * @return {Object} An object with x and y properties. e.g. * {x: (value), y: (value)} * @private */ translateXY: function(x, y) { var me = this, el = me.el, styles = el.getStyle(me._positionTopLeft), relative = styles.position === 'relative', left = parseFloat(styles.left), top = parseFloat(styles.top), xy = me.getXY(); if (Ext.isArray(x)) { y = x[1]; x = x[0]; } if (isNaN(left)) { left = relative ? 0 : el.dom.offsetLeft; } if (isNaN(top)) { top = relative ? 0 : el.dom.offsetTop; } left = (typeof x === 'number') ? x - xy[0] + left : undefined; top = (typeof y === 'number') ? y - xy[1] + top : undefined; return { x: left, y: top }; }, /** * Converts local coordinates into page-level coordinates * @param {Number[]} xy The local x and y coordinates * @return {Number[]} The translated coordinates * @private */ reverseTranslateXY: function(xy) { var coords = xy, el = this.el, dom = el.dom, offsetParent = dom.offsetParent, relative, offsetParentXY, x, y; if (offsetParent) { relative = el.isStyle('position', 'relative'), offsetParentXY = Ext.fly(offsetParent).getXY(), x = xy[0] + offsetParentXY[0] + offsetParent.clientLeft; y = xy[1] + offsetParentXY[1] + offsetParent.clientTop; if (relative) { // relative positioned elements sit inside the offsetParent's padding, // while absolute positioned element sit just inside the border x += el.getPadding('l'); y += el.getPadding('t'); } coords = [x, y]; } return coords; }, privates: { /** * Clips this Component/Element to fit within the passed element's or component's view area * @param {Ext.Component/Ext.Element/Ext.util.Region} clippingEl The Component or element * or Region which should clip this element even if this element is outside the bounds * of that region. * @param {Number} sides The sides to clip 1=top, 2=right, 4=bottom, 8=left. * * This is to support components being clipped to their logical owner, such as a grid row * editor when the row being edited scrolls out of sight. The editor should be clipped * at the edge of the scrolling element. * @private */ clipTo: function(clippingEl, sides) { var clippingRegion, el = this.el, floaterRegion = el.getRegion(), overflow, i, clipValues = [], clippedCls = this.clippedCls, clipStyle, clipped, shadow; // Allow a Region to be passed if (clippingEl.isRegion) { clippingRegion = clippingEl; } else { // eslint-disable-next-line max-len clippingRegion = (clippingEl.isComponent ? clippingEl.el : Ext.fly(clippingEl)).getConstrainRegion(); } // Default to clipping all round. if (!sides) { sides = 15; } // Calculate how much all sides exceed the clipping region if (sides & 1 && (overflow = clippingRegion.top - floaterRegion.top) > 0) { clipValues[0] = overflow; clipped = true; } else { clipValues[0] = -10000; } if (sides & 2 && (overflow = floaterRegion.right - clippingRegion.right) > 0) { clipValues[1] = Math.max(0, el.getWidth() - overflow); clipped = true; } else { clipValues[1] = 10000; } if (sides & 4 && (overflow = floaterRegion.bottom - clippingRegion.bottom) > 0) { clipValues[2] = Math.max(0, el.getHeight() - overflow); clipped = true; } else { clipValues[2] = 10000; } if (sides & 8 && (overflow = clippingRegion.left - floaterRegion.left) > 0) { clipValues[3] = overflow; clipped = true; } else { clipValues[3] = -10000; } clipStyle = 'rect('; for (i = 0; i < 4; ++i) { // Use the clipValue if there is one calculated. // If not, top and left must be 0px, right and bottom must be 'auto'. clipStyle += Ext.Element.addUnits(clipValues[i], 'px'); clipStyle += (i === 3) ? ')' : ','; } el.dom.style.clip = clipStyle; // hardware acceleration causes flickering problems on clipped elements. // disable it while an element is clipped. el.addCls(clippedCls); // Clip/unclip shadow too. // TODO: As SOON as IE8 retires, refactor Ext.dom.Shadow to use CSS3BoxShadow directly // on its el Then we won't have to bother clipping the shadow as well. We'll just // have to adjust the clipping on the element outwards in the unclipped dimensions // to keep the shadow visible. if ((shadow = el.shadow) && (el = shadow.el) && el.dom) { clipValues[2] -= shadow.offsets.y; clipValues[3] -= shadow.offsets.x; clipStyle = 'rect('; for (i = 0; i < 4; ++i) { // Use the clipValue if there is one calculated. // If not, clear the edges by 10px to allow the shadow's spread to be visible. clipStyle += Ext.Element.addUnits(clipValues[i], 'px'); clipStyle += (i === 3) ? ')' : ','; } el.dom.style.clip = clipStyle; // Clip does not work on IE8 shadows // TODO: As SOON as IE8 retires, refactor Ext.dom.Shadow to use CSS3BoxShadow // directly on its el if (clipped && !Ext.supports.CSS3BoxShadow) { el.dom.style.display = 'none'; } else { el.dom.style.display = ''; // hardware acceleration causes flickering problems on clipped elements. // disable it while an element is clipped. el.addCls(clippedCls); } } }, /** * Clears any clipping applied to this component by {@link #method-clipTo}. * @private */ clearClip: function() { var el = this.el, clippedCls = this.clippedCls; el.dom.style.clip = Ext.isIE8 ? 'auto' : ''; // hardware acceleration causes flickering problems on clipped elements. // re-enable it when an element is unclipped. el.removeCls(clippedCls); // unclip shadow too. if (el.shadow && el.shadow.el && el.shadow.el.dom) { el.shadow.el.dom.style.clip = Ext.isIE8 ? 'auto' : ''; // Clip does not work on IE8 shadows // TODO: As SOON as IE8 retires, refactor Ext.dom.Shadow to use CSS3BoxShadow // directly on its el if (!Ext.supports.CSS3BoxShadow) { el.dom.style.display = ''; // hardware acceleration causes flickering problems on clipped elements. // re-enable it when an element is unclipped. el.removeCls(clippedCls); } } } }});