"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["default"] = exports.TooltipDecorator = void 0;
var _handle = require("@enact/core/handle");
var _hoc = _interopRequireDefault(require("@enact/core/hoc"));
var _util = require("@enact/core/util");
var _I18nDecorator = require("@enact/i18n/I18nDecorator");
var _FloatingLayer = require("@enact/ui/FloatingLayer");
var _resolution = _interopRequireDefault(require("@enact/ui/resolution"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _react = require("react");
var _Tooltip = require("./Tooltip");
var _util2 = require("./util");
var _jsxRuntime = require("react/jsx-runtime");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
function _possibleConstructorReturn(t, e) { if (e && ("object" == typeof e || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); }
function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); }
function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); }
function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); } /* global MutationObserver ResizeObserver */ /**
 * Agate styled tooltip components.
 *
 * @module agate/TooltipDecorator
 * @exports Tooltip
 * @exports TooltipBase
 * @exports TooltipDecorator
 */
var currentTooltip; // needed to know whether or not we should stop a showing job when unmounting

/**
 * Default config for {@link agate/TooltipDecorator.TooltipDecorator|TooltipDecorator}
 *
 * @memberof agate/TooltipDecorator.TooltipDecorator
 * @hocconfig
 */
var defaultConfig = {
  /**
   * The boundary around the screen which the tooltip should never cross, typically involving
   * flipping to an alternate orientation or adjusting its offset to remain on screen.
   * The default of 24 is derived from a standard 12px screen-keepout size plus the standard
   * Spotlight-outset (12px) margin/padding value which keeps elements and text aligned inside a
   * {@link agate/Panels.Panel|Panel}. Note: This value will be scaled according to the
   * resolution.
   *
   * @type {Number}
   * @default 24
   * @memberof agate/TooltipDecorator.TooltipDecorator.defaultConfig
   */
  screenEdgeKeepout: 12 + 12,
  /**
   * 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 agate/TooltipDecorator.TooltipDecorator.defaultConfig
   */
  tooltipDestinationProp: 'children'
};

/**
 * A higher-order component which positions {@link agate/TooltipDecorator.Tooltip|Tooltip} in
 * relation to the wrapped component.
 *
 * The tooltip is automatically displayed when the decorated 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
 * @hoc
 * @memberof agate/TooltipDecorator
 * @public
 */
