Merge "Submit requirements - do not render neutral gr-vote-chip"
diff --git a/java/com/google/gerrit/extensions/client/ProjectWatchInfo.java b/java/com/google/gerrit/extensions/client/ProjectWatchInfo.java
index 3c20ff7..8f5af76 100644
--- a/java/com/google/gerrit/extensions/client/ProjectWatchInfo.java
+++ b/java/com/google/gerrit/extensions/client/ProjectWatchInfo.java
@@ -54,6 +54,7 @@
   }
 
   @Override
+  @SuppressWarnings("OrphanedFormatString")
   public String toString() {
     StringBuilder b = new StringBuilder();
     b.append(project);
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts b/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts
index 4b1dba6..3c9f54c 100644
--- a/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts
+++ b/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts
@@ -14,19 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import '../../../styles/gr-font-styles';
-import '../../../styles/gr-hovercard-styles';
-import '../../../styles/shared-styles';
 import '../../shared/gr-button/gr-button';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {customElement, property} from '@polymer/decorators';
-import {HovercardBehaviorMixin} from '../../shared/gr-hovercard/gr-hovercard-behavior';
-import {htmlTemplate} from './gr-submit-requirement-hovercard_html';
+import '../../shared/gr-label-info/gr-label-info';
+import '../../shared/gr-limited-text/gr-limited-text';
+import {customElement, property} from 'lit/decorators';
 import {
   AccountInfo,
   SubmitRequirementExpressionInfo,
   SubmitRequirementResultInfo,
-  SubmitRequirementStatus,
 } from '../../../api/rest-api';
 import {
   extractAssociatedLabels,
@@ -34,16 +29,15 @@
 } from '../../../utils/label-util';
 import {ParsedChangeInfo} from '../../../types/types';
 import {Label} from '../gr-change-requirements/gr-change-requirements';
+import {css, html, LitElement} from 'lit';
+import {HovercardMixin} from '../../../mixins/hovercard-mixin/hovercard-mixin';
+import {fontStyles} from '../../../styles/gr-font-styles';
 
 // This avoids JSC_DYNAMIC_EXTENDS_WITHOUT_JSDOC closure compiler error.
-const base = HovercardBehaviorMixin(PolymerElement);
+const base = HovercardMixin(LitElement);
 
 @customElement('gr-submit-requirement-hovercard')
 export class GrHovercardRun extends base {
-  static get template() {
-    return htmlTemplate;
-  }
-
   @property({type: Object})
   requirement?: SubmitRequirementResultInfo;
 
@@ -59,16 +53,176 @@
   @property({type: Boolean})
   expanded = false;
 
-  @property({type: Array, computed: 'computeLabels(change, requirement)'})
-  _labels: Label[] = [];
+  static override get styles() {
+    return [
+      fontStyles,
+      base.styles || [],
+      css`
+        #container {
+          min-width: 356px;
+          max-width: 356px;
+          padding: var(--spacing-xl) 0 var(--spacing-m) 0;
+        }
+        section.label {
+          display: table-row;
+        }
+        .label-title {
+          min-width: 10em;
+          padding-top: var(--spacing-s);
+        }
+        .label-value {
+          padding-top: var(--spacing-s);
+        }
+        .label-title,
+        .label-value {
+          display: table-cell;
+          vertical-align: top;
+        }
+        .row {
+          display: flex;
+        }
+        .title {
+          color: var(--deemphasized-text-color);
+          margin-right: var(--spacing-m);
+        }
+        div.section {
+          margin: 0 var(--spacing-xl) var(--spacing-m) var(--spacing-xl);
+          display: flex;
+          align-items: center;
+        }
+        div.sectionIcon {
+          flex: 0 0 30px;
+        }
+        div.sectionIcon iron-icon {
+          position: relative;
+          width: 20px;
+          height: 20px;
+        }
+        .condition {
+          background-color: var(--gray-background);
+          padding: var(--spacing-m);
+          flex-grow: 1;
+        }
+        .expression {
+          color: var(--gray-foreground);
+        }
+        iron-icon.check {
+          color: var(--success-foreground);
+        }
+        iron-icon.close {
+          color: var(--warning-foreground);
+        }
+        .showConditions iron-icon {
+          color: inherit;
+        }
+        div.showConditions {
+          border-top: 1px solid var(--border-color);
+          margin-top: var(--spacing-m);
+          padding: var(--spacing-m) var(--spacing-xl) 0;
+        }
+      `,
+    ];
+  }
 
-  computeLabels(
-    change?: ParsedChangeInfo,
-    requirement?: SubmitRequirementResultInfo
-  ) {
-    if (!requirement) return [];
-    const requirementLabels = extractAssociatedLabels(requirement);
-    const labels = change?.labels ?? {};
+  override render() {
+    if (!this.requirement) return;
+    const icon = iconForStatus(this.requirement.status);
+    return html` <div id="container" role="tooltip" tabindex="-1">
+      <div class="section">
+        <div class="sectionIcon">
+          <iron-icon class="${icon}" icon="gr-icons:${icon}"></iron-icon>
+        </div>
+        <div class="sectionContent">
+          <h3 class="name heading-3">
+            <span>${this.requirement.name}</span>
+          </h3>
+        </div>
+      </div>
+      <div class="section">
+        <div class="sectionIcon">
+          <iron-icon class="small" icon="gr-icons:info-outline"></iron-icon>
+        </div>
+        <div class="sectionContent">
+          <div class="row">
+            <div class="title">Status</div>
+            <div>${this.requirement.status}</div>
+          </div>
+        </div>
+      </div>
+      ${this.renderLabelSection()} ${this.renderConditionSection()}
+    </div>`;
+  }
+
+  private renderLabelSection() {
+    const labels = this.computeLabels();
+    return html` <div class="section">
+      ${labels.map(l => this.renderLabel(l))}
+    </div>`;
+  }
+
+  private renderLabel(label: Label) {
+    return html`
+      <section class="label">
+        <div class="label-title">
+          <gr-limited-text
+            class="name"
+            limit="25"
+            text="${label.labelName}"
+          ></gr-limited-text>
+        </div>
+        <div class="label-value">
+          <gr-label-info
+            .change=${this.change}
+            .account=${this.account}
+            .mutable=${this.mutable}
+            .label="${label.labelName}"
+            .labelInfo="${label.labelInfo}"
+          ></gr-label-info>
+        </div>
+      </section>
+    `;
+  }
+
+  private renderConditionSection() {
+    if (!this.expanded) {
+      return html` <div class="showConditions">
+        <gr-button
+          link=""
+          class="showConditions"
+          @click="${(_: MouseEvent) => this.handleShowConditions()}"
+        >
+          View condition
+          <iron-icon icon="gr-icons:expand-more"></iron-icon
+        ></gr-button>
+      </div>`;
+    } else {
+      return html`
+        <div class="section">
+          <div class="sectionIcon">
+            <iron-icon icon="gr-icons:description"></iron-icon>
+          </div>
+          <div class="sectionContent">${this.requirement?.description}</div>
+        </div>
+        ${this.renderCondition(
+          'Blocking condition',
+          this.requirement?.submittability_expression_result
+        )}
+        ${this.renderCondition(
+          'Application condition',
+          this.requirement?.applicability_expression_result
+        )}
+        ${this.renderCondition(
+          'Override condition',
+          this.requirement?.override_expression_result
+        )}
+      `;
+    }
+  }
+
+  private computeLabels() {
+    if (!this.requirement) return [];
+    const requirementLabels = extractAssociatedLabels(this.requirement);
+    const labels = this.change?.labels ?? {};
 
     const allLabels: Label[] = [];
 
@@ -85,17 +239,23 @@
     return allLabels;
   }
 
-  computeIcon(status: SubmitRequirementStatus) {
-    return iconForStatus(status);
-  }
-
-  renderCondition(expression?: SubmitRequirementExpressionInfo) {
+  private renderCondition(
+    name: string,
+    expression?: SubmitRequirementExpressionInfo
+  ) {
     if (!expression) return '';
-
-    return expression.expression;
+    return html`
+      <div class="section">
+        <div class="sectionIcon"></div>
+        <div class="sectionContent condition">
+          ${name}:<br />
+          <span class="expression"> ${expression.expression} </span>
+        </div>
+      </div>
+    `;
   }
 
-  _handleShowConditions() {
+  private handleShowConditions() {
     this.expanded = true;
   }
 }
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard_html.ts b/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard_html.ts
deleted file mode 100644
index 5023895..0000000
--- a/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard_html.ts
+++ /dev/null
@@ -1,192 +0,0 @@
-/**
- * @license
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
-  <style include="gr-font-styles">
-    /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
-  </style>
-  <style include="shared-styles">
-    /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
-  </style>
-  <style include="gr-hovercard-styles">
-    #container {
-      min-width: 356px;
-      max-width: 356px;
-      padding: var(--spacing-xl) 0 var(--spacing-m) 0;
-    }
-    section.label {
-      display: table-row;
-    }
-    .label-title {
-      min-width: 10em;
-      padding-top: var(--spacing-s);
-    }
-    .label-value {
-      padding-top: var(--spacing-s);
-    }
-    .label-title,
-    .label-value {
-      display: table-cell;
-      vertical-align: top;
-    }
-    .row {
-      display: flex;
-    }
-    .title {
-      color: var(--deemphasized-text-color);
-      margin-right: var(--spacing-m);
-    }
-    div.section {
-      margin: 0 var(--spacing-xl) var(--spacing-m) var(--spacing-xl);
-      display: flex;
-      align-items: center;
-    }
-    div.sectionIcon {
-      flex: 0 0 30px;
-    }
-    div.sectionIcon iron-icon {
-      position: relative;
-      width: 20px;
-      height: 20px;
-    }
-    .condition {
-      background-color: var(--gray-background);
-      padding: var(--spacing-m);
-      flex-grow: 1;
-    }
-    .expression {
-      color: var(--gray-foreground);
-    }
-    iron-icon.check {
-      color: var(--success-foreground);
-    }
-    iron-icon.close {
-      color: var(--warning-foreground);
-    }
-    .showConditions iron-icon {
-      color: inherit;
-    }
-    div.showConditions {
-      border-top: 1px solid var(--border-color);
-      margin-top: var(--spacing-m);
-      padding: var(--spacing-m) var(--spacing-xl) 0;
-    }
-  </style>
-  <div id="container" role="tooltip" tabindex="-1">
-    <div class="section">
-      <div class="sectionIcon">
-        <iron-icon
-          class$="[[computeIcon(requirement.status)]]"
-          icon="gr-icons:[[computeIcon(requirement.status)]]"
-        ></iron-icon>
-      </div>
-      <div class="sectionContent">
-        <h3 class="name heading-3">
-          <span>[[requirement.name]]</span>
-        </h3>
-      </div>
-    </div>
-    <div class="section">
-      <div class="sectionIcon">
-        <iron-icon class="small" icon="gr-icons:info-outline"></iron-icon>
-      </div>
-      <div class="sectionContent">
-        <div class="row">
-          <div class="title">Status</div>
-          <div>[[requirement.status]]</div>
-        </div>
-      </div>
-    </div>
-    <div class="section">
-      <template is="dom-repeat" items="[[_labels]]">
-        <section class="label">
-          <div class="label-title">
-            <gr-limited-text
-              class="name"
-              limit="25"
-              text="[[item.labelName]]"
-            ></gr-limited-text>
-          </div>
-          <div class="label-value">
-            <gr-label-info
-              change="{{change}}"
-              account="[[account]]"
-              mutable="[[mutable]]"
-              label="[[item.labelName]]"
-              label-info="[[item.labelInfo]]"
-            ></gr-label-info>
-          </div>
-        </section>
-      </template>
-    </div>
-    <template is="dom-if" if="[[!expanded]]">
-      <div class="showConditions">
-        <gr-button
-          link=""
-          class="showConditions"
-          on-click="_handleShowConditions"
-        >
-          View condition
-          <iron-icon icon="gr-icons:expand-more"></iron-icon
-        ></gr-button>
-      </div>
-    </template>
-    <template is="dom-if" if="[[expanded]]">
-      <div class="section">
-        <div class="sectionIcon">
-          <iron-icon icon="gr-icons:description"></iron-icon>
-        </div>
-        <div class="sectionContent">[[requirement.description]]</div>
-      </div>
-      <div class="section">
-        <div class="sectionIcon"></div>
-        <div class="sectionContent condition">
-          Blocking condition:<br />
-          <span class="expression">
-            [[renderCondition(requirement.submittability_expression_result)]]
-          </span>
-        </div>
-      </div>
-      <template
-        is="dom-if"
-        if="[[requirement.applicability_expression_result]]"
-      >
-        <div class="section">
-          <div class="sectionIcon"></div>
-          <div class="sectionContent condition">
-            Application condition:<br />
-            <span class="expression">
-              [[renderCondition(requirement.applicability_expression_result)]]
-            </span>
-          </div>
-        </div>
-      </template>
-      <template is="dom-if" if="[[requirement.override_expression_result]]">
-        <div class="section">
-          <div class="sectionIcon"></div>
-          <div class="sectionContent condition">
-            Override condition:<br />
-            <span class="expression">
-              [[renderCondition(requirement.override_expression_result)]]
-            </span>
-          </div>
-        </div>
-      </template>
-    </template>
-  </div>
-`;
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
index c2b54a6..004d594 100644
--- a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
+++ b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
@@ -81,20 +81,6 @@
         iron-icon.close {
           color: var(--warning-foreground);
         }
-        .testing {
-          margin-top: var(--spacing-xxl);
-          padding-left: var(--metadata-horizontal-padding);
-          color: var(--deemphasized-text-color);
-        }
-        .testing gr-button {
-          min-width: 25px;
-        }
-        .testing * {
-          visibility: hidden;
-        }
-        .testing:hover * {
-          visibility: visible;
-        }
         .requirements,
         section.trigger-votes {
           margin-left: var(--spacing-l);
@@ -193,9 +179,7 @@
           ></gr-submit-requirement-hovercard>
         `
       )}
-      ${this.renderTriggerVotes(
-        submit_requirements
-      )}${this.renderFakeControls()}`;
+      ${this.renderTriggerVotes(submit_requirements)}`;
   }
 
   renderStatus(status: SubmitRequirementStatus) {
@@ -291,49 +275,6 @@
         )}
       </section>`;
   }
-
-  renderFakeControls() {
-    return html`
-      <div class="testing">
-        <div>Toggle fake data:</div>
-        <gr-button link @click="${() => this.renderFakeSubmitRequirements()}"
-          >G</gr-button
-        >
-      </div>
-    `;
-  }
-
-  renderFakeSubmitRequirements() {
-    if (!this.change) return;
-    this.change = {
-      ...this.change,
-      submit_requirements: [
-        {
-          name: 'Code-Review',
-          status: SubmitRequirementStatus.SATISFIED,
-          description:
-            "At least one maximum vote for label 'Code-Review' is required",
-          submittability_expression_result: {
-            expression: 'label:Code-Review=MAX -label:Code-Review=MIN',
-            fulfilled: true,
-            passing_atoms: [],
-            failing_atoms: [],
-          },
-        },
-        {
-          name: 'Verified',
-          status: SubmitRequirementStatus.UNSATISFIED,
-          description: 'CI build and tests results are verified',
-          submittability_expression_result: {
-            expression: 'label:Verified=MAX -label:Verified=MIN',
-            fulfilled: false,
-            passing_atoms: [],
-            failing_atoms: [],
-          },
-        },
-      ],
-    };
-  }
 }
 
 @customElement('gr-trigger-vote')
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts
deleted file mode 100644
index 7edb728a..0000000
--- a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard-behavior.ts
+++ /dev/null
@@ -1,487 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import '../../../styles/shared-styles';
-import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
-import {getRootElement} from '../../../scripts/rootElement';
-import {Constructor} from '../../../utils/common-util';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {observe, property} from '@polymer/decorators';
-import {
-  pushScrollLock,
-  removeScrollLock,
-} from '@polymer/iron-overlay-behavior/iron-scroll-manager';
-import {ShowAlertEventDetail} from '../../../types/events';
-import {debounce, DelayedTask} from '../../../utils/async-util';
-interface ReloadEventDetail {
-  clearPatchset?: boolean;
-}
-
-const HOVER_CLASS = 'hovered';
-const HIDE_CLASS = 'hide';
-
-/**
- * ID for the container element.
- */
-const containerId = 'gr-hovercard-container';
-
-function getHovercardContainer(
-  options: {createIfNotExists: boolean} = {createIfNotExists: false}
-): HTMLElement | null {
-  let container = getRootElement().querySelector<HTMLElement>(
-    `#${containerId}`
-  );
-  if (!container && options.createIfNotExists) {
-    // If it does not exist, create and initialize the hovercard container.
-    container = document.createElement('div');
-    container.setAttribute('id', containerId);
-    getRootElement().appendChild(container);
-  }
-  return container;
-}
-
-/**
- * How long should we wait before showing the hovercard when the user hovers
- * over the element?
- */
-const SHOW_DELAY_MS = 550;
-
-/**
- * How long should we wait before hiding the hovercard when the user moves from
- * target to the hovercard.
- *
- * Note: this should be lower than SHOW_DELAY_MS to avoid flickering.
- */
-const HIDE_DELAY_MS = 500;
-
-/**
- * The mixin for gr-hovercard-behavior.
- *
- * @example
- *
- * class YourComponent extends hovercardBehaviorMixin(
- *  PolymerElement
- *
- * @see gr-hovercard.ts
- *
- * // following annotations are required for polylint
- * @polymer
- * @mixinFunction
- */
-export const HovercardBehaviorMixin = <T extends Constructor<PolymerElement>>(
-  superClass: T
-) => {
-  /**
-   * @polymer
-   * @mixinClass
-   */
-  class Mixin extends superClass {
-    @property({type: Object})
-    _target: HTMLElement | null = null;
-
-    // Determines whether or not the hovercard is visible.
-    @property({type: Boolean})
-    _isShowing = false;
-
-    // The `id` of the element that the hovercard is anchored to.
-    @property({type: String})
-    for?: string;
-
-    /**
-     * The spacing between the top of the hovercard and the element it is
-     * anchored to.
-     */
-    @property({type: Number})
-    offset = 14;
-
-    /**
-     * Positions the hovercard to the top, right, bottom, left, bottom-left,
-     * bottom-right, top-left, or top-right of its content.
-     */
-    @property({type: String})
-    position = 'right';
-
-    @property({type: Object})
-    container: HTMLElement | null = null;
-
-    private hideTask?: DelayedTask;
-
-    private showTask?: DelayedTask;
-
-    private isScheduledToShow?: boolean;
-
-    private isScheduledToHide?: boolean;
-
-    override connectedCallback() {
-      super.connectedCallback();
-      if (!this._target) {
-        this._target = this.target;
-        this.addTargetEventListeners();
-      }
-
-      // show the hovercard if mouse moves to hovercard
-      // this will cancel pending hide as well
-      this.addEventListener('mouseenter', this.show);
-      this.addEventListener('mouseenter', this.lock);
-      // when leave hovercard, hide it immediately
-      this.addEventListener('mouseleave', this.hide);
-      this.addEventListener('mouseleave', this.unlock);
-    }
-
-    override disconnectedCallback() {
-      this.cancelShowTask();
-      this.cancelHideTask();
-      this.unlock();
-      super.disconnectedCallback();
-    }
-
-    addTargetEventListeners() {
-      this._target?.addEventListener('mouseenter', this.debounceShow);
-      this._target?.addEventListener('focus', this.debounceShow);
-      this._target?.addEventListener('mouseleave', this.debounceHide);
-      this._target?.addEventListener('blur', this.debounceHide);
-      this._target?.addEventListener('click', this.hide);
-    }
-
-    removeTargetEventListeners() {
-      this._target?.removeEventListener('mouseenter', this.debounceShow);
-      this._target?.removeEventListener('focus', this.debounceShow);
-      this._target?.removeEventListener('mouseleave', this.debounceHide);
-      this._target?.removeEventListener('blur', this.debounceHide);
-      this._target?.removeEventListener('click', this.hide);
-    }
-
-    override ready() {
-      super.ready();
-      // First, check to see if the container has already been created.
-      this.container = getHovercardContainer({createIfNotExists: true});
-    }
-
-    readonly debounceHide = () => {
-      this.cancelShowTask();
-      if (!this._isShowing || this.isScheduledToHide) return;
-      this.isScheduledToHide = true;
-      this.hideTask = debounce(
-        this.hideTask,
-        () => {
-          // This happens when hide immediately through click or mouse leave
-          // on the hovercard
-          if (!this.isScheduledToHide) return;
-          this.hide();
-        },
-        HIDE_DELAY_MS
-      );
-    };
-
-    cancelHideTask() {
-      if (!this.hideTask) return;
-      this.hideTask.cancel();
-      this.isScheduledToHide = false;
-      this.hideTask = undefined;
-    }
-
-    /**
-     * Hovercard elements are created outside of <gr-app>, so if you want to fire
-     * events, then you probably want to do that through the target element.
-     */
-
-    dispatchEventThroughTarget(eventName: string): void;
-
-    dispatchEventThroughTarget(
-      eventName: 'show-alert',
-      detail: ShowAlertEventDetail
-    ): void;
-
-    dispatchEventThroughTarget(
-      eventName: 'reload',
-      detail: ReloadEventDetail
-    ): void;
-
-    dispatchEventThroughTarget(eventName: string, detail?: unknown) {
-      if (!detail) detail = {};
-      if (this._target)
-        this._target.dispatchEvent(
-          new CustomEvent(eventName, {
-            detail,
-            bubbles: true,
-            composed: true,
-          })
-        );
-    }
-
-    /**
-     * Returns the target element that the hovercard is anchored to (the `id` of
-     * the `for` property).
-     */
-    get target(): HTMLElement {
-      const parentNode = this.parentNode;
-      // If the parentNode is a document fragment, then we need to use the host.
-      const ownerRoot = this.getRootNode() as ShadowRoot;
-      let target;
-      if (this.for) {
-        target = ownerRoot.querySelector('#' + this.for);
-      } else {
-        target =
-          !parentNode || parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE
-            ? ownerRoot.host
-            : parentNode;
-      }
-      return target as HTMLElement;
-    }
-
-    /**
-     * unlock scroll, this will resume the scroll outside of the hovercard.
-     */
-    readonly unlock = () => {
-      removeScrollLock(this);
-    };
-
-    /**
-     * Hides/closes the hovercard. This occurs when the user triggers the
-     * `mouseleave` event on the hovercard's `target` element (as long as the
-     * user is not hovering over the hovercard).
-     *
-     */
-    readonly hide = (e?: MouseEvent) => {
-      this.cancelHideTask();
-      this.cancelShowTask();
-      if (!this._isShowing) {
-        return;
-      }
-
-      // If the user is now hovering over the hovercard or the user is returning
-      // from the hovercard but now hovering over the target (to stop an annoying
-      // flicker effect), just return.
-      if (e) {
-        if (
-          e.relatedTarget === this ||
-          (e.target === this && e.relatedTarget === this._target)
-        ) {
-          return;
-        }
-      }
-
-      // Mark that the hovercard is not visible and do not allow focusing
-      this._isShowing = false;
-
-      // Clear styles in preparation for the next time we need to show the card
-      this.classList.remove(HOVER_CLASS);
-
-      // Reset and remove the hovercard from the DOM
-      this.style.cssText = '';
-      this.$['container'].setAttribute('tabindex', '-1');
-
-      // Remove the hovercard from the container, given that it is still a child
-      // of the container.
-      if (this.container?.contains(this)) {
-        this.container.removeChild(this);
-      }
-    };
-
-    /**
-     * Shows/opens the hovercard with a fixed delay.
-     */
-    readonly debounceShow = () => {
-      this.debounceShowBy(SHOW_DELAY_MS);
-    };
-
-    /**
-     * Shows/opens the hovercard with the given delay.
-     */
-    debounceShowBy(delayMs: number) {
-      this.cancelHideTask();
-      if (this._isShowing || this.isScheduledToShow) return;
-      this.isScheduledToShow = true;
-      this.showTask = debounce(
-        this.showTask,
-        () => {
-          // This happens when the mouse leaves the target before the delay is over.
-          if (!this.isScheduledToShow) return;
-          this.show();
-        },
-        delayMs
-      );
-    }
-
-    cancelShowTask() {
-      if (!this.showTask) return;
-      this.showTask.cancel();
-      this.isScheduledToShow = false;
-      this.showTask = undefined;
-    }
-
-    /**
-     * Lock background scroll but enable scroll inside of current hovercard.
-     */
-    readonly lock = () => {
-      pushScrollLock(this);
-    };
-
-    /**
-     * Shows/opens the hovercard. This occurs when the user triggers the
-     * `mousenter` event on the hovercard's `target` element.
-     */
-    readonly show = async () => {
-      this.cancelHideTask();
-      this.cancelShowTask();
-      if (this._isShowing || !this.container) {
-        return;
-      }
-
-      // Mark that the hovercard is now visible
-      this._isShowing = true;
-      this.setAttribute('tabindex', '0');
-
-      // Add it to the DOM and calculate its position
-      this.container.appendChild(this);
-      // We temporarily hide the hovercard until we have found the correct
-      // position for it.
-      this.classList.add(HIDE_CLASS);
-      this.classList.add(HOVER_CLASS);
-      // Make sure that the hovercard actually rendered and all dom-if
-      // statements processed, so that we can measure the (invisible)
-      // hovercard properly in updatePosition().
-      await flush();
-      this.updatePosition();
-      this.classList.remove(HIDE_CLASS);
-    };
-
-    updatePosition() {
-      const positionsToTry = new Set([
-        this.position,
-        'right',
-        'bottom-right',
-        'top-right',
-        'bottom',
-        'top',
-        'bottom-left',
-        'top-left',
-        'left',
-      ]);
-      for (const position of positionsToTry) {
-        this.updatePositionTo(position);
-        if (this._isInsideViewport()) return;
-      }
-      console.warn('Could not find a visible position for the hovercard.');
-    }
-
-    _isInsideViewport() {
-      const thisRect = this.getBoundingClientRect();
-      if (thisRect.top < 0) return false;
-      if (thisRect.left < 0) return false;
-      const docuRect = document.documentElement.getBoundingClientRect();
-      if (thisRect.bottom > docuRect.height) return false;
-      if (thisRect.right > docuRect.width) return false;
-      return true;
-    }
-
-    /**
-     * Updates the hovercard's position based the current position of the `target`
-     * element.
-     *
-     * The hovercard is supposed to stay open if the user hovers over it.
-     * To keep it open when the user moves away from the target, the bounding
-     * rects of the target and hovercard must touch or overlap.
-     *
-     * NOTE: You do not need to directly call this method unless you need to
-     * update the position of the tooltip while it is already visible (the
-     * target element has moved and the tooltip is still open).
-     */
-    updatePositionTo(position: string) {
-      if (!this._target) {
-        return;
-      }
-
-      // Make sure that thisRect will not get any paddings and such included
-      // in the width and height of the bounding client rect.
-      this.style.cssText = '';
-
-      const docuRect = document.documentElement.getBoundingClientRect();
-      const targetRect = this._target.getBoundingClientRect();
-      const thisRect = this.getBoundingClientRect();
-
-      const targetLeft = targetRect.left - docuRect.left;
-      const targetTop = targetRect.top - docuRect.top;
-
-      let hovercardLeft;
-      let hovercardTop;
-
-      switch (position) {
-        case 'top':
-          hovercardLeft = targetLeft + (targetRect.width - thisRect.width) / 2;
-          hovercardTop = targetTop - thisRect.height - this.offset;
-          break;
-        case 'bottom':
-          hovercardLeft = targetLeft + (targetRect.width - thisRect.width) / 2;
-          hovercardTop = targetTop + targetRect.height + this.offset;
-          break;
-        case 'left':
-          hovercardLeft = targetLeft - thisRect.width - this.offset;
-          hovercardTop = targetTop + (targetRect.height - thisRect.height) / 2;
-          break;
-        case 'right':
-          hovercardLeft = targetLeft + targetRect.width + this.offset;
-          hovercardTop = targetTop + (targetRect.height - thisRect.height) / 2;
-          break;
-        case 'bottom-right':
-          hovercardLeft = targetLeft + targetRect.width + this.offset;
-          hovercardTop = targetTop;
-          break;
-        case 'bottom-left':
-          hovercardLeft = targetLeft - thisRect.width - this.offset;
-          hovercardTop = targetTop;
-          break;
-        case 'top-left':
-          hovercardLeft = targetLeft - thisRect.width - this.offset;
-          hovercardTop = targetTop + targetRect.height - thisRect.height;
-          break;
-        case 'top-right':
-          hovercardLeft = targetLeft + targetRect.width + this.offset;
-          hovercardTop = targetTop + targetRect.height - thisRect.height;
-          break;
-      }
-
-      this.style.left = `${hovercardLeft}px`;
-      this.style.top = `${hovercardTop}px`;
-    }
-
-    /**
-     * Responds to a change in the `for` value and gets the updated `target`
-     * element for the hovercard.
-     */
-    @observe('for')
-    _forChanged() {
-      this.removeTargetEventListeners();
-      this._target = this.target;
-      this.addTargetEventListeners();
-    }
-  }
-
-  return Mixin as T & Constructor<GrHovercardBehaviorInterface>;
-};
-
-export interface GrHovercardBehaviorInterface {
-  _target: HTMLElement | null;
-  _isShowing: boolean;
-  ready(): void;
-  dispatchEventThroughTarget(eventName: string, detail?: unknown): void;
-  hide(e?: MouseEvent): void;
-  debounceShow(): void;
-  debounceShowBy(delayMs: number): void;
-  cancelShowTask(): void;
-  show(): void;
-  updatePosition(): void;
-}