Skip to content

Commit d0f1554

Browse files
committed
fix scroll issues
1 parent 828329e commit d0f1554

File tree

3 files changed

+95
-46
lines changed

3 files changed

+95
-46
lines changed

src/commandPalette.tsx

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,48 @@
33
* Modal overlay with command search and execution
44
*/
55

6-
import { For, Show, createMemo } from "solid-js";
6+
import { For, Show, createMemo, createEffect } from "solid-js";
77
import { useStore } from "./store";
88
import { getCommands, filterCommands } from "./commands";
99
import { useTerminalDimensions } from "@opentui/solid";
10-
import { RGBA } from "@opentui/core";
10+
import { RGBA, type ScrollBoxRenderable } from "@opentui/core";
1111

1212
export function CommandPalette() {
1313
const { store, actions } = useStore();
1414
const dimensions = useTerminalDimensions();
15+
let scrollBoxRef: ScrollBoxRenderable | undefined;
1516

1617
// Get filtered commands
1718
const filteredCommands = createMemo(() => {
1819
const allCommands = getCommands(actions);
1920
return filterCommands(allCommands, store.ui.commandPaletteQuery);
2021
});
2122

23+
// Auto-scroll when selection changes
24+
createEffect(() => {
25+
const selectedIndex = store.ui.selectedCommandIndex;
26+
if (!scrollBoxRef) return;
27+
28+
// Find the target box by ID
29+
const target = scrollBoxRef.getChildren().find((child) => {
30+
return child.id === `command-${selectedIndex}`;
31+
});
32+
33+
if (!target) return;
34+
35+
// Calculate relative position
36+
const y = target.y - scrollBoxRef.y;
37+
38+
// Scroll down if needed
39+
if (y >= scrollBoxRef.height) {
40+
scrollBoxRef.scrollBy(y - scrollBoxRef.height + 1);
41+
}
42+
// Scroll up if needed
43+
if (y < 0) {
44+
scrollBoxRef.scrollBy(y);
45+
}
46+
});
47+
2248
return (
2349
<Show when={store.ui.showCommandPalette}>
2450
{/* Full-screen overlay - positioned absolutely */}
@@ -38,7 +64,7 @@ export function CommandPalette() {
3864
maxWidth={dimensions().width - 2}
3965
flexDirection="column"
4066
backgroundColor="#1a1b26"
41-
border={["all"]}
67+
border={["top", "bottom", "left", "right"]}
4268
borderColor="#414868"
4369
paddingTop={1}
4470
>
@@ -56,11 +82,13 @@ export function CommandPalette() {
5682

5783
{/* Command list */}
5884
<scrollbox
85+
ref={(r) => (scrollBoxRef = r)}
5986
flexDirection="column"
60-
height={12}
87+
height={5}
6188
flexShrink={0}
6289
paddingLeft={2}
6390
paddingRight={2}
91+
scrollbarOptions={{ visible: false }}
6492
>
6593
<Show
6694
when={filteredCommands().length > 0}
@@ -75,6 +103,7 @@ export function CommandPalette() {
75103

76104
return (
77105
<box
106+
id={`command-${index()}`}
78107
flexDirection="row"
79108
justifyContent="space-between"
80109
backgroundColor={isSelected() ? "#414868" : undefined}

src/index.tsx

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env node
2+
console.log("[INDEX MODULE] Loading index.tsx");
23
import { render, useKeyboard, useRenderer } from "@opentui/solid";
3-
import { Show, createMemo, onMount } from "solid-js";
4+
import { Show, createMemo, onMount, createEffect } from "solid-js";
45
import * as Option from "effect/Option";
56
import { PORT } from "./runtime";
67
import { StoreProvider, useStore, type FocusedSection } from "./store";
@@ -95,6 +96,33 @@ function AppContent() {
9596
}
9697
});
9798

99+
// Auto-scroll to keep selected span in view
100+
createEffect(() => {
101+
const selectedSpanId = store.ui.selectedSpanId;
102+
if (!selectedSpanId || !spansScrollBoxRef) return;
103+
104+
// The span tree is wrapped in a box, so we need to get the children of that box
105+
const scrollBoxChildren = spansScrollBoxRef.getChildren();
106+
if (scrollBoxChildren.length === 0) return;
107+
108+
// Get the actual span text elements (children of the first box)
109+
const spanElements = scrollBoxChildren[0].getChildren();
110+
const target = spanElements.find((child) => child.id === selectedSpanId);
111+
if (!target) return;
112+
113+
// Calculate relative position
114+
const y = target.y - spansScrollBoxRef.y;
115+
116+
// Scroll down if needed
117+
if (y >= spansScrollBoxRef.height) {
118+
spansScrollBoxRef.scrollBy(y - spansScrollBoxRef.height + 1);
119+
}
120+
// Scroll up if needed
121+
if (y < 0) {
122+
spansScrollBoxRef.scrollBy(y);
123+
}
124+
});
125+
98126
// Setup keyboard handlers
99127
useKeyboard((key) => {
100128
// Quit handlers

src/store.tsx

Lines changed: 33 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
} from "solid-js";
1919
import type * as Domain from "@effect/experimental/DevTools/Domain";
2020
import type { Client } from "./server";
21+
import { getCommands, filterCommands } from "./commands";
2122

2223
// =============================================================================
2324
// Types
@@ -587,57 +588,48 @@ export function StoreProvider(props: ParentProps) {
587588
},
588589

589590
navigateCommandUp: () => {
590-
// Import commands to get the filtered list length
591-
import("./commands").then(({ getCommands, filterCommands }) => {
592-
const allCommands = getCommands(actions);
593-
const filtered = filterCommands(
594-
allCommands,
595-
store.ui.commandPaletteQuery,
596-
);
597-
if (filtered.length === 0) return;
591+
const allCommands = getCommands(actions);
592+
const filtered = filterCommands(
593+
allCommands,
594+
store.ui.commandPaletteQuery,
595+
);
596+
if (filtered.length === 0) return;
598597

599-
setStore("ui", "selectedCommandIndex", (prev) => {
600-
if (prev <= 0) return filtered.length - 1;
601-
return prev - 1;
602-
});
598+
setStore("ui", "selectedCommandIndex", (prev) => {
599+
if (prev <= 0) return filtered.length - 1;
600+
return prev - 1;
603601
});
604602
},
605603

606604
navigateCommandDown: () => {
607-
// Import commands to get the filtered list length
608-
import("./commands").then(({ getCommands, filterCommands }) => {
609-
const allCommands = getCommands(actions);
610-
const filtered = filterCommands(
611-
allCommands,
612-
store.ui.commandPaletteQuery,
613-
);
614-
if (filtered.length === 0) return;
605+
const allCommands = getCommands(actions);
606+
const filtered = filterCommands(
607+
allCommands,
608+
store.ui.commandPaletteQuery,
609+
);
610+
if (filtered.length === 0) return;
615611

616-
setStore("ui", "selectedCommandIndex", (prev) => {
617-
if (prev >= filtered.length - 1) return 0;
618-
return prev + 1;
619-
});
612+
setStore("ui", "selectedCommandIndex", (prev) => {
613+
if (prev >= filtered.length - 1) return 0;
614+
return prev + 1;
620615
});
621616
},
622617

623618
executeSelectedCommand: () => {
624-
// Import commands to get the selected command
625-
import("./commands").then(({ getCommands, filterCommands }) => {
626-
const allCommands = getCommands(actions);
627-
const filtered = filterCommands(
628-
allCommands,
629-
store.ui.commandPaletteQuery,
630-
);
631-
const selected = filtered[store.ui.selectedCommandIndex];
632-
if (selected) {
633-
selected.execute();
634-
batch(() => {
635-
setStore("ui", "showCommandPalette", false);
636-
setStore("ui", "commandPaletteQuery", "");
637-
setStore("ui", "selectedCommandIndex", 0);
638-
});
639-
}
640-
});
619+
const allCommands = getCommands(actions);
620+
const filtered = filterCommands(
621+
allCommands,
622+
store.ui.commandPaletteQuery,
623+
);
624+
const selected = filtered[store.ui.selectedCommandIndex];
625+
if (selected) {
626+
selected.execute();
627+
batch(() => {
628+
setStore("ui", "showCommandPalette", false);
629+
setStore("ui", "commandPaletteQuery", "");
630+
setStore("ui", "selectedCommandIndex", 0);
631+
});
632+
}
641633
},
642634

643635
expandAllSpans: () => {

0 commit comments

Comments
 (0)