Merge "Send hover events to context buttons"
diff --git a/polygerrit-ui/app/api/diff.ts b/polygerrit-ui/app/api/diff.ts
index f6b3aa0..8e1ffef 100644
--- a/polygerrit-ui/app/api/diff.ts
+++ b/polygerrit-ui/app/api/diff.ts
@@ -302,6 +302,15 @@
   buttonType: ContextButtonType;
 }
 
+/**
+ * Details to be externally accessed when hovering context
+ * expansion buttons
+ */
+export declare interface DiffContextButtonHoveredDetail {
+  linesToExpand: number;
+  buttonType: ContextButtonType;
+}
+
 export declare type ImageDiffAction =
   | {type: 'overview-image-clicked'}
   | {type: 'overview-frame-dragged'}
diff --git a/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts b/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts
index 41a448e..e78945b 100644
--- a/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts
+++ b/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts
@@ -23,6 +23,8 @@
 import '@polymer/paper-item/paper-item';
 import '@polymer/paper-listbox/paper-listbox';
 import '@polymer/paper-tooltip/paper-tooltip.js';
+import {of, EMPTY, Subject} from 'rxjs';
+import {switchMap, delay, takeUntil} from 'rxjs/operators';
 
 import '../../shared/gr-button/gr-button';
 import {pluralize} from '../../../utils/string-util';
@@ -40,12 +42,19 @@
 
 import {
   ContextButtonType,
+  DiffContextButtonHoveredDetail,
   RenderPreferences,
   SyntaxBlock,
 } from '../../../api/diff';
 
 import {GrDiffGroup, hideInContextControl} from '../gr-diff/gr-diff-group';
 
+declare global {
+  interface HTMLElementEventMap {
+    'diff-context-button-hovered': CustomEvent<DiffContextButtonHoveredDetail>;
+  }
+}
+
 const PARTIAL_CONTEXT_AMOUNT = 10;
 
 /**
@@ -88,6 +97,14 @@
 
   @property({type: Boolean}) showBelow = false;
 
+  private expandButtonsHover = new Subject<{
+    eventType: 'enter' | 'leave';
+    buttonType: ContextButtonType;
+    linesToExpand: number;
+  }>();
+
+  private disconnected$ = new Subject();
+
   static styles = css`
     :host {
       display: flex;
@@ -193,6 +210,36 @@
     </custom-style>
   `;
 
+  connectedCallback() {
+    super.connectedCallback();
+    this.setupButtonHoverHandler();
+  }
+
+  disconnectedCallback() {
+    this.disconnected$.next();
+  }
+
+  setupButtonHoverHandler() {
+    this.expandButtonsHover
+      .pipe(
+        switchMap(e => {
+          if (e.eventType === 'leave') {
+            // cancel any previous delay
+            // for mouse enter
+            return EMPTY;
+          }
+          return of(e).pipe(delay(500));
+        }),
+        takeUntil(this.disconnected$)
+      )
+      .subscribe(({buttonType, linesToExpand}) => {
+        fire(this, 'diff-context-button-hovered', {
+          buttonType,
+          linesToExpand,
+        });
+      });
+  }
+
   private numLines() {
     const {leftStart, leftEnd} = this.contextRange();
     return leftEnd - leftStart + 1;
@@ -276,12 +323,22 @@
       groups
     );
 
+    const mouseHander = (eventType: 'enter' | 'leave') => {
+      this.expandButtonsHover.next({
+        eventType,
+        buttonType: type,
+        linesToExpand,
+      });
+    };
+
     const button = html` <gr-button
       class="${classes}"
       link="true"
       no-uppercase="true"
       aria-label="${ariaLabel}"
       @click="${expandHandler}"
+      @mouseenter="${() => mouseHander('enter')}"
+      @mouseleave="${() => mouseHander('leave')}"
     >
       <span class="showContext">${text}</span>
       ${tooltip}