mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-18 06:34:50 +00:00
fix(app): terminal focus issues and jank
This commit is contained in:
@@ -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 { createStore } from "solid-js/store"
|
||||||
import { createMediaQuery } from "@solid-primitives/media"
|
import { createMediaQuery } from "@solid-primitives/media"
|
||||||
import { useParams } from "@solidjs/router"
|
import { useParams } from "@solidjs/router"
|
||||||
@@ -17,7 +17,7 @@ import { useLanguage } from "@/context/language"
|
|||||||
import { useLayout } from "@/context/layout"
|
import { useLayout } from "@/context/layout"
|
||||||
import { useTerminal, type LocalPTY } from "@/context/terminal"
|
import { useTerminal, type LocalPTY } from "@/context/terminal"
|
||||||
import { terminalTabLabel } from "@/pages/session/terminal-label"
|
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"
|
import { getTerminalHandoff, setTerminalHandoff } from "@/pages/session/handoff"
|
||||||
|
|
||||||
export function TerminalPanel() {
|
export function TerminalPanel() {
|
||||||
@@ -33,7 +33,6 @@ export function TerminalPanel() {
|
|||||||
|
|
||||||
const opened = createMemo(() => view().terminal.opened())
|
const opened = createMemo(() => view().terminal.opened())
|
||||||
const open = createMemo(() => isDesktop() && opened())
|
const open = createMemo(() => isDesktop() && opened())
|
||||||
const panel = createPresence(open)
|
|
||||||
const size = createSizing()
|
const size = createSizing()
|
||||||
const height = createMemo(() => layout.terminal.height())
|
const height = createMemo(() => layout.terminal.height())
|
||||||
const close = () => view().terminal.close()
|
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(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
() => terminal.active(),
|
() => [open(), terminal.active()] as const,
|
||||||
(activeId) => {
|
([next, id]) => {
|
||||||
if (!activeId || !panel.open()) return
|
if (!next || !id) return
|
||||||
if (document.activeElement instanceof HTMLElement) {
|
const stop = focus(id)
|
||||||
document.activeElement.blur()
|
onCleanup(stop)
|
||||||
}
|
|
||||||
setTimeout(() => focusTerminalById(activeId), 0)
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (panel.open()) return
|
if (open()) return
|
||||||
const active = document.activeElement
|
const active = document.activeElement
|
||||||
if (!(active instanceof HTMLElement)) return
|
if (!(active instanceof HTMLElement)) return
|
||||||
if (!root?.contains(active)) return
|
if (!root?.contains(active)) return
|
||||||
@@ -138,30 +158,38 @@ export function TerminalPanel() {
|
|||||||
|
|
||||||
const activeId = terminal.active()
|
const activeId = terminal.active()
|
||||||
if (!activeId) return
|
if (!activeId) return
|
||||||
setTimeout(() => {
|
requestAnimationFrame(() => {
|
||||||
|
if (terminal.active() !== activeId) return
|
||||||
focusTerminalById(activeId)
|
focusTerminalById(activeId)
|
||||||
}, 0)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={panel.show()}>
|
<Show when={isDesktop()}>
|
||||||
<div
|
<div
|
||||||
ref={root}
|
ref={root}
|
||||||
id="terminal-panel"
|
id="terminal-panel"
|
||||||
role="region"
|
role="region"
|
||||||
aria-label={language.t("terminal.title")}
|
aria-label={language.t("terminal.title")}
|
||||||
aria-hidden={!panel.open()}
|
aria-hidden={!open()}
|
||||||
inert={!panel.open()}
|
inert={!open()}
|
||||||
class="relative w-full shrink-0 overflow-hidden"
|
class="relative w-full shrink-0 overflow-hidden"
|
||||||
classList={{
|
classList={{
|
||||||
"opacity-100": panel.open(),
|
"transition-[height] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[height] motion-reduce:transition-none":
|
||||||
"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":
|
|
||||||
!size.active(),
|
!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()}>
|
<div onPointerDown={() => size.start()}>
|
||||||
<ResizeHandle
|
<ResizeHandle
|
||||||
direction="vertical"
|
direction="vertical"
|
||||||
|
|||||||
Reference in New Issue
Block a user