import { _ as _extends$1 } from '../extends-C4cduGrv.js';
import { _ as _slicedToArray } from '../slicedToArray-BB-AwwkK.js';
import React__default, { useState, useRef, useEffect, Children, Fragment, cloneElement } from 'react';
import PropTypes from 'prop-types';
import { u as useId } from '../auto-id.esm-Cly76abB.js';
import { u as useIsomorphicLayoutEffect, f as forwardRefWithAs, b as useForkedRef, l as checkStyles, g as getOwnerDocument, m as makeId, w as wrapEvent, t as getDocumentDimensions } from '../utils.esm-CKyxlHOy.js';
import { P as Portal } from '../portal.esm-CuKGPwhL.js';
import VisuallyHidden from '@reach/visually-hidden';
import { w as warning_1 } from '../warning-s9zGJbnw.js';
import Portal$1 from '../portal/index.js';
import 'react-dom';
import '../objectWithoutProperties-TXwlfNxG.js';
import '../use-rerender-once/index.js';
import '../use-force-update/index.js';

var props = ["bottom", "height", "left", "right", "top", "width"];

var rectChanged = function rectChanged(a, b) {
  if (a === void 0) {
    a = {};
  }

  if (b === void 0) {
    b = {};
  }

  return props.some(function (prop) {
    return a[prop] !== b[prop];
  });
};

var observedNodes = /*#__PURE__*/new Map();
var rafId;

var run = function run() {
  var changedStates = [];
  observedNodes.forEach(function (state, node) {
    var newRect = node.getBoundingClientRect();

    if (rectChanged(newRect, state.rect)) {
      state.rect = newRect;
      changedStates.push(state);
    }
  });
  changedStates.forEach(function (state) {
    state.callbacks.forEach(function (cb) {
      return cb(state.rect);
    });
  });
  rafId = window.requestAnimationFrame(run);
};

function observeRect(node, cb) {
  return {
    observe: function observe() {
      var wasEmpty = observedNodes.size === 0;

      if (observedNodes.has(node)) {
        observedNodes.get(node).callbacks.push(cb);
      } else {
        observedNodes.set(node, {
          rect: undefined,
          hasRectChanged: false,
          callbacks: [cb]
        });
      }

      if (wasEmpty) run();
    },
    unobserve: function unobserve() {
      var state = observedNodes.get(node);

      if (state) {
        // Remove the callback
        var index = state.callbacks.indexOf(cb);
        if (index >= 0) state.callbacks.splice(index, 1); // Remove the node reference

        if (!state.callbacks.length) observedNodes["delete"](node); // Stop the loop

        if (!observedNodes.size) cancelAnimationFrame(rafId);
      }
    }
  };
}

if (process.env.NODE_ENV !== "production") {
  ({
    children: PropTypes.func.isRequired,
    observe: PropTypes.bool,
    onChange: PropTypes.func
  });
} ////////////////////////////////////////////////////////////////////////////////

/**
 * useRect
 *
 * @param nodeRef
 * @param observe
 * @param onChange
 */


