Skip to content

Commit 85afaaa

Browse files
committed
fix(app): terminal focus issues and jank
1 parent 4906151 commit 85afaaa

File tree

1 file changed

+49
-21
lines changed

1 file changed

+49
-21
lines changed

packages/app/src/pages/session/terminal-panel.tsx

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { For, Show, createEffect, createMemo, on } from "solid-js"
1+
import { For, Show, createEffect, createMemo, on, onCleanup } from "solid-js"
22
import { createStore } from "solid-js/store"
33
import { createMediaQuery } from "@solid-primitives/media"
44
import { useParams } from "@solidjs/router"
@@ -17,7 +17,7 @@ import { useLanguage } from "@/context/language"
1717
import { useLayout } from "@/context/layout"
1818
import { useTerminal, type LocalPTY } from "@/context/terminal"
1919
import { terminalTabLabel } from "@/pages/session/terminal-label"
20-
import { createPresence, createSizing, focusTerminalById } from "@/pages/session/helpers"
20+
import { createSizing, focusTerminalById } from "@/pages/session/helpers"
2121
import { getTerminalHandoff, setTerminalHandoff } from "@/pages/session/handoff"
2222

2323
export function TerminalPanel() {
@@ -33,7 +33,6 @@ export function TerminalPanel() {
3333

3434
const opened = createMemo(() => view().terminal.opened())
3535
const open = createMemo(() => isDesktop() && opened())
36-
const panel = createPresence(open)
3736
const size = createSizing()
3837
const height = createMemo(() => layout.terminal.height())
3938
const close = () => view().terminal.close()
@@ -66,21 +65,42 @@ export function TerminalPanel() {
6665
),
6766
)
6867

68+
const focus = (id: string) => {
69+
focusTerminalById(id)
70+
71+
const frame = requestAnimationFrame(() => {
72+
if (!open()) return
73+
if (terminal.active() !== id) return
74+
focusTerminalById(id)
75+
})
76+
77+
const timers = [120, 240].map((ms) =>
78+
window.setTimeout(() => {
79+
if (!open()) return
80+
if (terminal.active() !== id) return
81+
focusTerminalById(id)
82+
}, ms),
83+
)
84+
85+
return () => {
86+
cancelAnimationFrame(frame)
87+
for (const timer of timers) clearTimeout(timer)
88+
}
89+
}
90+
6991
createEffect(
7092
on(
71-
() => terminal.active(),
72-
(activeId) => {
73-
if (!activeId || !panel.open()) return
74-
if (document.activeElement instanceof HTMLElement) {
75-
document.activeElement.blur()
76-
}
77-
setTimeout(() => focusTerminalById(activeId), 0)
93+
() => [open(), terminal.active()] as const,
94+
([next, id]) => {
95+
if (!next || !id) return
96+
const stop = focus(id)
97+
onCleanup(stop)
7898
},
7999
),
80100
)
81101

82102
createEffect(() => {
83-
if (panel.open()) return
103+
if (open()) return
84104
const active = document.activeElement
85105
if (!(active instanceof HTMLElement)) return
86106
if (!root?.contains(active)) return
@@ -138,30 +158,38 @@ export function TerminalPanel() {
138158

139159
const activeId = terminal.active()
140160
if (!activeId) return
141-
setTimeout(() => {
161+
requestAnimationFrame(() => {
162+
if (terminal.active() !== activeId) return
142163
focusTerminalById(activeId)
143-
}, 0)
164+
})
144165
}
145166

146167
return (
147-
<Show when={panel.show()}>
168+
<Show when={isDesktop()}>
148169
<div
149170
ref={root}
150171
id="terminal-panel"
151172
role="region"
152173
aria-label={language.t("terminal.title")}
153-
aria-hidden={!panel.open()}
154-
inert={!panel.open()}
174+
aria-hidden={!open()}
175+
inert={!open()}
155176
class="relative w-full shrink-0 overflow-hidden"
156177
classList={{
157-
"opacity-100": panel.open(),
158-
"opacity-0 pointer-events-none": !panel.open(),
159-
"transition-[height,opacity] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[height] motion-reduce:transition-none":
178+
"transition-[height] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[height] motion-reduce:transition-none":
160179
!size.active(),
161180
}}
162-
style={{ height: panel.open() ? `${height()}px` : "0px" }}
181+
style={{ height: open() ? `${height()}px` : "0px" }}
163182
>
164-
<div class="size-full flex flex-col border-t border-border-weak-base">
183+
<div
184+
class="absolute inset-x-0 top-0 flex flex-col border-t border-border-weak-base"
185+
classList={{
186+
"translate-y-0 opacity-100": open(),
187+
"translate-y-full opacity-0 pointer-events-none": !open(),
188+
"transition-[transform,opacity] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[transform,opacity] motion-reduce:transition-none":
189+
!size.active(),
190+
}}
191+
style={{ height: `${height()}px` }}
192+
>
165193
<div onPointerDown={() => size.start()}>
166194
<ResizeHandle
167195
direction="vertical"

0 commit comments

Comments
 (0)