"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
Object.defineProperty(exports, "Tooltip", {
  enumerable: true,
  get: function get() {
    return _Tooltip.Tooltip;
  }
});
Object.defineProperty(exports, "TooltipBase", {
  enumerable: true,
  get: function get() {
    return _Tooltip.TooltipBase;
  }
});
exports.TooltipDecorator = exports.default = void 0;

var _hoc = _interopRequireDefault(require("@enact/core/hoc"));

var _I18nDecorator = require("@enact/i18n/I18nDecorator");

var _FloatingLayer = require("@enact/ui/FloatingLayer");

var _handle = require("@enact/core/handle");

var _util = require("@enact/core/util");

var _dispatcher = require("@enact/core/dispatcher");

var _react = _interopRequireDefault(require("react"));

var _propTypes = _interopRequireDefault(require("prop-types"));

var _resolution = _interopRequireDefault(require("@enact/ui/resolution"));

var _Tooltip = require("./Tooltip");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }

function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }

function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }

function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }

function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }

var currentTooltip; // needed to know whether or not we should stop a showing job when unmounting

/**
 * Default config for [TooltipDecorator]{@link moonstone/TooltipDecorator.TooltipDecorator}
 *
 * @memberof moonstone/TooltipDecorator.TooltipDecorator
 * @hocconfig
 */

var defaultConfig = {
  /**
   * The name of the property which will receive the tooltip node.
   *
   * By default, `TooltipDecorator` will add a new child to the wrapped component, following any
   * other children passed in. If a component needs to, it can specify another property to receive
   * the tooltip and the `children` property will not be modified.
   *
   * @type {String}
   * @default 'children'
   * @memberof moonstone/TooltipDecorator.TooltipDecorator.defaultConfig
   */
  tooltipDestinationProp: 'children'
};
/**
 * A higher-order component which positions [Tooltip]{@link moonstone/TooltipDecorator.Tooltip} in
 * relation to the wrapped component.
 *
 * The tooltip is automatically displayed when the decoratorated component is focused after a set
 * period of time.
 *
 * The tooltip is positioned around the decorator where there is available window space.
 *
 * Note that the direction of tooltip will be flipped horizontally in RTL locales.
 *
 * @class TooltipDecorator
 * @memberof moonstone/TooltipDecorator
 * @hoc
 * @public
 */