function useRect(nodeRef, observe, onChange) {
  if (observe === void 0) {
    observe = true;
  }

  var _useState = useState(nodeRef.current),
      element = _useState[0],
      setElement = _useState[1];

  var initialRectIsSet = useRef(false);
  var initialRefIsSet = useRef(false);

  var _useState2 = useState(null),
      rect = _useState2[0],
      setRect = _useState2[1];

  var onChangeRef = useRef();
  useIsomorphicLayoutEffect(function () {
    onChangeRef.current = onChange;

    if (nodeRef.current !== element) {
      setElement(nodeRef.current);
    }
  });
  useIsomorphicLayoutEffect(function () {
    if (element && !initialRectIsSet.current) {
      initialRectIsSet.current = true;
      setRect(element.getBoundingClientRect());
    }
  }, [element]);
  useIsomorphicLayoutEffect(function () {
    var observer;
    var elem = element; // State initializes before refs are placed, meaning the element state will
    // be undefined on the first render. We still want the rect on the first
    // render, so initially we'll use the nodeRef that was passed instead of
    // state for our measurements.

    if (!initialRefIsSet.current) {
      initialRefIsSet.current = true;
      elem = nodeRef.current;
    }

    if (!elem) {
      if (process.env.NODE_ENV !== "production") {
        console.warn("You need to place the ref");
      }

      return cleanup;
    }

    observer = observeRect(elem, function (rect) {
      onChangeRef.current && onChangeRef.current(rect);
      setRect(rect);
    });
    observe && observer.observe();
    return cleanup;

    function cleanup() {
      observer && observer.unobserve();
    }
  }, [observe, element]);
  return rect;
}

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 _objectWithoutPropertiesLoose(source, excluded) {
  if (source == null) return {};
  var target = {};
  var sourceKeys = Object.keys(source);
  var key, i;

  for (i = 0; i < sourceKeys.length; i++) {
    key = sourceKeys[i];
    if (excluded.indexOf(key) >= 0) continue;
    target[key] = source[key];
  }

  return target;
}

var _on, _on2, _on3, _on4, _on5, _states;

var MOUSE_REST_TIMEOUT = 100;
var LEAVE_TIMEOUT = 500; ////////////////////////////////////////////////////////////////////////////////
// States
// Nothing goin' on

var IDLE = "IDLE"; // We're considering showing the tooltip, but we're gonna wait a sec

var FOCUSED = "FOCUSED"; // It's on!

var VISIBLE = "VISIBLE"; // Focus has left, but we want to keep it visible for a sec

var LEAVING_VISIBLE = "LEAVING_VISIBLE"; // The user clicked the tool, so we want to hide the thing, we can't just use
// IDLE because we need to ignore mousemove, etc.

var DISMISSED = "DISMISSED"; ////////////////////////////////////////////////////////////////////////////////
// Events

var BLUR = "BLUR";
var FOCUS = "FOCUS";
var GLOBAL_MOUSE_MOVE = "GLOBAL_MOUSE_MOVE";
var MOUSE_DOWN = "MOUSE_DOWN";
var MOUSE_ENTER = "MOUSE_ENTER";
var MOUSE_LEAVE = "MOUSE_LEAVE";
var MOUSE_MOVE = "MOUSE_MOVE";
var REST = "REST";
var SELECT_WITH_KEYBOARD = "SELECT_WITH_KEYBOARD";
var TIME_COMPLETE = "TIME_COMPLETE";
var chart = {
  initial: IDLE,
  states: (_states = {}, _states[IDLE] = {
    enter: clearContextId,
    on: (_on = {}, _on[MOUSE_ENTER] = FOCUSED, _on[FOCUS] = VISIBLE, _on)
  }, _states[FOCUSED] = {
    enter: startRestTimer,
    leave: clearRestTimer,
    on: (_on2 = {}, _on2[MOUSE_MOVE] = FOCUSED, _on2[MOUSE_LEAVE] = IDLE, _on2[MOUSE_DOWN] = DISMISSED, _on2[BLUR] = IDLE, _on2[REST] = VISIBLE, _on2)
  }, _states[VISIBLE] = {
    on: (_on3 = {}, _on3[FOCUS] = FOCUSED, _on3[MOUSE_ENTER] = FOCUSED, _on3[MOUSE_LEAVE] = LEAVING_VISIBLE, _on3[BLUR] = LEAVING_VISIBLE, _on3[MOUSE_DOWN] = DISMISSED, _on3[SELECT_WITH_KEYBOARD] = DISMISSED, _on3[GLOBAL_MOUSE_MOVE] = LEAVING_VISIBLE, _on3)
  }, _states[LEAVING_VISIBLE] = {
    enter: startLeavingVisibleTimer,
    leave: function leave() {
      clearLeavingVisibleTimer();
      clearContextId();
    },
    on: (_on4 = {}, _on4[MOUSE_ENTER] = VISIBLE, _on4[FOCUS] = VISIBLE, _on4[TIME_COMPLETE] = IDLE, _on4)
  }, _states[DISMISSED] = {
    leave: function leave() {
      // allows us to come on back later w/o entering something else first
      context.id = null;
    },
    on: (_on5 = {}, _on5[MOUSE_LEAVE] = IDLE, _on5[BLUR] = IDLE, _on5)
  }, _states)
};
/*
 * Chart context allows us to persist some data around, in Tooltip all we use
 * is the id of the current tooltip being interacted with.
 */

