Files
ttrpg-tools/src/components/md-commander/CommanderInput.tsx
T
2026-03-01 11:38:14 +08:00

103 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {type Component, For, Show, createEffect, on} from "solid-js";
import type { CompletionItem } from "./types";
export interface CommanderInputProps {
placeholder?: string;
inputValue: () => string;
onInput: (e: Event) => void;
onKeyDown: (e: KeyboardEvent) => void;
onFocus: () => void;
onBlur: () => void;
onSubmit: () => void;
showCompletions: () => boolean;
completions: () => CompletionItem[];
selectedCompletion: () => number;
onSelectCompletion: (idx: number) => void;
onAcceptCompletion: () => void;
}
export const CommanderInput: Component<CommanderInputProps> = (props) => {
let containerRef: HTMLDivElement | undefined;
// 当选中项变化时自动滚动到可见区域
createEffect(
on(
() => props.selectedCompletion(),
() => {
if (containerRef) {
const selectedItem = containerRef.querySelector('[data-selected="true"]');
if (selectedItem) {
selectedItem.scrollIntoView({ block: "nearest" });
}
}
},
),
);
const maxVisible = 3;
return (
<div class="flex items-center border-b border-gray-300 bg-gray-50 px-3 py-2">
<span class="text-gray-500 mr-2">❯</span>
<input
type="text"
value={props.inputValue()}
onInput={props.onInput}
onKeyDown={props.onKeyDown}
onFocus={props.onFocus}
onBlur={props.onBlur}
placeholder={props.placeholder || "输入命令..."}
class="flex-1 bg-transparent outline-none text-gray-800"
/>
<button
onClick={props.onSubmit}
class="ml-2 px-3 py-1 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
>
执行
</button>
{/* 自动补全下拉框 - 向上弹出 */}
<Show when={props.showCompletions() && props.completions().length > 0}>
<div
ref={containerRef}
class="absolute left-0 bottom-full w-full bg-white border border-gray-300 shadow-lg mb-1 overflow-auto"
style={{ "max-height": `${maxVisible * 40}px` }}
>
<For each={props.completions()}>{(comp, idx) => (
<div
data-selected={idx() === props.selectedCompletion()}
class={`px-3 py-2 cursor-pointer flex justify-between items-center ${
idx() === props.selectedCompletion()
? "bg-blue-100"
: "hover:bg-gray-100"
}`}
onClick={() => {
props.onSelectCompletion(idx());
props.onAcceptCompletion();
}}
>
<div class="flex items-center gap-2">
<span
class={`text-xs px-1 rounded ${
comp.type === "command"
? "bg-purple-100 text-purple-700"
: comp.type === "option"
? "bg-green-100 text-green-700"
: "bg-gray-100 text-gray-700"
}`}
>
{comp.type}
</span>
<span class="font-mono text-sm">{comp.label}</span>
</div>
<Show when={comp.description}>
<span class="text-xs text-gray-500">{comp.description}</span>
</Show>
</div>
)}</For>
</div>
</Show>
</div>
);
};