@kmenu/react
Version:
React adapter for kmenu
293 lines (288 loc) • 10.4 kB
TypeScript
import React, { HTMLAttributes, ReactNode, InputHTMLAttributes } from 'react';
/**
* Describes an actionable or navigable option in the command menu.
*/
interface CommandOption$1<T = any> {
id?: string;
label: string;
keywords?: string[];
disabled?: boolean;
group?: string;
data?: T;
children?: CommandOption$1<T>[];
parent?: string;
action?: () => void | Promise<void> | CommandOption$1<T>[];
}
/**
* Breadcrumb segment representing the current submenu path.
*/
interface Breadcrumb {
id: string;
label: string;
}
/**
* Complete immutable snapshot of the command system state.
*/
interface CommandState<T = any> {
open: boolean;
input: string;
activeId?: string;
activeIndex: number;
filtered: CommandOption$1<T>[];
options: CommandOption$1<T>[];
groups: Map<string, CommandOption$1<T>[]>;
menuStack: string[];
currentLevel: number;
breadcrumbs: Breadcrumb[];
allOptions: CommandOption$1<T>[];
currentOptions: CommandOption$1<T>[];
}
/**
* Discriminated union of events emitted by the command core.
*/
type CommandEvent<T = any> = {
type: "open";
} | {
type: "close";
} | {
type: "change";
input: string;
} | {
type: "select";
option: CommandOption$1<T>;
} | {
type: "navigate";
activeId: string | undefined;
activeIndex: number;
} | {
type: "submenu";
option: CommandOption$1<T>;
level: number;
} | {
type: "back";
level: number;
};
/** Handler for a specific command event type. */
type EventHandler<T = any> = (event: CommandEvent<T>) => void;
/** Function that filters `options` using a text `query`. */
type FilterFunction<T = any> = (options: CommandOption$1<T>[], query: string) => CommandOption$1<T>[];
/** Callback to unsubscribe an event handler. */
type Unsubscribe = () => void;
/**
* Configuration for the `CommandCore` behavior and callbacks.
*/
interface CommandCoreConfig<T = any> {
filter?: FilterFunction<T>;
onOpen?: () => void;
onClose?: () => void;
onChange?: (input: string) => void;
onSelect?: (option: CommandOption$1<T>) => void;
}
/**
* Headless command menu engine with filtering, navigation, and submenu support.
* Provides ARIA props and emits events for UI integration.
*/
declare class CommandCore<T = any> {
private stateMachine;
private state;
private listeners;
private filter;
private keydownHandler?;
private globalKeyHandler?;
private inputElement?;
private listElement?;
private optionElements;
private destroyed;
private lastScrollTime;
constructor(config?: CommandCoreConfig<T>);
private setupStateMachine;
private setupGlobalKeyboardShortcut;
private defaultFilter;
/** Open the command menu if not destroyed. */
open(): void;
/** Close the command menu if not destroyed. */
close(): void;
/** Toggle the command menu open/closed state. */
toggle(): void;
/**
* Set the input query and update the filtered options.
* Emits a `change` event with the latest input.
*/
setInput(value: string): void;
/**
* Replace the registered options and rebuild all derived state.
*/
registerOptions(options: CommandOption$1<T>[]): void;
private processOptionsWithIds;
private ensureUniqueId;
private rebuildGroups;
private reorderByGroups;
private flattenOptions;
/**
* Set the active option by filtered index. Optionally provide the
* navigation direction to optimize scroll behavior.
*/
setActiveByIndex(index: number, direction?: "up" | "down"): void;
private scrollActiveIntoView;
/** Set the active option by option id. */
setActiveById(id: string): void;
/** Move the active selection to the previous enabled option. */
navigateUp(): void;
/** Move the active selection to the next enabled option. */
navigateDown(): void;
/** Subscribe to a command event. Returns an unsubscribe function. */
on(event: CommandEvent<T>["type"], handler: EventHandler<T>): Unsubscribe;
/** Get a shallow-copied snapshot of the current state. */
getState(): CommandState<T>;
private emit;
private updateFiltered;
private selectFirstAvailableOption;
/** Enter a submenu using the given parent `option` with children. */
enterSubmenu(option: CommandOption$1<T>): void;
/** Go back one submenu level. Returns `false` if already at root. */
goBack(): boolean;
private findOptionById;
private updateAriaActiveDescendant;
/**
* Return ARIA attributes for the combobox container element.
*/
getComboboxProps(): {
role: "combobox";
"aria-expanded": boolean;
"aria-haspopup": "listbox";
"aria-controls": string;
};
/**
* Return ARIA attributes and event handlers for the input element.
*/
getInputProps(): {
ref: (el: HTMLInputElement | null) => void;
role: "combobox";
"aria-autocomplete": "list";
"aria-expanded": boolean;
"aria-controls": string;
"aria-activedescendant": string | undefined;
value: string;
onInput: (e: Event) => void;
onKeyDown: (e: KeyboardEvent) => void;
};
/** Return ARIA attributes for the listbox element. */
getListboxProps(): {
ref: (el: HTMLElement | null) => void;
id: string;
role: "listbox";
"aria-label": string;
tabIndex: number;
};
/**
* Return ARIA attributes and event handlers for an option element by id.
*/
getOptionProps(id: string): {
ref: (el: HTMLElement | null) => void;
id: string;
role: "option";
"aria-selected": boolean;
"aria-disabled": boolean | undefined;
tabIndex: number;
onClick: () => void;
onMouseEnter: () => void;
};
private handleKeyDown;
/** Select the active option or enter its submenu if it has children. */
selectActive(): void;
/** Cleanup event listeners and internal references. Safe to call multiple times. */
destroy(): void;
}
/**
* Root command component props.
*/
interface CommandProps<T = any> extends Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "onSelect"> {
children?: ReactNode;
value?: string;
onValueChange?: (value: string) => void;
open?: boolean;
onOpenChange?: (open: boolean) => void;
options?: CommandOption$1<T>[];
filter?: FilterFunction<T>;
onSelect?: (option: CommandOption$1<T>) => void;
shouldFilter?: boolean;
}
/**
* Ref handle exposed by `Command`.
*/
interface CommandRef<T = any> {
command: CommandCore<T> | null;
}
/**
* Root component that wires the headless core to React and provides context.
*/
declare const Command: React.ForwardRefExoticComponent<CommandProps<any> & React.RefAttributes<CommandRef<any>>>;
/** Props for the command input element. */
interface CommandInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "onChange"> {
value?: string;
onValueChange?: (value: string) => void;
}
declare const CommandInput: React.ForwardRefExoticComponent<CommandInputProps & React.RefAttributes<HTMLInputElement>>;
/** Props for the listbox container. */
interface CommandListProps extends HTMLAttributes<HTMLDivElement> {
children?: ReactNode;
indicatorOffsetY?: number;
}
declare const CommandList: React.ForwardRefExoticComponent<CommandListProps & React.RefAttributes<HTMLDivElement>>;
/** Props for an individual command option element. */
interface CommandOptionProps<T = any> extends HTMLAttributes<HTMLDivElement> {
value: CommandOption$1<T>;
disabled?: boolean;
children?: ReactNode;
}
declare const CommandOption: React.ForwardRefExoticComponent<CommandOptionProps<any> & React.RefAttributes<HTMLDivElement>>;
/** Props for a visual grouping of options. */
interface CommandGroupProps extends HTMLAttributes<HTMLDivElement> {
heading?: ReactNode;
children?: ReactNode;
}
declare const CommandGroup: React.ForwardRefExoticComponent<CommandGroupProps & React.RefAttributes<HTMLDivElement>>;
/** Props for the empty state container. */
interface CommandEmptyProps extends HTMLAttributes<HTMLDivElement> {
children?: ReactNode;
}
declare const CommandEmpty: React.ForwardRefExoticComponent<CommandEmptyProps & React.RefAttributes<HTMLDivElement>>;
/** Props for the breadcrumb navigation container. */
interface CommandBreadcrumbsProps extends HTMLAttributes<HTMLDivElement> {
children?: ReactNode;
}
declare const CommandBreadcrumbs: React.ForwardRefExoticComponent<CommandBreadcrumbsProps & React.RefAttributes<HTMLDivElement>>;
/** Props for the loading state container. */
interface CommandLoadingProps extends HTMLAttributes<HTMLDivElement> {
children?: ReactNode;
}
declare const CommandLoading: React.ForwardRefExoticComponent<CommandLoadingProps & React.RefAttributes<HTMLDivElement>>;
/** Props for a separator between list sections. */
interface CommandSeparatorProps extends HTMLAttributes<HTMLDivElement> {
}
declare const CommandSeparator: React.ForwardRefExoticComponent<CommandSeparatorProps & React.RefAttributes<HTMLDivElement>>;
/**
* Context value shared by command components.
*/
interface CommandContextValue<T = any> {
command: CommandCore<T> | null;
state: {
open: boolean;
input: string;
activeId?: string;
activeIndex: number;
filtered: CommandOption$1<T>[];
options: CommandOption$1<T>[];
menuStack: string[];
currentLevel: number;
breadcrumbs: {
id: string;
label: string;
}[];
};
}
/**
* Hook to access the command context. Throws if used outside of `Command`.
*/
declare function useCommand<T = any>(): CommandContextValue<T>;
export { Command, CommandBreadcrumbs, type CommandBreadcrumbsProps, type CommandContextValue, CommandEmpty, type CommandEmptyProps, type CommandEvent as CommandEventType, CommandGroup, type CommandGroupProps, CommandInput, type CommandInputProps, CommandList, type CommandListProps, CommandLoading, type CommandLoadingProps, CommandOption, type CommandOptionProps, type CommandOption$1 as CommandOptionType, type CommandProps, type CommandRef, CommandSeparator, type CommandSeparatorProps, type CommandState as CommandStateType, type FilterFunction as FilterFunctionType, useCommand };