var context = {
  id: null
};
var state = chart.initial; ////////////////////////////////////////////////////////////////////////////////
// Subscriptions:
//
// We could require apps to render a <TooltipProvider> around the app and use
// React context to notify Tooltips of changes to our state machine, instead
// we manage subscriptions ourselves and simplify the Tooltip API.
//
// Maybe if default context could take a hook (instead of just a static value)
// that was rendered at the root for us, that'd be cool! But it doesn't.

var subscriptions = [];

function subscribe(fn) {
  subscriptions.push(fn);
  return function () {
    subscriptions.splice(subscriptions.indexOf(fn), 1);
  };
}

function notify() {
  subscriptions.forEach(function (fn) {
    return fn(state, context);
  });
} ////////////////////////////////////////////////////////////////////////////////
// Timeouts:
// Manages when the user "rests" on an element. Keeps the interface from being
// flashing tooltips all the time as the user moves the mouse around the screen.


var restTimeout;

function startRestTimer() {
  window.clearTimeout(restTimeout);
  restTimeout = window.setTimeout(function () {
    return transition(REST);
  }, MOUSE_REST_TIMEOUT);
}

function clearRestTimer() {
  window.clearTimeout(restTimeout);
} // Manages the delay to hide the tooltip after rest leaves.


var leavingVisibleTimer;

function startLeavingVisibleTimer() {
  window.clearTimeout(leavingVisibleTimer);
  leavingVisibleTimer = window.setTimeout(function () {
    return transition(TIME_COMPLETE);
  }, LEAVE_TIMEOUT);
}

function clearLeavingVisibleTimer() {
  window.clearTimeout(leavingVisibleTimer);
} // allows us to come on back later w/o entering something else first after the
// user leaves or dismisses


function clearContextId() {
  context.id = null;
} ////////////////////////////////////////////////////////////////////////////////

/**
 * useTooltip
 *
 * @param params
 */


