refactor command feature
This commit is contained in:
parent
824d98b9c8
commit
6e17401992
8 changed files with 216 additions and 378 deletions
51
src/features/command/command.ts
Normal file
51
src/features/command/command.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { DictionaryKey } from "../i18n";
|
||||
|
||||
export enum Modifier {
|
||||
None = 0,
|
||||
Shift = 1 << 0,
|
||||
Control = 1 << 1,
|
||||
Meta = 1 << 2,
|
||||
Alt = 1 << 3,
|
||||
}
|
||||
|
||||
export interface CommandType<T extends (...args: any[]) => any = (...args: any[]) => any> {
|
||||
(...args: Parameters<T>): Promise<ReturnType<T>>;
|
||||
label: DictionaryKey;
|
||||
shortcut?: {
|
||||
key: string;
|
||||
modifier: Modifier;
|
||||
};
|
||||
withLabel(label: string): CommandType<T>;
|
||||
with<A extends any[], B extends any[]>(this: (this: ThisParameterType<T>, ...args: [...A, ...B]) => ReturnType<T>, ...args: A): CommandType<(...args: B) => ReturnType<T>>;
|
||||
}
|
||||
|
||||
export const createCommand = <T extends (...args: any[]) => any>(label: DictionaryKey, command: T, shortcut?: CommandType['shortcut']): CommandType<T> => {
|
||||
return Object.defineProperties(((...args: Parameters<T>) => command(...args)) as any, {
|
||||
label: {
|
||||
value: label,
|
||||
configurable: false,
|
||||
writable: false,
|
||||
},
|
||||
shortcut: {
|
||||
value: shortcut ? { key: shortcut.key.toLowerCase(), modifier: shortcut.modifier } : undefined,
|
||||
configurable: false,
|
||||
writable: false,
|
||||
},
|
||||
withLabel: {
|
||||
value(label: DictionaryKey) {
|
||||
return createCommand(label, command, shortcut);
|
||||
},
|
||||
configurable: false,
|
||||
writable: false,
|
||||
},
|
||||
with: {
|
||||
value<A extends any[], B extends any[]>(this: (this: ThisParameterType<T>, ...args: [...A, ...B]) => ReturnType<T>, ...args: A): CommandType<(...args: B) => ReturnType<T>> {
|
||||
return createCommand(label, command.bind(undefined, ...args), shortcut);
|
||||
},
|
||||
configurable: false,
|
||||
writable: false,
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const noop = createCommand('noop' as any, () => { });
|
154
src/features/command/context.tsx
Normal file
154
src/features/command/context.tsx
Normal file
|
@ -0,0 +1,154 @@
|
|||
import { Accessor, children, Component, createContext, createEffect, createMemo, For, JSX, ParentComponent, ParentProps, Show, useContext } from 'solid-js';
|
||||
import { useI18n } from '../i18n';
|
||||
import { createStore } from 'solid-js/store';
|
||||
import { CommandType, Modifier } from './command';
|
||||
|
||||
interface CommandContextType {
|
||||
readonly commands: Accessor<CommandType[]>;
|
||||
set(commands: CommandType<any>[]): void;
|
||||
addContextualArguments<T extends (...args: any[]) => any = any>(command: CommandType<T>, target: EventTarget, args: Accessor<Parameters<T>>): void;
|
||||
execute<T extends (...args: any[]) => any = any>(command: CommandType<T>, event: Event): void;
|
||||
}
|
||||
|
||||
interface CommandContextStateType {
|
||||
commands: CommandType[];
|
||||
contextualArguments: Map<CommandType, WeakMap<EventTarget, Accessor<any[]>>>;
|
||||
}
|
||||
|
||||
const CommandContext = createContext<CommandContextType>();
|
||||
|
||||
export const useCommands = () => useContext(CommandContext);
|
||||
|
||||
const Root: ParentComponent<{ commands: CommandType[] }> = (props) => {
|
||||
const [store, setStore] = createStore<CommandContextStateType>({ commands: [], contextualArguments: new Map() });
|
||||
|
||||
const context = {
|
||||
commands: createMemo(() => store.commands),
|
||||
|
||||
set(commands: CommandType<any>[]): void {
|
||||
setStore('commands', existing => new Set([...existing, ...commands]).values().toArray());
|
||||
},
|
||||
|
||||
addContextualArguments<T extends (...args: any[]) => any = any>(command: CommandType<T>, target: EventTarget, args: Accessor<Parameters<T>>): void {
|
||||
setStore('contextualArguments', prev => {
|
||||
if (prev.has(command) === false) {
|
||||
prev.set(command, new WeakMap());
|
||||
}
|
||||
|
||||
prev.get(command)?.set(target, args);
|
||||
|
||||
return new Map(prev);
|
||||
})
|
||||
},
|
||||
|
||||
execute<T extends (...args: any[]) => any = any>(command: CommandType<T>, event: Event): boolean | undefined {
|
||||
const args = ((): Parameters<T> => {
|
||||
const contexts = store.contextualArguments.get(command);
|
||||
|
||||
if (contexts === undefined) {
|
||||
return [] as any;
|
||||
}
|
||||
|
||||
const element = event.composedPath().find(el => contexts.has(el));
|
||||
|
||||
if (element === undefined) {
|
||||
return [] as any;
|
||||
}
|
||||
|
||||
const args = contexts.get(element)! as Accessor<Parameters<T>>;
|
||||
|
||||
return args();
|
||||
})();
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
command(...args);
|
||||
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
context.set(props.commands ?? []);
|
||||
});
|
||||
|
||||
const listener = (e: KeyboardEvent) => {
|
||||
const key = e.key.toLowerCase();
|
||||
const modifiers =
|
||||
(e.shiftKey ? 1 : 0) << 0 |
|
||||
(e.ctrlKey ? 1 : 0) << 1 |
|
||||
(e.metaKey ? 1 : 0) << 2 |
|
||||
(e.altKey ? 1 : 0) << 3;
|
||||
|
||||
const command = store.commands.values().find(c => c.shortcut?.key === key && (c.shortcut.modifier === undefined || c.shortcut.modifier === modifiers));
|
||||
|
||||
if (command === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
return context.execute(command, e);
|
||||
};
|
||||
|
||||
return <CommandContext.Provider value={context}>
|
||||
<div tabIndex={0} style="display: contents;" onKeyDown={listener}>{props.children}</div>
|
||||
</CommandContext.Provider>;
|
||||
};
|
||||
|
||||
const Add: Component<{ command: CommandType, commands: undefined } | { commands: CommandType[] }> = (props) => {
|
||||
const context = useCommands();
|
||||
const commands = createMemo<CommandType[]>(() => props.commands ?? [props.command]);
|
||||
|
||||
createEffect(() => {
|
||||
context?.set(commands());
|
||||
});
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const Context = <T extends (...args: any[]) => any = any>(props: ParentProps<{ for: CommandType<T>, with: Parameters<T> }>): JSX.Element => {
|
||||
const resolved = children(() => props.children);
|
||||
const context = useCommands();
|
||||
const args = createMemo(() => props.with);
|
||||
|
||||
createEffect(() => {
|
||||
const children = resolved();
|
||||
|
||||
if (Array.isArray(children) || !(children instanceof Element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context?.addContextualArguments(props.for, children, args);
|
||||
});
|
||||
|
||||
return <>{resolved()}</>;
|
||||
};
|
||||
|
||||
const Handle: Component<{ command: CommandType }> = (props) => {
|
||||
const { t } = useI18n();
|
||||
|
||||
return <>
|
||||
{String(t(props.command.label))}
|
||||
|
||||
<Show when={props.command.shortcut}>{
|
||||
shortcut => {
|
||||
const modifier = shortcut().modifier;
|
||||
const modifierMap: Record<number, string> = {
|
||||
[Modifier.Shift]: 'Shft',
|
||||
[Modifier.Control]: 'Ctrl',
|
||||
[Modifier.Meta]: 'Meta',
|
||||
[Modifier.Alt]: 'Alt',
|
||||
};
|
||||
|
||||
return <samp>
|
||||
<For each={Object.values(Modifier).filter((m): m is number => typeof m === 'number').filter(m => modifier & m)}>{
|
||||
(m) => <><kbd>{modifierMap[m]}</kbd>+</>
|
||||
}</For>
|
||||
<kbd>{shortcut().key}</kbd>
|
||||
</samp>;
|
||||
}
|
||||
}</Show>
|
||||
</>;
|
||||
};
|
||||
|
||||
export const Command = { Root, Handle, Add, Context };
|
|
@ -1,5 +1,5 @@
|
|||
import { Accessor, Component, createContext, createEffect, createMemo, createSignal, For, JSX, ParentComponent, splitProps, useContext } from "solid-js";
|
||||
import { CommandType } from "./index";
|
||||
import { CommandType } from "./command";
|
||||
import css from "./contextMenu.module.css";
|
||||
|
||||
interface ContextMenuType {
|
||||
|
|
|
@ -1,208 +1,7 @@
|
|||
import { Accessor, children, Component, createContext, createEffect, createMemo, For, JSX, ParentComponent, ParentProps, Show, useContext } from 'solid-js';
|
||||
import { Dictionary, DictionaryKey, useI18n } from '../i18n';
|
||||
import { createStore, produce } from 'solid-js/store';
|
||||
|
||||
interface CommandContextType {
|
||||
readonly commands: Accessor<CommandType[]>;
|
||||
set(commands: CommandType<any>[]): void;
|
||||
addContextualArguments<T extends (...args: any[]) => any = any>(command: CommandType<T>, target: EventTarget, args: Accessor<Parameters<T>>): void;
|
||||
execute<T extends (...args: any[]) => any = any>(command: CommandType<T>, event: Event): void;
|
||||
}
|
||||
|
||||
interface CommandContextStateType {
|
||||
commands: CommandType[];
|
||||
contextualArguments: Map<CommandType, WeakMap<EventTarget, Accessor<any[]>>>;
|
||||
}
|
||||
|
||||
const CommandContext = createContext<CommandContextType>();
|
||||
|
||||
export const useCommands = () => useContext(CommandContext);
|
||||
|
||||
const Root: ParentComponent<{ commands: CommandType[] }> = (props) => {
|
||||
const [store, setStore] = createStore<CommandContextStateType>({ commands: [], contextualArguments: new Map() });
|
||||
|
||||
const context = {
|
||||
commands: createMemo(() => store.commands),
|
||||
|
||||
set(commands: CommandType<any>[]): void {
|
||||
setStore('commands', existing => new Set([...existing, ...commands]).values().toArray());
|
||||
},
|
||||
|
||||
addContextualArguments<T extends (...args: any[]) => any = any>(command: CommandType<T>, target: EventTarget, args: Accessor<Parameters<T>>): void {
|
||||
setStore('contextualArguments', prev => {
|
||||
if (prev.has(command) === false) {
|
||||
prev.set(command, new WeakMap());
|
||||
}
|
||||
|
||||
prev.get(command)?.set(target, args);
|
||||
|
||||
return new Map(prev);
|
||||
})
|
||||
},
|
||||
|
||||
execute<T extends (...args: any[]) => any = any>(command: CommandType<T>, event: Event): boolean | undefined {
|
||||
const args = ((): Parameters<T> => {
|
||||
const contexts = store.contextualArguments.get(command);
|
||||
|
||||
if (contexts === undefined) {
|
||||
return [] as any;
|
||||
}
|
||||
|
||||
const element = event.composedPath().find(el => contexts.has(el));
|
||||
|
||||
if (element === undefined) {
|
||||
return [] as any;
|
||||
}
|
||||
|
||||
const args = contexts.get(element)! as Accessor<Parameters<T>>;
|
||||
|
||||
return args();
|
||||
})();
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
command(...args);
|
||||
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
context.set(props.commands ?? []);
|
||||
});
|
||||
|
||||
const listener = (e: KeyboardEvent) => {
|
||||
const key = e.key.toLowerCase();
|
||||
const modifiers =
|
||||
(e.shiftKey ? 1 : 0) << 0 |
|
||||
(e.ctrlKey ? 1 : 0) << 1 |
|
||||
(e.metaKey ? 1 : 0) << 2 |
|
||||
(e.altKey ? 1 : 0) << 3;
|
||||
|
||||
const command = store.commands.values().find(c => c.shortcut?.key === key && (c.shortcut.modifier === undefined || c.shortcut.modifier === modifiers));
|
||||
|
||||
if (command === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
return context.execute(command, e);
|
||||
};
|
||||
|
||||
return <CommandContext.Provider value={context}>
|
||||
<div tabIndex={0} style="display: contents;" onKeyDown={listener}>{props.children}</div>
|
||||
</CommandContext.Provider>;
|
||||
};
|
||||
|
||||
const Add: Component<{ command: CommandType, commands: undefined } | { commands: CommandType[] }> = (props) => {
|
||||
const context = useCommands();
|
||||
const commands = createMemo<CommandType[]>(() => props.commands ?? [props.command]);
|
||||
|
||||
createEffect(() => {
|
||||
context?.set(commands());
|
||||
});
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const Context = <T extends (...args: any[]) => any = any>(props: ParentProps<{ for: CommandType<T>, with: Parameters<T> }>): JSX.Element => {
|
||||
const resolved = children(() => props.children);
|
||||
const context = useCommands();
|
||||
const args = createMemo(() => props.with);
|
||||
|
||||
createEffect(() => {
|
||||
const children = resolved();
|
||||
|
||||
if (Array.isArray(children) || !(children instanceof Element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context?.addContextualArguments(props.for, children, args);
|
||||
});
|
||||
|
||||
return <>{resolved()}</>;
|
||||
};
|
||||
|
||||
const Handle: Component<{ command: CommandType }> = (props) => {
|
||||
const { t } = useI18n();
|
||||
|
||||
return <>
|
||||
{String(t(props.command.label))}
|
||||
|
||||
<Show when={props.command.shortcut}>{
|
||||
shortcut => {
|
||||
const modifier = shortcut().modifier;
|
||||
const modifierMap: Record<number, string> = {
|
||||
[Modifier.Shift]: 'Shft',
|
||||
[Modifier.Control]: 'Ctrl',
|
||||
[Modifier.Meta]: 'Meta',
|
||||
[Modifier.Alt]: 'Alt',
|
||||
};
|
||||
|
||||
return <samp>
|
||||
<For each={Object.values(Modifier).filter((m): m is number => typeof m === 'number').filter(m => modifier & m)}>{
|
||||
(m) => <><kbd>{modifierMap[m]}</kbd>+</>
|
||||
}</For>
|
||||
<kbd>{shortcut().key}</kbd>
|
||||
</samp>;
|
||||
}
|
||||
}</Show>
|
||||
</>;
|
||||
};
|
||||
|
||||
export const Command = { Root, Handle, Add, Context };
|
||||
|
||||
export enum Modifier {
|
||||
None = 0,
|
||||
Shift = 1 << 0,
|
||||
Control = 1 << 1,
|
||||
Meta = 1 << 2,
|
||||
Alt = 1 << 3,
|
||||
}
|
||||
|
||||
export interface CommandType<T extends (...args: any[]) => any = (...args: any[]) => any> {
|
||||
(...args: Parameters<T>): Promise<ReturnType<T>>;
|
||||
label: DictionaryKey;
|
||||
shortcut?: {
|
||||
key: string;
|
||||
modifier: Modifier;
|
||||
};
|
||||
withLabel(label: string): CommandType<T>;
|
||||
with<A extends any[], B extends any[]>(this: (this: ThisParameterType<T>, ...args: [...A, ...B]) => ReturnType<T>, ...args: A): CommandType<(...args: B) => ReturnType<T>>;
|
||||
}
|
||||
|
||||
export const createCommand = <T extends (...args: any[]) => any>(label: DictionaryKey, command: T, shortcut?: CommandType['shortcut']): CommandType<T> => {
|
||||
return Object.defineProperties(((...args: Parameters<T>) => command(...args)) as any, {
|
||||
label: {
|
||||
value: label,
|
||||
configurable: false,
|
||||
writable: false,
|
||||
},
|
||||
shortcut: {
|
||||
value: shortcut ? { key: shortcut.key.toLowerCase(), modifier: shortcut.modifier } : undefined,
|
||||
configurable: false,
|
||||
writable: false,
|
||||
},
|
||||
withLabel: {
|
||||
value(label: DictionaryKey) {
|
||||
return createCommand(label, command, shortcut);
|
||||
},
|
||||
configurable: false,
|
||||
writable: false,
|
||||
},
|
||||
with: {
|
||||
value<A extends any[], B extends any[]>(this: (this: ThisParameterType<T>, ...args: [...A, ...B]) => ReturnType<T>, ...args: A): CommandType<(...args: B) => ReturnType<T>> {
|
||||
return createCommand(label, command.bind(undefined, ...args), shortcut);
|
||||
},
|
||||
configurable: false,
|
||||
writable: false,
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const noop = createCommand('noop' as any, () => { });
|
||||
|
||||
|
||||
export type { CommandType } from './command';
|
||||
export type { CommandPaletteApi } from './palette';
|
||||
|
||||
export { createCommand, noop, Modifier } from './command';
|
||||
export { useCommands, Command } from './context';
|
||||
export { Context } from './contextMenu';
|
||||
export { CommandPalette } from './palette';
|
|
@ -1,7 +1,8 @@
|
|||
import { Accessor, Component, createEffect, createMemo, createSignal, For, JSX, Show } from "solid-js";
|
||||
import { CommandType, useCommands } from ".";
|
||||
import css from "./palette.module.css";
|
||||
import { useI18n } from "../i18n";
|
||||
import { CommandType } from "./command";
|
||||
import { useCommands } from "./context";
|
||||
import css from "./palette.module.css";
|
||||
|
||||
export interface CommandPaletteApi {
|
||||
readonly open: Accessor<boolean>;
|
||||
|
@ -168,10 +169,3 @@ function SearchableList<T>(props: SearchableListProps<T>): JSX.Element {
|
|||
let keyCounter = 0;
|
||||
const createUniqueId = () => `key-${keyCounter++}`;
|
||||
|
||||
declare module "solid-js" {
|
||||
namespace JSX {
|
||||
interface HTMLAttributes<T> {
|
||||
anchor?: string | undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ const useMenu = () => {
|
|||
return context;
|
||||
}
|
||||
|
||||
type ItemProps<T extends (...args: any[]) => any> = { label: string, children: JSX.Element } | { command: CommandType<T> };
|
||||
type ItemProps<T extends (...args: any[]) => any> = { label: string, children: JSX.Element, command: undefined } | { command: CommandType<T> };
|
||||
|
||||
function Item<T extends (...args: any[]) => any>(props: ItemProps<T>) {
|
||||
const id = createUniqueId();
|
||||
|
@ -187,166 +187,6 @@ const Mount: Component = (props) => {
|
|||
|
||||
export const Menu = { Mount, Root, Item, Separator } as const;
|
||||
|
||||
export interface CommandPaletteApi {
|
||||
readonly open: Accessor<boolean>;
|
||||
show(): void;
|
||||
hide(): void;
|
||||
}
|
||||
|
||||
export const CommandPalette: Component<{ api?: (api: CommandPaletteApi) => any, onSubmit?: SubmitHandler<CommandType> }> = (props) => {
|
||||
const [open, setOpen] = createSignal<boolean>(false);
|
||||
const [root, setRoot] = createSignal<HTMLDialogElement>();
|
||||
const [search, setSearch] = createSignal<SearchContext<CommandType>>();
|
||||
const context = useMenu();
|
||||
|
||||
const api = {
|
||||
open,
|
||||
show() {
|
||||
setOpen(true);
|
||||
},
|
||||
hide() {
|
||||
setOpen(false);
|
||||
},
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
props.api?.(api);
|
||||
});
|
||||
|
||||
|
||||
createEffect(() => {
|
||||
const isOpen = open();
|
||||
|
||||
if (isOpen) {
|
||||
search()?.clear();
|
||||
root()?.showModal();
|
||||
} else {
|
||||
root()?.close();
|
||||
}
|
||||
});
|
||||
|
||||
const onSubmit = (command: CommandType) => {
|
||||
setOpen(false);
|
||||
props.onSubmit?.(command);
|
||||
|
||||
command();
|
||||
};
|
||||
|
||||
return <dialog ref={setRoot} class={css.commandPalette} onClose={() => setOpen(false)}>
|
||||
<SearchableList title="command palette" items={context.commands()} keySelector={item => item.label} context={setSearch} onSubmit={onSubmit}>{
|
||||
(item, ctx) => <For each={item.label.split(ctx.filter())}>{
|
||||
(part, index) => <>
|
||||
<Show when={index() !== 0}><b>{ctx.filter()}</b></Show>
|
||||
{part}
|
||||
</>
|
||||
}</For>
|
||||
}</SearchableList>
|
||||
</dialog>;
|
||||
};
|
||||
|
||||
interface SubmitHandler<T> {
|
||||
(item: T): any;
|
||||
}
|
||||
|
||||
interface SearchContext<T> {
|
||||
readonly filter: Accessor<string>;
|
||||
readonly results: Accessor<T[]>;
|
||||
readonly value: Accessor<T | undefined>;
|
||||
searchFor(term: string): void;
|
||||
clear(): void;
|
||||
}
|
||||
|
||||
interface SearchableListProps<T> {
|
||||
items: T[];
|
||||
title?: string;
|
||||
keySelector(item: T): string;
|
||||
filter?: (item: T, search: string) => boolean;
|
||||
children(item: T, context: SearchContext<T>): JSX.Element;
|
||||
context?: (context: SearchContext<T>) => any,
|
||||
onSubmit?: SubmitHandler<T>;
|
||||
}
|
||||
|
||||
function SearchableList<T>(props: SearchableListProps<T>): JSX.Element {
|
||||
const [term, setTerm] = createSignal<string>('');
|
||||
const [input, setInput] = createSignal<HTMLInputElement>();
|
||||
const [selected, setSelected] = createSignal<number>(0);
|
||||
const id = createUniqueId();
|
||||
|
||||
const results = createMemo(() => {
|
||||
const search = term();
|
||||
|
||||
if (search === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return props.items.filter(item => props.filter ? props.filter(item, search) : props.keySelector(item).includes(search));
|
||||
});
|
||||
|
||||
const value = createMemo(() => results().at(selected()));
|
||||
|
||||
const ctx = {
|
||||
filter: term,
|
||||
results,
|
||||
value,
|
||||
searchFor(term: string) {
|
||||
setTerm(term);
|
||||
},
|
||||
clear() {
|
||||
setTerm('');
|
||||
setSelected(0);
|
||||
},
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
props.context?.(ctx);
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
const length = results().length - 1;
|
||||
|
||||
setSelected(current => Math.min(current, length));
|
||||
});
|
||||
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'ArrowUp') {
|
||||
setSelected(current => Math.max(0, current - 1));
|
||||
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
if (e.key === 'ArrowDown') {
|
||||
setSelected(current => Math.min(results().length - 1, current + 1));
|
||||
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = (e: SubmitEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const v = value();
|
||||
|
||||
if (v === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.clear();
|
||||
props.onSubmit?.(v);
|
||||
};
|
||||
|
||||
return <search title={props.title}>
|
||||
<form method="dialog" class={css.search} onkeydown={onKeyDown} onsubmit={onSubmit}>
|
||||
<input id={`search-${id}`} ref={setInput} value={term()} oninput={(e) => setTerm(e.target.value)} placeholder="start typing for command" autofocus autocomplete="off" enterkeyhint="go" />
|
||||
|
||||
<output for={`search-${id}`}>
|
||||
<For each={results()}>{
|
||||
(result, index) => <div classList={{ [css.selected]: index() === selected() }}>{props.children(result, ctx)}</div>
|
||||
}</For>
|
||||
</output>
|
||||
</form>
|
||||
</search>;
|
||||
};
|
||||
|
||||
let keyCounter = 0;
|
||||
const createUniqueId = () => `key-${keyCounter++}`;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Accessor, children, createContext, createEffect, createMemo, createRenderEffect, createSignal, onCleanup, onMount, ParentComponent, ParentProps, Setter, Signal, useContext } from "solid-js";
|
||||
import { Accessor, children, createContext, createEffect, createMemo, createRenderEffect, createSignal, onCleanup, onMount, ParentComponent, ParentProps, Signal, useContext } from "solid-js";
|
||||
import { createStore } from "solid-js/store";
|
||||
import { isServer } from "solid-js/web";
|
||||
import css from "./index.module.css";
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Sidebar } from "~/components/sidebar";
|
|||
import { emptyFolder, FolderEntry, walk as fileTreeWalk, Tree } from "~/components/filetree";
|
||||
import { Menu } from "~/features/menu";
|
||||
import { Grid, load, useFiles } from "~/features/file";
|
||||
import { Command, CommandType, Context, createCommand, Modifier, noop, useCommands } from "~/features/command";
|
||||
import { Command, CommandType, Context, createCommand, Modifier } from "~/features/command";
|
||||
import { Entry, GridApi } from "~/features/file/grid";
|
||||
import { Tab, Tabs } from "~/components/tabs";
|
||||
import { isServer } from "solid-js/web";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue