import { defineWebComponent } from './define'; import { Form, type FormController, type FormDefinition } from '../components/form'; import type { CardResolution } from '../primitives/card-contract'; interface Props extends Record { /** The form definition — a JSON Schema (`type:'object'`) + `x-kai-*` UI hints * (the CardEnvelope.data). Set as a JS PROPERTY: `el.data = { type:'object', * properties:{…} }`. Import the `FormDefinition` type from `@kitn.ai/ui` for * the full shape (it is self-referential, so the element types it loosely). */ data?: Record; /** Stable card id correlating every emitted CardEvent. Attribute: `card-id`. */ cardId?: string; /** Heading rendered in the card chrome (= CardEnvelope.title). Attribute: `heading`. */ heading?: string; /** Set when the user resolved this card; renders the read-only view. Property: * `el.resolution = { kind:'submit', data:{…} }`. */ resolution?: Record; /** Controlled field values (JS property). When set, it wins over local edits. */ values?: Record; /** Initial values overlaying the schema defaults (uncontrolled seed; JS property). */ defaultValues?: Record; /** Disable all fields + submit. Attribute: `disabled`. */ disabled?: boolean; } /** Events fired by ``. (Resolution still flows up the bubbling `kai-card` * contract event — `kai-values-change` is the live change signal, distinct from the * terminal submit.) */ interface Events { /** The form's values changed on input — current coerced values + validity. */ 'kai-values-change': { values: Record; valid: boolean }; } /** * `` — renders a JSON-Schema **form definition** (set via the `data` * property) into themed, accessible widgets inside `` chrome, validates * input client-side, and emits the collected, coerced, validated object up the * Card contract as a bubbling **`kai-card`** CustomEvent of `{ kind:'submit' }`. * It also emits `ready` on mount, `action`/`dismiss` for secondary affordances, and * `error` for a malformed definition. Routes through a `CardProvider` when present, * else the bubbling `kai-card` event (so a bare `` works without a host). * Isolated in Shadow DOM; theme-aware via the shared kit tokens. */ defineWebComponent('kai-form', { data: undefined, cardId: undefined, heading: undefined, resolution: undefined, values: undefined, defaultValues: undefined, disabled: false, }, (props, { element, dispatch, flag, expose }) => { // Pattern C: the Form component owns the value store + validation and hands up a // controller; the facade captures it and exposes delegating instance methods. let controller: FormController | undefined; expose({ /** Focus the first control, or the first INVALID control after a failed validation. */ focus: (options?: FocusOptions) => controller?.focus(options), /** Validate + submit programmatically — focus the first invalid field on failure, * else emit the `submit` CardEvent and resolve. Named `send`, not `submit`. */ send: () => controller?.send(), /** Run client-side validation now and return `{ valid, errors? }` WITHOUT submitting. */ validate: () => controller?.validate() ?? { valid: false }, /** Re-seed the form from each field's `default` and clear errors. */ reset: () => controller?.reset(), /** Trigger the dismiss path (emit `dismiss` + collapse to the re-openable stub). */ dismiss: () => controller?.dismiss(), /** Re-open a dismissed card from its stub (emit `reopen`). */ reopen: () => controller?.reopen(), }); return (
| undefined} defaultValues={props.defaultValues as Record | undefined} disabled={flag('disabled')} onValuesChange={(payload) => dispatch('kai-values-change', payload)} controllerRef={(c) => (controller = c)} hostElement={element} /> ); });