import "core-js/modules/es.error.cause.js";
function _classPrivateMethodInitSpec(obj, privateSet) { _checkPrivateRedeclaration(obj, privateSet); privateSet.add(obj); }
function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); }
function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(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 _classPrivateFieldGet(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
function _classApplyDescriptorGet(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; }
function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "set"); _classApplyDescriptorSet(receiver, descriptor, value); return value; }
function _classExtractFieldDescriptor(receiver, privateMap, action) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to " + action + " private field on non-instance"); } return privateMap.get(receiver); }
function _classApplyDescriptorSet(receiver, descriptor, value) { if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } }
function _classPrivateMethodGet(receiver, privateSet, fn) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return fn; }
import { BasePlugin } from "../base/index.mjs";
import { arrayEach } from "../../helpers/array.mjs";
import { objectEach } from "../../helpers/object.mjs";
import { CommandExecutor } from "../contextMenu/commandExecutor.mjs";
import { getDocumentOffsetByElement } from "../contextMenu/utils.mjs";
import { hasClass, setAttribute } from "../../helpers/dom/element.mjs";
import { ItemsFactory } from "../contextMenu/itemsFactory.mjs";
import { Menu } from "../contextMenu/menu/index.mjs";
import Hooks from "../../pluginHooks.mjs";
import { COLUMN_LEFT, COLUMN_RIGHT, REMOVE_COLUMN, CLEAR_COLUMN, READ_ONLY, ALIGNMENT, SEPARATOR } from "../contextMenu/predefinedItems/index.mjs";
import { A11Y_HASPOPUP, A11Y_HIDDEN, A11Y_LABEL } from "../../helpers/a11y.mjs";
Hooks.getSingleton().register('afterDropdownMenuDefaultOptions');
Hooks.getSingleton().register('beforeDropdownMenuShow');
Hooks.getSingleton().register('afterDropdownMenuShow');
Hooks.getSingleton().register('afterDropdownMenuHide');
Hooks.getSingleton().register('afterDropdownMenuExecute');
export const PLUGIN_KEY = 'dropdownMenu';
export const PLUGIN_PRIORITY = 230;
const BUTTON_CLASS_NAME = 'changeType';
const SHORTCUTS_GROUP = PLUGIN_KEY;

/* eslint-disable jsdoc/require-description-complete-sentence */
/**
 * @plugin DropdownMenu
 * @class DropdownMenu
 *
 * @description
 * This plugin creates the Handsontable Dropdown Menu. It allows to create a new row or column at any place in the grid
 * among [other features](@/guides/accessories-and-menus/context-menu.md#context-menu-with-specific-options).
 * Possible values:
 * * `true` (to enable default options),
 * * `false` (to disable completely).
 *
 * or array of any available strings:
 * * `["row_above", "row_below", "col_left", "col_right",
 * "remove_row", "remove_col", "---------", "undo", "redo"]`.
 *
 * See [the dropdown menu demo](@/guides/columns/column-menu.md) for examples.
 *
 * @example
 * ::: only-for javascript
 * ```js
 * const container = document.getElementById('example');
 * const hot = new Handsontable(container, {
 *   data: data,
 *   colHeaders: true,
 *   // enable dropdown menu
 *   dropdownMenu: true
 * });
 *
 * // or
 * const hot = new Handsontable(container, {
 *   data: data,
 *   colHeaders: true,
 *   // enable and configure dropdown menu
 *   dropdownMenu: ['remove_col', '---------', 'make_read_only', 'alignment']
 * });
 * ```
 * :::
 *
 * ::: only-for react
 * ```jsx
 * <HotTable
 *   data={data}
 *   comments={true}
 *   // enable and configure dropdown menu
 *   dropdownMenu={['remove_col', '---------', 'make_read_only', 'alignment']}
 * />
 * ```
 * :::
 */
