fix(app): don't scroll code search input
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { type FileContents, File, FileOptions, LineAnnotation, type SelectedLineRange } from "@pierre/diffs"
|
import { type FileContents, File, FileOptions, LineAnnotation, type SelectedLineRange } from "@pierre/diffs"
|
||||||
import { ComponentProps, createEffect, createMemo, createSignal, onCleanup, onMount, Show, splitProps } from "solid-js"
|
import { ComponentProps, createEffect, createMemo, createSignal, onCleanup, onMount, Show, splitProps } from "solid-js"
|
||||||
|
import { Portal } from "solid-js/web"
|
||||||
import { createDefaultOptions, styleVariables } from "../pierre"
|
import { createDefaultOptions, styleVariables } from "../pierre"
|
||||||
import { getWorkerPool } from "../pierre/worker"
|
import { getWorkerPool } from "../pierre/worker"
|
||||||
import { Icon } from "./icon"
|
import { Icon } from "./icon"
|
||||||
@@ -125,11 +126,9 @@ export function Code<T>(props: CodeProps<T>) {
|
|||||||
let wrapper!: HTMLDivElement
|
let wrapper!: HTMLDivElement
|
||||||
let container!: HTMLDivElement
|
let container!: HTMLDivElement
|
||||||
let findInput: HTMLInputElement | undefined
|
let findInput: HTMLInputElement | undefined
|
||||||
let findBar: HTMLDivElement | undefined
|
|
||||||
let findOverlay!: HTMLDivElement
|
let findOverlay!: HTMLDivElement
|
||||||
let findOverlayFrame: number | undefined
|
let findOverlayFrame: number | undefined
|
||||||
let findOverlayScroll: HTMLElement[] = []
|
let findOverlayScroll: HTMLElement[] = []
|
||||||
let findScroll: HTMLElement | undefined
|
|
||||||
let observer: MutationObserver | undefined
|
let observer: MutationObserver | undefined
|
||||||
let renderToken = 0
|
let renderToken = 0
|
||||||
let selectionFrame: number | undefined
|
let selectionFrame: number | undefined
|
||||||
@@ -159,6 +158,8 @@ export function Code<T>(props: CodeProps<T>) {
|
|||||||
let findMode: "highlights" | "overlay" = "overlay"
|
let findMode: "highlights" | "overlay" = "overlay"
|
||||||
let findHits: Range[] = []
|
let findHits: Range[] = []
|
||||||
|
|
||||||
|
const [findPos, setFindPos] = createSignal<{ top: number; right: number }>({ top: 8, right: 8 })
|
||||||
|
|
||||||
const file = createMemo(
|
const file = createMemo(
|
||||||
() =>
|
() =>
|
||||||
new File<T>(
|
new File<T>(
|
||||||
@@ -291,23 +292,26 @@ export function Code<T>(props: CodeProps<T>) {
|
|||||||
setFindIndex(0)
|
setFindIndex(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getScrollParent = (el: HTMLElement): HTMLElement | null => {
|
const getScrollParent = (el: HTMLElement): HTMLElement | undefined => {
|
||||||
let parent = el.parentElement
|
let parent = el.parentElement
|
||||||
while (parent) {
|
while (parent) {
|
||||||
const style = getComputedStyle(parent)
|
const style = getComputedStyle(parent)
|
||||||
if (style.overflowY === "auto" || style.overflowY === "scroll") return parent
|
if (style.overflowY === "auto" || style.overflowY === "scroll") return parent
|
||||||
parent = parent.parentElement
|
parent = parent.parentElement
|
||||||
}
|
}
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const positionFindBar = () => {
|
const positionFindBar = () => {
|
||||||
if (!findBar || !wrapper) return
|
if (typeof window === "undefined") return
|
||||||
const scrollTop = findScroll ? findScroll.scrollTop : window.scrollY
|
|
||||||
findBar.style.position = "absolute"
|
const root = getScrollParent(wrapper) ?? wrapper
|
||||||
findBar.style.top = `${scrollTop + 8}px`
|
const rect = root.getBoundingClientRect()
|
||||||
findBar.style.right = "8px"
|
const title = parseFloat(getComputedStyle(root).getPropertyValue("--session-title-height"))
|
||||||
findBar.style.left = ""
|
const header = Number.isNaN(title) ? 0 : title
|
||||||
|
setFindPos({
|
||||||
|
top: Math.round(rect.top) + header - 4,
|
||||||
|
right: Math.round(window.innerWidth - rect.right) + 8,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const scanFind = (root: ShadowRoot, query: string) => {
|
const scanFind = (root: ShadowRoot, query: string) => {
|
||||||
@@ -426,7 +430,6 @@ export function Code<T>(props: CodeProps<T>) {
|
|||||||
}
|
}
|
||||||
if (opts?.scroll && active) {
|
if (opts?.scroll && active) {
|
||||||
scrollToRange(active)
|
scrollToRange(active)
|
||||||
positionFindBar()
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -435,7 +438,6 @@ export function Code<T>(props: CodeProps<T>) {
|
|||||||
syncOverlayScroll()
|
syncOverlayScroll()
|
||||||
if (opts?.scroll && active) {
|
if (opts?.scroll && active) {
|
||||||
scrollToRange(active)
|
scrollToRange(active)
|
||||||
positionFindBar()
|
|
||||||
}
|
}
|
||||||
scheduleOverlay()
|
scheduleOverlay()
|
||||||
}
|
}
|
||||||
@@ -464,14 +466,12 @@ export function Code<T>(props: CodeProps<T>) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
scrollToRange(active)
|
scrollToRange(active)
|
||||||
positionFindBar()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
clearHighlightFind()
|
clearHighlightFind()
|
||||||
syncOverlayScroll()
|
syncOverlayScroll()
|
||||||
scrollToRange(active)
|
scrollToRange(active)
|
||||||
positionFindBar()
|
|
||||||
scheduleOverlay()
|
scheduleOverlay()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,11 +484,9 @@ export function Code<T>(props: CodeProps<T>) {
|
|||||||
findCurrent = host
|
findCurrent = host
|
||||||
findTarget = host
|
findTarget = host
|
||||||
|
|
||||||
findScroll = getScrollParent(wrapper) ?? undefined
|
|
||||||
if (!findOpen()) setFindOpen(true)
|
if (!findOpen()) setFindOpen(true)
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
applyFind({ scroll: true })
|
applyFind({ scroll: true })
|
||||||
positionFindBar()
|
|
||||||
findInput?.focus()
|
findInput?.focus()
|
||||||
findInput?.select()
|
findInput?.select()
|
||||||
})
|
})
|
||||||
@@ -514,18 +512,18 @@ export function Code<T>(props: CodeProps<T>) {
|
|||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!findOpen()) return
|
if (!findOpen()) return
|
||||||
findScroll = getScrollParent(wrapper) ?? undefined
|
|
||||||
const target = findScroll ?? window
|
|
||||||
|
|
||||||
const handler = () => positionFindBar()
|
const update = () => positionFindBar()
|
||||||
target.addEventListener("scroll", handler, { passive: true })
|
requestAnimationFrame(update)
|
||||||
window.addEventListener("resize", handler, { passive: true })
|
window.addEventListener("resize", update, { passive: true })
|
||||||
handler()
|
|
||||||
|
const root = getScrollParent(wrapper) ?? wrapper
|
||||||
|
const observer = typeof ResizeObserver === "undefined" ? undefined : new ResizeObserver(() => update())
|
||||||
|
observer?.observe(root)
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
target.removeEventListener("scroll", handler)
|
window.removeEventListener("resize", update)
|
||||||
window.removeEventListener("resize", handler)
|
observer?.disconnect()
|
||||||
findScroll = undefined
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -916,6 +914,64 @@ export function Code<T>(props: CodeProps<T>) {
|
|||||||
pendingSelectionEnd = false
|
pendingSelectionEnd = false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const FindBar = (barProps: { class: string; style?: ComponentProps<"div">["style"] }) => (
|
||||||
|
<div class={barProps.class} style={barProps.style} onPointerDown={(e) => e.stopPropagation()}>
|
||||||
|
<Icon name="magnifying-glass" size="small" class="text-text-weak shrink-0" />
|
||||||
|
<input
|
||||||
|
ref={findInput}
|
||||||
|
placeholder="Find"
|
||||||
|
value={findQuery()}
|
||||||
|
class="w-40 bg-transparent outline-none text-14-regular text-text-strong placeholder:text-text-weak"
|
||||||
|
onInput={(e) => {
|
||||||
|
setFindQuery(e.currentTarget.value)
|
||||||
|
setFindIndex(0)
|
||||||
|
applyFind({ reset: true, scroll: true })
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
e.preventDefault()
|
||||||
|
closeFind()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (e.key !== "Enter") return
|
||||||
|
e.preventDefault()
|
||||||
|
stepFind(e.shiftKey ? -1 : 1)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div class="shrink-0 text-12-regular text-text-weak tabular-nums text-right" style={{ width: "10ch" }}>
|
||||||
|
{findCount() ? `${findIndex() + 1}/${findCount()}` : "0/0"}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong disabled:opacity-40 disabled:pointer-events-none"
|
||||||
|
disabled={findCount() === 0}
|
||||||
|
aria-label="Previous match"
|
||||||
|
onClick={() => stepFind(-1)}
|
||||||
|
>
|
||||||
|
<Icon name="chevron-down" size="small" class="rotate-180" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong disabled:opacity-40 disabled:pointer-events-none"
|
||||||
|
disabled={findCount() === 0}
|
||||||
|
aria-label="Next match"
|
||||||
|
onClick={() => stepFind(1)}
|
||||||
|
>
|
||||||
|
<Icon name="chevron-down" size="small" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong"
|
||||||
|
aria-label="Close search"
|
||||||
|
onClick={closeFind}
|
||||||
|
>
|
||||||
|
<Icon name="close-small" size="small" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-component="code"
|
data-component="code"
|
||||||
@@ -936,65 +992,15 @@ export function Code<T>(props: CodeProps<T>) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Show when={findOpen()}>
|
<Show when={findOpen()}>
|
||||||
<div
|
<Portal>
|
||||||
ref={findBar}
|
<FindBar
|
||||||
class="z-50 flex h-8 items-center gap-2 rounded-md border border-border-base bg-background-base px-3 shadow-md"
|
class="fixed z-50 flex h-8 items-center gap-2 rounded-md border border-border-base bg-background-base px-3 shadow-md"
|
||||||
onPointerDown={(e) => e.stopPropagation()}
|
style={{
|
||||||
>
|
top: `${findPos().top}px`,
|
||||||
<Icon name="magnifying-glass" size="small" class="text-text-weak shrink-0" />
|
right: `${findPos().right}px`,
|
||||||
<input
|
|
||||||
ref={findInput}
|
|
||||||
placeholder="Find"
|
|
||||||
value={findQuery()}
|
|
||||||
class="w-40 bg-transparent outline-none text-14-regular text-text-strong placeholder:text-text-weak"
|
|
||||||
onInput={(e) => {
|
|
||||||
setFindQuery(e.currentTarget.value)
|
|
||||||
setFindIndex(0)
|
|
||||||
applyFind({ reset: true, scroll: true })
|
|
||||||
}}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === "Escape") {
|
|
||||||
e.preventDefault()
|
|
||||||
closeFind()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (e.key !== "Enter") return
|
|
||||||
e.preventDefault()
|
|
||||||
stepFind(e.shiftKey ? -1 : 1)
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div class="shrink-0 text-12-regular text-text-weak tabular-nums text-right" style={{ width: "10ch" }}>
|
</Portal>
|
||||||
{findCount() ? `${findIndex() + 1}/${findCount()}` : "0/0"}
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong disabled:opacity-40 disabled:pointer-events-none"
|
|
||||||
disabled={findCount() === 0}
|
|
||||||
aria-label="Previous match"
|
|
||||||
onClick={() => stepFind(-1)}
|
|
||||||
>
|
|
||||||
<Icon name="chevron-down" size="small" class="rotate-180" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong disabled:opacity-40 disabled:pointer-events-none"
|
|
||||||
disabled={findCount() === 0}
|
|
||||||
aria-label="Next match"
|
|
||||||
onClick={() => stepFind(1)}
|
|
||||||
>
|
|
||||||
<Icon name="chevron-down" size="small" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong"
|
|
||||||
aria-label="Close search"
|
|
||||||
onClick={closeFind}
|
|
||||||
>
|
|
||||||
<Icon name="close-small" size="small" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</Show>
|
</Show>
|
||||||
<div ref={container} />
|
<div ref={container} />
|
||||||
<div ref={findOverlay} class="pointer-events-none absolute inset-0 z-0" />
|
<div ref={findOverlay} class="pointer-events-none absolute inset-0 z-0" />
|
||||||
|
|||||||
Reference in New Issue
Block a user