/**
 * This class provides extra contextual information for components/elements by
 * attaching to a {@link #target}. The tip will show based on mouseover (or touch, 
 * depending on the environment) and {@link #align} itself to the {@link #target}.
 *
 * Typically, tooltips will be created via {@link Ext.Component#tooltip components}, however
 * it is possible to create instances directly.
 *
 *     new Ext.tip.ToolTip({
 *         target: myComponent,
 *         html: 'Here is some help text about this!'
 *     });
 *
 * # Shared instance
 * New instances of tooltips do not need to be created for every item that requires
 * a tooltip. In most cases it is sufficient to use a single shared instance across 
 * the application, which provides a performance benefit. See {@link Ext.tip.Manager} 
 * for an explanation of how shared tips are used.
 *
 * # Delegation
 * 
 * It is common to want to show a tooltip for a repeated view and dynamically update
 * the content based on the current item within this view. This can be achieved using
 * the {@link #delegate} configuration. This means that the tip will only activate
 * when over an item inside the target that matches the {@link #delegate}. After this,
 * the {@link #currentTarget} can be interrogated to get contextual information about which
 * delegate item triggered the show.
 *
 *     var el = Ext.getBody().createChild({
 *         html: '<div data-num="1" class="item">Foo</div>' +
 *               '<div data-num="2" class="item">Bar</div>' +
 *               '<div data-num="3" class="item">Baz</div>' +
 *               '<div class="notip">No tip here</div>'
 *     });
 *
 *     new Ext.tip.ToolTip({
 *         target: el,
 *         delegate: '.item',
 *         listeners: {
 *             beforeshow: function(tip) {
 *                 var current = tip.currentTarget.dom;
 *                 tip.setHtml('Item #' + current.getAttribute('data-num'));
 *             }
 *         }
 *     });
 *
 * # Alignment
 *
 * The following configuration properties allow control over how the ToolTip is aligned relative to
 * the target element and/or mouse pointer:
 *
 * - {@link #anchor}
 * - {@link #anchorToTarget}
 * - {@link #trackMouse}
 * - {@link #mouseOffset}
 *
 * # Showing/Hiding
 *
 * The following configuration properties allow control over how and when the ToolTip is 
 * automatically shown and hidden:
 *
 * - {@link #autoHide}
 * - {@link #showDelay}
 * - {@link #hideDelay}
 * - {@link #dismissDelay}
 * 
 * 
 * @since 6.2.0
 */