function useTooltip(_temp) {
  var _ref = _temp === void 0 ? {} : _temp,
      idProp = _ref.id,
      onMouseEnter = _ref.onMouseEnter,
      onMouseMove = _ref.onMouseMove,
      onMouseLeave = _ref.onMouseLeave,
      onFocus = _ref.onFocus,
      onBlur = _ref.onBlur,
      onKeyDown = _ref.onKeyDown,
      onMouseDown = _ref.onMouseDown,
      forwardedRef = _ref.ref,
      DEBUG_STYLE = _ref.DEBUG_STYLE;

  var id = String(useId(idProp));

  var _useState = useState(DEBUG_STYLE ? true : id === null ? false : context.id === id && state === VISIBLE),
      isVisible = _useState[0],
      setIsVisible = _useState[1]; // hopefully they always pass a ref if they ever pass one


  var ownRef = useRef(null);
  var ref = useForkedRef(forwardedRef, ownRef); // TODO: Fix in utils

  var triggerRect = useRect(ownRef, isVisible);
  useEffect(function () {
    return subscribe(function () {
      if (context.id === id && (state === VISIBLE || state === LEAVING_VISIBLE)) {
        setIsVisible(true);
      } else {
        setIsVisible(false);
      }
    });
  }, [id]);
  useEffect(function () {
    return checkStyles("tooltip");
  }, []);
  useEffect(function () {
    var ownerDocument = getOwnerDocument(ownRef.current) || document;

    function listener(event) {
      if ((event.key === "Escape" || event.key === "Esc") && state === VISIBLE) {
        transition(SELECT_WITH_KEYBOARD);
      }
    }

    ownerDocument.addEventListener("keydown", listener);
    return function () {
      return ownerDocument.removeEventListener("keydown", listener);
    };
  }, []);

  function handleMouseEnter() {
    transition(MOUSE_ENTER, {
      id: id
    });
  }

  function handleMouseMove() {
    transition(MOUSE_MOVE, {
      id: id
    });
  }

  function handleFocus() {
    // @ts-ignore
    if (window.__REACH_DISABLE_TOOLTIPS) {
      return;
    }

    transition(FOCUS, {
      id: id
    });
  }

  function handleMouseLeave() {
    transition(MOUSE_LEAVE);
  }

  function handleBlur() {
    // Allow quick click from one tool to another
    if (context.id !== id) return;
    transition(BLUR);
  }

  function handleMouseDown() {
    // Allow quick click from one tool to another
    if (context.id !== id) return;
    transition(MOUSE_DOWN);
  }

  function handleKeyDown(event) {
    if (event.key === "Enter" || event.key === " ") {
      transition(SELECT_WITH_KEYBOARD);
    }
  }

  var trigger = {
    // The element that triggers the tooltip references the tooltip element with
    // `aria-describedby`.
    // https://www.w3.org/TR/wai-aria-practices-1.2/#tooltip
    "aria-describedby": isVisible ? makeId("tooltip", id) : undefined,
    "data-reach-tooltip-trigger": "",
    ref: ref,
    onMouseEnter: wrapEvent(onMouseEnter, handleMouseEnter),
    onMouseMove: wrapEvent(onMouseMove, handleMouseMove),
    onFocus: wrapEvent(onFocus, handleFocus),
    onBlur: wrapEvent(onBlur, handleBlur),
    onMouseLeave: wrapEvent(onMouseLeave, handleMouseLeave),
    onKeyDown: wrapEvent(onKeyDown, handleKeyDown),
    onMouseDown: wrapEvent(onMouseDown, handleMouseDown)
  };
  var tooltip = {
    id: id,
    triggerRect: triggerRect,
    isVisible: isVisible
  };
  return [trigger, tooltip, isVisible];
} ////////////////////////////////////////////////////////////////////////////////

/**
 * Tooltip
 *
 * @see Docs https://reach.tech/tooltip#tooltip
 */


var Tooltip$1 = /*#__PURE__*/forwardRefWithAs(function (_ref2, forwardedRef) {
  var children = _ref2.children,
      label = _ref2.label,
      DEPRECATED_ariaLabel = _ref2.ariaLabel,
      id = _ref2.id,
      DEBUG_STYLE = _ref2.DEBUG_STYLE,
      props = _objectWithoutPropertiesLoose(_ref2, ["children", "label", "ariaLabel", "id", "DEBUG_STYLE"]);

  var child = Children.only(children);
  process.env.NODE_ENV !== "production" ? warning_1(!DEPRECATED_ariaLabel, "The `ariaLabel prop is deprecated and will be removed from @reach/tooltip in a future version. Please use `aria-label` instead.") : void 0; // We need to pass some properties from the child into useTooltip
  // to make sure users can maintain control over the trigger's ref and events

  var _useTooltip = useTooltip({
    id: id,
    onMouseEnter: child.props.onMouseEnter,
    onMouseMove: child.props.onMouseMove,
    onMouseLeave: child.props.onMouseLeave,
    onFocus: child.props.onFocus,
    onBlur: child.props.onBlur,
    onKeyDown: child.props.onKeyDown,
    onMouseDown: child.props.onMouseDown,
    ref: child.ref,
    DEBUG_STYLE: DEBUG_STYLE
  }),
      trigger = _useTooltip[0],
      tooltip = _useTooltip[1];

  return /*#__PURE__*/React__default.createElement(Fragment, null, /*#__PURE__*/cloneElement(child, trigger), /*#__PURE__*/React__default.createElement(TooltipPopup, Object.assign({
    ref: forwardedRef,
    label: label,
    "aria-label": DEPRECATED_ariaLabel
  }, tooltip, props)));
});

