diff options
| author | Adam <[email protected]> | 2026-02-02 11:42:05 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2026-02-02 14:24:25 -0600 |
| commit | 69f5f657f2b3b98d213a7bedd46624cda0e78bcd (patch) | |
| tree | 77fc3b6d3177163e82a85665fc1a09338fd50992 /packages/ui/src/components/code.tsx | |
| parent | 0405b425f528ce9042ff0eeb511512e239cb1b5f (diff) | |
| download | opencode-69f5f657f2b3b98d213a7bedd46624cda0e78bcd.tar.gz opencode-69f5f657f2b3b98d213a7bedd46624cda0e78bcd.zip | |
chore: cleanup
Diffstat (limited to 'packages/ui/src/components/code.tsx')
| -rw-r--r-- | packages/ui/src/components/code.tsx | 118 |
1 files changed, 91 insertions, 27 deletions
diff --git a/packages/ui/src/components/code.tsx b/packages/ui/src/components/code.tsx index 38dfcd838..0b0646f0e 100644 --- a/packages/ui/src/components/code.tsx +++ b/packages/ui/src/components/code.tsx @@ -109,9 +109,8 @@ function installFindShortcuts() { return } - if (isEditable(event.target)) return - - const host = hostForNode(document.activeElement) ?? findTarget ?? Array.from(findHosts)[0] + const host = + hostForNode(document.activeElement) ?? hostForNode(event.target) ?? findTarget ?? Array.from(findHosts)[0] if (!host) return event.preventDefault() @@ -126,9 +125,11 @@ export function Code<T>(props: CodeProps<T>) { let wrapper!: HTMLDivElement let container!: HTMLDivElement let findInput: HTMLInputElement | undefined + let findBar: HTMLDivElement | undefined let findOverlay!: HTMLDivElement let findOverlayFrame: number | undefined let findOverlayScroll: HTMLElement[] = [] + let findPositionFrame: number | undefined let observer: MutationObserver | undefined let renderToken = 0 let selectionFrame: number | undefined @@ -290,6 +291,41 @@ export function Code<T>(props: CodeProps<T>) { setFindIndex(0) } + const getScrollParent = (el: HTMLElement): HTMLElement | null => { + let parent = el.parentElement + while (parent) { + const style = getComputedStyle(parent) + if (style.overflowY === "auto" || style.overflowY === "scroll") return parent + parent = parent.parentElement + } + return null + } + + const positionFindBar = () => { + if (!findBar || !wrapper) return + const scrollParent = getScrollParent(wrapper) + if (!scrollParent) { + findBar.style.position = "absolute" + findBar.style.top = "8px" + findBar.style.right = "8px" + findBar.style.left = "" + return + } + const scrollTop = scrollParent.scrollTop + findBar.style.position = "absolute" + findBar.style.top = `${scrollTop + 8}px` + findBar.style.right = "8px" + findBar.style.left = "" + } + + const scheduleFindPosition = () => { + if (findPositionFrame !== undefined) return + findPositionFrame = requestAnimationFrame(() => { + findPositionFrame = undefined + positionFindBar() + }) + } + const scanFind = (root: ShadowRoot, query: string) => { const needle = query.toLowerCase() const out: Range[] = [] @@ -458,6 +494,7 @@ export function Code<T>(props: CodeProps<T>) { if (!findOpen()) setFindOpen(true) requestAnimationFrame(() => { + positionFindBar() findInput?.focus() findInput?.select() }) @@ -482,6 +519,25 @@ export function Code<T>(props: CodeProps<T>) { }) }) + createEffect(() => { + if (!findOpen()) return + const scrollParent = getScrollParent(wrapper) + const target = scrollParent ?? window + + const handler = () => scheduleFindPosition() + target.addEventListener("scroll", handler, { passive: true }) + window.addEventListener("resize", handler, { passive: true }) + + onCleanup(() => { + target.removeEventListener("scroll", handler) + window.removeEventListener("resize", handler) + if (findPositionFrame !== undefined) { + cancelAnimationFrame(findPositionFrame) + findPositionFrame = undefined + } + }) + }) + const applyCommentedLines = (ranges: SelectedLineRange[]) => { const root = getRoot() if (!root) return @@ -862,6 +918,11 @@ export function Code<T>(props: CodeProps<T>) { dragFrame = undefined } + if (findPositionFrame !== undefined) { + cancelAnimationFrame(findPositionFrame) + findPositionFrame = undefined + } + dragStart = undefined dragEnd = undefined dragMoved = false @@ -888,19 +949,18 @@ export function Code<T>(props: CodeProps<T>) { findTarget = host }} > - <div ref={container} /> - <div ref={findOverlay} class="pointer-events-none absolute inset-0 z-0" /> <Show when={findOpen()}> <div - class="absolute top-2 right-2 z-10 flex items-center gap-1 rounded-md border border-border-weak-base bg-surface-raised-base px-2 py-1 shadow-xs-border" + ref={findBar} + class="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()} > - <Icon name="magnifying-glass" size="small" class="text-text-weak" /> + <Icon name="magnifying-glass" size="small" class="text-text-weak shrink-0" /> <input ref={findInput} placeholder="Find" value={findQuery()} - class="w-48 bg-transparent outline-none text-12-regular text-text-strong placeholder:text-text-weak" + 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) @@ -917,30 +977,32 @@ export function Code<T>(props: CodeProps<T>) { stepFind(e.shiftKey ? -1 : 1) }} /> - <div class="px-1 text-12-regular text-text-weak tabular-nums"> + <div class="shrink-0 text-12-regular text-text-weak tabular-nums"> {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" - 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={findCount() === 0} - aria-label="Next match" - onClick={() => stepFind(1)} - > - <Icon name="chevron-down" size="small" /> - </button> - <button - type="button" - class="ml-1 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} > @@ -948,6 +1010,8 @@ export function Code<T>(props: CodeProps<T>) { </button> </div> </Show> + <div ref={container} /> + <div ref={findOverlay} class="pointer-events-none absolute inset-0 z-0" /> </div> ) } |
