<commit_before>import { forwardFocus, stateChanged } from './utilities.js';
import { getSuperProperty } from './workarounds.js';
import { merge } from './updates.js';
import * as symbols from './symbols.js';
import * as template from './template.js';
import KeyboardMixin from './KeyboardMixin.js';
import PopupSource from './PopupSource.js';
import SeamlessButton from './SeamlessButton.js';


const previousStateKey = Symbol('previousState');


const Base =
  KeyboardMixin(
    PopupSource
  );


/**
 * A text input paired with a popup that can be used as an alternative to typing
 * 
 * @inherits PopupSource
 * @mixes KeyboardMixin
 * @elementrole {'input'} input
 * @elementrole {'div'} source
 * @elementrole {SeamlessButton} toggleButton
 */
class ComboBox extends Base {
  
  // Forward any ARIA label to the input element.
  get ariaLabel() {
    return this.state.ariaLabel;
  }
  set ariaLabel(ariaLabel) {
    this.setState({ ariaLabel });
  }

  [symbols.beforeUpdate]() {
    if (super[symbols.beforeUpdate]) { super[symbols.beforeUpdate](); }
    if (this[symbols.renderedRoles].inputRole !== this.state.inputRole) {
      template.transmute(this.$.input, this.state.inputRole);

      this.$.input.addEventListener('blur', () => {
        // If we're open and lose focus, then close.
        if (this.opened) {
          this[symbols.raiseChangeEvents] = true;
          this.setState({
            focused: false
          });
          this.close();
          this[symbols.raiseChangeEvents] = false;
        }
      });
  
      this.$.input.addEventListener('focus', () => {
        this[symbols.raiseChangeEvents] = true;
        this.setState({
          focused: true
        });
        this[symbols.raiseChangeEvents] = false;
      });

      this.$.input.addEventListener('input', () => {
        this[symbols.raiseChangeEvents] = true;
        /** @type {any} */
        const cast = this.$.input;
        const value = cast.value;
        const changes = {
          value,
          selectText: false
        };
        if (this.closed && value > '') {
          // If user types while popup is closed, implicitly open it.
          changes.opened = true
        }
        this.setState(changes);
        this[symbols.raiseChangeEvents] = false;
      })

      this.$.input.addEventListener('keydown', () => {
        this[symbols.raiseChangeEvents] = true;
        this.setState({
          selectText: false
        });
        this[symbols.raiseChangeEvents] = false;
      })
  
      // If the user clicks on the input and the popup is closed, open it.
      this.$.input.addEventListener('mousedown', () => {
        this[symbols.raiseChangeEvents] = true;
        this.setState({
          selectText: false
        });
        if (this.closed) {
          this.open();
        }
        this[symbols.raiseChangeEvents] = false;
      });
  
      this[symbols.renderedRoles].inputRole = this.state.inputRole;
    }

    if (this[symbols.renderedRoles].toggleButtonRole !== this.state.toggleButtonRole) {
      if (this.$.toggleButton instanceof HTMLElement) {
        // Stop forwarding focus of current (old) toggle button.
        forwardFocus(this.$.toggleButton, null);
      }
      template.transmute(this.$.toggleButton, this.state.toggleButtonRole);
      if (this.$.toggleButton instanceof HTMLElement &&
          this.$.input instanceof HTMLElement) {
        // Forward focus for new toggle button.
        forwardFocus(this.$.toggleButton, this.$.input);
      }
      this.$.toggleButton.addEventListener('mousedown', () => {
        this[symbols.raiseChangeEvents] = true;
        this.toggle();
        this[symbols.raiseChangeEvents] = false;
      });
      this[symbols.renderedRoles].toggleButtonRole = this.state.toggleButtonRole;
    }
  }

  componentDidUpdate(previousState) {
    if (super.componentDidUpdate) { super.componentDidUpdate(previousState); }
    if (this.state.selectText) {
      // Select the text in the input.
      const value = this.value;
      if (value > "") {
        /** @type {any} */
        const cast = this.$.input;
        // Give the inner input a chance to render the value.
        setTimeout(() => {
          cast.selectionStart = 0;
          cast.selectionEnd = cast.value.length;
        });
      }
    }  
  }

  get defaultState() {
    return Object.assign({}, super.defaultState, {
      ariaLabel: '',
      horizontalAlign: 'left',
      inputRole: 'input',
      orientation: 'vertical',
      role: 'combobox',
      selectText: false,
      sourceRole: 'div',
      tabindex: null,
      toggleButtonRole: SeamlessButton,
      value: '',
    });
  }