if (process.env.NODE_ENV !== "production") {
  Tooltip$1.displayName = "Tooltip";
  Tooltip$1.propTypes = {
    children: PropTypes.node.isRequired,
    label: PropTypes.node.isRequired,
    ariaLabel: PropTypes.string
  };
}
/**
 * TooltipPopup
 *
 * @see Docs https://reach.tech/tooltip#tooltippopup
 */


var TooltipPopup = /*#__PURE__*/forwardRefWithAs(function TooltipPopup(_ref3, forwardRef) {
  var label = _ref3.label,
      DEPRECATED_ariaLabel = _ref3.ariaLabel,
      isVisible = _ref3.isVisible,
      id = _ref3.id,
      props = _objectWithoutPropertiesLoose(_ref3, ["label", "ariaLabel", "isVisible", "id"]);

  return isVisible ? /*#__PURE__*/React__default.createElement(Portal, null, /*#__PURE__*/React__default.createElement(TooltipContent, Object.assign({
    ref: forwardRef,
    label: label,
    "aria-label": DEPRECATED_ariaLabel,
    isVisible: isVisible
  }, props, {
    id: makeId("tooltip", String(id))
  }))) : null;
});

if (process.env.NODE_ENV !== "production") {
  TooltipPopup.displayName = "TooltipPopup";
  TooltipPopup.propTypes = {
    label: PropTypes.node.isRequired,
    ariaLabel: PropTypes.string,
    position: PropTypes.func
  };
}
/**
 * TooltipContent
 *
 * We need a separate component so that useRect works inside the portal.
 *
 * @see Docs https://reach.tech/tooltip#tooltipcontent
 */


var TooltipContent = /*#__PURE__*/forwardRefWithAs(function TooltipContent(_ref4, forwardedRef) {
  var ariaLabel = _ref4.ariaLabel,
      realAriaLabel = _ref4["aria-label"],
      _ref4$as = _ref4.as,
      Comp = _ref4$as === void 0 ? "div" : _ref4$as,
      id = _ref4.id,
      isVisible = _ref4.isVisible,
      label = _ref4.label,
      _ref4$position = _ref4.position,
      position = _ref4$position === void 0 ? positionDefault : _ref4$position,
      style = _ref4.style,
      triggerRect = _ref4.triggerRect,
      props = _objectWithoutPropertiesLoose(_ref4, ["ariaLabel", "aria-label", "as", "id", "isVisible", "label", "position", "style", "triggerRect"]); // The element that serves as the tooltip container has role tooltip.
  // https://www.w3.org/TR/wai-aria-practices-1.2/#tooltip
  // When an app passes an `aria-label`, we actually want to implement
  // `role="tooltip"` on a visually hidden element inside of the trigger.
  // In these cases we want the screen reader user to know both the content in
  // the tooltip, but also the content in the badge. For screen reader users,
  // the only content announced to them is whatever is in the tooltip.


  var hasAriaLabel = (realAriaLabel || ariaLabel) != null;
  var ownRef = useRef(null);
  var ref = useForkedRef(forwardedRef, ownRef);
  var tooltipRect = useRect(ownRef, isVisible);
  return /*#__PURE__*/React__default.createElement(Fragment, null, /*#__PURE__*/React__default.createElement(Comp, Object.assign({
    role: hasAriaLabel ? undefined : "tooltip"
  }, props, {
    ref: ref,
    "data-reach-tooltip": "",
    id: hasAriaLabel ? undefined : id,
    style: _extends({}, style, getStyles(position, triggerRect, tooltipRect))
  }), label), hasAriaLabel && /*#__PURE__*/React__default.createElement(VisuallyHidden, {
    role: "tooltip",
    id: id
  }, realAriaLabel || ariaLabel));
});

if (process.env.NODE_ENV !== "production") {
  TooltipContent.displayName = "TooltipContent";
  TooltipContent.propTypes = {};
} ////////////////////////////////////////////////////////////////////////////////
// feels awkward when it's perfectly aligned w/ the trigger