var _isButtonClicked = /*#__PURE__*/new WeakMap();
var _addCustomShortcuts = /*#__PURE__*/new WeakSet();
var _onTableClick = /*#__PURE__*/new WeakSet();
var _onAfterGetColHeader = /*#__PURE__*/new WeakSet();
var _onMenuBeforeOpen = /*#__PURE__*/new WeakSet();
var _onMenuAfterOpen = /*#__PURE__*/new WeakSet();
var _onSubMenuAfterOpen = /*#__PURE__*/new WeakSet();
var _onMenuAfterClose = /*#__PURE__*/new WeakSet();
var _onBeforeViewportScrollHorizontally = /*#__PURE__*/new WeakSet();
var _onBeforeOnCellMouseDown = /*#__PURE__*/new WeakSet();
export class DropdownMenu extends BasePlugin {
  static get PLUGIN_KEY() {
    return PLUGIN_KEY;
  }
  static get PLUGIN_PRIORITY() {
    return PLUGIN_PRIORITY;
  }
  static get PLUGIN_DEPS() {
    return ['plugin:AutoColumnSize'];
  }

  /**
   * Default menu items order when `dropdownMenu` is enabled by setting the config item to `true`.
   *
   * @returns {Array}
   */
  static get DEFAULT_ITEMS() {
    return [COLUMN_LEFT, COLUMN_RIGHT, SEPARATOR, REMOVE_COLUMN, SEPARATOR, CLEAR_COLUMN, SEPARATOR, READ_ONLY, SEPARATOR, ALIGNMENT];
  }

  /**
   * Instance of {@link CommandExecutor}.
   *
   * @private
   * @type {CommandExecutor}
   */

  constructor(hotInstance) {
    super(hotInstance);

    // One listener for enable/disable functionality
    /**
     * Hook sets the internal flag to `true` when the button is clicked.
     *
     * @param {MouseEvent} event The mouse event object.
     */
    _classPrivateMethodInitSpec(this, _onBeforeOnCellMouseDown);
    /**
     * Hook allows blocking horizontal scroll when the menu is opened by clicking on
     * the column header button. This prevents from scrolling the viewport (jump effect) when
     * the button is clicked.
     *
     * @param {number} visualColumn Visual column index.
     * @returns {number | null}
     */
    _classPrivateMethodInitSpec(this, _onBeforeViewportScrollHorizontally);
    /**
     * On menu after close listener.
     *
     * @private
     * @fires Hooks#afterDropdownMenuHide
     */
    _classPrivateMethodInitSpec(this, _onMenuAfterClose);
    /**
     * Listener for the `afterSubmenuOpen` hook.
     *
     * @private
     * @param {Menu} subMenuInstance The opened sub menu instance.
     */
    _classPrivateMethodInitSpec(this, _onSubMenuAfterOpen);
    /**
     * On menu after open listener.
     *
     * @private
     * @fires Hooks#afterDropdownMenuShow
     */
    _classPrivateMethodInitSpec(this, _onMenuAfterOpen);
    /**
     * On menu before open listener.
     *
     * @private
     * @fires Hooks#beforeDropdownMenuShow
     */
    _classPrivateMethodInitSpec(this, _onMenuBeforeOpen);
    /**
     * On after get column header listener.
     *
     * @private
     * @param {number} col Visual column index.
     * @param {HTMLTableCellElement} TH Header's TH element.
     */
    _classPrivateMethodInitSpec(this, _onAfterGetColHeader);
    /**
     * Table click listener.
     *
     * @private
     * @param {Event} event The mouse event object.
     */
    _classPrivateMethodInitSpec(this, _onTableClick);
    /**
     * Add custom shortcuts to the provided menu instance.
     *
     * @param {Menu} menuInstance The menu instance.
     */
    _classPrivateMethodInitSpec(this, _addCustomShortcuts);
    _defineProperty(this, "commandExecutor", new CommandExecutor(this.hot));
    /**
     * Instance of {@link ItemsFactory}.
     *
     * @private
     * @type {ItemsFactory}
     */
    _defineProperty(this, "itemsFactory", null);
    /**
     * Instance of {@link Menu}.
     *
     * @private
     * @type {Menu}
     */
    _defineProperty(this, "menu", null);
    /**
     * Flag which determines if the button that opens the menu was clicked.
     *
     * @type {boolean}
     */
    _classPrivateFieldInitSpec(this, _isButtonClicked, {
      writable: true,
      value: false
    });
    this.hot.addHook('afterGetColHeader', (col, TH) => _classPrivateMethodGet(this, _onAfterGetColHeader, _onAfterGetColHeader2).call(this, col, TH));
  }

  /**
   * Checks if the plugin is enabled in the handsontable settings. This method is executed in {@link Hooks#beforeInit}
   * hook and if it returns `true` then the {@link DropdownMenu#enablePlugin} method is called.
   *
   * @returns {boolean}
   */
  isEnabled() {
    return this.hot.getSettings()[PLUGIN_KEY];
  }

