"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _react = _interopRequireDefault(require("react"));
var _focusTrap = _interopRequireDefault(require("focus-trap"));
var _rafSchd = _interopRequireDefault(require("raf-schd"));
var _reactDom = require("react-dom");
var _editorSharedStyles = require("@atlaskit/editor-shared-styles");
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
var _utils = require("./utils");
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) { (0, _defineProperty2.default)(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 _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0, _possibleConstructorReturn2.default)(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2.default)(t).constructor) : o.apply(t, e)); }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
// Ignored via go/ees005
// eslint-disable-next-line @repo/internal/react/no-class-components
var Popup = exports.default = /*#__PURE__*/function (_React$Component) {
  function Popup() {
    var _window;
    var _this;
    (0, _classCallCheck2.default)(this, Popup);
    for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
      args[_key] = arguments[_key];
    }
    _this = _callSuper(this, Popup, [].concat(args));
    (0, _defineProperty2.default)(_this, "rafIds", new Set());
    (0, _defineProperty2.default)(_this, "state", {
      validPosition: true
    });
    (0, _defineProperty2.default)(_this, "popupRef", /*#__PURE__*/_react.default.createRef());
    (0, _defineProperty2.default)(_this, "placement", ['', '']);
    (0, _defineProperty2.default)(_this, "handleRef", function (popup) {
      if (!popup) {
        return;
      }
      _this.initPopup(popup);
    });
    (0, _defineProperty2.default)(_this, "scheduledUpdatePosition", (0, _rafSchd.default)(function (_props) {
      _this.updatePosition(_this.props);
    }));
    (0, _defineProperty2.default)(_this, "onResize", function () {
      return _this.scheduledUpdatePosition(_this.props);
    });
    (0, _defineProperty2.default)(_this, "resizeObserver", (_window = window) !== null && _window !== void 0 && _window.ResizeObserver ? new ResizeObserver(function () {
      _this.scheduledUpdatePosition(_this.props);
    }) : undefined);
    /**
     * Raf scheduled so that it also occurs after the initial update position
     */
    (0, _defineProperty2.default)(_this, "initFocusTrap", (0, _rafSchd.default)(function () {
      var popup = _this.popupRef.current;
      if (!popup) {
        return;
      }
      var defaultTrapConfig = {
        clickOutsideDeactivates: true,
        escapeDeactivates: true,
        initialFocus: popup,
        fallbackFocus: popup,
        returnFocusOnDeactivate: false
      };
      var trapConfig = typeof _this.props.focusTrap === 'boolean' ? defaultTrapConfig : _objectSpread(_objectSpread({}, defaultTrapConfig), _this.props.focusTrap);
      _this.focusTrap = (0, _focusTrap.default)(popup, (0, _experiments.editorExperiment)('platform_editor_block_menu', true) ? trapConfig : defaultTrapConfig);
      _this.focusTrap.activate();
    }));
    return _this;
  }
  (0, _inherits2.default)(Popup, _React$Component);
  return (0, _createClass2.default)(Popup, [{
    key: "calculatePosition",
    value:
    /**
     * Calculates new popup position
     */
    function calculatePosition(props, popup) {
      var target = props.target,
        fitHeight = props.fitHeight,
        fitWidth = props.fitWidth,
        boundariesElement = props.boundariesElement,
        minPopupMargin = props.minPopupMargin,
        offset = props.offset,
        onPositionCalculated = props.onPositionCalculated,
        onPlacementChanged = props.onPlacementChanged,
        alignX = props.alignX,
        alignY = props.alignY,
        stick = props.stick,
        forcePlacement = props.forcePlacement,
        allowOutOfBounds = props.allowOutOfBounds,
        rect = props.rect,
        preventOverflow = props.preventOverflow,
        absoluteOffset = props.absoluteOffset;
      if (!target || !popup) {
        return {};
      }
      var placement = (0, _utils.calculatePlacement)(target, boundariesElement || document.body, fitWidth, fitHeight, alignX, alignY, forcePlacement, preventOverflow);
      if (onPlacementChanged && this.placement.join('') !== placement.join('')) {
        onPlacementChanged(placement);
        this.placement = placement;
      }
      var position = (0, _utils.calculatePosition)({
        placement: placement,
        popup: popup,
        target: target,
        stick: stick,
        // Ignored via go/ees005
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        offset: offset,
        allowOutOfBounds: allowOutOfBounds,
        rect: rect,
        boundariesElement: (0, _platformFeatureFlags.fg)('platform_editor_link_popup_position_fix_aifc') ? boundariesElement || document.body : undefined,
        minPopupMargin: minPopupMargin
      });
      position = onPositionCalculated ? onPositionCalculated(position) : position;
      if (typeof position.top !== 'undefined' && absoluteOffset !== null && absoluteOffset !== void 0 && absoluteOffset.top) {
        position.top = position.top + absoluteOffset.top;
      }
      if (typeof position.bottom !== 'undefined' && absoluteOffset !== null && absoluteOffset !== void 0 && absoluteOffset.bottom) {
        position.bottom = position.bottom + absoluteOffset.bottom;
      }
      if (typeof position.right !== 'undefined' && absoluteOffset !== null && absoluteOffset !== void 0 && absoluteOffset.right) {
        position.right = position.right + absoluteOffset.right;
      }
      if (typeof position.left !== 'undefined' && absoluteOffset !== null && absoluteOffset !== void 0 && absoluteOffset.left) {
        position.left = position.left + absoluteOffset.left;
      }
      return {
        position: position,
        validPosition: (0, _utils.validatePosition)(target)
      };
    }
  }, {
    key: "updatePosition",
    value: function updatePosition() {
      var _this2 = this;
      var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.props;
      var state = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.state;
      var popup = state.popup;
      var _this$calculatePositi = this.calculatePosition(props, popup),
        position = _this$calculatePositi.position,
        validPosition = _this$calculatePositi.validPosition;
      if (position && validPosition) {
        (0, _reactDom.flushSync)(function () {
          _this2.setState({
            position: position,
            validPosition: validPosition
          });
        });
      }
    }
  }, {
    key: "cannotSetPopup",
    value: function cannotSetPopup(popup, target, overflowScrollParent) {
      /**
       * Check whether:
       * 1. Popup's offset targets which means whether or not its possible to correctly position popup along with given target.
       * 2. Popup is inside "overflow: scroll" container, but its offset parent isn't.
       *
       * Currently Popup isn't capable of position itself correctly in case 2,
       * Add "position: relative" to "overflow: scroll" container or to some other FloatingPanel wrapper inside it.
       */
      return !target || document.body.contains(target) && popup.offsetParent &&
      // Ignored via go/ees005
      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
      !popup.offsetParent.contains(target) || overflowScrollParent && !overflowScrollParent.contains(popup.offsetParent);
    }

    /**
     * Popup initialization.
     * Checks whether it's possible to position popup along given target, and if it's not throws an error.
     */
  }, {
    key: "initPopup",
    value: function initPopup(popup) {
      if (this.popupRef.current && (0, _platformFeatureFlags.fg)('platform_editor_link_popup_position_fix_aifc')) {
        var _this$resizeObserver;
        (_this$resizeObserver = this.resizeObserver) === null || _this$resizeObserver === void 0 || _this$resizeObserver.unobserve(this.popupRef.current);
      }
      this.popupRef.current = popup;
      var target = this.props.target;
      var overflowScrollParent = (0, _utils.findOverflowScrollParent)(popup);
      if (this.cannotSetPopup(popup, target, overflowScrollParent)) {
        return;
      }
      this.setState({
        popup: popup
      });
      /**
       * Some plugins (like image) have async rendering of component in floating toolbar(which is popup).
       * Now, floating toolbar position depends on it's size.
       * Size of floating toolbar changes, when async component renders.
       * There is currently, no way to re position floating toolbar or
       *  better to not show floating toolbar till all the async component are ready to render.
       * Also, it is not even Popup's responsibility to take care of it as popup's children are passed
       *  as a prop.
       * So, calling scheduledUpdatePosition to position popup on next request animation frame,
       * which is currently working for most of the floating toolbar and other popups.
       */
      this.scheduledUpdatePosition(this.props);
      if (this.props.focusTrap) {
        this.initFocusTrap();
      }
      if (this.popupRef.current && (0, _platformFeatureFlags.fg)('platform_editor_link_popup_position_fix_aifc')) {
        var _this$resizeObserver2;
        (_this$resizeObserver2 = this.resizeObserver) === null || _this$resizeObserver2 === void 0 || _this$resizeObserver2.observe(this.popupRef.current);
      }
    }
  }, {
    key: "destroyFocusTrap",
    value:
    /**
     * Cancels the initialisation of the focus trap if it has not yet occured
     * Deactivates the focus trap if it exists
     */
    function destroyFocusTrap() {
      var _this$focusTrap;
      this.initFocusTrap.cancel();
      (_this$focusTrap = this.focusTrap) === null || _this$focusTrap === void 0 || _this$focusTrap.deactivate();
    }

    /**
     * Handle pausing, unpausing, and initialising (if not yet initialised) of the focus trap
     */
  }, {
    key: "handleChangedFocusTrapProp",
    value: function handleChangedFocusTrapProp(prevProps) {
      if (prevProps.focusTrap !== this.props.focusTrap) {
        // If currently set to disable, then pause the trap if it exists
        if (!this.props.focusTrap) {
          var _this$focusTrap2;
          return (_this$focusTrap2 = this.focusTrap) === null || _this$focusTrap2 === void 0 ? void 0 : _this$focusTrap2.pause();
        }

        // If set to enabled and trap already exists, unpause
        if (this.focusTrap) {
          this.focusTrap.unpause();
        }

        // Else initialise the focus trap
        return this.initFocusTrap();
      }
    }
  }, {
    key: "componentDidUpdate",
    value: function componentDidUpdate(prevProps) {
      this.handleChangedFocusTrapProp(prevProps);
      if (this.props !== prevProps) {
        this.scheduledUpdatePosition(prevProps);
      }
    }
  }, {
    key: "componentDidMount",
    value: function componentDidMount() {
      // Ignored via go/ees005
      // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
      window.addEventListener('resize', this.onResize);
      var stick = this.props.stick;

      // Ignored via go/ees005
      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
      this.scrollParentElement = (0, _utils.findOverflowScrollParent)(this.props.target);
      if (this.scrollParentElement && this.resizeObserver) {
        this.resizeObserver.observe(this.scrollParentElement);
      }
      if (stick) {
        this.scrollElement = this.scrollParentElement;
      } else {
        this.scrollElement = this.props.scrollableElement;
      }
      if (this.scrollElement) {
        // Ignored via go/ees005
        // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
        this.scrollElement.addEventListener('scroll', this.onResize);
      }
    }
  }, {
    key: "componentWillUnmount",
    value: function componentWillUnmount() {
      // Ignored via go/ees005
      // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
      window.removeEventListener('resize', this.onResize);
      if (this.scrollElement) {
        // Ignored via go/ees005
        // eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
        this.scrollElement.removeEventListener('scroll', this.onResize);
      }
      if ((0, _platformFeatureFlags.fg)('platform_editor_link_popup_position_fix_aifc')) {
        var _this$resizeObserver3;
        (_this$resizeObserver3 = this.resizeObserver) === null || _this$resizeObserver3 === void 0 || _this$resizeObserver3.disconnect();
      } else {
        if (this.scrollParentElement && this.resizeObserver) {
          this.resizeObserver.unobserve(this.scrollParentElement);
        }
      }
      this.scheduledUpdatePosition.cancel();
      this.destroyFocusTrap();
      var onUnmount = this.props.onUnmount;
      if (onUnmount) {
        onUnmount();
      }
    }
  }, {
    key: "renderPopup",
    value: function renderPopup() {
      var _this$props$ariaLabel,
        _this3 = this;
      var position = this.state.position;
      var shouldRenderPopup = this.props.shouldRenderPopup;
      if (shouldRenderPopup && !shouldRenderPopup(position || {})) {
        return null;
      }

      /**
       * https://a11y.atlassian.net/browse/A11Y-9995
       * We set aria-label to undefined if it's null, no more 'Popup' fallback.
       * It is meaningless for screen readers and causes confusion.
       */
      var ariaLabel = (0, _platformFeatureFlags.fg)('_editor_a11y_aria_label_removal_popup') ? (_this$props$ariaLabel = this.props.ariaLabel) !== null && _this$props$ariaLabel !== void 0 ? _this$props$ariaLabel : undefined : this.props.ariaLabel === null ? undefined : this.props.ariaLabel || 'Popup';
      var getRole = function getRole() {
        // Provide a valid role only when aria-label is present to satisfy a11y rules, as when aria-label is present, role is required
        // use role = dialog as default role, as dialog role itself is not a parent role that requires specific children to function as some other ARIA roles(menu) do
        // if set default role to menu, tons of integration tests will fail as many of our popup usages do not have children that satisfy menu role requirements
        if (ariaLabel) {
          return _this3.props.role || 'dialog';
        }
        return undefined;
      };
      return /*#__PURE__*/_react.default.createElement("div", {
        ref: this.handleRef,
        style: _objectSpread(_objectSpread({
          // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
          position: 'absolute',
          // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
          zIndex: this.props.zIndex || _editorSharedStyles.akEditorFloatingPanelZIndex
        }, position), this.props.style),
        role: getRole(),
        "aria-label": ariaLabel,
        "data-testid": "popup-wrapper"
        // Indicates component is an editor pop. Required for focus handling in Message.tsx
        ,
        "data-editor-popup": true
      }, this.props.children);
    }
  }, {
    key: "render",
    value: function render() {
      var _this$props = this.props,
        target = _this$props.target,
        mountTo = _this$props.mountTo;
      var validPosition = this.state.validPosition;
      if (!target || !validPosition) {
        return null;
      }
      if (mountTo) {
        return /*#__PURE__*/(0, _reactDom.createPortal)(this.renderPopup(), mountTo);
      }

      // Without mountTo property renders popup as is,
      // which means it will be cropped by "overflow: hidden" container.
      return this.renderPopup();
    }
  }]);
}(_react.default.Component);
(0, _defineProperty2.default)(Popup, "defaultProps", {
  offset: [0, 0],
  allowOutOfBound: false
});