{"version":3,"sources":["../src/elements/combo.ts","../src/styles/elements/combo.ts"],"sourcesContent":["import { html, LitElement } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { emitEvent } from '../utilities/events';\nimport { stylesBase } from '../styles/elements/combo';\nimport type KemetField from './field';\n\nexport interface InterfaceKemetSelectionEvent {\n  element: KemetCombo;\n  text: string;\n  id: string;\n}\n\n/**\n * @since 1.0.0\n * @status stable\n *\n * @tagname kemet-combo\n * @summary Allows the user to select a choice filtered through an Input. May only be used as a component of Field.\n *\n * @prop {string} slug - Uniquely identifies the component. Should match the slug used in a control.\n * @prop {array} options - An array of items listed for the combo box\n * @prop {boolean} show - Controls the display of the combo box\n *\n * @csspart combobox - The combo's container element.\n * @csspart listbox - The list element in the combo.\n *\n * @cssproperty --kemet-combo-width - The width of the combo box.\n * @cssproperty --kemet-combo-margin - The margins on the combo box.\n * @cssproperty --kemet-combo-max-height - The max height of the combo box.\n * @cssproperty --kemet-combo-border - The border of the combo box.\n * @cssproperty --kemet-combo-border-radius - The border radius of the combo box.\n * @cssproperty --kemet-combo-background-color - The background color of the combo box.\n * @cssproperty --kemet-combo-shadow - The shadow of the combo box.\n * @cssproperty --kemet-combo-hover-color - The hover item's text color.\n * @cssproperty --kemet-combo-hover-background-color - The hover item's background color.\n *\n * @event kemet-selection - Fires when a selection has been made\n *\n */\n\n@customElement('kemet-combo')\nexport default class KemetCombo extends LitElement {\n  static styles = [stylesBase];\n\n  @property({ type: String })\n  slug: string;\n\n  @property({ type: Array })\n  options: string[] = [];\n\n  @property({ type: Boolean, reflect: true })\n  show: boolean;\n\n  @state()\n  filteredOptions: string[];\n\n  @state()\n  field: KemetField;\n\n  @state()\n  input: HTMLInputElement;\n\n  firstUpdated() {\n    // standard properties\n    this.field = this.closest('kemet-field');\n    this.input = this.field.querySelector('[slot=\"input\"]');\n\n    // managed properties\n    this.slug = this.field.slug || 'slug';\n\n    // events listeners\n    this.input.addEventListener('kemet-input', this.handleInput.bind(this));\n    this.input.addEventListener('kemet-focus', this.handleFocus.bind(this));\n    this.input.addEventListener('keydown', event => this.handleInputKeyDown(event));\n  }\n\n  render() {\n    return html`\n      <div\n        role=\"combobox\"\n        part=\"combobox\"\n        aria-expanded=${this.show}\n        aria-controls=\"${this.slug}-listbox\"\n        aria-haspopup=\"listbox\"\n        id=\"${this.slug}-combobox\"\n        aria-label=\"${this.field?.label}\"\n      >\n        <ul role=\"listbox\" aria-labelledby=\"${this.slug}-label\" id=\"${this.slug}-listbox\" part=\"listbox\">\n          ${this.makeOptions()}\n        </ul>\n      </div>\n    `;\n  }\n\n  makeOptions() {\n    if (this.input) {\n      this.filteredOptions = this.options.filter(\n        option => option.toLowerCase().indexOf(this.input.value?.toLowerCase()) !== -1,\n      );\n\n      if (this.filteredOptions.length < 1) {\n        this.show = false;\n        return null;\n      }\n\n      return this.filteredOptions.map(\n        (option, index) => html`<li\n            id=\"${this.slug}-option-${index}\"\n            role=\"option\"\n            tabindex=\"0\"\n            aria-selected=\"false\"\n            @click=${(event: KeyboardEvent) => this.handleSelection(event)}\n            @keydown=${(event: KeyboardEvent) => this.handleOptionKeyDown(event)}\n          >\n            ${option}\n          </li>`,\n      );\n    }\n\n    return null;\n  }\n\n  handleInput(event: CustomEvent) {\n    this.makeOptions();\n    this.show = event.detail.value.length > 0;\n  }\n\n  handleFocus() {\n    this.makeOptions();\n    this.show = true;\n  }\n\n  handleSelection(event: KeyboardEvent) {\n    const target = event.target as HTMLElement;\n    this.input.value = target.innerText;\n    this.show = false;\n\n    emitEvent(this, 'kemet-selection', {\n      element: target,\n      text: target.innerText,\n      id: target.getAttribute('id'),\n    });\n  }\n\n  handleOptionKeyDown(event: KeyboardEvent) {\n    event.preventDefault();\n    const target = event.target as HTMLElement;\n\n    if (event.code === 'ArrowDown') {\n      const next = target.nextElementSibling as HTMLElement;\n\n      if (next) {\n        next.focus();\n      } else {\n        this.shadowRoot.querySelector('li').focus();\n      }\n    }\n\n    if (event.code === 'ArrowUp') {\n      const previous = target.previousElementSibling as HTMLElement;\n      const lastChild = this.shadowRoot.querySelector('li:last-child') as HTMLElement;\n\n      if (previous) {\n        previous.focus();\n      } else {\n        lastChild.focus();\n      }\n    }\n\n    if (event.code === 'Escape' || event.code === 'Tab') {\n      this.show = false;\n      this.input.focus();\n    }\n\n    if (event.code === 'Enter' || event.code === 'Space') {\n      this.handleSelection(event);\n    }\n  }\n\n  handleInputKeyDown(event: KeyboardEvent) {\n    if (event.code === 'ArrowDown') {\n      this.shadowRoot.querySelector('li').focus();\n    }\n\n    if (event.code === 'Escape') {\n      this.show = false;\n    }\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'kemet-combo': KemetCombo\n  }\n}\n","import { css } from 'lit';\n\nexport const stylesBase = css`\n  :host {\n    --kemet-combo-width: calc(100% - 2px);\n    --kemet-combo-margin: 1rem auto;\n    --kemet-combo-max-height: calc(5 * 3rem);\n    --kemet-combo-border: 1px solid rgb(var(--kemet-color-foreground));\n    --kemet-combo-border-radius: var(--kemet-border-radius-md);\n    --kemet-combo-background-color: rgb(var(--kemet-color-white-to-black));\n    --kemet-combo-shadow: var(--kemet-elevation-layer-5);\n    --kemet-combo-hover-color: rgb(var(--kemet-color-white));\n    --kemet-combo-hover-background-color: rgb(var(--kemet-color-primary));\n\n    opacity: 0;\n    width: var(--kemet-combo-width);\n    margin: var(--kemet-combo-margin);\n    pointer-events: none;\n    display: block;\n    position: relative;\n    transition: opacity 0.3s ease-in-out;\n  }\n\n  :host([show]) {\n    opacity: 1;\n    pointer-events: auto;\n  }\n\n  ul {\n    width: 100%;\n    max-height: var(--kemet-combo-max-height);\n    position: absolute;\n    z-index: 1;\n    list-style: none;\n    margin: 0;\n    padding: 0;\n    overflow-y: scroll;\n    border: var(--kemet-combo-border);\n    border-radius: var(--kemet-combo-border-radius);\n    background-color: var(--kemet-combo-background-color);\n    box-shadow: var(--kemet-combo-shadow);\n  }\n\n  li {\n    line-height: 3rem;\n    padding: 0 1.5rem;\n    cursor: pointer;\n  }\n\n  li:hover,\n  li:focus {\n    color: var(--kemet-combo-hover-color);\n    outline: none;\n    background-color: var(--kemet-combo-hover-background-color);\n  }\n`;\n"],"mappings":"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","names":[]}