| /** | 
 |  * @license | 
 |  * Copyright 2022 Google LLC | 
 |  * SPDX-License-Identifier: Apache-2.0 | 
 |  */ | 
 | import {html, LitElement, nothing, PropertyValues} from 'lit'; | 
 | import {property, state} from 'lit/decorators.js'; | 
 | import {ifDefined} from 'lit/directives/if-defined.js'; | 
 | import {createRef, Ref, ref} from 'lit/directives/ref.js'; | 
 | import { | 
 |   DiffResponsiveMode, | 
 |   Side, | 
 |   LineNumber, | 
 |   DiffLayer, | 
 |   GrDiffLineType, | 
 |   LOST, | 
 |   FILE, | 
 | } from '../../../api/diff'; | 
 | import {BlameInfo} from '../../../types/common'; | 
 | import {assertIsDefined} from '../../../utils/common-util'; | 
 | import {fire} from '../../../utils/event-util'; | 
 | import {getBaseUrl} from '../../../utils/url-util'; | 
 | import {otherSide} from '../../../utils/diff-util'; | 
 | import './gr-diff-text'; | 
 | import { | 
 |   diffClasses, | 
 |   GrDiffCommentThread, | 
 |   isLongCommentRange, | 
 |   isResponsive, | 
 | } from '../gr-diff/gr-diff-utils'; | 
 | import {resolve} from '../../../models/dependency'; | 
 | import { | 
 |   ColumnsToShow, | 
 |   diffModelToken, | 
 |   NO_COLUMNS, | 
 | } from '../gr-diff-model/gr-diff-model'; | 
 | import {when} from 'lit/directives/when.js'; | 
 | import {isDefined} from '../../../types/types'; | 
 | import {BehaviorSubject, combineLatest} from 'rxjs'; | 
 | import '../../../elements/shared/gr-hovercard/gr-hovercard'; | 
 | import {GrDiffLine} from '../gr-diff/gr-diff-line'; | 
 | import {distinctUntilChanged, map} from 'rxjs/operators'; | 
 | import {deepEqual} from '../../../utils/deep-util'; | 
 | import {subscribe} from '../../../elements/lit/subscription-controller'; | 
 |  | 
 | export class GrDiffRow extends LitElement { | 
 |   contentLeftRef: Ref<LitElement> = createRef(); | 
 |  | 
 |   contentRightRef: Ref<LitElement> = createRef(); | 
 |  | 
 |   contentCellLeftRef: Ref<HTMLTableCellElement> = createRef(); | 
 |  | 
 |   contentCellRightRef: Ref<HTMLTableCellElement> = createRef(); | 
 |  | 
 |   lineNumberLeftRef: Ref<HTMLTableCellElement> = createRef(); | 
 |  | 
 |   lineNumberRightRef: Ref<HTMLTableCellElement> = createRef(); | 
 |  | 
 |   blameCellRef: Ref<HTMLTableCellElement> = createRef(); | 
 |  | 
 |   tableRowRef: Ref<HTMLTableRowElement> = createRef(); | 
 |  | 
 |   @property({type: Object}) | 
 |   left?: GrDiffLine; | 
 |  | 
 |   private left$ = new BehaviorSubject<GrDiffLine | undefined>(undefined); | 
 |  | 
 |   @property({type: Object}) | 
 |   right?: GrDiffLine; | 
 |  | 
 |   private right$ = new BehaviorSubject<GrDiffLine | undefined>(undefined); | 
 |  | 
 |   @property({type: Object}) | 
 |   blameInfo?: BlameInfo; | 
 |  | 
 |   @property({type: Object}) | 
 |   responsiveMode?: DiffResponsiveMode; | 
 |  | 
 |   @property({type: Boolean}) | 
 |   unifiedDiff = false; | 
 |  | 
 |   @property({type: Number}) | 
 |   tabSize = 2; | 
 |  | 
 |   @property({type: Number}) | 
 |   lineLength = 80; | 
 |  | 
 |   @property({type: Boolean}) | 
 |   hideFileCommentButton = false; | 
 |  | 
 |   @property({type: Object}) | 
 |   layers: DiffLayer[] = []; | 
 |  | 
 |   /** | 
 |    * Semantic DOM diff testing does not work with just table fragments, so when | 
 |    * running such tests the render() method has to wrap the DOM in a proper | 
 |    * <table> element. | 
 |    */ | 
 |   @state() addTableWrapperForTesting = false; | 
 |  | 
 |   @state() leftComments: GrDiffCommentThread[] = []; | 
 |  | 
 |   @state() rightComments: GrDiffCommentThread[] = []; | 
 |  | 
 |   @state() columns: ColumnsToShow = NO_COLUMNS; | 
 |  | 
 |   /** | 
 |    * Keeps track of whether diff layers have already been applied to the diff | 
 |    * row. That happens after the DOM has been created in the `updated()` | 
 |    * lifecycle callback. | 
 |    * | 
 |    * Once layers are applied, the diff row requires two rendering passes for an | 
 |    * update: 1. Remove all <gr-diff-text> elements and their layer manipulated | 
 |    * DOMs. 2. Add fresh <gr-diff-text> elements and let layers re-apply in | 
 |    * `updated()`. | 
 |    */ | 
 |   private layersApplied = false; | 
 |  | 
 |   private readonly getDiffModel = resolve(this, diffModelToken); | 
 |  | 
 |   constructor() { | 
 |     super(); | 
 |     subscribe( | 
 |       this, | 
 |       () => | 
 |         combineLatest([this.left$, this.getDiffModel().comments$]).pipe( | 
 |           map(([left, comments]) => | 
 |             comments.filter( | 
 |               c => | 
 |                 c.line === left?.lineNumber(Side.LEFT) && c.side === Side.LEFT | 
 |             ) | 
 |           ), | 
 |           distinctUntilChanged(deepEqual) | 
 |         ), | 
 |       leftComments => (this.leftComments = leftComments) | 
 |     ); | 
 |     subscribe( | 
 |       this, | 
 |       () => | 
 |         combineLatest([this.right$, this.getDiffModel().comments$]).pipe( | 
 |           map(([right, comments]) => | 
 |             comments.filter( | 
 |               c => | 
 |                 c.line === right?.lineNumber(Side.RIGHT) && | 
 |                 c.side === Side.RIGHT | 
 |             ) | 
 |           ), | 
 |           distinctUntilChanged(deepEqual) | 
 |         ), | 
 |       rightComments => (this.rightComments = rightComments) | 
 |     ); | 
 |     subscribe( | 
 |       this, | 
 |       () => this.getDiffModel().columnsToShow$, | 
 |       columnsToShow => (this.columns = columnsToShow) | 
 |     ); | 
 |   } | 
 |  | 
 |   override willUpdate(changedProperties: PropertyValues) { | 
 |     if (changedProperties.has('left')) this.left$.next(this.left); | 
 |     if (changedProperties.has('right')) this.right$.next(this.right); | 
 |   } | 
 |  | 
 |   /** | 
 |    * The browser API for handling selection does not (yet) work for selection | 
 |    * across multiple shadow DOM elements. So we are rendering gr-diff components | 
 |    * into the light DOM instead of the shadow DOM by overriding this method, | 
 |    * which was the recommended workaround by the lit team. | 
 |    * See also https://github.com/WICG/webcomponents/issues/79. | 
 |    */ | 
 |   override createRenderRoot() { | 
 |     return this; | 
 |   } | 
 |  | 
 |   override updated() { | 
 |     if (this.layersApplied) { | 
 |       // <gr-diff-text> elements have been removed during rendering. Let's start | 
 |       // another rendering cycle with freshly created <gr-diff-text> elements. | 
 |       this.updateComplete.then(() => { | 
 |         this.layersApplied = false; | 
 |         this.requestUpdate(); | 
 |       }); | 
 |     } else { | 
 |       this.updateLayers(Side.LEFT); | 
 |       this.updateLayers(Side.RIGHT); | 
 |     } | 
 |   } | 
 |  | 
 |   /** | 
 |    * The diff layers API is designed to let layers manipulate the DOM. So we | 
 |    * have to apply them after the rendering cycle is done (`updated()`). But | 
 |    * when re-rendering a row that already has layers applied, then we have to | 
 |    * first wipe away <gr-diff-text>. This is achieved by | 
 |    * `this.layersApplied = true`. | 
 |    */ | 
 |   private async updateLayers(side: Side) { | 
 |     const line = this.line(side); | 
 |     const contentEl = this.contentRef(side).value; | 
 |     const lineNumberEl = this.lineNumberRef(side).value; | 
 |     if (!line || !contentEl || !lineNumberEl) return; | 
 |  | 
 |     // We have to wait for the <gr-diff-text> child component to finish | 
 |     // rendering before we can apply layers, which will re-write the HTML. | 
 |     await contentEl?.updateComplete; | 
 |     for (const layer of this.layers) { | 
 |       if (typeof layer.annotate === 'function') { | 
 |         layer.annotate(contentEl, lineNumberEl, line, side); | 
 |       } | 
 |     } | 
 |     // At this point we consider layers applied. So as soon as <gr-diff-row> | 
 |     // enters a new rendering cycle <gr-diff-text> elements will be removed. | 
 |     this.layersApplied = true; | 
 |   } | 
 |  | 
 |   override render() { | 
 |     if (!this.left || !this.right) return; | 
 |     const classes = this.unifiedDiff ? ['unified'] : ['side-by-side']; | 
 |     const unifiedType = this.unifiedType(); | 
 |     if (this.unifiedDiff && unifiedType) classes.push(unifiedType); | 
 |     const row = html` | 
 |       <tr | 
 |         ${ref(this.tableRowRef)} | 
 |         class=${diffClasses('diff-row', ...classes)} | 
 |         left-type=${ifDefined(this.getType(Side.LEFT))} | 
 |         right-type=${ifDefined(this.getType(Side.RIGHT))} | 
 |         tabindex="-1" | 
 |         aria-labelledby=${this.ariaLabelIds()} | 
 |       > | 
 |         ${this.renderBlameCell()} ${this.renderLineNumberCell(Side.LEFT)} | 
 |         ${this.renderSignCell(Side.LEFT)} ${this.renderContentCell(Side.LEFT)} | 
 |         ${this.renderLineNumberCell(Side.RIGHT)} | 
 |         ${this.renderSignCell(Side.RIGHT)} ${this.renderContentCell(Side.RIGHT)} | 
 |       </tr> | 
 |       ${this.renderPostLineSlot(Side.LEFT)} | 
 |       ${this.renderPostLineSlot(Side.RIGHT)} | 
 |     `; | 
 |     if (this.addTableWrapperForTesting) { | 
 |       return html`<table> | 
 |         ${row} | 
 |       </table>`; | 
 |     } | 
 |     return row; | 
 |   } | 
 |  | 
 |   private ariaLabelIds() { | 
 |     const ids: string[] = []; | 
 |     ids.push(this.lineNumberId(Side.LEFT)); | 
 |     if (!this.unifiedDiff) ids.push(this.contentId(Side.LEFT)); | 
 |     ids.push(this.lineNumberId(Side.RIGHT)); | 
 |     if (!this.unifiedDiff) ids.push(this.contentId(Side.RIGHT)); | 
 |     if (this.unifiedDiff) ids.push(this.contentId(this.unifiedSide())); | 
 |     return ids.filter(id => !!id).join(' '); | 
 |   } | 
 |  | 
 |   private lineNumberId(side: Side): string { | 
 |     const lineNumber = this.lineNumber(side); | 
 |     if (!lineNumber) return ''; | 
 |     return `${side}-button-${lineNumber}`; | 
 |   } | 
 |  | 
 |   private unifiedSide() { | 
 |     const isLeft = this.line(Side.RIGHT)?.type === GrDiffLineType.BLANK; | 
 |     return isLeft ? Side.LEFT : Side.RIGHT; | 
 |   } | 
 |  | 
 |   private contentId(side: Side): string { | 
 |     const lineNumber = this.lineNumber(side); | 
 |     if (!lineNumber) return ''; | 
 |     return `${side}-content-${lineNumber}`; | 
 |   } | 
 |  | 
 |   getTableRow(): HTMLTableRowElement | undefined { | 
 |     return this.tableRowRef.value; | 
 |   } | 
 |  | 
 |   getLineNumberCell(side: Side): HTMLTableCellElement | undefined { | 
 |     return this.lineNumberRef(side).value; | 
 |   } | 
 |  | 
 |   getContentCell(side: Side) { | 
 |     return this.contentCellRef(side)?.value; | 
 |   } | 
 |  | 
 |   getBlameCell() { | 
 |     return this.blameCellRef.value; | 
 |   } | 
 |  | 
 |   private renderBlameCell() { | 
 |     if (!this.columns.blame) return nothing; | 
 |     // td.blame has `white-space: pre`, so prettier must not add spaces. | 
 |     // prettier-ignore | 
 |     return html` | 
 |       <td | 
 |         ${ref(this.blameCellRef)} | 
 |         class=${diffClasses('blame')} | 
 |         data-line-number=${this.left?.beforeNumber ?? 0} | 
 |       >${this.renderBlameElement()}</td> | 
 |     `; | 
 |   } | 
 |  | 
 |   private renderBlameElement() { | 
 |     const lineNum = this.left?.beforeNumber; | 
 |     const commit = this.blameInfo; | 
 |     if (!lineNum || !commit) return; | 
 |  | 
 |     const isStartOfRange = commit.ranges.some(r => r.start === lineNum); | 
 |     const extras: string[] = []; | 
 |     if (isStartOfRange) extras.push('startOfRange'); | 
 |     const date = new Date(commit.time * 1000).toLocaleDateString(); | 
 |     const shortName = commit.author.split(' ')[0]; | 
 |     const url = `${getBaseUrl()}/q/${commit.id}`; | 
 |  | 
 |     // td.blame has `white-space: pre`, so prettier must not add spaces. | 
 |     // prettier-ignore | 
 |     return html`<span class=${diffClasses(...extras)} | 
 |         ><a href=${url} class=${diffClasses('blameDate')}>${date}</a | 
 |         ><span class=${diffClasses('blameAuthor')}> ${shortName}</span | 
 |         ><gr-hovercard class=${diffClasses()}> | 
 |           <span class=${diffClasses('blameHoverCard')}> | 
 |             Commit ${commit.id}<br /> | 
 |             Author: ${commit.author}<br /> | 
 |             Date: ${date}<br /> | 
 |             <br /> | 
 |             ${commit.commit_msg} | 
 |           </span> | 
 |         </gr-hovercard | 
 |       ></span>`; | 
 |   } | 
 |  | 
 |   private renderLineNumberCell(side: Side) { | 
 |     if (!this.columns.leftNumber && side === Side.LEFT) return nothing; | 
 |     if (!this.columns.rightNumber && side === Side.RIGHT) return nothing; | 
 |     const line = this.line(side); | 
 |     const lineNumber = this.lineNumber(side); | 
 |     const isBlank = line?.type === GrDiffLineType.BLANK; | 
 |     if (!line || !lineNumber || isBlank || this.layersApplied) { | 
 |       const blankClass = isBlank ? 'blankLineNum' : ''; | 
 |       return html`<td | 
 |         ${ref(this.lineNumberRef(side))} | 
 |         class=${diffClasses(side, blankClass)} | 
 |       ></td>`; | 
 |     } | 
 |  | 
 |     return html`<td | 
 |       ${ref(this.lineNumberRef(side))} | 
 |       class=${diffClasses(side, 'lineNum')} | 
 |       data-value=${lineNumber} | 
 |     > | 
 |       ${this.renderLineNumberButton(line, lineNumber, side)} | 
 |     </td>`; | 
 |   } | 
 |  | 
 |   private renderLineNumberButton( | 
 |     line: GrDiffLine, | 
 |     lineNumber: LineNumber, | 
 |     side: Side | 
 |   ) { | 
 |     if (this.hideFileCommentButton && lineNumber === FILE) return; | 
 |     if (lineNumber === LOST) return; | 
 |     // .lineNumButton has `white-space: pre`, so prettier must not add spaces. | 
 |     // prettier-ignore | 
 |     return html` | 
 |       <button | 
 |         id=${this.lineNumberId(side)} | 
 |         class=${diffClasses('lineNumButton', side)} | 
 |         tabindex="-1" | 
 |         data-value=${lineNumber} | 
 |         aria-label=${ifDefined( | 
 |           this.computeLineNumberAriaLabel(line, lineNumber) | 
 |     )} | 
 |         @click=${() => this.getDiffModel().createCommentOnLine(lineNumber, side)} | 
 |         @mouseenter=${() => | 
 |           fire(this, 'line-mouse-enter', {lineNum: lineNumber, side})} | 
 |         @mouseleave=${() => | 
 |           fire(this, 'line-mouse-leave', {lineNum: lineNumber, side})} | 
 |       >${lineNumber === FILE ? 'FILE' : lineNumber.toString()}</button> | 
 |     `; | 
 |   } | 
 |  | 
 |   private computeLineNumberAriaLabel(line: GrDiffLine, lineNumber: LineNumber) { | 
 |     if (lineNumber === FILE) return 'Add file comment'; | 
 |  | 
 |     // Add aria-labels for valid line numbers. | 
 |     // For unified diff, this method will be called with number set to 0 for | 
 |     // the empty line number column for added/removed lines. This should not | 
 |     // be announced to the screenreader. | 
 |     if ( | 
 |       lineNumber === LOST || | 
 |       (typeof lineNumber === 'number' && lineNumber <= 0) | 
 |     ) | 
 |       return undefined; | 
 |  | 
 |     switch (line.type) { | 
 |       case GrDiffLineType.REMOVE: | 
 |         return `${lineNumber} removed`; | 
 |       case GrDiffLineType.ADD: | 
 |         return `${lineNumber} added`; | 
 |       case GrDiffLineType.BOTH: | 
 |       case GrDiffLineType.BLANK: | 
 |         return `${lineNumber} unmodified`; | 
 |     } | 
 |   } | 
 |  | 
 |   private renderContentCell(side: Side) { | 
 |     if (!this.columns.leftContent && side === Side.LEFT) return nothing; | 
 |     if (!this.columns.rightContent && side === Side.RIGHT) return nothing; | 
 |  | 
 |     let line = this.line(side); | 
 |     if (this.unifiedDiff) { | 
 |       if (line?.type === GrDiffLineType.BLANK) { | 
 |         side = Side.LEFT; | 
 |         line = this.line(Side.LEFT); | 
 |       } | 
 |     } | 
 |     const lineNumber = this.lineNumber(side); | 
 |     assertIsDefined(line, 'line'); | 
 |     const extras: string[] = [line.type, side]; | 
 |     if (line.type !== GrDiffLineType.BLANK) extras.push('content'); | 
 |     if (!line.hasIntralineInfo) extras.push('no-intraline-info'); | 
 |     if (line.beforeNumber === FILE) extras.push('file'); | 
 |     if (line.beforeNumber === LOST) extras.push('lost'); | 
 |  | 
 |     // .content has `white-space: pre`, so prettier must not add spaces. | 
 |     // prettier-ignore | 
 |     return html` | 
 |       <td | 
 |         ${ref(this.contentCellRef(side))} | 
 |         class=${diffClasses(...extras)} | 
 |         @click=${() => { | 
 |           if (lineNumber) { | 
 |             this.getDiffModel().selectLine(lineNumber, side); | 
 |           } | 
 |         }} | 
 |         @mouseenter=${() => { | 
 |           if (lineNumber) | 
 |             fire(this, 'line-mouse-enter', {lineNum: lineNumber, side}); | 
 |         }} | 
 |         @mouseleave=${() => { | 
 |           if (lineNumber) | 
 |             fire(this, 'line-mouse-leave', {lineNum: lineNumber, side}); | 
 |         }} | 
 |       >${this.renderText(side)}${this.renderLostMessage(side)}${this.renderThreadGroup(side)}</td> | 
 |     `; | 
 |   } | 
 |  | 
 |   private renderSignCell(side: Side) { | 
 |     if (!this.columns.leftSign && side === Side.LEFT) return nothing; | 
 |     if (!this.columns.rightSign && side === Side.RIGHT) return nothing; | 
 |  | 
 |     const line = this.line(side); | 
 |     assertIsDefined(line, 'line'); | 
 |     const isBlank = line.type === GrDiffLineType.BLANK; | 
 |     const isAdd = line.type === GrDiffLineType.ADD && side === Side.RIGHT; | 
 |     const isRemove = line.type === GrDiffLineType.REMOVE && side === Side.LEFT; | 
 |     const extras: string[] = ['sign', side]; | 
 |     if (isBlank) extras.push('blank'); | 
 |     if (isAdd) extras.push('add'); | 
 |     if (isRemove) extras.push('remove'); | 
 |     if (!line.hasIntralineInfo) extras.push('no-intraline-info'); | 
 |  | 
 |     const sign = isAdd ? '+' : isRemove ? '-' : ''; | 
 |     return html`<td class=${diffClasses(...extras)}>${sign}</td>`; | 
 |   } | 
 |  | 
 |   private renderLostMessage(side: Side) { | 
 |     if (this.lineNumber(side) !== LOST) return nothing; | 
 |     if (this.getComments(side).length === 0) return nothing; | 
 |     // .content has `white-space: pre`, so prettier must not add spaces. | 
 |     // prettier-ignore | 
 |     return html`<div class="lost-message" | 
 |       ><gr-icon icon="info"></gr-icon | 
 |       ><span>Original comment position not found in this patchset</span | 
 |     ></div>`; | 
 |   } | 
 |  | 
 |   private renderThreadGroup(side: Side) { | 
 |     if (!this.lineNumber(side)) return nothing; | 
 |  | 
 |     if ( | 
 |       this.getComments(side).length === 0 && | 
 |       (!this.unifiedDiff || this.getComments(otherSide(side)).length === 0) | 
 |     ) { | 
 |       return nothing; | 
 |     } | 
 |     return html`<div class="thread-group" data-side=${side}> | 
 |       ${this.renderSlot(side)} | 
 |       ${when(this.unifiedDiff, () => this.renderSlot(otherSide(side)))} | 
 |     </div>`; | 
 |   } | 
 |  | 
 |   private renderSlot(side: Side) { | 
 |     const line = this.lineNumber(side); | 
 |     if (!line) return nothing; | 
 |     if (this.getComments(side).length === 0) return nothing; | 
 |     return html` | 
 |       ${this.renderRangedCommentHints(side)} | 
 |       <slot name="${side}-${line}"></slot> | 
 |     `; | 
 |   } | 
 |  | 
 |   private renderRangedCommentHints(side: Side) { | 
 |     const ranges = this.getComments(side) | 
 |       .map(c => c.range) | 
 |       .filter(isDefined) | 
 |       .filter(isLongCommentRange); | 
 |     return ranges.map( | 
 |       range => | 
 |         html` | 
 |           <gr-ranged-comment-hint .range=${range}></gr-ranged-comment-hint> | 
 |         ` | 
 |     ); | 
 |   } | 
 |  | 
 |   private contentRef(side: Side) { | 
 |     return side === Side.LEFT ? this.contentLeftRef : this.contentRightRef; | 
 |   } | 
 |  | 
 |   private contentCellRef(side: Side) { | 
 |     return side === Side.LEFT | 
 |       ? this.contentCellLeftRef | 
 |       : this.contentCellRightRef; | 
 |   } | 
 |  | 
 |   private lineNumberRef(side: Side) { | 
 |     return side === Side.LEFT | 
 |       ? this.lineNumberLeftRef | 
 |       : this.lineNumberRightRef; | 
 |   } | 
 |  | 
 |   lineNumber(side: Side) { | 
 |     return this.line(side)?.lineNumber(side); | 
 |   } | 
 |  | 
 |   line(side: Side) { | 
 |     return side === Side.LEFT ? this.left : this.right; | 
 |   } | 
 |  | 
 |   private getComments(side: Side) { | 
 |     return side === Side.LEFT ? this.leftComments : this.rightComments; | 
 |   } | 
 |  | 
 |   private getType(side?: Side): string | undefined { | 
 |     if (this.unifiedDiff) return undefined; | 
 |     if (side === Side.LEFT) return this.left?.type; | 
 |     if (side === Side.RIGHT) return this.right?.type; | 
 |     return undefined; | 
 |   } | 
 |  | 
 |   private unifiedType() { | 
 |     return this.left?.type === GrDiffLineType.BLANK | 
 |       ? this.right?.type | 
 |       : this.left?.type; | 
 |   } | 
 |  | 
 |   /** | 
 |    * Returns a 'div' element containing the supplied |text| as its innerText, | 
 |    * with '\t' characters expanded to a width determined by |tabSize|, and the | 
 |    * text wrapped at column |lineLimit|, which may be Infinity if no wrapping is | 
 |    * desired. | 
 |    */ | 
 |   private renderText(side: Side) { | 
 |     const line = this.line(side); | 
 |     const lineNumber = this.lineNumber(side); | 
 |     if (typeof lineNumber !== 'number') return; | 
 |  | 
 |     // Note that `this.layersApplied` will wipe away the <gr-diff-text>, and | 
 |     // another rendering cycle will be initiated in `updated()`. | 
 |     // prettier-ignore | 
 |     const textElement = line?.text && !this.layersApplied | 
 |       ? html`<gr-diff-text | 
 |           ${ref(this.contentRef(side))} | 
 |           data-side=${ifDefined(side)} | 
 |           .text=${line?.text} | 
 |           .tabSize=${this.tabSize} | 
 |           .lineLimit=${this.lineLength} | 
 |           .isResponsive=${isResponsive(this.responsiveMode)} | 
 |         ></gr-diff-text>` : ''; | 
 |     // .content has `white-space: pre`, so prettier must not add spaces. | 
 |     // prettier-ignore | 
 |     return html`<div | 
 |         class=${diffClasses('contentText')} | 
 |         data-side=${ifDefined(side)} | 
 |         id=${this.contentId(side)} | 
 |       >${textElement}</div>`; | 
 |   } | 
 |  | 
 |   private renderPostLineSlot(side: Side) { | 
 |     const lineNumber = this.lineNumber(side); | 
 |     return lineNumber && Number.isInteger(lineNumber) | 
 |       ? html`<slot name="post-${side}-line-${lineNumber}"></slot>` | 
 |       : nothing; | 
 |   } | 
 | } | 
 |  | 
 | customElements.define('gr-diff-row', GrDiffRow); | 
 |  | 
 | declare global { | 
 |   interface HTMLElementTagNameMap { | 
 |     'gr-diff-row': GrDiffRow; | 
 |   } | 
 | } |