import { TemplateResult } from 'lit'; import { BahtElement } from './base.js'; import { DerivedFieldState, FeatureFlags, FormDefinition, Registry, ValidationError } from '../core/types.js'; export interface BahtFormOptions { validateOn?: 'change' | 'blur' | 'submit'; /** Debounce (ms) for typed fields; unset = BahtFieldBase.defaultDebounce (150). 0 = none. */ inputDebounce?: number; } /** * — dumb controlled form renderer (spec 01). * JSON in (form + value + registry) -> UI out; emits `baht-change` / `baht-validity`. * No buttons, no cross-field logic — that's or the host. */ export declare class BahtForm extends BahtElement { form: FormDefinition; value: Record; registry: Registry; /** Label every non-required field "(optional)" (localized) instead of relying on asterisks alone. */ showOptional: boolean; options: BahtFormOptions; /** L2 (workflow) derived per-field UI state, keyed by field key. Optional. */ fieldStates: Record; /** * Name of a view from `form.views`. Display-only: when set, only that view's * fields are rendered and validated (the user can only fix what they see), * but getData() still returns the FULL form — the server always validates * the complete schema, views never change the submit payload. */ view?: string | undefined; /** * Feature-flag values (spec 01 §12) — fields whose `flag` is off behave as * if they were never in the schema: no render, no validation, no payload. */ flags: FeatureFlags; /** Force every field readonly — a display-only render of this form's own layout/widths. */ readonly: boolean; /** Edits the user made since the host last set `value` (controlled overlay). */ private edits; private errors; /** Per-field overrides set by scripts — layered over `fieldStates` (script wins). */ private scriptStates; /** showFieldMsg() messages, one per field. */ private fieldMessages; /** addErrorMessage()/addInfoMessage() — reset at the start of every submit pass. */ private formMessages; /** Read-only view: labels of reference ids, keyed `lookup\u0000id` (resolved async). */ private referenceLabels; private referenceResolving; private warnedLookups; /** The `g` handed to scripts; rebuilt when `form` changes. */ private scriptContext; private scriptContextForm; /** Depth of the current setValue -> change-script cascade. */ private scriptDepth; /** The `form` identity `load` scripts last ran for. */ private loadedForm; /** Resolved dynamic options + the dep snapshot / fetch epoch they belong to. */ private dynamicOptions; private optionsSnapshots; private optionsEpoch; /** Unknown `view` values already warned about — avoid re-warning on every render. */ private warnedViews; /** applyFlags result, memoized on (form, flags) identity. */ private flagCache; /** * `form` as the host may actually have left it: a framework can connect the element * before it binds the property, so internally it is optional until then. */ private get assignedForm(); private get effectiveForm(); protected willUpdate(changed: Map): void; /** * Refetch `options_from` options whose dep snapshot changed. Sync fetchers * land immediately; async ones re-render on resolve (stale responses are * dropped by epoch). */ private syncDynamicOptions; /** * A user edit invalidates every field whose options_deps include the edited * key — their values reset to defaults, cascading down chains * (province -> district -> subdistrict). */ private clearDependents; private get sortedFields(); /** * Visibility as of the last completed render, by key. A field that was * hidden there and is visible now gets `baht-field--revealed` for one * render so the stylesheet can fade it in; fields visible from the first * render never animate (a form should not shimmer on mount). */ private lastVisible; private nextVisible; private revealClass; private stateFor; /** The row sub-field states of one repeater: `fieldStates` keys starting `key[`. */ private rowStatesFor; private valueFor; /** Debounced keystrokes still in a control land in `edits` before anyone reads them. */ private flushTyped; getData(): Record; /** * Merge values programmatically. Runs each key's `change` scripts (like * ServiceNow's g_form.setValue) but emits no `baht-change` — the host * already knows, and a store adapter would otherwise loop. */ setValue(patch: Record): void; /** Declarative + registry validators over the visible fields (no scripts). */ private runValidators; /** * Full validation pass: validators, then `submit` scripts (spec 01 §14). A * script returning `false` blocks; its `addErrorMessage()` calls become * `{ key: '$' }` errors so hosts see why. */ validate(): { valid: boolean; errors: ValidationError[]; }; reset(): void; /** The `g` (g_form) for the current form — built lazily over this component's state. */ private g; /** A script wrote a value: same path as a user edit (emit + cascade), minus the formatter. */ private applyScriptValue; private runChangeScripts; /** * Form load (ServiceNow order): every field's `change` scripts with * isLoading=true, then the `load` scripts. Once per `form` identity — a * workflow reuses this element across steps, so each step loads. */ private runLoadScripts; protected updated(changed: Map): void; private onFieldInput; /** Validate a single field against current form data (siblings included). */ private validateOneField; /** `validateOn: 'blur'` — validate just the field the user left, same as * how errors are shown elsewhere (merged into `this.errors`, painted by * `renderField`/`.baht-error`). No-op for 'change'/'submit' modes. */ private onFieldBlur; private renderField; /** `registry.references[field.reference]`, with `search` bound to the form's current data. */ private lookupFor; /** Read-only view of a reference: its resolved label once known, the raw id until then. */ private referenceText; private uploaderFor; private renderFieldMsg; /** * Resolve the descriptor at event time — it may have changed since the control * was first rendered (the same node is reused across renders, see render()). */ private onControlInput; /** Uploaded files in read-only mode: each a download link when it has a safe url. */ private renderReadonlyFiles; /** The value part of a read-only field, by how `displayValue` says it reads. */ private renderReadonlyBody; private renderReadonlyField; protected render(): TemplateResult; } declare global { interface HTMLElementTagNameMap { 'baht-form': BahtForm; } }