feat(journal): improve command completion behavior

- Scroll the selected completion item into view
- Filter commands based on prefix when typing a slash command
- Keep completion menu open while typing a command
- Add data attributes to track completion indices for scrolling
This commit is contained in:
hypercross 2026-07-06 17:20:20 +08:00
parent e0d61cf91e
commit 276241f3b0
1 changed files with 28 additions and 16 deletions

View File

@ -12,6 +12,7 @@
import {
Component,
createSignal,
createEffect,
onMount,
onCleanup,
Show,
@ -126,18 +127,34 @@ export const JournalInput: Component = () => {
revertLatest();
}
// ---- Scroll selected completion into view ----
createEffect(() => {
const idx = selectedIdx();
if (!showCompletions() || !completionsRef) return;
const el = completionsRef.querySelector(`[data-comp-idx="${idx}"]`);
if (el) el.scrollIntoView({ block: "nearest" });
});
// ---- Completions ----
function buildCompletions(): CompletionItem[] {
const raw = text().trim();
const data = comp.data;
// Show commands when user types /
if (raw === "" || raw === "/") {
return [
{ label: "/roll", kind: "command", insertText: "/roll " },
{ label: "/link", kind: "command", insertText: "/link " },
const commands = [
{ label: "/roll", kind: "command" as const, insertText: "/roll " },
{ label: "/link", kind: "command" as const, insertText: "/link " },
];
// Show commands when user types / or starts typing a command name
if (raw.startsWith("/") && !raw.includes(" ")) {
const prefix = raw.toLowerCase();
const matches = commands.filter((c) =>
c.label.toLowerCase().startsWith(prefix),
);
return matches.length > 0
? matches
: [{ label: "Unknown command", kind: "no-results", insertText: "" }];
}
// After /roll — show dice suggestions
@ -226,9 +243,9 @@ export const JournalInput: Component = () => {
}
return;
}
// If not open and typing /, open completions
// If not open and starts with /, open completions
const raw = text();
if (raw === "/" || raw.startsWith("/roll") || raw.startsWith("/link")) {
if (raw.startsWith("/")) {
e.preventDefault();
setShowCompletions(true);
setSelectedIdx(0);
@ -271,14 +288,8 @@ export const JournalInput: Component = () => {
const raw = input.value;
setText(raw);
// Completions visibility
if (
raw === "/" ||
raw.startsWith("/roll ") ||
raw.startsWith("/link ") ||
raw === "/roll" ||
raw === "/link"
) {
// Completions visibility — keep open while user types any /
if (raw.startsWith("/")) {
setShowCompletions(true);
setSelectedIdx(0);
} else {
@ -317,6 +328,7 @@ export const JournalInput: Component = () => {
<For each={comps}>
{(item, idx) => (
<div
data-comp-idx={idx()}
class={`flex items-center gap-2 px-3 py-1.5 cursor-pointer text-sm ${
item.kind === "no-results"
? "text-gray-400 italic cursor-default"