chromium/ui/webui/resources/cr_components/history_clusters/search_query.ts

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {CrRippleMixin} from '//resources/cr_elements/cr_ripple/cr_ripple_mixin.js';
import {assert} from '//resources/js/assert.js';
import {CrLitElement} from '//resources/lit/v3_0/lit.rollup.js';

import {BrowserProxyImpl} from './browser_proxy.js';
import type {SearchQuery} from './history_cluster_types.mojom-webui.js';
import {RelatedSearchAction} from './history_clusters.mojom-webui.js';
import {MetricsProxyImpl} from './metrics_proxy.js';
import {getCss} from './search_query.css.js';
import {getHtml} from './search_query.html.js';

/**
 * @fileoverview This file provides a custom element displaying a search query.
 */

export interface SearchQueryElement {
  $: {
    searchQueryLink: HTMLElement,
  };
}

const SearchQueryElementBase = CrRippleMixin(CrLitElement);

export class SearchQueryElement extends SearchQueryElementBase {
  static get is() {
    return 'search-query';
  }

  static override get styles() {
    return getCss();
  }

  override render() {
    return getHtml.bind(this)();
  }

  static override get properties() {
    return {
      /**
       * The index of the search query pill.
       */
      index: {type: Number},

      /**
       * The search query to display.
       */
      searchQuery: {type: Object},
    };
  }

  //============================================================================
  // Properties
  //============================================================================

  index: number = -1;  // Initialized to an invalid value.
  searchQuery?: SearchQuery;

  //============================================================================
  // Event handlers
  //============================================================================

  override firstUpdated() {
    this.addEventListener('pointerdown', this.onPointerDown_.bind(this));
    this.addEventListener('pointercancel', this.onPointerCancel_.bind(this));
  }

  protected onAuxClick_() {
    MetricsProxyImpl.getInstance().recordRelatedSearchAction(
        RelatedSearchAction.kClicked, this.index);

    // Notify the parent <history-cluster> element of this event.
    this.fire('related-search-clicked');
  }

  protected onClick_(event: MouseEvent) {
    event.preventDefault();  // Prevent default browser action (navigation).

    // To record metrics.
    this.onAuxClick_();

    this.openUrl_(event);
  }

  protected onKeydown_(e: KeyboardEvent) {
    // Disable ripple on Space.
    this.noink = e.key === ' ';

    // To be consistent with <history-list>, only handle Enter, and not Space.
    if (e.key !== 'Enter') {
      return;
    }

    this.getRipple().uiDownAction();

    // To record metrics.
    this.onAuxClick_();

    this.openUrl_(e);

    setTimeout(() => this.getRipple().uiUpAction(), 100);
  }

  private onPointerDown_() {
    // Ensure ripple is visible.
    this.noink = false;
    this.ensureRipple();
  }

  private onPointerCancel_() {
    this.getRipple().clear();
  }

  private openUrl_(event: MouseEvent|KeyboardEvent) {
    assert(this.searchQuery);
    BrowserProxyImpl.getInstance().handler.openHistoryCluster(
        this.searchQuery.url, {
          middleButton: false,
          altKey: event.altKey,
          ctrlKey: event.ctrlKey,
          metaKey: event.metaKey,
          shiftKey: event.shiftKey,
        });
  }

  // Overridden from CrRippleMixin
  override createRipple() {
    this.rippleContainer = this.$.searchQueryLink;
    const ripple = super.createRipple();
    return ripple;
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'search-query': SearchQueryElement;
  }
}

customElements.define(SearchQueryElement.is, SearchQueryElement);