fix(app): line selection styling

This commit is contained in:
adamelmore
2026-01-25 11:25:26 -06:00
parent 65ac318282
commit 9a89cd91d7
3 changed files with 131 additions and 30 deletions

View File

@@ -29,10 +29,16 @@ export function Diff<T>(props: SSRDiffProps<T>) {
const getRoot = () => fileDiffRef?.shadowRoot ?? undefined const getRoot = () => fileDiffRef?.shadowRoot ?? undefined
const findSide = (element: HTMLElement): "additions" | "deletions" => { const findSide = (element: HTMLElement): "additions" | "deletions" => {
const line = element.closest("[data-line], [data-alt-line]")
if (line instanceof HTMLElement) {
const type = line.dataset.lineType
if (type === "change-deletion") return "deletions"
if (type === "change-addition" || type === "change-additions") return "additions"
}
const code = element.closest("[data-code]") const code = element.closest("[data-code]")
if (!(code instanceof HTMLElement)) return "additions" if (!(code instanceof HTMLElement)) return "additions"
if (code.hasAttribute("data-deletions")) return "deletions" return code.hasAttribute("data-deletions") ? "deletions" : "additions"
return "additions"
} }
const applyCommentedLines = (ranges: SelectedLineRange[]) => { const applyCommentedLines = (ranges: SelectedLineRange[]) => {
@@ -45,19 +51,69 @@ export function Diff<T>(props: SSRDiffProps<T>) {
node.removeAttribute("data-comment-selected") node.removeAttribute("data-comment-selected")
} }
for (const range of ranges) { const diffs = root.querySelector("[data-diffs]")
const start = Math.max(1, Math.min(range.start, range.end)) if (!(diffs instanceof HTMLElement)) return
const end = Math.max(range.start, range.end)
for (let line = start; line <= end; line++) { const split = diffs.dataset.type === "split"
const expectedSide =
line === end ? (range.endSide ?? range.side) : line === start ? range.side : (range.side ?? range.endSide) const code = Array.from(diffs.querySelectorAll("[data-code]")).filter(
(node): node is HTMLElement => node instanceof HTMLElement,
)
if (code.length === 0) return
const lineIndex = (element: HTMLElement) => {
const raw = element.dataset.lineIndex
if (!raw) return
const values = raw
.split(",")
.map((value) => parseInt(value, 10))
.filter((value) => !Number.isNaN(value))
if (values.length === 0) return
if (!split) return values[0]
if (values.length === 2) return values[1]
return values[0]
}
const rowIndex = (line: number, side: "additions" | "deletions" | undefined) => {
const nodes = Array.from(root.querySelectorAll(`[data-line="${line}"], [data-alt-line="${line}"]`)).filter(
(node): node is HTMLElement => node instanceof HTMLElement,
)
if (nodes.length === 0) return
const targetSide = side ?? "additions"
const nodes = Array.from(root.querySelectorAll(`[data-line="${line}"], [data-alt-line="${line}"]`))
for (const node of nodes) { for (const node of nodes) {
if (!(node instanceof HTMLElement)) continue if (findSide(node) === targetSide) return lineIndex(node)
if (expectedSide && findSide(node) !== expectedSide) continue if (parseInt(node.dataset.altLine ?? "", 10) === line) return lineIndex(node)
node.setAttribute("data-comment-selected", "") }
}
for (const range of ranges) {
const start = rowIndex(range.start, range.side)
if (start === undefined) continue
const end = (() => {
const same = range.end === range.start && (range.endSide == null || range.endSide === range.side)
if (same) return start
return rowIndex(range.end, range.endSide ?? range.side)
})()
if (end === undefined) continue
const first = Math.min(start, end)
const last = Math.max(start, end)
for (const block of code) {
for (const element of Array.from(block.children)) {
if (!(element instanceof HTMLElement)) continue
const idx = lineIndex(element)
if (idx === undefined) continue
if (idx > last) break
if (idx < first) continue
element.setAttribute("data-comment-selected", "")
const next = element.nextSibling
if (next instanceof HTMLElement && next.hasAttribute("data-line-annotation")) {
next.setAttribute("data-comment-selected", "")
}
} }
} }
} }

View File

@@ -191,24 +191,69 @@ export function Diff<T>(props: DiffProps<T>) {
node.removeAttribute("data-comment-selected") node.removeAttribute("data-comment-selected")
} }
for (const range of ranges) { const diffs = root.querySelector("[data-diffs]")
const start = Math.max(1, Math.min(range.start, range.end)) if (!(diffs instanceof HTMLElement)) return
const end = Math.max(range.start, range.end)
for (let line = start; line <= end; line++) { const split = diffs.dataset.type === "split"
const expectedSide =
line === end ? (range.endSide ?? range.side) : line === start ? range.side : (range.side ?? range.endSide)
const nodes = Array.from(root.querySelectorAll(`[data-line="${line}"], [data-alt-line="${line}"]`)) const code = Array.from(diffs.querySelectorAll("[data-code]")).filter(
for (const node of nodes) { (node): node is HTMLElement => node instanceof HTMLElement,
if (!(node instanceof HTMLElement)) continue )
if (code.length === 0) return
if (expectedSide) { const lineIndex = (element: HTMLElement) => {
const side = findSide(node) const raw = element.dataset.lineIndex
if (side && side !== expectedSide) continue if (!raw) return
const values = raw
.split(",")
.map((value) => parseInt(value, 10))
.filter((value) => !Number.isNaN(value))
if (values.length === 0) return
if (!split) return values[0]
if (values.length === 2) return values[1]
return values[0]
} }
node.setAttribute("data-comment-selected", "") const rowIndex = (line: number, side: SelectionSide | undefined) => {
const nodes = Array.from(root.querySelectorAll(`[data-line="${line}"], [data-alt-line="${line}"]`)).filter(
(node): node is HTMLElement => node instanceof HTMLElement,
)
if (nodes.length === 0) return
const targetSide = side ?? "additions"
for (const node of nodes) {
if (findSide(node) === targetSide) return lineIndex(node)
if (parseInt(node.dataset.altLine ?? "", 10) === line) return lineIndex(node)
}
}
for (const range of ranges) {
const start = rowIndex(range.start, range.side)
if (start === undefined) continue
const end = (() => {
const same = range.end === range.start && (range.endSide == null || range.endSide === range.side)
if (same) return start
return rowIndex(range.end, range.endSide ?? range.side)
})()
if (end === undefined) continue
const first = Math.min(start, end)
const last = Math.max(start, end)
for (const block of code) {
for (const element of Array.from(block.children)) {
if (!(element instanceof HTMLElement)) continue
const idx = lineIndex(element)
if (idx === undefined) continue
if (idx > last) break
if (idx < first) continue
element.setAttribute("data-comment-selected", "")
const next = element.nextSibling
if (next instanceof HTMLElement && next.hasAttribute("data-line-annotation")) {
next.setAttribute("data-comment-selected", "")
}
} }
} }
} }

View File

@@ -44,12 +44,12 @@ const unsafeCSS = `
background-color: var(--diffs-bg-selection-text); background-color: var(--diffs-bg-selection-text);
} }
[data-diffs] [data-comment-selected] { [data-diffs] [data-comment-selected]:not([data-selected-line]) [data-column-content] {
background-color: var(--diffs-bg-selection); box-shadow: inset 0 0 0 9999px var(--diffs-bg-selection);
} }
[data-diffs] [data-comment-selected] [data-column-number] { [data-diffs] [data-comment-selected]:not([data-selected-line]) [data-column-number] {
background-color: var(--diffs-bg-selection-number); box-shadow: inset 0 0 0 9999px var(--diffs-bg-selection-number);
color: var(--diffs-selection-number-fg); color: var(--diffs-selection-number-fg);
} }
@@ -73,7 +73,7 @@ const unsafeCSS = `
/* The deletion word-diff emphasis is stronger than additions; soften it while selected so the selection highlight reads consistently. */ /* The deletion word-diff emphasis is stronger than additions; soften it while selected so the selection highlight reads consistently. */
[data-diffs] [data-line-type='change-deletion'][data-selected-line] { [data-diffs] [data-line-type='change-deletion'][data-selected-line] {
--diffs-bg-deletion-emphasis: light-dark( --diffs-bg-deletion-emphasis: light-dark(
rgb(from var(--diffs-deletion-base) r g b / 0.15), rgb(from var(--diffs-deletion-base) r g b / 0.07),
rgb(from var(--diffs-deletion-base) r g b / 0.1) rgb(from var(--diffs-deletion-base) r g b / 0.1)
); );
} }