var OFFSET = 8;

function getStyles(position, triggerRect, tooltipRect) {
  var haventMeasuredTooltipYet = !tooltipRect;

  if (haventMeasuredTooltipYet) {
    return {
      visibility: "hidden"
    };
  }

  return position(triggerRect, tooltipRect);
}

var positionDefault = function positionDefault(triggerRect, tooltipRect) {
  var _getDocumentDimension = getDocumentDimensions(),
      windowWidth = _getDocumentDimension.width,
      windowHeight = _getDocumentDimension.height;

  if (!triggerRect || !tooltipRect) {
    return {};
  }

  var collisions = {
    top: triggerRect.top - tooltipRect.height < 0,
    right: windowWidth < triggerRect.left + tooltipRect.width,
    bottom: windowHeight < triggerRect.bottom + tooltipRect.height + OFFSET,
    left: triggerRect.left - tooltipRect.width < 0
  };
  var directionRight = collisions.right && !collisions.left;
  var directionUp = collisions.bottom && !collisions.top;
  return {
    left: directionRight ? triggerRect.right - tooltipRect.width + window.pageXOffset + "px" : triggerRect.left + window.pageXOffset + "px",
    top: directionUp ? triggerRect.top - OFFSET - tooltipRect.height + window.pageYOffset + "px" : triggerRect.top + OFFSET + triggerRect.height + window.pageYOffset + "px"
  };
}; ////////////////////////////////////////////////////////////////////////////////

/**
 * Finds the next state from the current state + action. If the chart doesn't
 * describe that transition, it will throw.
 *
 * It also manages lifecycles of the machine, (enter/leave hooks on the state
 * chart)
 *
 * @param event
 * @param payload
 */


var transition = function transition(event, payload) {
  var stateDef = chart.states[state];
  var nextState = stateDef && stateDef.on && stateDef.on[event]; // Really useful for debugging
  // console.log({ event, state, nextState, contextId: context.id });
  // !nextState && console.log('no transition taken')

  if (!nextState) {
    return;
  }

  if (stateDef && stateDef.leave) {
    stateDef.leave();
  }

  if (payload) {
    context = payload;
  }

  var nextDef = chart.states[nextState];

  if (nextDef && nextDef.enter) {
    nextDef.enter();
  }

  state = nextState;
  notify();
};

var centered = function centered(triggerRect, tooltipRect) {
  var triggerCenter = triggerRect.left + triggerRect.width / 2;
  var left = triggerCenter - tooltipRect.width / 2;
  var maxLeft = window.innerWidth - tooltipRect.width - 2;
  return {
    left: Math.min(Math.max(2, left), maxLeft) + window.scrollX,
    top: triggerRect.bottom + 8 + window.scrollY
  };
};

function Tooltip(_ref) {
  var children = _ref.children,
      label = _ref.label;

  var _useTooltip = useTooltip(),
      _useTooltip2 = _slicedToArray(_useTooltip, 2),
      trigger = _useTooltip2[0],
      tooltip = _useTooltip2[1];

  var isVisible = tooltip.isVisible,
      triggerRect = tooltip.triggerRect;
  return /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/cloneElement(children, trigger), isVisible && /*#__PURE__*/React__default.createElement(Portal$1, null, /*#__PURE__*/React__default.createElement("div", {
    className: "tooltip-arrow",
    style: {
      position: "absolute",
      left: triggerRect && triggerRect.left - 10 + triggerRect.width / 2,
      top: triggerRect && triggerRect.bottom + window.scrollY,
      width: 0,
      height: 0,
      borderLeft: "10px solid transparent",
      borderRight: "10px solid transparent",
      borderBottom: "10px solid #000000"
    }
  })), /*#__PURE__*/React__default.createElement(TooltipPopup, _extends$1({}, tooltip, {
    label: label,
    position: centered
  })));
}

Tooltip.propTypes = {
  children: PropTypes.node,
  label: PropTypes.string.isRequired
};

export { Tooltip as default };
