import { Host, h } from '@stencil/core';
/**
 * Adds a container for a list of selectable options for the `<bs-dropdown>`
 * component. Use `<bs-dropdown-option>` as children components.
 *
 * ### Accessibility Notes
 * Toggle element should have:
 * - `aria-haspopup="listbox"`.
 * - `aria-expanded = true` when the dropdown is visible.
 * - `aria-controls` attribute which matches `optionsId` prop.
 */
export class BsDropdownOptionList {
  constructor() {
    /** Track which option is focused */
    this.focusedIndex = -1;
    /** Track selected option */
    this.selectedIndex = -1;
    /** Will observe for child option updates */
    this.mutationObserver = null;
    this.optionsId = undefined;
    this.visible = false;
    this.focusDelay = 75;
    this.label = undefined;
    this.selectedValue = undefined;
    this.activeDescendantId = undefined;
  }
  componentDidLoad() {
    this.passOptionProps();
    // Set selected and focus on mount, if possible
    this.handleVisible();
    this.mutationObserverSetup();
  }
  /** Pass down relevant props */
  passOptionProps() {
    this.optionEls.forEach((el, index) => {
      el.setAttribute('index', String(index));
      // Stencil Vue wrappers have an issue with camelCase props currently
      // So, sending both attributes
      el.setAttribute('options-id', this.optionsId);
      el.setAttribute('optionsid', this.optionsId);
    });
  }
  /** Watch for options being added/removed */
  mutationObserverSetup() {
    this.mutationObserver = new MutationObserver(() => {
      this._optionEls = null;
      this.passOptionProps();
    });
    this.mutationObserver.observe(this.optionsEl, { childList: true });
  }
  disconnectedCallback() {
    var _a, _b;
    // Clean up mutation observer
    (_b = (_a = this.mutationObserver) === null || _a === void 0 ? void 0 : _a.disconnect) === null || _b === void 0 ? void 0 : _b.call(_a);
    this.mutationObserver = null;
  }
  /** Get the dropdown option elements. */
  get optionEls() {
    if (!this._optionEls) {
      const els = this.optionsEl.querySelectorAll('bs-dropdown-option');
      this._optionEls = Array.from(els);
    }
    return this._optionEls;
  }
  /** Removes focus from the currently focused option, if applicable */
  removeCurrentFocus() {
    var _a;
    if (this.focusedIndex > -1) {
      (_a = this.optionEls[this.focusedIndex]) === null || _a === void 0 ? void 0 : _a.removeAttribute('focused');
    }
  }
  /** Handles all applicable navigation key presses for dropdown accessibility */
  handleKeyDown(ev) {
    if (this.visible) {
      switch (ev.key) {
        case 'ArrowDown':
          this.traverse('down');
          ev.preventDefault();
          break;
        case 'ArrowUp':
          this.traverse('up');
          ev.preventDefault();
          break;
        case 'Home':
          this.traverse('home');
          ev.preventDefault();
          break;
        case 'End':
          this.traverse('end');
          ev.preventDefault();
          break;
        case 'Tab':
          // Prevent tab from doing anything while dropdown is open
          ev.preventDefault();
          break;
      }
    }
  }
  /** Traverses focus of the option list via keyboard navigation */
  traverse(direction) {
    let newIndex;
    switch (direction) {
      case 'up':
        newIndex = (this.focusedIndex - 1 < 0) ?
          this.optionEls.length - 1 :
          this.focusedIndex - 1;
        break;
      case 'down':
        newIndex = (this.focusedIndex + 1) % this.optionEls.length;
        break;
      case 'home':
        newIndex = 0;
        break;
      case 'end':
        newIndex = this.optionEls.length - 1;
        break;
    }
    this.removeCurrentFocus();
    this.optionEls[newIndex].setAttribute('focused', 'true');
    this.focusedIndex = newIndex;
  }
  /** Handles moving focus on mouse hover */
  handleMouseFocus(ev) {
    if (this.visible) {
      this.removeCurrentFocus();
      this.focusedIndex = ev.detail;
    }
  }
  /**
   * Handles selection by updating various internal states and emitting to
   * parent
   */
  handleSelect(ev) {
    this.activeDescendantId = ev.detail.id;
    this.optselected.emit(ev.detail.value);
    this.removeCurrentFocus();
    this.close.emit();
  }
  /** Focuses and sets selected on a selected option on when visible */
  handleVisible() {
    var _a, _b, _c;
    if (this.visible) {
      // Find selected index
      this.selectedIndex = this.optionEls.findIndex((el) => el.getAttribute('value') === this.selectedValue);
      // Set selected / focused when applicable
      if (this.selectedIndex > -1) {
        (_a = this.optionEls[this.selectedIndex]) === null || _a === void 0 ? void 0 : _a.setAttribute('selected', 'true');
        this.focusedIndex = this.selectedIndex;
        if (this.focusDelay > 0) {
          setTimeout(() => {
            var _a;
            (_a = this.optionEls[this.selectedIndex]) === null || _a === void 0 ? void 0 : _a.setAttribute('focused', 'true');
          }, this.focusDelay);
        }
        else {
          (_b = this.optionEls[this.selectedIndex]) === null || _b === void 0 ? void 0 : _b.setAttribute('focused', 'true');
        }
      }
    }
    else {
      // Clear out selected item when not visible
      (_c = this.optionEls[this.selectedIndex]) === null || _c === void 0 ? void 0 : _c.removeAttribute('selected');
    }
  }
  render() {
    return (h(Host, null, h("ul", { class: "bs-dropdown-options", role: "listbox", id: this.optionsId, "aria-label": this.label || undefined, "aria-activedescendant": this.activeDescendantId || undefined, ref: el => this.optionsEl = el }, h("slot", null))));
  }
  static get is() { return "bs-dropdown-option-list"; }
  static get originalStyleUrls() {
    return {
      "$": ["bs-dropdown-option-list.scss"]
    };
  }
  static get styleUrls() {
    return {
      "$": ["bs-dropdown-option-list.css"]
    };
  }
  static get properties() {
    return {
      "optionsId": {
        "type": "string",
        "mutable": false,
        "complexType": {
          "original": "string",
          "resolved": "string",
          "references": {}
        },
        "required": true,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "Id of the options list. This should also be set on the toggle element's\n`aria-controls` attribute and the label's `for` attribute."
        },
        "attribute": "options-id",
        "reflect": false
      },
      "visible": {
        "type": "boolean",
        "mutable": false,
        "complexType": {
          "original": "boolean",
          "resolved": "boolean",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "Tells the option list that it is currently visible. When this is flipped\nto `true`, the selected option in the child list will be focused\nand keyboard navigation will be activated."
        },
        "attribute": "visible",
        "reflect": false,
        "defaultValue": "false"
      },
      "focusDelay": {
        "type": "number",
        "mutable": false,
        "complexType": {
          "original": "number",
          "resolved": "number",
          "references": {}
        },
        "required": false,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "The delay between when `visible = true` and when a selected item is\nfocused. This is used to allow time for the dropdown to transition open\nbefore focusing the selected item. Defaults to the Dropdown's\n`transitionDuration`."
        },
        "attribute": "focus-delay",
        "reflect": false,
        "defaultValue": "75"
      },
      "label": {
        "type": "string",
        "mutable": false,
        "complexType": {
          "original": "string",
          "resolved": "string",
          "references": {}
        },
        "required": false,
        "optional": true,
        "docs": {
          "tags": [],
          "text": "Optionally provide a label for the list which `aria-label` will get\nset to. This is used as an alternative to setting the `for` attribute on a\nlabel element when there is no visible label."
        },
        "attribute": "label",
        "reflect": false
      },
      "selectedValue": {
        "type": "string",
        "mutable": false,
        "complexType": {
          "original": "string",
          "resolved": "string",
          "references": {}
        },
        "required": false,
        "optional": true,
        "docs": {
          "tags": [],
          "text": "The option to be selected. This should match the desired option's\n`value` prop."
        },
        "attribute": "selected-value",
        "reflect": false
      }
    };
  }
  static get states() {
    return {
      "activeDescendantId": {}
    };
  }
  static get events() {
    return [{
        "method": "optselected",
        "name": "optselected",
        "bubbles": true,
        "cancelable": true,
        "composed": true,
        "docs": {
          "tags": [],
          "text": "Emits the value of an option when selected."
        },
        "complexType": {
          "original": "string",
          "resolved": "string",
          "references": {}
        }
      }, {
        "method": "close",
        "name": "close",
        "bubbles": true,
        "cancelable": true,
        "composed": true,
        "docs": {
          "tags": [],
          "text": "Emits when a close event has occurred. The consumer must handle\naccordingly (e.g. triggering leave transition, removing from DOM,\nre-focusing toggle, etc.)."
        },
        "complexType": {
          "original": "any",
          "resolved": "any",
          "references": {}
        }
      }];
  }
  static get elementRef() { return "hostEl"; }
  static get watchers() {
    return [{
        "propName": "visible",
        "methodName": "handleVisible"
      }];
  }
  static get listeners() {
    return [{
        "name": "keydown",
        "method": "handleKeyDown",
        "target": "window",
        "capture": false,
        "passive": false
      }, {
        "name": "optmouseenter",
        "method": "handleMouseFocus",
        "target": undefined,
        "capture": false,
        "passive": false
      }, {
        "name": "optselect",
        "method": "handleSelect",
        "target": undefined,
        "capture": false,
        "passive": false
      }];
  }
}
//# sourceMappingURL=bs-dropdown-option-list.js.map