var TooltipDecorator = (0, _hoc.default)(defaultConfig, function (config, Wrapped) {
  var _class, _temp;

  var tooltipDestinationProp = config.tooltipDestinationProp;
  var Decorator = (_temp = _class =
  /*#__PURE__*/
  function (_React$Component) {
    _inherits(Decorator, _React$Component);

    function Decorator(props) {
      var _this;

      _classCallCheck(this, Decorator);

      _this = _possibleConstructorReturn(this, (Decorator.__proto__ || Object.getPrototypeOf(Decorator)).call(this, props));
      Object.defineProperty(_assertThisInitialized(_this), "showTooltipJob", {
        configurable: true,
        enumerable: true,
        writable: true,
        value: new _util.Job(function () {
          if (!_this.state.showing) {
            _this.setState({
              showing: true
            });
          }
        })
      });
      Object.defineProperty(_assertThisInitialized(_this), "showTooltip", {
        configurable: true,
        enumerable: true,
        writable: true,
        value: function value(client) {
          var _this$props = _this.props,
              tooltipDelay = _this$props.tooltipDelay,
              tooltipText = _this$props.tooltipText;

          if (tooltipText) {
            _this.clientRef = client;
            currentTooltip = _assertThisInitialized(_this);

            _this.showTooltipJob.startAfter(tooltipDelay);
          }
        }
      });
      Object.defineProperty(_assertThisInitialized(_this), "hideTooltip", {
        configurable: true,
        enumerable: true,
        writable: true,
        value: function value() {
          if (_this.props.tooltipText) {
            _this.clientRef = null;
            currentTooltip = null;

            _this.showTooltipJob.stop();

            if (_this.state.showing) {
              _this.setState({
                showing: false
              });
            }
          }
        }
      });
      Object.defineProperty(_assertThisInitialized(_this), "handle", {
        configurable: true,
        enumerable: true,
        writable: true,
        value: _handle.handle.bind(_assertThisInitialized(_this))
      });
      Object.defineProperty(_assertThisInitialized(_this), "handleKeyDown", {
        configurable: true,
        enumerable: true,
        writable: true,
        value: _this.handle((0, _handle.forProp)('disabled', true), function () {
          _this.hideTooltip();

          (0, _dispatcher.off)('keydown', _this.handleKeyDown);
        })
      });
      Object.defineProperty(_assertThisInitialized(_this), "handleMouseOver", {
        configurable: true,
        enumerable: true,
        writable: true,
        value: _this.handle((0, _handle.forward)('onMouseOver'), (0, _handle.forProp)('disabled', true), function (ev) {
          _this.showTooltip(ev.target);

          (0, _dispatcher.on)('keydown', _this.handleKeyDown);
        })
      });
      Object.defineProperty(_assertThisInitialized(_this), "handleMouseOut", {
        configurable: true,
        enumerable: true,
        writable: true,
        value: _this.handle((0, _handle.forward)('onMouseOut'), (0, _handle.forProp)('disabled', true), function () {
          _this.hideTooltip();

          (0, _dispatcher.off)('keydown', _this.handleKeyDown);
        })
      });
      Object.defineProperty(_assertThisInitialized(_this), "handleFocus", {
        configurable: true,
        enumerable: true,
        writable: true,
        value: _this.handle((0, _handle.forward)('onFocus'), function (_ref) {
          var target = _ref.target;
          return _this.showTooltip(target);
        })
      });
      Object.defineProperty(_assertThisInitialized(_this), "handleBlur", {
        configurable: true,
        enumerable: true,
        writable: true,
        value: _this.handle((0, _handle.forward)('onBlur'), _this.hideTooltip)
      });
      Object.defineProperty(_assertThisInitialized(_this), "getTooltipRef", {
        configurable: true,
        enumerable: true,
        writable: true,
        value: function value(node) {
          _this.tooltipRef = node;

          if (node) {
            _this.setTooltipLayout();
          }
        }
      });
      _this.TOOLTIP_HEIGHT = _resolution.default.scale(18); // distance between client and tooltip's label

      _this.state = {
        showing: false,
        tooltipDirection: null,
        arrowAnchor: null,
        position: {
          top: 0,
          left: 0
        }
      };
      return _this;
    }

    _createClass(Decorator, [{
      key: "componentDidUpdate",
      value: function componentDidUpdate(prevProps) {
        if (this.state.showing && prevProps.tooltipText !== this.props.tooltipText) {
          this.setTooltipLayout();
        }
      }
    }, {
      key: "componentWillUnmount",
      value: function componentWillUnmount() {
        if (currentTooltip === this) {
          currentTooltip = null;
          this.showTooltipJob.stop();
        }

        if (this.props.disabled) {
          (0, _dispatcher.off)('keydown', this.handleKeyDown);
        }
      }
    }, {
      key: "setTooltipLayout",
      value: function setTooltipLayout() {
        if (!this.tooltipRef || !this.clientRef) return;
        var position = this.props.tooltipPosition;
        var arr = position.split(' ');
        var tooltipDirection = null;
        var arrowAnchor = null;

        if (arr.length === 2) {
          var _arr2 = _slicedToArray(arr, 2);

          tooltipDirection = _arr2[0];
          arrowAnchor = _arr2[1];
        } else if (position === 'above' || position === 'below') {
          tooltipDirection = position;
          arrowAnchor = 'right';
        } else {
          tooltipDirection = 'above';
          arrowAnchor = 'right';
        }

        var tooltipNode = this.tooltipRef.getBoundingClientRect(); // label bound

        var clientNode = this.clientRef.getBoundingClientRect(); // client bound

        var overflow = this.calcOverflow(tooltipNode, clientNode, tooltipDirection);
        tooltipDirection = this.adjustDirection(tooltipDirection, overflow);
        arrowAnchor = this.adjustAnchor(arrowAnchor, tooltipDirection, overflow);
        this.setState({
          tooltipDirection: tooltipDirection,
          arrowAnchor: arrowAnchor,
          position: this.getPosition(tooltipNode, clientNode, arrowAnchor, tooltipDirection)
        });
      }
    }, {
      key: "calcOverflow",
      value: function calcOverflow(tooltip, clientNode, tooltipDirection) {
        if (tooltipDirection === 'above' || tooltipDirection === 'below') {
          return {
            isOverTop: clientNode.top - tooltip.height - this.TOOLTIP_HEIGHT < 0,
            isOverBottom: clientNode.bottom + tooltip.height + this.TOOLTIP_HEIGHT > window.innerHeight,
            isOverLeft: clientNode.left - tooltip.width + clientNode.width / 2 < 0,
            isOverRight: clientNode.right + tooltip.width - clientNode.width / 2 > window.innerWidth
          };
        } else if (tooltipDirection === 'left' || tooltipDirection === 'right') {
          return {
            isOverTop: clientNode.top - tooltip.height + clientNode.height / 2 < 0,
            isOverBottom: clientNode.bottom + tooltip.height - clientNode.height / 2 > window.innerHeight,
            isOverLeft: clientNode.left - tooltip.width < 0,
            isOverRight: clientNode.right + tooltip.width > window.innerWidth
          };
        }
      }
    }, {
      key: "adjustDirection",
      value: function adjustDirection(tooltipDirection, overflow) {
        if (this.props.rtl && (tooltipDirection === 'left' || tooltipDirection === 'right')) {
          tooltipDirection = tooltipDirection === 'left' ? 'right' : 'left';
        } // Flip tooltip if it overlows towards the tooltip direction


        if (overflow.isOverTop && tooltipDirection === 'above') {
          tooltipDirection = 'below';
        } else if (overflow.isOverBottom && tooltipDirection === 'below') {
          tooltipDirection = 'above';
        } else if (overflow.isOverLeft && tooltipDirection === 'left') {
          tooltipDirection = 'right';
        } else if (overflow.isOverRight && tooltipDirection === 'right') {
          tooltipDirection = 'left';
        }

        return tooltipDirection;
      }
    }, {
      key: "adjustAnchor",
      value: function adjustAnchor(arrowAnchor, tooltipDirection, overflow) {
        if (tooltipDirection === 'above' || tooltipDirection === 'below') {
          if (this.props.rtl && arrowAnchor !== 'center') {
            arrowAnchor = arrowAnchor === 'left' ? 'right' : 'left';
          } // Flip sideways if it overflows to the sides


          if (overflow.isOverRight) {
            arrowAnchor = 'left';
          } else if (overflow.isOverLeft) {
            arrowAnchor = 'right';
          }
        }

        return arrowAnchor;
      }
    }, {
      key: "getPosition",
      value: function getPosition(tooltipNode, clientNode, arrowAnchor, tooltipDirection) {
        var position = {};

        switch (tooltipDirection) {
          case 'above':
            position.top = clientNode.top - tooltipNode.height - this.TOOLTIP_HEIGHT;
            break;

          case 'below':
            position.top = clientNode.bottom + this.TOOLTIP_HEIGHT;
            break;

          case 'right':
            position.left = clientNode.right + this.TOOLTIP_HEIGHT;
            break;

          case 'left':
            position.left = clientNode.left - tooltipNode.width - this.TOOLTIP_HEIGHT;
            break;

          default:
            position = {};
        }

        if (tooltipDirection === 'above' || tooltipDirection === 'below') {
          position.left = clientNode.left + clientNode.width / 2;

          if (arrowAnchor === 'left') {
            position.left -= tooltipNode.width;
          } else if (arrowAnchor === 'center') {
            position.left -= tooltipNode.width / 2;
          }
        } else if (tooltipDirection === 'left' || tooltipDirection === 'right') {
          position.top = clientNode.top + clientNode.height / 2;

          if (arrowAnchor === 'top') {
            position.top -= tooltipNode.height;
          } else if (arrowAnchor === 'middle') {
            position.top -= tooltipNode.height / 2;
          }
        }

        return position;
      }
    }, {
      key: "renderTooltip",

      /**
       * Conditionally creates the FloatingLayer and Tooltip based on the presence of
       * `tooltipText` and returns a property bag to pass onto the Wrapped component
       *
       * @returns {Object} Prop object
       * @private
       */
      value: function renderTooltip() {
        var _props = this.props,
            children = _props.children,
            tooltipCasing = _props.tooltipCasing,
            tooltipProps = _props.tooltipProps,
            tooltipText = _props.tooltipText,
            tooltipWidth = _props.tooltipWidth;

        if (tooltipText) {
          var renderedTooltip = _react.default.createElement(_FloatingLayer.FloatingLayerBase, {
            open: this.state.showing,
            noAutoDismiss: true,
            onDismiss: this.hideTooltip,
            scrimType: "none",
            key: "tooltipFloatingLayer"
          }, _react.default.createElement(_Tooltip.Tooltip, _extends({
            "aria-live": "off",
            role: "alert"
          }, tooltipProps, {
            arrowAnchor: this.state.arrowAnchor,
            casing: tooltipCasing,
            direction: this.state.tooltipDirection,
            position: this.state.position,
            tooltipRef: this.getTooltipRef,
            width: tooltipWidth
          }), tooltipText));

          if (tooltipDestinationProp === 'children') {
            return {
              children: [children, renderedTooltip]
            };
          } else {
            return _defineProperty({}, tooltipDestinationProp, renderedTooltip);
          }
        }

        return {
          children: children
        };
      }
    }, {
      key: "render",
      value: function render() {
        // minor optimization to merge all the props together once since we also have to delete
        // invalid props before passing downstream
        var props = Object.assign({}, this.props, this.renderTooltip(), {
          onBlur: this.handleBlur,
          onFocus: this.handleFocus,
          onMouseOut: this.handleMouseOut,
          onMouseOver: this.handleMouseOver
        });
        delete props.rtl;
        delete props.tooltipDelay;
        delete props.tooltipPosition;
        delete props.tooltipCasing;
        delete props.tooltipProps;
        delete props.tooltipText;
        delete props.tooltipWidth;
        return _react.default.createElement(Wrapped, props);
      }
    }]);

    return Decorator;
  }(_react.default.Component), Object.defineProperty(_class, "displayName", {
    configurable: true,
    enumerable: true,
    writable: true,
    value: 'TooltipDecorator'
  }), Object.defineProperty(_class, "propTypes", {
    configurable: true,
    enumerable: true,
    writable: true,
    value:
    /** @lends moonstone/TooltipDecorator.TooltipDecorator.prototype */
    {
      /**
       * Disables the component but does not affect tooltip operation.
       *
       * @type {Boolean}
       * @default false
       * @public
       */
      disabled: _propTypes.default.bool,

      /**
       * Indicates the tooltip text direction is right-to-left.
       *
       * @type {Boolean}
       * @private
       */
      rtl: _propTypes.default.bool,

      /**
       * The casing of `tooltipText`.
       *
       * @see i18n/Uppercase#casing
       * @type {String}
       * @default 'upper'
       * @public
       */
      tooltipCasing: _propTypes.default.oneOf(['upper', 'preserve', 'word', 'sentence']),

      /**
       * Time to wait (in milliseconds) before showing tooltip on hover.
       *
       * @type {Number}
       * @default 500
       * @public
       */
      tooltipDelay: _propTypes.default.number,

      /**
       * Position of the tooltip with respect to the activating control.
       *
       * * Values: `'above'`, `'above center'`, `'above left'`, `'above right'`, `'below'`,
       * `'below center'`, `'below left'`, `'below right'`, `'left bottom'`, `'left middle'`,
       * `'left top'`, `'right bottom'`, `'right middle'`, `'right top'`
       *
       * The values starting with `'left`' and `'right'` place the tooltip on the side
       * (sideways tooltip) with two additional positions available, `'top'` and `'bottom'`,
       * which place the tooltip content toward the top or bottom, with the tooltip pointer
       * middle-aligned to the activator.
       *
       * @type {String}
       * @default 'above'
       * @public
       */
      tooltipPosition: _propTypes.default.oneOf(['above', 'above center', 'above left', 'above right', 'below', 'below center', 'below left', 'below right', 'left bottom', 'left middle', 'left top', 'right bottom', 'right middle', 'right top']),

      /**
       * Properties to be passed to tooltip component.
       *
       * @type {Object}
       * @public
       */
      tooltipProps: _propTypes.default.object,

      /**
       * Tooltip content.
       *
       * @type {Node}
       * @public
       */
      tooltipText: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.node]),

      /**
       * The width of tooltip content in pixels (px).
       *
       * If the content goes over the given width, it will automatically wrap. When `null`,
       * content does not wrap.
       *
       * @type {Number|null}
       * @public
       */
      tooltipWidth: _propTypes.default.number
    }
  }), Object.defineProperty(_class, "defaultProps", {
    configurable: true,
    enumerable: true,
    writable: true,
    value: {
      disabled: false,
      tooltipCasing: 'upper',
      tooltipDelay: 500,
      tooltipPosition: 'above'
    }
  }), _temp);
  return (0, _I18nDecorator.I18nContextDecorator)({
    rtlProp: 'rtl'
  }, Decorator);
});
exports.TooltipDecorator = TooltipDecorator;
var _default = TooltipDecorator;
exports.default = _default;