Skip to content

Commit 5511955

Browse files
adamdotdevinopencode
authored andcommitted
fix(app): don't scroll code search input
1 parent fd55313 commit 5511955

1 file changed

Lines changed: 88 additions & 82 deletions

File tree

packages/ui/src/components/code.tsx

Lines changed: 88 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { type FileContents, File, FileOptions, LineAnnotation, type SelectedLineRange } from "@pierre/diffs"
22
import { ComponentProps, createEffect, createMemo, createSignal, onCleanup, onMount, Show, splitProps } from "solid-js"
3+
import { Portal } from "solid-js/web"
34
import { createDefaultOptions, styleVariables } from "../pierre"
45
import { getWorkerPool } from "../pierre/worker"
56
import { Icon } from "./icon"
@@ -125,11 +126,9 @@ export function Code<T>(props: CodeProps<T>) {
125126
let wrapper!: HTMLDivElement
126127
let container!: HTMLDivElement
127128
let findInput: HTMLInputElement | undefined
128-
let findBar: HTMLDivElement | undefined
129129
let findOverlay!: HTMLDivElement
130130
let findOverlayFrame: number | undefined
131131
let findOverlayScroll: HTMLElement[] = []
132-
let findScroll: HTMLElement | undefined
133132
let observer: MutationObserver | undefined
134133
let renderToken = 0
135134
let selectionFrame: number | undefined
@@ -159,6 +158,8 @@ export function Code<T>(props: CodeProps<T>) {
159158
let findMode: "highlights" | "overlay" = "overlay"
160159
let findHits: Range[] = []
161160

161+
const [findPos, setFindPos] = createSignal<{ top: number; right: number }>({ top: 8, right: 8 })
162+
162163
const file = createMemo(
163164
() =>
164165
new File<T>(
@@ -291,23 +292,26 @@ export function Code<T>(props: CodeProps<T>) {
291292
setFindIndex(0)
292293
}
293294

294-
const getScrollParent = (el: HTMLElement): HTMLElement | null => {
295+
const getScrollParent = (el: HTMLElement): HTMLElement | undefined => {
295296
let parent = el.parentElement
296297
while (parent) {
297298
const style = getComputedStyle(parent)
298299
if (style.overflowY === "auto" || style.overflowY === "scroll") return parent
299300
parent = parent.parentElement
300301
}
301-
return null
302302
}
303303

304304
const positionFindBar = () => {
305-
if (!findBar || !wrapper) return
306-
const scrollTop = findScroll ? findScroll.scrollTop : window.scrollY
307-
findBar.style.position = "absolute"
308-
findBar.style.top = `${scrollTop + 8}px`
309-
findBar.style.right = "8px"
310-
findBar.style.left = ""
305+
if (typeof window === "undefined") return
306+
307+
const root = getScrollParent(wrapper) ?? wrapper
308+
const rect = root.getBoundingClientRect()
309+
const title = parseFloat(getComputedStyle(root).getPropertyValue("--session-title-height"))
310+
const header = Number.isNaN(title) ? 0 : title
311+
setFindPos({
312+
top: Math.round(rect.top) + header - 4,
313+
right: Math.round(window.innerWidth - rect.right) + 8,
314+
})
311315
}
312316

313317
const scanFind = (root: ShadowRoot, query: string) => {
@@ -426,7 +430,6 @@ export function Code<T>(props: CodeProps<T>) {
426430
}
427431
if (opts?.scroll && active) {
428432
scrollToRange(active)
429-
positionFindBar()
430433
}
431434
return
432435
}
@@ -435,7 +438,6 @@ export function Code<T>(props: CodeProps<T>) {
435438
syncOverlayScroll()
436439
if (opts?.scroll && active) {
437440
scrollToRange(active)
438-
positionFindBar()
439441
}
440442
scheduleOverlay()
441443
}
@@ -464,14 +466,12 @@ export function Code<T>(props: CodeProps<T>) {
464466
return
465467
}
466468
scrollToRange(active)
467-
positionFindBar()
468469
return
469470
}
470471

471472
clearHighlightFind()
472473
syncOverlayScroll()
473474
scrollToRange(active)
474-
positionFindBar()
475475
scheduleOverlay()
476476
}
477477

@@ -484,11 +484,9 @@ export function Code<T>(props: CodeProps<T>) {
484484
findCurrent = host
485485
findTarget = host
486486

487-
findScroll = getScrollParent(wrapper) ?? undefined
488487
if (!findOpen()) setFindOpen(true)
489488
requestAnimationFrame(() => {
490489
applyFind({ scroll: true })
491-
positionFindBar()
492490
findInput?.focus()
493491
findInput?.select()
494492
})
@@ -514,18 +512,18 @@ export function Code<T>(props: CodeProps<T>) {
514512

515513
createEffect(() => {
516514
if (!findOpen()) return
517-
findScroll = getScrollParent(wrapper) ?? undefined
518-
const target = findScroll ?? window
519515

520-
const handler = () => positionFindBar()
521-
target.addEventListener("scroll", handler, { passive: true })
522-
window.addEventListener("resize", handler, { passive: true })
523-
handler()
516+
const update = () => positionFindBar()
517+
requestAnimationFrame(update)
518+
window.addEventListener("resize", update, { passive: true })
519+
520+
const root = getScrollParent(wrapper) ?? wrapper
521+
const observer = typeof ResizeObserver === "undefined" ? undefined : new ResizeObserver(() => update())
522+
observer?.observe(root)
524523

525524
onCleanup(() => {
526-
target.removeEventListener("scroll", handler)
527-
window.removeEventListener("resize", handler)
528-
findScroll = undefined
525+
window.removeEventListener("resize", update)
526+
observer?.disconnect()
529527
})
530528
})
531529

@@ -916,6 +914,64 @@ export function Code<T>(props: CodeProps<T>) {
916914
pendingSelectionEnd = false
917915
})
918916

917+
const FindBar = (barProps: { class: string; style?: ComponentProps<"div">["style"] }) => (
918+
<div class={barProps.class} style={barProps.style} onPointerDown={(e) => e.stopPropagation()}>
919+
<Icon name="magnifying-glass" size="small" class="text-text-weak shrink-0" />
920+
<input
921+
ref={findInput}
922+
placeholder="Find"
923+
value={findQuery()}
924+
class="w-40 bg-transparent outline-none text-14-regular text-text-strong placeholder:text-text-weak"
925+
onInput={(e) => {
926+
setFindQuery(e.currentTarget.value)
927+
setFindIndex(0)
928+
applyFind({ reset: true, scroll: true })
929+
}}
930+
onKeyDown={(e) => {
931+
if (e.key === "Escape") {
932+
e.preventDefault()
933+
closeFind()
934+
return
935+
}
936+
if (e.key !== "Enter") return
937+
e.preventDefault()
938+
stepFind(e.shiftKey ? -1 : 1)
939+
}}
940+
/>
941+
<div class="shrink-0 text-12-regular text-text-weak tabular-nums text-right" style={{ width: "10ch" }}>
942+
{findCount() ? `${findIndex() + 1}/${findCount()}` : "0/0"}
943+
</div>
944+
<div class="flex items-center">
945+
<button
946+
type="button"
947+
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"
948+
disabled={findCount() === 0}
949+
aria-label="Previous match"
950+
onClick={() => stepFind(-1)}
951+
>
952+
<Icon name="chevron-down" size="small" class="rotate-180" />
953+
</button>
954+
<button
955+
type="button"
956+
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"
957+
disabled={findCount() === 0}
958+
aria-label="Next match"
959+
onClick={() => stepFind(1)}
960+
>
961+
<Icon name="chevron-down" size="small" />
962+
</button>
963+
</div>
964+
<button
965+
type="button"
966+
class="size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong"
967+
aria-label="Close search"
968+
onClick={closeFind}
969+
>
970+
<Icon name="close-small" size="small" />
971+
</button>
972+
</div>
973+
)
974+
919975
return (
920976
<div
921977
data-component="code"
@@ -936,65 +992,15 @@ export function Code<T>(props: CodeProps<T>) {
936992
}}
937993
>
938994
<Show when={findOpen()}>
939-
<div
940-
ref={findBar}
941-
class="z-50 flex h-8 items-center gap-2 rounded-md border border-border-base bg-background-base px-3 shadow-md"
942-
onPointerDown={(e) => e.stopPropagation()}
943-
>
944-
<Icon name="magnifying-glass" size="small" class="text-text-weak shrink-0" />
945-
<input
946-
ref={findInput}
947-
placeholder="Find"
948-
value={findQuery()}
949-
class="w-40 bg-transparent outline-none text-14-regular text-text-strong placeholder:text-text-weak"
950-
onInput={(e) => {
951-
setFindQuery(e.currentTarget.value)
952-
setFindIndex(0)
953-
applyFind({ reset: true, scroll: true })
954-
}}
955-
onKeyDown={(e) => {
956-
if (e.key === "Escape") {
957-
e.preventDefault()
958-
closeFind()
959-
return
960-
}
961-
if (e.key !== "Enter") return
962-
e.preventDefault()
963-
stepFind(e.shiftKey ? -1 : 1)
995+
<Portal>
996+
<FindBar
997+
class="fixed z-50 flex h-8 items-center gap-2 rounded-md border border-border-base bg-background-base px-3 shadow-md"
998+
style={{
999+
top: `${findPos().top}px`,
1000+
right: `${findPos().right}px`,
9641001
}}
9651002
/>
966-
<div class="shrink-0 text-12-regular text-text-weak tabular-nums text-right" style={{ width: "10ch" }}>
967-
{findCount() ? `${findIndex() + 1}/${findCount()}` : "0/0"}
968-
</div>
969-
<div class="flex items-center">
970-
<button
971-
type="button"
972-
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"
973-
disabled={findCount() === 0}
974-
aria-label="Previous match"
975-
onClick={() => stepFind(-1)}
976-
>
977-
<Icon name="chevron-down" size="small" class="rotate-180" />
978-
</button>
979-
<button
980-
type="button"
981-
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"
982-
disabled={findCount() === 0}
983-
aria-label="Next match"
984-
onClick={() => stepFind(1)}
985-
>
986-
<Icon name="chevron-down" size="small" />
987-
</button>
988-
</div>
989-
<button
990-
type="button"
991-
class="size-6 grid place-items-center rounded text-text-weak hover:bg-surface-base-hover hover:text-text-strong"
992-
aria-label="Close search"
993-
onClick={closeFind}
994-
>
995-
<Icon name="close-small" size="small" />
996-
</button>
997-
</div>
1003+
</Portal>
9981004
</Show>
9991005
<div ref={container} />
10001006
<div ref={findOverlay} class="pointer-events-none absolute inset-0 z-0" />

0 commit comments

Comments
 (0)