  /**
   * The class, tag, or template used to create the input element.
   * 
   * @type {function|string|HTMLTemplateElement}
   * @default 'input'
   */
  get inputRole() {
    return this.state.inputRole;
  }<commit_msg>Double-check text selection is still on before selecting text.<commit_after>import { forwardFocus, stateChanged } from './utilities.js';
import { getSuperProperty } from './workarounds.js';
import { merge } from './updates.js';
import * as symbols from './symbols.js';
import * as template from './template.js';
import KeyboardMixin from './KeyboardMixin.js';
import PopupSource from './PopupSource.js';
import SeamlessButton from './SeamlessButton.js';


const previousStateKey = Symbol('previousState');


const Base =
  KeyboardMixin(
    PopupSource
  );


/**
 * A text input paired with a popup that can be used as an alternative to typing
 * 
 * @inherits PopupSource
 * @mixes KeyboardMixin
 * @elementrole {'input'} input
 * @elementrole {'div'} source
 * @elementrole {SeamlessButton} toggleButton
 */
class ComboBox extends Base {
  
  // Forward any ARIA label to the input element.
  get ariaLabel() {
    return this.state.ariaLabel;
  }
  set ariaLabel(ariaLabel) {
    this.setState({ ariaLabel });
  }

  [symbols.beforeUpdate]() {
    if (super[symbols.beforeUpdate]) { super[symbols.beforeUpdate](); }
    if (this[symbols.renderedRoles].inputRole !== this.state.inputRole) {
      template.transmute(this.$.input, this.state.inputRole);

      this.$.input.addEventListener('blur', () => {
        // If we're open and lose focus, then close.
        if (this.opened) {
          this[symbols.raiseChangeEvents] = true;
          this.setState({
            focused: false
          });
          this.close();
          this[symbols.raiseChangeEvents] = false;
        }
      });
  
      this.$.input.addEventListener('focus', () => {
        this[symbols.raiseChangeEvents] = true;
        this.setState({
          focused: true
        });
        this[symbols.raiseChangeEvents] = false;
      });

      this.$.input.addEventListener('input', () => {
        this[symbols.raiseChangeEvents] = true;
        /** @type {any} */
        const cast = this.$.input;
        const value = cast.value;
        const changes = {
          value,
          selectText: false
        };
        if (this.closed && value > '') {
          // If user types while popup is closed, implicitly open it.
          changes.opened = true
        }
        this.setState(changes);
        this[symbols.raiseChangeEvents] = false;
      })

      this.$.input.addEventListener('keydown', () => {
        this[symbols.raiseChangeEvents] = true;
        this.setState({
          selectText: false
        });
        this[symbols.raiseChangeEvents] = false;
      })
  
      // If the user clicks on the input and the popup is closed, open it.
      this.$.input.addEventListener('mousedown', () => {
        this[symbols.raiseChangeEvents] = true;
        this.setState({
          selectText: false
        });
        if (this.closed) {
          this.open();
        }
        this[symbols.raiseChangeEvents] = false;
      });
  
      this[symbols.renderedRoles].inputRole = this.state.inputRole;
    }

    if (this[symbols.renderedRoles].toggleButtonRole !== this.state.toggleButtonRole) {
      if (this.$.toggleButton instanceof HTMLElement) {
        // Stop forwarding focus of current (old) toggle button.
        forwardFocus(this.$.toggleButton, null);
      }
      template.transmute(this.$.toggleButton, this.state.toggleButtonRole);
      if (this.$.toggleButton instanceof HTMLElement &&
          this.$.input instanceof HTMLElement) {
        // Forward focus for new toggle button.
        forwardFocus(this.$.toggleButton, this.$.input);
      }
      this.$.toggleButton.addEventListener('mousedown', () => {
        this[symbols.raiseChangeEvents] = true;
        this.toggle();
        this[symbols.raiseChangeEvents] = false;
      });
      this[symbols.renderedRoles].toggleButtonRole = this.state.toggleButtonRole;
    }
  }

  componentDidUpdate(previousState) {
    if (super.componentDidUpdate) { super.componentDidUpdate(previousState); }
    if (this.state.selectText) {
      // Select the text in the input after giving the inner input a chance to render the value.
      setTimeout(() => {
        // Text selection might have been turned off in the interim;
        // double-check that we still want to select text.
        if (this.state.selectText) {
          /** @type {any} */
          const cast = this.$.input;
          const value = cast.value;
          if (value > '') {
            cast.selectionStart = 0;
            cast.selectionEnd = cast.value.length;
          }
        }
      });
    }  
  }

  get defaultState() {
    return Object.assign({}, super.defaultState, {
      ariaLabel: '',
      horizontalAlign: 'left',
      inputRole: 'input',
      orientation: 'vertical',
      role: 'combobox',
      selectText: false,
      sourceRole: 'div',
      tabindex: null,
      toggleButtonRole: SeamlessButton,
      value: '',
    });
  }

  /**
   * The class, tag, or template used to create the input element.
   * 
   * @type {function|string|HTMLTemplateElement}
   * @default 'input'
   */
  get inputRole() {
    return this.state.inputRole;
  }
{'fraction_non_alphanumeric': 0.12899361022364217, 'fraction_numerical': 0.00019968051118210862, 'mean_word_length': 2.354655056932351, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5238088', 'n_tokens_mistral': 2888, 'n_tokens_neox': 2839, 'n_words': 663}