text: /**
   * 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,
  
      ui: 'tooltip',
  
      hidden: true,
  
      shadow: true,
  
      border: true,
  
      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.
           *
           * In addition to the anchor points, the position parameter also supports the "?"
           * character. If "?" is passed at the end of the position string, 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. Following are all of the supported 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
           *
           * 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-c75'
           */
          align: 'l-r?',
  
          /**
           * @cfg {String} [alignDelegate]
           * A selector which identifies child elements of the  {@link #currentTarget} to
           * align to upon show.
           */
          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 selector 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.
           */
          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
      },
  
      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]);
  
          this.attachTargetListeners();
      },
  
      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 (!me.isConfiguring) {
              me.targetListeners = Ext.destroy(me.targetListeners);
              me.attachTargetListeners();
          }
      },
  
      updateTrackMouse: function (trackMouse) {
          // If tracking mouse, allow mouse to enter the tooltip without triggering dismiss
          if (!this.getAnchor()) {
              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,
              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);
              }
              me.callParent([alignDelegate ? target.child(alignDelegate, true) : target, alignment || me.getAlign(), passedOptions]);
          }
      },
  
      onViewportResize: function () {
          var me = this,
              currentTarget = me.currentTarget;
  
          if (me.isVisible() && !me.lastShowWasPointer && currentTarget.dom) {
              me.showByTarget(me.currentTarget);
          }
      },
  
      show: function () {
          var me = this,
              dismissDelay = me.getDismissDelay();
  
          // A programmatic show should align to the target
          if (!me.currentTarget.dom && (me.pointerEvent || me.getTarget())) {
              return me.showByTarget(me.getElFromTarget());
          }
          me.callParent();
          me.clearTimer('show');
          if (dismissDelay && me.getAutoHide()) {
              me.dismissTimer = Ext.defer(me.hide, dismissDelay, me);
          }
          me.toFront();
          Ext.getDoc().on('mousedown', me.onDocMouseDown, me);
      },
  
      hide: function () {
          var me = this;
  
          me.clearTimer('hide');
          me.clearTimer('dismiss');
          me.callParent();
          me.lastHidden = new Date();
          me.updateCurrentTarget(null);
          Ext.getDoc().un('mousedown', me.onDocMouseDown, me);
      },
  
      doDestroy: function () {
          var me = this;
  
          me.clearTimers();
          me.setTarget(null);
          me.overListeners = null;
          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.getElFromTarget()) && (!delegate || e.getTarget(delegate))) {
                      me.delayHide();
                  } else {
                      me.disable();
                      me.enableTimer = Ext.defer(me.enable, 100, me);
                  }
              }
          },
  
          forceTargetOver: function (e, newTarget) {
              this.pointerEvent = e;
              this.updateCurrentTarget(newTarget);
              this.handleTargetOver();
          },
  
          onTargetOver: function (e) {
              var me = this,
                  myTarget = me.getElFromTarget(),
                  delegate = me.getDelegate(),
                  currentTarget = me.currentTarget,
                  myListeners = me.hasListeners,
                  newTarget;
  
              if (me.getDisabled()) {
                  return;
              }
  
              if (delegate) {
                  // Moving inside a delegate
                  if (currentTarget.contains(e.target)) {
                      return;
                  }
                  newTarget = e.getTarget(delegate);
  
                  // 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) {
                  // If users need to see show events on target change, we must hide.
                  if ((myListeners.beforeshow || myListeners.show) && me.isVisible()) {
                      me.hide();
                      me.hiddenByTargetOver = me.isHidden();
                  }
  
                  me.forceTargetOver(e, newTarget);
              }
              // If over a non-delegate child, behave as in target out
              else if (currentTarget.dom) {
                  me.handleTargetOut();
              }
          },
  
          handleTargetOver: function () {
              // Separated from onTargetOver so that subclasses can handle target over in any way.
              this.delayShow(this.currentTarget);
          },
  
          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.
              if (e.pointerType !== 'mouse') {
                  this.onTargetOver(e);
              }
          },
  
          onTargetOut: function (e) {
              // We have exited the current target
              if (this.currentTarget.dom && !this.currentTarget.contains(e.relatedTarget)) {
                  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 () {
              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.
              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() || me.hiddenByTargetOver) && Ext.Date.getElapsed(me.lastHidden) < me.getQuickShowInterval()) {
                      me.hiddenByTargetOver = false;
                      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.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) {
                  clearTimeout(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) {
                  me.showByTarget(me.pointerEvent || dom);
              }
          },
  
          updateCurrentTarget: function (dom) {
              var me = this,
                  currentTarget = me.currentTarget,
                  was = currentTarget.dom;
  
              currentTarget.attach(dom);
  
              if (!me.isConfiguring) {
                  me.fireEvent('hovertarget', me, currentTarget, was);
              }
          },
  
          getElFromTarget: function () {
              var target = this.getTarget();
              if (target) {
                  if (target.isComponent) {
                      target = target.element;
                  }
              }
              return target;
          },
  
          attachTargetListeners: function (target) {
              var me = this,
                  listeners;
  
              if (target !== null) {
                  target = me.getElFromTarget();
              }
  
              me.targetListeners = Ext.destroy(me.targetListeners);
  
              if (target) {
                  listeners = {
                      mouseover: 'onTargetOver',
                      mouseout: 'onTargetOut',
                      mousemove: 'onMouseMove',
                      scope: me,
                      destroyable: true
                  };
  
                  if (me.getShowOnTap()) {
                      listeners.tap = 'onTargetTap';
                  }
                  me.targetListeners = target.on(listeners);
              }
          }
      }
  });
  