var TooltipDecorator = exports.TooltipDecorator = (0, _hoc["default"])(defaultConfig, function (config, Wrapped) {
  var _Class;
  var tooltipDestinationProp = config.tooltipDestinationProp;
  var Decorator = (_Class = /*#__PURE__*/function (_Component) {
    function Decorator(props) {
      var _this;
      _classCallCheck(this, Decorator);
      _this = _callSuper(this, Decorator, [props]);
      _this.showTooltipJob = new _util.Job(function () {
        if (!_this.state.showing) {
          _this.setState({
            showing: true
          });
        }
      });
      _this.setTooltipLayoutJob = new _util.Job(function () {
        _this.setTooltipLayout();
      });
      _this.startTooltipLayoutJob = function () {
        _this.setTooltipLayoutJob.startAfter(_this.props.tooltipUpdateDelay);
      };
      _this.showTooltip = function (client) {
        var _this$props = _this.props,
          tooltipDelay = _this$props.tooltipDelay,
          tooltipText = _this$props.tooltipText;
        if (tooltipText) {
          _this.clientRef = client;
          currentTooltip = _this;
          _this.showTooltipJob.startAfter(tooltipDelay);
          if (_this.mutationObserver) {
            _this.mutationObserver.observe(_this.clientRef, {
              attributes: true,
              childList: true
            });
          }
          if (_this.resizeObserver) {
            _this.resizeObserver.observe(_this.clientRef);
          }
        }
      };
      _this.hideTooltip = function () {
        if (_this.props.tooltipText) {
          if (_this.mutationObserver) {
            _this.mutationObserver.disconnect();
          }
          if (_this.resizeObserver) {
            _this.resizeObserver.disconnect();
          }
          _this.clientRef = null;
          currentTooltip = null;
          _this.showTooltipJob.stop();
          _this.setTooltipLayoutJob.stop();
          if (_this.state.showing) {
            _this.setState({
              showing: false
            });
          }
        }
      };
      _this.handle = _handle.handle.bind(_this);
      // Recalculate tooltip layout on keydown to make sure tooltip is positioned correctly in case something changes as a result of the keydown.
      _this.handleKeyDown = _this.handle((0, _handle.forward)('onKeyDown'), (0, _handle.forProp)('disabled', false), function () {
        _this.startTooltipLayoutJob();
      });
      _this.handleMouseOver = _this.handle((0, _handle.forward)('onMouseOver'), (0, _handle.forProp)('disabled', true), function (ev) {
        _this.showTooltip(ev.target);
      });
      _this.handleMouseOut = _this.handle((0, _handle.forward)('onMouseOut'), (0, _handle.forProp)('disabled', true), function () {
        _this.hideTooltip();
      });
      _this.handleFocus = _this.handle((0, _handle.forward)('onFocus'), function (_ref) {
        var target = _ref.target;
        return _this.showTooltip(target);
      });
      _this.handleBlur = _this.handle((0, _handle.forward)('onBlur'), _this.hideTooltip);
      _this.getTooltipRef = function (node) {
        _this.tooltipRef = node;
        if (node) {
          _this.setTooltipLayout();
        }
      };
      _this.state = {
        showing: false,
        tooltipDirection: null,
        arrowAnchor: null,
        position: {
          top: 0,
          left: 0
        }
      };
      return _this;
    }
    _inherits(Decorator, _Component);
    return _createClass(Decorator, [{
      key: "componentDidMount",
      value: function componentDidMount() {
        if (window.MutationObserver) {
          this.mutationObserver = new MutationObserver(this.startTooltipLayoutJob);
        }
        if (window.ResizeObserver) {
          this.resizeObserver = new ResizeObserver(this.startTooltipLayoutJob);
        }
      }
    }, {
      key: "componentDidUpdate",
      value: function componentDidUpdate(prevProps, prevState) {
        if (this.state.showing && (prevProps.tooltipText !== this.props.tooltipText || prevProps.tooltipPosition !== this.props.tooltipPosition || prevState.showing !== this.state.showing)) {
          this.setTooltipLayout();
        }
      }
    }, {
      key: "componentWillUnmount",
      value: function componentWillUnmount() {
        if (currentTooltip === this) {
          currentTooltip = null;
          if (this.mutationObserver) {
            this.mutationObserver.disconnect();
          }
          if (this.resizeObserver) {
            this.resizeObserver.disconnect();
          }
          this.showTooltipJob.stop();
          this.setTooltipLayoutJob.stop();
        }
      }
    }, {
      key: "setTooltipLayout",
      value: function setTooltipLayout() {
        if (!this.tooltipRef || !this.clientRef) return;
        var screenEdgeKeepout = _resolution["default"].scale(config.screenEdgeKeepout);
        var position = this.props.tooltipPosition;
        var arr = position.split(' ');
        var tooltipDirection = null;
        var arrowAnchor = null;
        if (arr.length === 2) {
          var _arr = _slicedToArray(arr, 2);
          tooltipDirection = _arr[0];
          arrowAnchor = _arr[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 = (0, _util2.calcOverflow)(tooltipNode, clientNode, tooltipDirection, screenEdgeKeepout);
        tooltipDirection = (0, _util2.adjustDirection)(tooltipDirection, overflow, this.props.rtl);
        arrowAnchor = (0, _util2.adjustAnchor)(arrowAnchor, tooltipDirection, overflow, this.props.rtl);
        var tooltipPosition = (0, _util2.getPosition)(clientNode, tooltipDirection);
        var labelOffset = arrowAnchor === 'center' ? (0, _util2.getLabelOffset)(tooltipNode, tooltipDirection, tooltipPosition, overflow) : null;
        var _this$state$position = this.state.position,
          top = _this$state$position.top,
          left = _this$state$position.left;
        if (tooltipPosition.top !== top || tooltipPosition.left !== left || labelOffset !== this.state.labelOffset || arrowAnchor !== this.state.arrowAnchor) {
          this.setState({
            tooltipDirection: tooltipDirection,
            arrowAnchor: arrowAnchor,
            labelOffset: labelOffset,
            position: tooltipPosition
          });
        }
      }
    }, {
      key: "renderTooltip",
      value:
      /**
       * 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
       */
      function renderTooltip() {
        var _this$props2 = this.props,
          children = _this$props2.children,
          tooltipMarquee = _this$props2.tooltipMarquee,
          tooltipProps = _this$props2.tooltipProps,
          tooltipRelative = _this$props2.tooltipRelative,
          tooltipText = _this$props2.tooltipText,
          tooltipWidth = _this$props2.tooltipWidth;
        var _this$state$position2 = this.state.position,
          top = _this$state$position2.top,
          left = _this$state$position2.left;
        var tooltipStyle = {
          // Moving the position to CSS variables where there are additional offset calculations
          '--tooltip-position-top': tooltipRelative ? null : _resolution["default"].unit(top, 'rem'),
          '--tooltip-position-left': tooltipRelative ? null : _resolution["default"].unit(left, 'rem')
        };
        if (tooltipText) {
          var renderedTooltip = /*#__PURE__*/(0, _jsxRuntime.jsx)(_Tooltip.Tooltip, _objectSpread(_objectSpread({
            "aria-live": "off",
            role: "alert",
            labelOffset: this.state.labelOffset
          }, tooltipProps), {}, {
            arrowAnchor: this.state.arrowAnchor,
            direction: this.state.tooltipDirection,
            marquee: tooltipMarquee,
            relative: tooltipRelative,
            style: tooltipStyle,
            tooltipRef: this.getTooltipRef,
            width: tooltipWidth,
            children: tooltipText
          }));
          if (!tooltipRelative) {
            renderedTooltip = /*#__PURE__*/(0, _jsxRuntime.jsx)(_FloatingLayer.FloatingLayerBase, {
              open: this.state.showing,
              noAutoDismiss: true,
              onDismiss: this.hideTooltip,
              scrimType: "none",
              children: renderedTooltip
            }, "tooltipFloatingLayer");
          } else if (!this.state.showing) {
            renderedTooltip = null;
          }
          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,
          onKeyDown: this.handleKeyDown
        });
        delete props.rtl;
        delete props.tooltipDelay;
        delete props.tooltipMarquee;
        delete props.tooltipPosition;
        delete props.tooltipProps;
        delete props.tooltipRelative;
        delete props.tooltipText;
        delete props.tooltipUpdateDelay;
        delete props.tooltipWidth;
        return /*#__PURE__*/(0, _jsxRuntime.jsx)(Wrapped, _objectSpread({}, props));
      }
    }]);
  }(_react.Component), _Class.displayName = 'TooltipDecorator', _Class.propTypes = /** @lends agate/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,
    /**
     * Time to wait (in milliseconds) before showing tooltip on hover.
     *
     * @type {Number}
     * @default 500
     * @public
     */
    tooltipDelay: _propTypes["default"].number,
    /**
     * Allows the tooltip to marquee.
     *
     * Specifying a {@link agate/TooltipDecorator.TooltipDecorator#tooltipWidth|tooltipWidth}
     * restricts the marquee to that size.
     *
     * @type {Boolean}
     * @public
     */
    tooltipMarquee: _propTypes["default"].bool,
    /**
     * Position of the tooltip with respect to the wrapped component.
     *
     * | *Value* | *Tooltip Direction* |
     * |---|---|
     * | `'above'` | Above component, flowing to the right |
     * | `'above center'` | Above component, centered |
     * | `'above left'` | Above component, flowing to the left |
     * | `'above right'` | Above component, flowing to the right |
     * | `'below'` | Below component, flowing to the right |
     * | `'below center'` | Below component, centered |
     * | `'below left'` | Below component, flowing to the left |
     * | `'below right'` | Below component, flowing to the right |
     * | `'left bottom'` | Left of the component, contents at the bottom |
     * | `'left middle'` | Left of the component, contents middle aligned |
     * | `'left top'` | Left of the component, contents at the top |
     * | `'right bottom'` | Right of the component, contents at the bottom |
     * | `'right middle'` | Right of the component, contents middle aligned |
     * | `'right top'` | Right of the component, contents at the top |
     *
     * `TooltipDecorator` attempts to choose the best direction to meet layout and language
     * requirements. Left and right directions will reverse for RTL languages. Additionally,
     * the tooltip will reverse direction if it will prevent overflowing off the viewport
     *
     * @type {('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')}
     * @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,
    /**
     * Positions the tooltip relative to its container.
     *
     * Determines whether your tooltip should position itself relative to its container or
     * relative to the screen (absolute positioning on the floating layer). When setting to
     * `true`, to enable relative positioning, it may be important to specify the
     * `tooltipDestinationProp` key in this HoC's config object. A relatively positioned
     * Tooltip for a `Button`, for example, must be placed in the `decoration` prop.
     *
     * It may be necessary to assign the CSS rule `position` to the containing element so
     * relatively positioned Tooltip has a frame to "stick to" the edge of.
     *
     * Anchoring points can be visualized as follows:
     * ```
     * ┌───◎───┐
     * ◎       ◎
     * └───◎───┘
     * ```
     *
     * @type {Boolean}
     * @public
     */
    tooltipRelative: _propTypes["default"].bool,
    /**
     * Tooltip content.
     *
     * @type {String|Node}
     * @public
     */
    tooltipText: _propTypes["default"].oneOfType([_propTypes["default"].string, _propTypes["default"].node]),
    /**
     * The interval (in milliseconds) to recheck the math for a currently showing tooltip's
     * positioning and orientation. Useful if your anchor element moves.
     *
     * @type {Number}
     * @default 400
     * @public
     */
    tooltipUpdateDelay: _propTypes["default"].number,
    /**
     * The width of tooltip content.
     *
     * Value expects a number of pixels, which will be automatically scaled to the
     * appropriate size given the current screen resolution, or a string value containing a
     * measurement and a valid CSS unit included. If the content goes over the given width,
     * it will automatically wrap, or marquee if `marquee` is enabled.
     *
     * When `null`, content will auto-size and not wrap. If `tooltipMarquee` is also
     * enabled, marqueeing will begin when the width is greater than the default (theme
     * specified) width.
     *
     * @type {Number|String}
     * @public
     */
    tooltipWidth: _propTypes["default"].oneOfType([_propTypes["default"].number, _propTypes["default"].string])
  }, _Class.defaultProps = {
    disabled: false,
    tooltipDelay: 500,
    tooltipPosition: 'above',
    tooltipUpdateDelay: 400
  }, _Class);
  return (0, _I18nDecorator.I18nContextDecorator)({
    rtlProp: 'rtl'
  }, Decorator);
});
var _default = exports["default"] = TooltipDecorator;