mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-30 13:54:01 +00:00
fix(app): terminal focus issues and jank
This commit is contained in:
parent
490615169e
commit
85afaaa13d
@ -1,4 +1,4 @@
|
||||
import { For, Show, createEffect, createMemo, on } from "solid-js"
|
||||
import { For, Show, createEffect, createMemo, on, onCleanup } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { createMediaQuery } from "@solid-primitives/media"
|
||||
import { useParams } from "@solidjs/router"
|
||||
@ -17,7 +17,7 @@ import { useLanguage } from "@/context/language"
|
||||
import { useLayout } from "@/context/layout"
|
||||
import { useTerminal, type LocalPTY } from "@/context/terminal"
|
||||
import { terminalTabLabel } from "@/pages/session/terminal-label"
|
||||
import { createPresence, createSizing, focusTerminalById } from "@/pages/session/helpers"
|
||||
import { createSizing, focusTerminalById } from "@/pages/session/helpers"
|
||||
import { getTerminalHandoff, setTerminalHandoff } from "@/pages/session/handoff"
|
||||
|
||||
export function TerminalPanel() {
|
||||
@ -33,7 +33,6 @@ export function TerminalPanel() {
|
||||
|
||||
const opened = createMemo(() => view().terminal.opened())
|
||||
const open = createMemo(() => isDesktop() && opened())
|
||||
const panel = createPresence(open)
|
||||
const size = createSizing()
|
||||
const height = createMemo(() => layout.terminal.height())
|
||||
const close = () => view().terminal.close()
|
||||
@ -66,21 +65,42 @@ export function TerminalPanel() {
|
||||
),
|
||||
)
|
||||
|
||||
const focus = (id: string) => {
|
||||
focusTerminalById(id)
|
||||
|
||||
const frame = requestAnimationFrame(() => {
|
||||
if (!open()) return
|
||||
if (terminal.active() !== id) return
|
||||
focusTerminalById(id)
|
||||
})
|
||||
|
||||
const timers = [120, 240].map((ms) =>
|
||||
window.setTimeout(() => {
|
||||
if (!open()) return
|
||||
if (terminal.active() !== id) return
|
||||
focusTerminalById(id)
|
||||
}, ms),
|
||||
)
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(frame)
|
||||
for (const timer of timers) clearTimeout(timer)
|
||||
}
|
||||
}
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => terminal.active(),
|
||||
(activeId) => {
|
||||
if (!activeId || !panel.open()) return
|
||||
if (document.activeElement instanceof HTMLElement) {
|
||||
document.activeElement.blur()
|
||||
}
|
||||
setTimeout(() => focusTerminalById(activeId), 0)
|
||||
() => [open(), terminal.active()] as const,
|
||||
([next, id]) => {
|
||||
if (!next || !id) return
|
||||
const stop = focus(id)
|
||||
onCleanup(stop)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
createEffect(() => {
|
||||
if (panel.open()) return
|
||||
if (open()) return
|
||||
const active = document.activeElement
|
||||
if (!(active instanceof HTMLElement)) return
|
||||
if (!root?.contains(active)) return
|
||||
@ -138,30 +158,38 @@ export function TerminalPanel() {
|
||||
|
||||
const activeId = terminal.active()
|
||||
if (!activeId) return
|
||||
setTimeout(() => {
|
||||
requestAnimationFrame(() => {
|
||||
if (terminal.active() !== activeId) return
|
||||
focusTerminalById(activeId)
|
||||
}, 0)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Show when={panel.show()}>
|
||||
<Show when={isDesktop()}>
|
||||
<div
|
||||
ref={root}
|
||||
id="terminal-panel"
|
||||
role="region"
|
||||
aria-label={language.t("terminal.title")}
|
||||
aria-hidden={!panel.open()}
|
||||
inert={!panel.open()}
|
||||
aria-hidden={!open()}
|
||||
inert={!open()}
|
||||
class="relative w-full shrink-0 overflow-hidden"
|
||||
classList={{
|
||||
"opacity-100": panel.open(),
|
||||
"opacity-0 pointer-events-none": !panel.open(),
|
||||
"transition-[height,opacity] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[height] motion-reduce:transition-none":
|
||||
"transition-[height] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[height] motion-reduce:transition-none":
|
||||
!size.active(),
|
||||
}}
|
||||
style={{ height: panel.open() ? `${height()}px` : "0px" }}
|
||||
style={{ height: open() ? `${height()}px` : "0px" }}
|
||||
>
|
||||
<div class="size-full flex flex-col border-t border-border-weak-base">
|
||||
<div
|
||||
class="absolute inset-x-0 top-0 flex flex-col border-t border-border-weak-base"
|
||||
classList={{
|
||||
"translate-y-0 opacity-100": open(),
|
||||
"translate-y-full opacity-0 pointer-events-none": !open(),
|
||||
"transition-[transform,opacity] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[transform,opacity] motion-reduce:transition-none":
|
||||
!size.active(),
|
||||
}}
|
||||
style={{ height: `${height()}px` }}
|
||||
>
|
||||
<div onPointerDown={() => size.start()}>
|
||||
<ResizeHandle
|
||||
direction="vertical"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user