import { Host, h } from '@stencil/core';
/** Options list body for the bs-dropdown component. This should always be used
 * as a child of that component. This component does have props and events, but
 * they are all passed down or handled by the parent.
 *
 * All <bs-dropdown-option> slotted children should be wrapped in a single
 * outer <div>.
 */
export class BsDropdownOptions {
  constructor() {
    /** Track which option is focused */
    this.focusedIndex = -1;
    /** Track selected option */
    this.selectedIndex = -1;
    // For handling animation. Would like to replace with a transition component
    // in the future
    this.animationDuration = 75;
    this.dropdownId = undefined;
    this.shown = undefined;
    this.label = undefined;
    this.position = undefined;
    this.variant = undefined;
    this.activeDescendantId = undefined;
  }
  /** Get the dropdown option elements. */
  get slotEls() {
    var _a, _b, _c;
    if (!this._slotEls) {
      const slotEl = (_a = this.hostEl.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('slot');
      // The multiple slot elements should be wrapped in an outer <div>
      const slotContainer = (_b = slotEl.assignedElements) === null || _b === void 0 ? void 0 : _b.call(slotEl)[0];
      this._slotEls = Array.from((_c = slotContainer === null || slotContainer === void 0 ? void 0 : slotContainer.children) !== null && _c !== void 0 ? _c : []);
    }
    return this._slotEls;
  }
  /** Helper for passing down props */
  passDownProps(el, index) {
    el === null || el === void 0 ? void 0 : el.setAttribute('shown', this.shown ? 'true' : 'false');
    el === null || el === void 0 ? void 0 : el.setAttribute('index', String(index));
    // Stencil Vue wrappers have an issue with camelCase props currently
    // So, sending both attributes
    el === null || el === void 0 ? void 0 : el.setAttribute('dropdown-id', this.dropdownId);
    el === null || el === void 0 ? void 0 : el.setAttribute('dropdownid', this.dropdownId);
  }
  /** Pass down initial props */
  componentDidLoad() {
    var _a;
    (_a = this.slotEls) === null || _a === void 0 ? void 0 : _a.forEach((el, index) => {
      if (el.tagName === 'BS-DROPDOWN-OPTION') {
        this.passDownProps(el, index);
        const selected = el === null || el === void 0 ? void 0 : el.getAttribute('selected');
        if (selected !== null) {
          this.selectedIndex = index;
        }
      }
    });
  }
  /** Pass down props on update */
  componentDidUpdate() {
    var _a;
    // Reset on shown/update to store a new list in case options have changed
    if (this.shown) {
      this._slotEls = undefined;
    }
    (_a = this.slotEls) === null || _a === void 0 ? void 0 : _a.forEach((el, index) => {
      if (el.tagName === 'BS-DROPDOWN-OPTION') {
        this.passDownProps(el, index);
        // Set the selected option
        if (this.selectedIndex === index) {
          el.setAttribute('selected', 'true');
        }
        else {
          el.removeAttribute('selected');
        }
      }
    });
  }
  /** Removes focus from the currently focused option, if applicable */
  removeCurrentFocus() {
    if (this.focusedIndex > -1) {
      this.slotEls[this.focusedIndex].removeAttribute('focused');
    }
  }
  /** Handles all applicable navigation key presses for dropdown accessibility */
  handleKeyDown(ev) {
    if (this.shown) {
      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;
        case 'Escape':
          this.closeOptions();
          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.slotEls.length - 1 :
          this.focusedIndex - 1;
        break;
      case 'down':
        newIndex = (this.focusedIndex + 1) % this.slotEls.length;
        break;
      case 'home':
        newIndex = 0;
        break;
      case 'end':
        newIndex = this.slotEls.length - 1;
        break;
    }
    this.removeCurrentFocus();
    this.slotEls[newIndex].setAttribute('focused', 'true');
    this.focusedIndex = newIndex;
  }
  /** Remove focus and emit close event */
  closeOptions() {
    this.removeCurrentFocus();
    this.close.emit();
  }
  /** Handles moving focus on mouse hover */
  handleMouseFocus(ev) {
    if (this.shown) {
      this.removeCurrentFocus();
      this.focusedIndex = ev.detail;
    }
  }
  /** Handles closing of the dropdown when clicking outside of it */
  handleClickOutside(ev) {
    if (this.shown) {
      if (!this.hostEl.contains(ev.target)) {
        this.closeOptions();
      }
    }
  }
  /** Handles selection by updating various internal states and emitting to parent */
  handleSelect(ev) {
    this.activeDescendantId = ev.detail.id;
    this.selectedIndex = ev.detail.index;
    this.dropdownchange.emit(ev.detail.value);
    this.closeOptions();
  }
  /** Clear out animation timers */
  clearTimers() {
    if (this.shownTimer) {
      clearTimeout(this.shownTimer);
      this.shownTimer = undefined;
    }
    if (this.animationTimer) {
      clearTimeout(this.animationTimer);
      this.animationTimer = undefined;
    }
  }
  /** Handles what happens on show/not showing of the dropdown */
  handleShown(newVal) {
    this.clearTimers();
    if (newVal) {
      // Keep track of toggle element when showing
      this.toggleEl = document.activeElement;
      // Make sure mounted before triggering open animations
      this.shownTimer = setTimeout(() => this.hostEl.setAttribute('data-shown', ''), 10);
      // Focus a selected item only after open animation plays
      // Added extra delay (* 2) to make sure everything is loaded in
      if (this.selectedIndex > -1) {
        this.animationTimer = setTimeout(() => {
          this.focusedIndex = this.selectedIndex;
          this.slotEls[this.focusedIndex].setAttribute('focused', 'true');
        }, this.animationDuration * 2);
      }
    }
    else {
      // Return focus to toggle element when closing
      this.removeCurrentFocus();
      if (this.toggleEl) {
        this.toggleEl.focus();
      }
      this.focusedIndex = -1;
      // Make sure to unmount after close animation
      this.shownTimer = setTimeout(() => this.hostEl.removeAttribute('data-shown'), this.animationDuration);
    }
  }
  render() {
    return (h(Host, { "data-position": this.position || undefined, "data-variant": this.variant || undefined }, h("ul", { id: this.dropdownId, role: "listbox", "aria-label": this.label || undefined, "aria-activedescendant": this.activeDescendantId || undefined, part: "options", ref: el => this.optionsEl = el }, h("slot", null))));
  }
  static get is() { return "bs-dropdown-options"; }
  static get encapsulation() { return "shadow"; }
  static get properties() {
    return {
      "dropdownId": {
        "type": "string",
        "mutable": false,
        "complexType": {
          "original": "string",
          "resolved": "string",
          "references": {}
        },
        "required": true,
        "optional": false,
        "docs": {
          "tags": [],
          "text": "Id of the dropdown. This should also be set on the toggle element's\n`aria-controls` attribute and the label's `for` attribute."
        },
        "attribute": "dropdown-id",
        "reflect": false
      },
      "shown": {
        "type": "boolean",
        "mutable": false,
        "complexType": {
          "original": "boolean",
          "resolved": "boolean",
          "references": {}
        },
        "required": false,
        "optional": true,
        "docs": {
          "tags": [],
          "text": "Whether or not the dropdown is currently shown. Be sure to also set\n`aria-expanded` equal to this value on the toggle element."
        },
        "attribute": "shown",
        "reflect": false
      },
      "label": {
        "type": "string",
        "mutable": false,
        "complexType": {
          "original": "string",
          "resolved": "string",
          "references": {}
        },
        "required": false,
        "optional": true,
        "docs": {
          "tags": [],
          "text": "Optionally provide a label for the dropdown 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
      },
      "position": {
        "type": "string",
        "mutable": false,
        "complexType": {
          "original": "'top'",
          "resolved": "\"top\"",
          "references": {}
        },
        "required": false,
        "optional": true,
        "docs": {
          "tags": [],
          "text": "Position of the options list. Currently only accepts \"top\"."
        },
        "attribute": "position",
        "reflect": false
      },
      "variant": {
        "type": "string",
        "mutable": false,
        "complexType": {
          "original": "'fit'",
          "resolved": "\"fit\"",
          "references": {}
        },
        "required": false,
        "optional": true,
        "docs": {
          "tags": [],
          "text": "Option to set the dropdown to fit content width rather than have a\nfixed width."
        },
        "attribute": "variant",
        "reflect": false
      }
    };
  }
  static get states() {
    return {
      "activeDescendantId": {}
    };
  }
  static get events() {
    return [{
        "method": "dropdownchange",
        "name": "dropdownchange",
        "bubbles": true,
        "cancelable": true,
        "composed": true,
        "docs": {
          "tags": [],
          "text": "Emits when an option is 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. updating show/hide)."
        },
        "complexType": {
          "original": "any",
          "resolved": "any",
          "references": {}
        }
      }];
  }
  static get elementRef() { return "hostEl"; }
  static get watchers() {
    return [{
        "propName": "shown",
        "methodName": "handleShown"
      }];
  }
  static get listeners() {
    return [{
        "name": "keydown",
        "method": "handleKeyDown",
        "target": "window",
        "capture": false,
        "passive": false
      }, {
        "name": "mousefocus",
        "method": "handleMouseFocus",
        "target": undefined,
        "capture": false,
        "passive": false
      }, {
        "name": "click",
        "method": "handleClickOutside",
        "target": "window",
        "capture": false,
        "passive": false
      }, {
        "name": "optionselect",
        "method": "handleSelect",
        "target": undefined,
        "capture": false,
        "passive": false
      }];
  }
}
//# sourceMappingURL=bs-dropdown-options.js.map