Ext.define('Ext.tip.ToolTip', {
    extend: 'Ext.Panel',
    xtype: 'tooltip',
 
    floated: true,
 
    hidden: true,
 
    shadow: true,
 
    border: true,
    bodyBorder: false,
 
    anchor: false,
 
    closeAction: 'hide',
 
    config: {
        /**
         * @cfg {String} [align]
         * A string which specifies how this ToolTip is to align with regard to its
         * {@link #currentTarget} by means of identifying the point of the tooltip to
         * join to the point of the target.
         *
         * By default, the tooltip shows at {@link #mouseOffset} pixels from the
         * triggering pointer event. Using this config anchors the ToolTip to its target
         * instead.
         *
         * This may take the following forms:
         * 
         * - **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 the top left corner of the tooltip with the top right corner of its target
         *     // (constrained to viewport).
         *     align: 'tl-tr?'
         *
         *     // align the bottom right corner of the tooltip with the center left edge of its 
         *     // target.
         *     align: 'br-l?'
         *
         *     // align the top center of the tooltip with the bottom left corner of its target.
         *     align: 't-bl'
         *
         *     // align the 25% point on the bottom edge of this tooltip
         *     // with the 75% point on the top edge of its target.
         *     align: 'b25-t75'
         */
        align: 'l-r?',
 
        /**
         * @cfg {String} [alignDelegate]
         * A selector which identifies a child element, or an ancestor element of the
         * {@link #currentTarget} to align to upon show.
         *
         * It will look for the first matching child first, and if none found, look for a
         * matching ancestor.
         */
        alignDelegate: null,
 
        /**
         * @cfg {Boolean} [allowOver=false]
         * Set to `true` to allow mouse exiting the target, but moving into the ToolTip to
         * keep the ToolTip visible. This may be useful for interactive tips.
         *
         * While the mouse is over the tip, the {@link #dismissDelay dismiss timer} is
         * inactive, so the tip will not {@link #autoHide}.
         *
         * On touch platforms, a touch on the tooltip is the equivalent, and this cancels
         * the dismiss timer so that a tap outside is then necessary to hide the tip.
         *
         * This is incompatible with the {@link #cfg-trackMouse} config.
         */
        allowOver: null,
 
        /**
         * @cfg {Boolean} [anchorToTarget]
         * By default, the {@link #align} config aligns to the {@link #target}.
         *
         * Configure this as `false` if an anchor is required, but positioning is still
         * relative to the pointer position on show.
         */
        anchorToTarget: true,
 
        /**
         * @cfg {Boolean} [autoHide]
         * True to automatically hide the tooltip after the mouse exits the target element
         * or after the `{@link #dismissDelay}` has expired if set.
         *
         * If `{@link #closable} = true` a close tool button will be rendered into the
         * tooltip header.
         */
        autoHide: true,
 
        /**
         * @cfg {String} [delegate]
         * A DOM querySelector which identifies child elements of the target which trigger showing
         * this ToolTip. The {@link #currentTarget} property is set to the triggering
         * element.
         */
        delegate: null,
 
        /**
         * @cfg {Number} [dismissDelay]
         * Delay in milliseconds before the tooltip automatically hides.
         *
         * Set this value to `0` to disable automatic hiding.
         */
        dismissDelay: 5000,
 
        /**
         * @cfg {Number} [hideDelay]
         * Delay in milliseconds after the mouse exits the target element but before the
         * tooltip actually hides.
         *
         * Set to `0` for the tooltip to hide immediately.
         */
        hideDelay: 300,
 
        /**
         * @cfg {Number[]} [mouseOffset]
         * An XY offset from the triggering pointer event position where the tooltip
         * should be shown unless aligned to the target element.
         */
        mouseOffset: [15, 18],
 
        /**
         * @cfg {Number} [quickShowInterval]
         * If a show is triggered within this number of milliseconds of this ToolTip being
         * hidden, it shows immediately regardless of the delay. If rapidly moving from
         * target to target, this ensure that each separate target does not get its own
         * delay.
         */
        quickShowInterval: 250,
 
        /**
         * @cfg {Number} [showDelay]
         * Delay in milliseconds before the tooltip displays after the mouse enters the
         * target element.
         *
         * On touch platforms, if {@link #showOnTap} is `true`, a tap on the target shows
         * the tip, and this timer is ignored - the tip is shown immediately.
         */
        showDelay: 500,
 
        /**
         * @cfg {Boolean/String[]} [showOnTap=false]
         * `true` to show this tip on a tap event on the target. If specified as a string,
         * it should be the {@link Ext.event.Event#pointerType} of the event that should
         * trigger a show. Typically, this will be `touch`.
         *
         * This is useful for adding tips on elements which do not have tap listeners. It
         * would not be appropriate for a ToolTip on a {@link Ext.Button Button}.
         */
        showOnTap: null,
 
        /**
         * @cfg {Ext.Component/Ext.dom.Element} target
         * The target that should trigger showing this ToolTip.
         *
         * If the specified target is a Component, then the lifecycle of this ToolTip
         * is bound to the lifecycle of its target. This ToolTip will be destroyed when
         * the target Component is destroyed.
         */
        target: null,
 
        /**
         * @cfg {Boolean} [trackMouse]
         * True to have the tooltip follow the mouse as it moves over the target element.
         *
         * Only effective on platforms with pointing devices, this does not address touch
         * events.
         */
        trackMouse: false
    },
 
    classCls: Ext.baseCSSPrefix + 'tooltip',
 
    headerCls: Ext.baseCSSPrefix + 'tooltipheader',
    titleCls: Ext.baseCSSPrefix + 'tooltiptitle',
    toolCls: [
        Ext.baseCSSPrefix + 'paneltool',
        Ext.baseCSSPrefix + 'tooltiptool'
    ],
 
    closeToolText: null,
 
    constructor: function(config) {
        /**
         * @property {Ext.dom.Fly} currentTarget
         * Only attached to a DOM element  when this ToolTip is active. The current target.
         * This is usually the {@link #cfg-target}, but if the {@link #cfg-delegate} option
         * is used, it may be a child element of the main target.
         * @readonly
         */
        this.currentTarget = new Ext.dom.Fly();
 
        this.callParent([config]);
    },
 
    getRefOwner: function() {
        var target = this.getTarget();
 
        return (target && target.isComponent) ? target : this.callParent();
    },
 
    updateAnchor: function() {
        this.doRealignToTarget();
    },
 
    applyAlign: function(align) {
        var lastChar = align[align.length - 1];
 
        // Tooltips constrain themselves.
        if (lastChar !== '?' && lastChar !== '!') {
            align += '?';
        }
 
        return align;
    },
 
    updateAlign: function() {
        this.doRealignToTarget();
    },
 
    updateAllowOver: function(allowOver) {
        var me = this;
 
        me.overListeners = Ext.destroy(me.overListeners);
 
        // Use the mouseleave and mouseenter events because we do not need delegation
        if (allowOver) {
            me.overListeners = me.el.on({
                mouseenter: 'onTipOver',
                mouseleave: 'onTipOut',
                scope: me,
                destroyable: true
            });
        }
    },
 
    applyTarget: function(target) {
        if (target) {
            if (!target.isComponent) {
                target = Ext.get(target.el || target);
            }
        }
 
        return target;
    },
 
    updateTarget: function(target, oldTarget) {
        var me = this;
 
        if (oldTarget) {
            oldTarget.un('destroy', me.destroy, me);
        }
 
        if (target) {
            if (target.isComponent) {
                me.targetElement = target.element;
 
                // If we're taking care of one component, we die with it.
                // And we attach listeners to its element.
                target.on('destroy', me.destroy, me);
            }
            else {
                me.targetElement = Ext.get(target);
            }
        }
        else {
            me.targetElement = null;
        }
 
        me.attachTargetListeners();
    },
 
    updateTrackMouse: function(trackMouse) {
        // If tracking mouse, allow mouse to enter the tooltip without triggering dismiss
        if (trackMouse) {
            this.setAllowOver(trackMouse);
        }
    },
 
    updateDisabled: function(disabled, oldDisabled) {
        var me = this,
            val;
 
        me.callParent([disabled, oldDisabled]);
 
        if (disabled) {
            me.clearTimers();
            me.hide();
            val = null;
        }
 
        // If we pass null, it won't attempt to attach listeners
        me.attachTargetListeners(val);
    },
 
    updateShowOnTap: function(showOnTap) {
        if (!this.isConfiguring) {
            this.attachTargetListeners();
        }
    },
 
    /**
     * Realign this tip to the current target if it is currently visible.
     *
     * @since 6.2.1
     */
    realignToTarget: function() {
        this.doRealignToTarget();
    },
 
    showBy: function(target, alignment, passedOptions) {
        var me = this,
            alignTarget = target,
            alignDelegate = me.getAlignDelegate();
 
        // If we are trackMouse: true, we will be asked to show by a pointer event
        if (target.isEvent) {
            me.alignToEvent(target);
        }
        else {
            if (target.isWidget) {
                me.updateCurrentTarget(target.element.dom);
            }
            else if (target.isElement) {
                me.updateCurrentTarget(target.dom);
            }
            else if (target.nodeType) {
                me.updateCurrentTarget(target);
            }
 
            // Use the alignDelegate to find a matching descendant, or a matching ancestor.
            if (alignDelegate) {
                target = Ext.fly(target);
                alignTarget = target.down(alignDelegate, true) ||
                              target.up(alignDelegate, me.targetElement, true);
            }
 
            me.callParent([alignTarget, alignment || me.getAlign(), passedOptions]);
        }
    },
 
    beforeShow: function(options) {
        var me = this,
            result = me.callParent(arguments);
 
        // Show is going ahead. Ensure there is alignment if a raw show() call used.
        // Cancel an impending hide.
        if (result !== false) {
            // A call to show with no alignment specified should align to the target
            if (!options.alignment && (me.pointerEvent || me.getTarget())) {
                options.alignment = {
                    component: me.targetElement,
                    alignment: me.getAlign(),
                    options: {
                        overlap: me.getTrackMouse() && !me.getAnchor()
                    }
                };
            }
 
            me.clearTimer('dismiss');
        }
    },
 
    afterShow: function() {
        var me = this;
 
        me.callParent();
        me.postprocessShow();
        me.mousedownListener = Ext.on({
            mousedown: 'onDocMouseDown',
            scope: me,
            destroyable: true
        });
    },
 
    hide: function() {
        var me = this;
 
        me.clearTimer('hide');
        me.clearTimer('dismiss');
        me.callParent();
        me.lastHidden = new Date();
        me.updateCurrentTarget(null);
        Ext.destroy(me.mousedownListener);
    },
 
    doDestroy: function() {
        var me = this;
 
        me.clearTimers();
        me.setTarget(null);
        me.destroyMembers('mousedownListener', 'overListeners');
        me.callParent();
    },
 
    privates: {
        allowRealign: true,
 
        onDocMouseDown: function(e) {
            var me = this,
                delegate = me.getDelegate();
 
            if (e.within(me.el.dom)) {
                // A real touch event inside the tip is the equivalent of
                // mousing over the tip to keep it visible, so cancel the
                // dismiss timer.
                if (e.pointerType !== 'mouse' && me.getAllowOver()) {
                    me.clearTimer('dismiss');
                }
            }
            // Only respond to the mousedown if it's not on this tip, and it's not on a target.
            // If it's on a target, onTargetTap will handle it.
            else if (!me.getClosable()) {
                if (
                    e.within(me.targetElement) &&
                    (!delegate || e.getTarget(delegate, me.targetElement))
                ) {
                    me.delayHide();
                }
                else {
                    me.disable();
                    me.enableTimer = Ext.defer(me.enable, 100, me);
                }
            }
        },
 
        onTargetOver: function(e) {
            var me = this,
                myTarget = me.targetElement,
                delegate = me.getDelegate(),
                currentTarget = me.currentTarget,
                newTarget;
 
            if (me.getDisabled()) {
                return;
            }
 
            // If mouse moves over the tip, ignore it if that is allowed.
            if (me.getAllowOver() && me.el.contains(e.target)) {
                return;
            }
 
            if (delegate) {
                // Moving inside a delegate
                if (currentTarget.contains(e.target)) {
                    return;
                }
 
                newTarget = e.getTarget(delegate, myTarget);
 
                // Mouseovers while within a target do nothing
                if (newTarget && e.getRelatedTarget(delegate) === newTarget) {
                    return;
                }
            }
            // Moved from outside the target
            else if (!myTarget.contains(e.relatedTarget)) {
                newTarget = myTarget.dom;
            }
            // Moving inside the target
            else {
                return;
            }
 
            // If pointer entered the target or a delegate child, then show.
            if (newTarget) {
                me.handleTargetOver(e, newTarget);
            }
            // If over a non-delegate child, behave as in target out
            else if (currentTarget.dom) {
                me.handleTargetOut();
            }
        },
 
        handleTargetOver: function(e, newTarget) {
            var me = this,
                myListeners = me.hasListeners;
 
            me.pointerEvent = e;
            me.updateCurrentTarget(newTarget);
 
            // We are over a new target. If we are still visible, we
            // do not want to hide to avoid flickering. But if there is a
            // beforeshow listener which may mutate us, we still have to
            // consult it. If it returns a veto, then we do in fact hide.
            // Under normal circumstances we continue with no delay into
            // showByTarget in a visible state.
            // We must handle the post show tasks like starting the autoHide timer etc.
            if (me.isVisible()) {
                if (myListeners.beforeshow && me.fireEvent('beforeshow', me) === false) {
                    return me.hide();
                }
 
                me.clearTimer('hide');
                me.clearTimer('dismiss');
                me.showByTarget(newTarget);
 
                if (myListeners.show) {
                    me.fireEvent('show', me);
                }
 
                me.postprocessShow();
            }
            else {
                me.delayShow(newTarget);
            }
        },
 
        postprocessShow: function() {
            var me = this,
                dismissDelay = me.getDismissDelay();
 
            me.clearTimer('show');
 
            if (dismissDelay && me.getAutoHide()) {
                me.dismissTimer = Ext.defer(me.hide, dismissDelay, me);
            }
 
            me.toFront();
        },
 
        onTargetTap: function(e) {
            // On hybrid mouse/touch systems, we want to show the tip on touch, but
            // we don't want to show it if this is coming from a click event, because
            // the mouse is already hovered. Tap occasionally hides - eg: pickers, menus.
            if (e.pointerType !== 'mouse' && Ext.fly(e.target).isVisible(true)) {
                this.onTargetOver(e);
            }
        },
 
        onTargetOut: function(e) {
            // We have exited the current target
            if (this.currentTarget.dom && !this.currentTarget.contains(e.relatedTarget)) {
 
                // If we haven't moved into the tip with allowOver set, then kick off ths hide timer
                if (!this.getAllowOver() && e.within(this.el, true)) {
                    this.handleTargetOut();
                }
            }
        },
 
        handleTargetOut: function() {
            // Separated from onTargetOut so that subclasses can handle target out in any way.
            var me = this;
 
            if (me.showTimer) {
                me.clearTimer('show');
            }
 
            if (me.isVisible() && me.getAutoHide()) {
                me.delayHide();
            }
        },
 
        onTipOver: function() {
            this.clearTimer('hide');
            this.clearTimer('dismiss');
        },
 
        onTipOut: function(e) {
            // A mouseout of the tip itself is only a mouseout if the pointer has already moved
            // outside the target and we have no current target, or the mouseout point is outside
            // of the target.
            if (
                !this.currentTarget.dom ||
                !e.getPoint().isContainedBy(this.currentTarget.getRegion())
            ) {
                this.handleTargetOut();
            }
        },
 
        onMouseMove: function(e) {
            var me = this,
                dismissDelay = me.getDismissDelay();
 
            // Always update pointerEvent, so that if there's a delayed show
            // scheduled, it gets the latest pointer to align to.
            if (!me.el.contains(e.target)) {
                me.pointerEvent = e;
            }
 
            if (me.isVisible() && me.currentTarget.contains(e.target)) {
                // If they move the mouse, restart the dismiss delay
                if (dismissDelay && me.getAutoHide() !== false) {
                    me.clearTimer('dismiss');
                    me.dismissTimer = Ext.defer(me.hide, dismissDelay, me);
                }
 
                if (me.getTrackMouse()) {
                    me.alignToEvent(e);
                }
            }
        },
 
        delayShow: function(target) {
            var me = this;
 
            me.clearTimer('hide');
 
            if (me.getHidden() && !me.showTimer) {
                // Allow rapid movement from delegate to delegate to show immediately
                if (
                    me.getDelegate() &&
                    Ext.Date.getElapsed(me.lastHidden) < me.getQuickShowInterval()
                ) {
                    me.showByTarget(target);
                }
                else {
                    // If a tap event triggered, do not wait. Show immediately.
                    me.showTimer = Ext.defer(
                        me.showByTarget,
                        (!me.pointerEvent || me.pointerEvent.pointerType === 'mouse')
                            ? me.getShowDelay()
                            : 0,
                        me,
                        [target]
                    );
                }
            }
            else if (!me.getHidden() && me.getAutoHide() !== false) {
                me.showByTarget(target);
            }
        },
 
        showByTarget: function(target) {
            var me = this,
                isTarget = me.getAnchorToTarget() && !me.getTrackMouse();
 
            me.lastShowWasPointer = !isTarget;
            // Show by the correct thing.
            // If trackMouse, or we are not anchored to the target, then it's the
            // current pointer event. Otherwise it's either the current target, or 
            // the alignDelegate within that.
            me.showBy(
                isTarget ? target : me.pointerEvent,
                me.getAlign(), { overlap: me.getTrackMouse() && !me.getAnchor() }
            );
        },
 
        delayHide: function() {
            var me = this;
 
            if (!me.isHidden() && !me.hideTimer) {
                me.clearTimer('dismiss');
                me.hideTimer = Ext.defer(me.hide, me.getHideDelay(), me);
            }
        },
 
        alignToEvent: function(event) {
            var me = this,
                options = {
                    // Allow the "exclusion area", the zone of mouseOffset
                    // created as a Region around the mouse to overlap
                    // the tip position.
                    overlap: me.getTrackMouse() && !me.getAnchor()
                },
                mouseOffset = me.getMouseOffset(),
                target = event.getPoint().adjust(
                    -Math.abs(mouseOffset[1]),
                    Math.abs(mouseOffset[0]),
                    Math.abs(mouseOffset[1]),
                    -Math.abs(mouseOffset[0])
                ),
                align = me.getAnchor() ? me.getAlign() : null;
 
            if (!align && mouseOffset) {
                if (mouseOffset[0] > 0) {
                    if (mouseOffset[1] > 0) {
                        align = 'tl-br?';
                    }
                    else {
                        align = 'bl-tr?';
                    }
                }
                else {
                    if (mouseOffset[1] > 0) {
                        align = 'tr-bl?';
                    }
                    else {
                        align = 'br-tl?';
                    }
                }
            }
 
            if (me.isVisible()) {
                me.clearTimer('hide');
                me.alignTo(target, align, options);
            }
            else {
                me.showBy(target, align, options);
            }
        },
 
        _timerNames: {},
 
        clearTimer: function(name) {
            var me = this,
                names = me._timerNames,
                propName = names[name] || (names[name] = name + 'Timer'),
                timer = me[propName];
 
            if (timer) {
                Ext.undefer(timer);
                me[propName] = null;
 
                // We were going to show against the target, but now not.
                if (name === 'show' && me.isHidden()) {
                    me.updateCurrentTarget(null);
                }
            }
        },
 
        /**
         * @private
         */
        clearTimers: function() {
            var me = this;
 
            me.clearTimer('show');
            me.clearTimer('dismiss');
            me.clearTimer('hide');
            me.clearTimer('enable');
        },
 
        clipTo: function(clippingEl, sides) {
        // Override because we also need to clip the anchor
            var clippingRegion;
 
            // Allow a Region to be passed
            if (clippingEl.isRegion) {
                clippingRegion = clippingEl;
            }
            else {
                clippingRegion = (
                    clippingEl.isComponent ? clippingEl.el : Ext.fly(clippingEl)
                ).getConstrainRegion();
            }
 
            // this method is borrowed by the Widget override
            // @noOptimize.callParent
            this.callParent([clippingRegion, sides]);
 
            // Clip the anchor to the same bounds
            this.tipElement.clipTo(clippingRegion, sides);
        },
 
        doRealignToTarget: function() {
            var me = this,
                currentTarget = me.currentTarget,
                dom = currentTarget && currentTarget.dom;
 
            me.clearTimers();
 
            if (me.allowRealign && me.isVisible() && dom) {
                // Realign, overriding possibly stale alignment
                me.realign(null, me.getAlign());
            }
        },
 
        updateCurrentTarget: function(dom) {
            var me = this,
                currentTarget = me.currentTarget,
                was = currentTarget.dom;
 
            currentTarget.attach(dom);
 
            if (!me.isConfiguring) {
                me.fireEvent('hovertarget', me, currentTarget, was);
            }
        },
 
        attachTargetListeners: function(target) {
            var me = this,
                listeners;
 
            me.targetListeners = Ext.destroy(me.targetListeners);
 
            // The only time the target argument is passed is as null
            // to remove listeners on disable.
            // In all other cases, it's not passed, and we use our target.
            if (target === null) {
                return;
            }
 
            // The target argument is not passed when we are being
            // asked to attach listeners.
            target = me.targetElement;
 
            if (target) {
                listeners = {
                    mouseover: 'onTargetOver',
                    mouseout: 'onTargetOut',
                    mousemove: 'onMouseMove',
                    scope: me,
                    destroyable: true
                };
 
                if (me.getShowOnTap()) {
                    listeners.tap = 'onTargetTap';
                }
 
                me.targetListeners = target.on(listeners);
            }
        }
    }
});