import { M as MapToComponentMethod, V as VegaSlimmer } from './global-slimmer-registry-0a7aa308.js';
import { l as checkIsVoiceOverClick, m as getFirstFocusableElement, e as getDeepActiveElementInShadow } from './ui-6dd51115.js';
import { S as SynchronizeTaskQueue } from './synchronize-task-queue-ac8acc3b.js';
import { O as Observer } from './observer-3959f9dd.js';
import { C as ChangeManager } from './change-manager-ecbd3a2a.js';
import { C as VegaInternalDialogContentReady } from './vega-internal-event-id-a2d4c714.js';
import { d as domNodeSubjectFactory } from './dom-node-subject-factory-6894df15.js';

var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  return c > 3 && r && Object.defineProperty(target, key, r), r;
};
/**
 * The `AriaDialogFocusableTrapSlimmer` class is designed to manage focus behavior for dialog elements appended to the DOM, particularly in scenarios where screen readers like VoiceOver are used.
 * It detects when a click event is likely triggered by VoiceOver and ensures that focus is appropriately set to the first focusable element
 * within the dialog content when it is shown, and returns focus to the triggering element when the dialog is hidden.
 * This helps maintain a logical focus order and improves accessibility for keyboard users and screen reader users.
 */
class AriaDialogFocusableTrapSlimmer extends VegaSlimmer {
  constructor(entry) {
    super();
    this.isVoiceOverClick = false;
    this.focusTriggerElement = null;
    this.focusDialogTaskQueue = new SynchronizeTaskQueue();
    this.dialogContentReadyObserver = null;
    this.dialogContentReadySubject = null;
    this.isChildFocused = false;
    /**
     * Sets up the event listener for click events on the trigger element or the window.
     */
    this.eventListenerHandler = () => {
      const target = this.entry.triggerEl() || window;
      target.addEventListener('click', this.handleClickEvent, { capture: true });
      target.addEventListener('contextmenu', this.handleContextMenuEvent, { capture: true });
    };
    /**
     * Removes the event listener for click events from the trigger element or the window.
     */
    this.eventListenerRemover = () => {
      const target = this.entry.triggerEl();
      target && target.removeEventListener('click', this.handleClickEvent, { capture: true });
      window.removeEventListener('click', this.handleClickEvent, { capture: true });
      target && target.removeEventListener('contextmenu', this.handleContextMenuEvent, { capture: true });
    };
    /**
     * Handles the click event and determines if it is likely triggered by VoiceOver.
     *  If it is a VoiceOver click, it sets the `isVoiceOverClick` flag to true and stores the currently focused element as the trigger element.
     * 	If it is not a VoiceOver click, it resets the `focusTriggerElement` to null.
     *
     * @param {MouseEvent} e - The click event object that is passed to the event handler.
     */
    this.handleClickEvent = (e) => {
      this.isVoiceOverClick = checkIsVoiceOverClick(e);
      if (!this.isVoiceOverClick) {
        this.focusTriggerElement = null;
      }
    };
    /**
     * Handles the context menu event to prevent the default context menu from appearing when a VoiceOver click is detected.
     * This is important because VoiceOver users may trigger a context menu with a specific gesture, and preventing the default behavior can help ensure that the focus management works as intended without interference from the context menu.
     *
     * @param {MouseEvent} e - The context menu event object that is passed to the event handler.
     */
    this.handleContextMenuEvent = (e) => {
      if (this.isVoiceOverClick) {
        e.preventDefault();
        e.stopPropagation();
      }
    };
    /**
     * Focuses on the first focusable element within the dialog when the dialog content is ready and the last interaction was detected as a VoiceOver click. This method is called in response to the dialog content ready notification, ensuring that focus is set appropriately when the dialog is shown after being triggered by VoiceOver.
     * It retrieves the currently active element, stores it as the trigger element, and then focuses on the first focusable element within the dialog content.
     *
     */
    this.focusOnFirstFocusableElement = () => {
      if (!this.isChildFocused) {
        const dialogElement = this.entry.dialogEl();
        const focusableElement = getFirstFocusableElement(dialogElement);
        if (focusableElement) {
          this.isChildFocused = true;
          focusableElement.focus();
        }
        else {
          dialogElement.focus();
        }
      }
    };
    this.entry = entry;
  }
  /**
   * Ensures that the event listener for click events is set up when the component is connected to the DOM. This is important for cases where the component might be added to the DOM after it has been initialized, ensuring that the click events are properly handled to manage focus behavior for dialog elements.
   */
  connectedCallback() {
    this.eventListenerHandler();
    this.subscribeDialogContentReady();
  }
  /**
   * Removes the event listener for click events from the trigger element or the window when the component is disconnected from the DOM.
   */
  disconnectedCallback() {
    this.eventListenerRemover();
    this.unsubscribeDialogContentReady();
  }
  /**
   * Sets up the event listener for click events on the trigger element or the window after the component has loaded.
   */
  componentDidLoad() {
    this.eventListenerHandler();
    this.subscribeDialogContentReady();
  }
  /**
   * Ensures that any pending focus actions are executed after the component has rendered.
   */
  async componentDidRender() {
    await this.focusDialogTaskQueue.runTask();
  }
  /**
   * When showing the dialog, if the last active element before the click event is detected as a VoiceOver click,
   *
   * @param {boolean} isOpen - A boolean indicating whether the dialog should be shown or hidden.
   * @returns {Promise<void>} A promise that resolves when the focus action is complete.
   */
  async toggle(isOpen) {
    if (isOpen) {
      await this.show();
    }
    else {
      await this.hide();
    }
  }
  /**
   * When showing the appended element, if the last active element before the click event is detected as a VoiceOver click,
   *   it will attempt to focus the first focusable element within the appended element.
   *
   * @returns {Promise<void>} A promise that resolves when the focus action is complete.
   */
  async show() {
    this.entry.dialogEl().removeAttribute('inert');
    // eslint-disable-next-line no-restricted-globals
    const activeEl = getDeepActiveElementInShadow(document, true);
    if (activeEl) {
      this.isChildFocused = false;
      await this.focusDialogTaskQueue.enqueue({
        // eslint-disable-next-line jsdoc/require-jsdoc
        run: async () => {
          if (this.isVoiceOverClick) {
            this.focusTriggerElement = activeEl;
            this.focusOnFirstFocusableElement();
          }
        },
      }, false);
    }
  }
  /**
   * When hiding the appended element, if the last active element before the click event is detected as a VoiceOver click,
   *    it will attempt to focus back on the element that triggered the show action.
   *
   * @returns {Promise<void>} A promise that resolves when the focus action is complete.
   */
  async hide() {
    if (this.focusTriggerElement) {
      this.entry.dialogEl().setAttribute('inert', '');
      await this.focusDialogTaskQueue.enqueue({
        // eslint-disable-next-line jsdoc/require-jsdoc
        run: async () => {
          this.focusTriggerElement && this.focusTriggerElement.focus();
          this.focusTriggerElement = null;
        },
      }, false);
    }
  }
  /**
   * Subscribes to the dialog content ready ChangeManager subject if configured.
   * When the external component notifies that dialog content is rendered,
   * the observer retries focusing the first focusable element in the dialog.
   */
  subscribeDialogContentReady() {
    const dialogElement = this.entry.dialogEl();
    if (dialogElement && !this.dialogContentReadySubject) {
      this.dialogContentReadySubject = domNodeSubjectFactory.getSubject(this.entry.dialogEl(), VegaInternalDialogContentReady);
      this.dialogContentReadyObserver = new Observer((_payload) => true, () => {
        if (this.isVoiceOverClick) {
          this.focusOnFirstFocusableElement();
        }
      });
      ChangeManager.register(this.dialogContentReadySubject, this.dialogContentReadyObserver);
    }
  }
  /**
   * Unsubscribes from the dialog content ready ChangeManager subject.
   */
  unsubscribeDialogContentReady() {
    if (this.dialogContentReadyObserver && this.dialogContentReadySubject) {
      ChangeManager.unregister(this.dialogContentReadySubject, this.dialogContentReadyObserver);
      this.dialogContentReadyObserver = null;
      this.dialogContentReadySubject = null;
    }
  }
}
__decorate([
  MapToComponentMethod('connectedCallback')
], AriaDialogFocusableTrapSlimmer.prototype, "connectedCallback", null);
__decorate([
  MapToComponentMethod('disconnectedCallback')
], AriaDialogFocusableTrapSlimmer.prototype, "disconnectedCallback", null);
__decorate([
  MapToComponentMethod('componentDidLoad')
], AriaDialogFocusableTrapSlimmer.prototype, "componentDidLoad", null);
__decorate([
  MapToComponentMethod('componentDidRender')
], AriaDialogFocusableTrapSlimmer.prototype, "componentDidRender", null);

export { AriaDialogFocusableTrapSlimmer as A };
