@kitn.ai/ui
Version:
Framework-agnostic, Shadow-DOM web components for building AI chat interfaces — works in React, Vue, Angular, Svelte, or plain HTML. Authored in SolidJS.
105 lines (104 loc) • 5 kB
TypeScript
import { JSX } from 'solid-js';
import { CardEnvelope, CardHost, CardResolution } from '../primitives/card-contract';
export interface ChoiceOptionMedia {
image?: string;
imageAlt?: string;
icon?: string;
}
export interface ChoiceOption {
id: string;
label: string;
description?: string;
media?: ChoiceOptionMedia;
meta?: string;
recommended?: boolean;
disabled?: boolean;
payload?: unknown;
}
export type ChoiceAllowOther = boolean | {
label?: string;
placeholder?: string;
};
export interface ChoiceCardData {
prompt?: string;
options: ChoiceOption[];
allowOther?: ChoiceAllowOther;
submitLabel?: string;
dismissible?: boolean;
}
export type ChoiceCardEnvelope = CardEnvelope<'choice', ChoiceCardData>;
export declare const CHOICE_CARD_TYPE: "choice";
/** The reserved action id emitted by the `allowOther` free-text submit. */
export declare const OTHER_ACTION: "__other__";
/** De-dupe options by id (first wins) and validate their shape. Returns the usable
* list + an optional error message when there's nothing renderable. Modeled on
* `normalizeActions`. */
export declare function normalizeOptions(options: unknown): {
options: ChoiceOption[];
error?: string;
};
/** Resolve the `allowOther` data field to a config (or null when off). */
export declare function resolveOtherConfig(allowOther: ChoiceAllowOther | undefined): {
label: string;
placeholder: string | undefined;
} | null;
/** Index of the next non-disabled option, moving by `dir` (+1/-1) from `from`,
* wrapping. Returns `from` when nothing else is focusable. */
export declare function nextEnabledIndex(options: ChoiceOption[], from: number, dir: 1 | -1): number;
/** The first non-disabled option index (the initial roving tab stop), or -1. */
export declare function firstEnabledIndex(options: ChoiceOption[]): number;
/** Imperative handle exposed via `controllerRef` — surfaces the choice card's
* latent selection/submit/dismiss capabilities so the `<kai-choice>` facade can
* forward them as instance methods (focus/select/send/dismiss/reopen). */
export interface ChoiceController {
/** Focus the radiogroup roving tab stop (or the Other input when selected). */
focus(options?: FocusOptions): void;
/** Select an option by id locally (no emit) — same as a row click. */
select(optionId: string): void;
/** Submit the current selection — emits the `action` verb + resolves single-shot. */
send(): void;
/** Dismiss the card — emits `dismiss` + optimistically collapses to the stub. */
dismiss(): void;
/** Re-open a dismissed card from its stub — emits `reopen`. */
reopen(): void;
}
export interface ChoiceCardProps {
/** The choice definition (CardEnvelope.data). */
data?: ChoiceCardData;
/** The card id used to correlate every emitted CardEvent. */
cardId?: string;
/** The envelope title rendered in the card chrome. */
heading?: string;
/** Optional explicit CardHost (otherwise read from a CardProvider, otherwise the
* bubbling `kai-card` CustomEvent off `hostElement`). */
host?: CardHost;
/** The custom-element host node, for the bubbling `kai-card` fallback emit. */
hostElement?: HTMLElement;
class?: string;
/** When set, render the chromed read-only view instead of the interactive radiogroup. */
resolution?: CardResolution;
/** Controlled selection (option id). When set, the consumer owns the current pick. */
value?: string;
/** Option id to pre-select on mount (uncontrolled seed). */
defaultValue?: string;
/** Disable the whole radiogroup + Submit (e.g. while the agent is busy). */
disabled?: boolean;
/** Fires when the selection changes BEFORE submit (row click or select()). */
onValueChange?: (value: string) => void;
/** Receive the imperative controller once mounted. The `<kai-choice>` facade
* forwards these as element methods (focus/select/send/dismiss/reopen). */
controllerRef?: (controller: ChoiceController) => void;
}
/**
* `ChoiceCard` — a single-select "pick one of N rich options" card (plans, products,
* flights, quick replies) inside `Card` chrome. The options are a WAI-ARIA radiogroup
* (list rows) with roving tabindex: clicking a row (or Space/Enter on the focused row)
* **selects** it locally without emitting; a **Submit** button below the list then emits
* the Card contract's **`action`** verb (`{ kind:'action', cardId, action: option.id,
* payload }`) and resolves the card (chosen option shown read-only) so the same pick can't
* double-fire. An optional `allowOther` free-text escape appends a selectable "Other…" row;
* selecting it reveals an inline text input, and the same Submit emits `action:'__other__'`
* with `{ text }`. Emits `ready` on mount and `error` for an unusable definition (inline
* error state).
*/
export declare function ChoiceCard(props: ChoiceCardProps): JSX.Element;