ttrpg-tools/src/components/md-commander/index.tsx

109 lines
3.4 KiB
TypeScript
Raw Normal View History

2026-02-28 16:28:07 +08:00
import { customElement, noShadowDOM } from "solid-element";
import { onMount, onCleanup } from "solid-js";
import { useCommander } from "./hooks";
import { CommanderInput } from "./CommanderInput";
import { CommanderEntries } from "./CommanderEntries";
import type { MdCommanderProps } from "./types";
export { CommanderInput } from './CommanderInput';
export type { CommanderInputProps } from './CommanderInput';
export { CommanderEntries } from './CommanderEntries';
export type { CommanderEntriesProps } from './CommanderEntries';
export type {
MdCommanderProps,
MdCommanderCommand,
MdCommanderOption,
MdCommanderOptionType,
CommanderEntry,
CompletionItem,
} from './types';
customElement<MdCommanderProps>(
"md-commander",
{ placeholder: "", class: "", height: "" },
(props, { element }) => {
noShadowDOM();
const commander = useCommander(props.commands);
const handleKeyDown = (e: KeyboardEvent) => {
if (commander.showCompletions() && commander.completions().length > 0) {
if (e.key === "ArrowDown") {
e.preventDefault();
commander.setSelectedCompletion(
(prev) => (prev + 1) % commander.completions().length,
);
return;
}
if (e.key === "ArrowUp") {
e.preventDefault();
commander.setSelectedCompletion(
(prev) => (prev - 1 + commander.completions().length) % commander.completions().length,
);
return;
}
if (e.key === "Tab") {
e.preventDefault();
commander.acceptCompletion();
return;
}
if (e.key === "Escape") {
commander.setShowCompletions(false);
return;
}
}
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
commander.handleCommand();
}
};
const heightStyle = () => props.height || "400px";
onMount(() => {
const handleClickOutside = (e: MouseEvent) => {
if (!element?.contains(e.target as Node)) {
commander.setShowCompletions(false);
}
};
document.addEventListener("click", handleClickOutside);
onCleanup(() => document.removeEventListener("click", handleClickOutside));
});
return (
<div
class={`md-commander flex flex-col border border-gray-300 rounded-lg overflow-hidden ${props.class || ""}`}
style={{ height: heightStyle() }}
>
{/* 命令输入框 */}
<div class="relative">
<CommanderInput
placeholder={props.placeholder}
inputValue={commander.inputValue}
onInput={(e) => {
commander.setInputValue((e.target as HTMLInputElement).value);
commander.updateCompletions();
}}
onKeyDown={handleKeyDown}
onFocus={() => {
commander.setIsFocused(true);
commander.updateCompletions();
}}
onBlur={() => commander.setIsFocused(false)}
onSubmit={commander.handleCommand}
showCompletions={commander.showCompletions}
completions={commander.completions}
selectedCompletion={commander.selectedCompletion}
onSelectCompletion={commander.setSelectedCompletion}
onAcceptCompletion={commander.acceptCompletion}
/>
</div>
{/* 命令执行结果 */}
<CommanderEntries entries={commander.entries} />
</div>
);
},
);