  /**
   * Enables the plugin functionality for this Handsontable instance.
   *
   * @fires Hooks#afterDropdownMenuDefaultOptions
   * @fires Hooks#beforeDropdownMenuSetItems
   */
  enablePlugin() {
    var _this = this;
    if (this.enabled) {
      return;
    }
    this.itemsFactory = new ItemsFactory(this.hot, DropdownMenu.DEFAULT_ITEMS);
    this.addHook('beforeOnCellMouseDown', function () {
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      return _classPrivateMethodGet(_this, _onBeforeOnCellMouseDown, _onBeforeOnCellMouseDown2).call(_this, ...args);
    });
    this.addHook('beforeViewportScrollHorizontally', function () {
      for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
        args[_key2] = arguments[_key2];
      }
      return _classPrivateMethodGet(_this, _onBeforeViewportScrollHorizontally, _onBeforeViewportScrollHorizontally2).call(_this, ...args);
    });
    const settings = this.hot.getSettings()[PLUGIN_KEY];
    const predefinedItems = {
      items: this.itemsFactory.getItems(settings)
    };
    this.registerEvents();
    if (typeof settings.callback === 'function') {
      this.commandExecutor.setCommonCallback(settings.callback);
    }
    this.registerShortcuts();
    super.enablePlugin();
    this.callOnPluginsReady(() => {
      this.hot.runHooks('afterDropdownMenuDefaultOptions', predefinedItems);
      this.itemsFactory.setPredefinedItems(predefinedItems.items);
      const menuItems = this.itemsFactory.getItems(settings);
      if (this.menu) {
        this.menu.destroy();
      }
      this.menu = new Menu(this.hot, {
        className: 'htDropdownMenu',
        keepInViewport: true,
        container: settings.uiContainer || this.hot.rootDocument.body
      });
      this.hot.runHooks('beforeDropdownMenuSetItems', menuItems);
      this.menu.setMenuItems(menuItems);
      this.menu.addLocalHook('beforeOpen', () => _classPrivateMethodGet(this, _onMenuBeforeOpen, _onMenuBeforeOpen2).call(this));
      this.menu.addLocalHook('afterOpen', () => _classPrivateMethodGet(this, _onMenuAfterOpen, _onMenuAfterOpen2).call(this));
      this.menu.addLocalHook('afterSubmenuOpen', subMenuInstance => _classPrivateMethodGet(this, _onSubMenuAfterOpen, _onSubMenuAfterOpen2).call(this, subMenuInstance));
      this.menu.addLocalHook('afterClose', () => _classPrivateMethodGet(this, _onMenuAfterClose, _onMenuAfterClose2).call(this));
      this.menu.addLocalHook('executeCommand', function () {
        for (var _len3 = arguments.length, params = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
          params[_key3] = arguments[_key3];
        }
        return _this.executeCommand.call(_this, ...params);
      });

      // Register all commands. Predefined and added by user or by plugins
      arrayEach(menuItems, command => this.commandExecutor.registerCommand(command.key, command));
    });
  }

  /**
   * Updates the plugin's state.
   *
   * This method is executed when [`updateSettings()`](@/api/core.md#updatesettings) is invoked with any of the following configuration options:
   *  - [`dropdownMenu`](@/api/options.md#dropdownmenu)
   */
  updatePlugin() {
    this.disablePlugin();
    this.enablePlugin();
    super.updatePlugin();
  }

  /**
   * Disables the plugin functionality for this Handsontable instance.
   */
  disablePlugin() {
    this.close();
    if (this.menu) {
      this.menu.destroy();
    }
    this.unregisterShortcuts();
    super.disablePlugin();
  }

  /**
   * Register shortcuts responsible for toggling dropdown menu.
   *
   * @private
   */
  registerShortcuts() {
    const gridContext = this.hot.getShortcutManager().getContext('grid');
    const callback = () => {
      const {
        highlight
      } = this.hot.getSelectedRangeLast();
      if ((highlight.isHeader() && highlight.row === -1 || highlight.isCell()) && highlight.col >= 0) {
        this.hot.selectColumns(highlight.col, highlight.col, -1);
        const {
          from
        } = this.hot.getSelectedRangeLast();
        const offset = getDocumentOffsetByElement(this.menu.container, this.hot.rootDocument);
        const target = this.hot.getCell(-1, from.col, true);
        const rect = target.getBoundingClientRect();
        this.open({
          left: rect.left + offset.left,
          top: rect.top + target.offsetHeight + offset.top
        }, {
          left: rect.width
        });
        // Make sure the first item is selected (role=menuitem). Otherwise, screen readers
        // will block the Esc key for the whole menu.
        this.menu.getNavigator().toFirstItem();
      }
    };
    gridContext.addShortcuts([{
      keys: [['Shift', 'Alt', 'ArrowDown'], ['Control/Meta', 'Enter']],
      callback,
      runOnlyIf: () => {
        var _this$hot$getSelected;
        const highlight = (_this$hot$getSelected = this.hot.getSelectedRangeLast()) === null || _this$hot$getSelected === void 0 ? void 0 : _this$hot$getSelected.highlight;
        return highlight && this.hot.selection.isCellVisible(highlight) && highlight.isHeader() && !this.menu.isOpened();
      },
      captureCtrl: true,
      group: SHORTCUTS_GROUP
    }, {
      keys: [['Shift', 'Alt', 'ArrowDown']],
      callback,
      runOnlyIf: () => {
        var _this$hot$getSelected2;
        const highlight = (_this$hot$getSelected2 = this.hot.getSelectedRangeLast()) === null || _this$hot$getSelected2 === void 0 ? void 0 : _this$hot$getSelected2.highlight;
        return highlight && this.hot.selection.isCellVisible(highlight) && highlight.isCell() && !this.menu.isOpened();
      },
      group: SHORTCUTS_GROUP
    }]);
  }

  /**
   * Unregister shortcuts responsible for toggling dropdown menu.
   *
   * @private
   */
  unregisterShortcuts() {
    this.hot.getShortcutManager().getContext('grid').removeShortcutsByGroup(SHORTCUTS_GROUP);
  }

  /**
   * Registers the DOM listeners.
   *
   * @private
   */
  registerEvents() {
    this.eventManager.addEventListener(this.hot.rootElement, 'click', event => _classPrivateMethodGet(this, _onTableClick, _onTableClick2).call(this, event));
  }

  /**
   * Opens menu and re-position it based on the passed coordinates.
   *
   * @param {{ top: number, left: number }|Event} position An object with `top` and `left` properties
   * which contains coordinates relative to the browsers viewport (without included scroll offsets).
   * Or if the native event is passed the menu will be positioned based on the `pageX` and `pageY`
   * coordinates.
   * @param {{ above: number, below: number, left: number, right: number }} offset An object allows applying
   * the offset to the menu position.
   * @fires Hooks#beforeDropdownMenuShow
   * @fires Hooks#afterDropdownMenuShow
   */
  open(position) {
    var _this$menu;
    let offset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
      above: 0,
      below: 0,
      left: 0,
      right: 0
    };
    if ((_this$menu = this.menu) !== null && _this$menu !== void 0 && _this$menu.isOpened()) {
      return;
    }
    this.menu.open();
    objectEach(offset, (value, key) => {
      this.menu.setOffset(key, value);
    });
    this.menu.setPosition(position);
  }

  /**
   * Closes dropdown menu.
   */
  close() {
    var _this$menu2;
    (_this$menu2 = this.menu) === null || _this$menu2 === void 0 || _this$menu2.close();
  }

  /**
   * Executes context menu command.
   *
   * The `executeCommand()` method works only for selected cells.
   *
   * When no cells are selected, `executeCommand()` doesn't do anything.
   *
   * You can execute all predefined commands:
   *  * `'row_above'` - Insert row above
   *  * `'row_below'` - Insert row below
   *  * `'col_left'` - Insert column left
   *  * `'col_right'` - Insert column right
   *  * `'clear_column'` - Clear selected column
   *  * `'remove_row'` - Remove row
   *  * `'remove_col'` - Remove column
   *  * `'undo'` - Undo last action
   *  * `'redo'` - Redo last action
   *  * `'make_read_only'` - Make cell read only
   *  * `'alignment:left'` - Alignment to the left
   *  * `'alignment:top'` - Alignment to the top
   *  * `'alignment:right'` - Alignment to the right
   *  * `'alignment:bottom'` - Alignment to the bottom
   *  * `'alignment:middle'` - Alignment to the middle
   *  * `'alignment:center'` - Alignment to the center (justify).
   *
   * Or you can execute command registered in settings where `key` is your command name.
   *
   * @param {string} commandName Command name to execute.
   * @param {*} params Additional parameters passed to the command executor.
   */
  executeCommand(commandName) {
    for (var _len4 = arguments.length, params = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {
      params[_key4 - 1] = arguments[_key4];
    }
    this.commandExecutor.execute(commandName, ...params);
  }

  /**
   * Turns on / off listening on dropdown menu.
   *
   * @private
   * @param {boolean} listen Turn on listening when value is set to true, otherwise turn it off.
   */
  setListening() {
    let listen = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
    if (this.menu.isOpened()) {
      if (listen) {
        this.menu.hotMenu.listen();
      } else {
        this.menu.hotMenu.unlisten();
      }
    }
  }
  /**
   * Destroys the plugin instance.
   */
  destroy() {
    this.close();
    if (this.menu) {
      this.menu.destroy();
    }
    super.destroy();
  }
}
function _addCustomShortcuts2(menuInstance) {
  menuInstance.getKeyboardShortcutsCtrl().addCustomShortcuts([{
    keys: [['Control/Meta', 'A']],
    callback: () => false
  }]);
}
function _onTableClick2(event) {
  event.stopPropagation();
  if (hasClass(event.target, BUTTON_CLASS_NAME)) {
    const offset = getDocumentOffsetByElement(this.menu.container, this.hot.rootDocument);
    const rect = event.target.getBoundingClientRect();
    _classPrivateFieldSet(this, _isButtonClicked, false);
    this.open({
      left: rect.left + offset.left,
      top: rect.top + event.target.offsetHeight + 3 + offset.top
    }, {
      left: rect.width
    });
  }
}
function _onAfterGetColHeader2(col, TH) {
  // Corner or a higher-level header
  const headerRow = TH.parentNode;
  if (!headerRow) {
    return;
  }
  const headerRowList = headerRow.parentNode.childNodes;
  const level = Array.prototype.indexOf.call(headerRowList, headerRow);
  if (col < 0 || level !== headerRowList.length - 1) {
    return;
  }
  const existingButton = TH.querySelector(`.${BUTTON_CLASS_NAME}`);

  // Plugin enabled and buttons already exists, return.
  if (this.enabled && existingButton) {
    return;
  }
  // Plugin disabled and buttons still exists, so remove them.
  if (!this.enabled) {
    if (existingButton) {
      existingButton.parentNode.removeChild(existingButton);
    }
    return;
  }
  const button = this.hot.rootDocument.createElement('button');
  button.className = BUTTON_CLASS_NAME;
  button.type = 'button';
  button.tabIndex = -1;
  if (this.hot.getSettings().ariaTags) {
    setAttribute(button, [A11Y_HIDDEN(), A11Y_LABEL(' ')]);
    setAttribute(TH, [A11Y_HASPOPUP('menu')]);
  }

  // prevent page reload on button click
  button.onclick = function () {
    return false;
  };
  TH.firstChild.insertBefore(button, TH.firstChild.firstChild);
}
function _onMenuBeforeOpen2() {
  this.hot.runHooks('beforeDropdownMenuShow', this);
}
function _onMenuAfterOpen2() {
  this.hot.runHooks('afterDropdownMenuShow', this);
  _classPrivateMethodGet(this, _addCustomShortcuts, _addCustomShortcuts2).call(this, this.menu);
}
function _onSubMenuAfterOpen2(subMenuInstance) {
  _classPrivateMethodGet(this, _addCustomShortcuts, _addCustomShortcuts2).call(this, subMenuInstance);
}
function _onMenuAfterClose2() {
  this.hot.listen();
  this.hot.runHooks('afterDropdownMenuHide', this);
}
function _onBeforeViewportScrollHorizontally2(visualColumn) {
  return _classPrivateFieldGet(this, _isButtonClicked) ? null : visualColumn;
}
function _onBeforeOnCellMouseDown2(event) {
  if (hasClass(event.target, BUTTON_CLASS_NAME)) {
    _classPrivateFieldSet(this, _isButtonClicked, true);
  }
}
DropdownMenu.SEPARATOR = {
  name: SEPARATOR
};