UNPKG

@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.

138 lines (137 loc) 6.59 kB
import { JSX } from 'solid-js'; import { CardEnvelope, CardHost, CardResolution } from '../primitives/card-contract'; /** A field definition (the JSON Schema subset kai-form renders). */ export interface FormField { type: 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object'; title?: string; description?: string; default?: unknown; enum?: unknown[]; format?: 'email' | 'uri' | 'url' | 'date' | 'date-time' | 'time'; minimum?: number; maximum?: number; minLength?: number; maxLength?: number; pattern?: string; minItems?: number; maxItems?: number; items?: FormField | { enum: unknown[]; }; properties?: Record<string, FormField>; required?: string[]; readOnly?: boolean; 'x-kai-widget'?: 'textarea' | 'slider' | 'rating' | 'radio' | 'select' | 'checkbox' | 'password' | 'switch'; 'x-kai-placeholder'?: string; 'x-kai-step'?: number; } /** The form definition = CardEnvelope.data for type:'form'. */ export interface FormDefinition { type: 'object'; title?: string; description?: string; required?: string[]; properties: Record<string, FormField>; 'x-kai-order'?: string[]; 'x-kai-inlineMax'?: number; 'x-kai-submitLabel'?: string; 'x-kai-dismissible'?: boolean; 'x-kai-actions'?: { id: string; label: string; variant?: 'default' | 'ghost' | 'outline'; }[]; } export type FormCardEnvelope = CardEnvelope<'form', FormDefinition>; /** The internal widget identifiers `widgetFor` resolves to. */ export type WidgetKind = 'text' | 'textarea' | 'password' | 'email' | 'url' | 'date' | 'datetime' | 'time' | 'number' | 'slider' | 'rating' | 'switch' | 'checkbox' | 'radio' | 'select' | 'checkbox-group' | 'multiselect' | 'repeater' | 'taglist' | 'fieldset' | 'unsupported'; export declare const DEFAULT_INLINE_MAX = 4; /** Resolve the widget for a field. An explicit valid `x-kai-widget` always wins; * otherwise the type/format/enum/constraint combination selects the widget. */ export declare function widgetFor(field: FormField, inlineMax: number): WidgetKind; /** Humanize a camelCase / snake_case property key into a label. */ export declare function humanize(key: string): string; /** Field order: `x-kai-order` (filtered to known keys, missing appended) if * present, else `required` first then schema declaration order. */ export declare function orderedKeys(def: FormDefinition): string[]; /** Coerce a raw control value to the field's JSON type. Empty number string → * undefined; number/integer → Number; boolean → real boolean. */ export declare function coerceValue(field: FormField, raw: unknown): unknown; export interface FormValidation { valid: boolean; fieldErrors: Record<string, string>; } /** Full client-side validation of `values` against the form definition. Returns a * per-field error map (the contract validator subset, applied field-by-field so * each field can show its own inline message). */ export declare function validateForm(def: FormDefinition, values: Record<string, unknown>): FormValidation; export interface FormSummaryRow { key: string; label: string; value: string; } /** Format one field's value for the read-only summary. */ export declare function formatFieldValue(field: FormField | undefined, raw: unknown): string; /** Build the label→value rows for a submitted form, honoring x-kai-order. */ export declare function summarizeForm(def: FormDefinition, data: Record<string, unknown>): FormSummaryRow[]; /** Build the result object: coerced values with empty optional fields omitted. * `false` and `0` are kept (they are real values, not "empty"). */ export declare function buildResult(def: FormDefinition, values: Record<string, unknown>): Record<string, unknown>; /** Imperative handle exposed via `controllerRef` — surfaces the form's latent * capabilities (focus the first/first-invalid control, validate, programmatic * send, reset to defaults, dismiss/reopen) so the `<kai-form>` facade can forward * them as instance methods. */ export interface FormController { /** Focus the first control, or the first INVALID control after a failed validation. */ focus(options?: FocusOptions): void; /** Run full validation + submit: focus the first invalid field on failure, else emit `submit`. */ send(): void; /** Run client-side validation now and return per-field errors WITHOUT submitting. */ validate(): { valid: boolean; errors?: Record<string, string>; }; /** Re-seed from each field's `default` and clear errors. */ reset(): void; /** Trigger the dismiss path (emit `dismiss` + collapse to the re-openable stub). */ dismiss(): void; /** Re-open a dismissed card from its stub (emit `reopen`). */ reopen(): void; } export interface FormProps { /** The form definition (CardEnvelope.data). */ data?: FormDefinition; /** 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 form inputs. */ resolution?: CardResolution; /** Controlled field values — when set, this wins over internal/uncontrolled state. */ values?: Record<string, unknown>; /** Initial values overlaying the schema defaults (uncontrolled seed). */ defaultValues?: Record<string, unknown>; /** Disable all fields + submit. */ disabled?: boolean; /** Fires on input with the current coerced values + validity (distinct from submit). */ onValuesChange?: (payload: { values: Record<string, unknown>; valid: boolean; }) => void; /** Receive the imperative controller once mounted. */ controllerRef?: (controller: FormController) => void; } /** * `Form` — renders a JSON-Schema form definition into themed, accessible widgets * inside `Card` chrome, validates input against that schema, and emits the * collected, coerced, validated object up the Card contract as `submit`. * Reads context/emits via a `CardProvider` when present, else the bubbling * `kai-card` CustomEvent. */ export declare function Form(props: FormProps): JSX.Element;