trellis
Version:
Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications
158 lines • 5.82 kB
TypeScript
/**
* Headless Forms — the form descriptor contract.
*
* A {@link FormDescriptor} is a pure, JSON-serializable description of a
* form, **derived from an entity schema** (`SchemaDefinition`). It carries
* everything a UI needs to render a create/edit/view form — field controls,
* options, validation, ordering, section grouping — and binds to nothing:
* no framework, no components, no store. Any client (Vue, React, Svelte,
* native, headless) renders it.
*
* The graph layer (`trellis:Form` entities, see `./ontology.ts`) provides
* optional **overrides** on top of the derived descriptor: rename fields,
* hide them, reorder, change controls. Derivation is the default; the graph
* is the exception.
*
* @module trellis/forms
*/
import type { Atom } from '../core/store/eav-store.js';
import type { PropertyType } from '../core/ontology/types.js';
/** A form is derived for one interaction mode. */
export type FormMode = 'create' | 'edit' | 'view';
export declare const FORM_MODES: readonly FormMode[];
/** Framework-agnostic control kinds a renderer must support. */
export declare const FIELD_CONTROLS: readonly ["text", "textarea", "number", "checkbox", "select", "multi_select", "date", "relation", "people", "files", "json", "readonly"];
export type FieldControl = (typeof FIELD_CONTROLS)[number];
export interface FieldOption {
value: Atom;
label: string;
}
/** Validation constraints lifted from the schema spec. */
export interface FieldValidation {
required?: boolean;
min?: number;
max?: number;
pattern?: string;
minLength?: number;
maxLength?: number;
}
/** Relation target information for `relation` controls. */
export interface FieldRelation {
targetSchema?: string;
cardinality?: 'one' | 'many';
}
export interface FormFieldDescriptor {
/** Attribute name in the entity record (matches `PropertyValueSpecification.name`). */
name: string;
/** Humanized label (schema has no per-field labels; overrides can set one). */
label: string;
control: FieldControl;
/** Original ontology property type — renderers can specialize further. */
valueType: PropertyType;
required: boolean;
/** Computed/derived or `editable: false` fields are readonly. */
readonly: boolean;
description?: string;
placeholder?: string;
options?: FieldOption[];
defaultValue?: Atom;
validation?: FieldValidation;
relation?: FieldRelation;
/** Keyboard hint for text controls (email/url/tel/numeric). */
inputMode?: 'text' | 'email' | 'url' | 'tel' | 'numeric' | 'datetime-local';
/** Schema `display` hint (pill/toggle/inline-input/popover). */
display?: 'pill' | 'toggle' | 'inline-input' | 'popover';
/** The interaction modes this field participates in. */
modes: FormMode[];
/** Stable order within the form (declaration or `propertyFieldIds` order). */
order: number;
/** Section id this field lives in (`field.group`, `properties` default). */
section: string;
}
export interface FormSectionDescriptor {
/** Group name from the schema (`field.group`) — `Properties` when ungrouped. */
id: string;
title: string;
fields: FormFieldDescriptor[];
}
export interface FormDescriptor {
/** `${schema['@id']}:${mode}` — stable identity for caching/lookup. */
formId: string;
/** Schema `@id` (e.g. `trellis:Task`). */
schemaId: string;
/** Bare entity type name (e.g. `Task`). */
entityType: string;
label: string;
labelPlural?: string;
icon?: string;
/** Form-level description (graph override only). */
description?: string;
/** Primary title attribute, if the schema declares one. */
titleField?: string;
mode: FormMode;
/** Schema version the form was derived from. */
schemaVersion: string;
/** True when a graph `Form` entity modified this descriptor. */
overridden: boolean;
/** Derived-only — never hand-authored (except via overrides). */
derived: true;
sections: FormSectionDescriptor[];
/** Flattened fields in render order (across sections). */
fields: FormFieldDescriptor[];
}
/** One `trellis:FormField` override record as read from the graph. */
export interface FormFieldOverride {
fieldName: string;
label?: string;
control?: FieldControl;
required?: boolean;
readonly?: boolean;
hidden?: boolean;
order?: number;
section?: string;
options?: Atom[];
defaultValue?: Atom;
placeholder?: string;
description?: string;
}
/** One `trellis:Form` override record as read from the graph. */
export interface FormOverride {
id?: string;
entityType: string;
/** Undefined = applies to all modes. */
mode?: FormMode;
title?: string;
description?: string;
fields: FormFieldOverride[];
}
/** Field-shaped input for {@link applyFieldOverrides}. */
export type FieldOverrideInput = Pick<FormFieldOverride, 'fieldName'> & Partial<Omit<FormFieldOverride, 'fieldName'>>;
/** Shape of the graph field entity (`InferType<typeof FormFieldType>`). */
export type FormFieldEntityLike = {
id: string;
type: 'FormField';
fieldName: string;
label?: string;
control?: FieldControl;
required?: boolean;
readonly?: boolean;
hidden?: boolean;
order?: number;
section?: string;
options?: Atom[];
defaultValue?: Atom;
placeholder?: string;
description?: string;
};
/** Shape of the graph form entity (`InferType<typeof FormType>`). */
export type FormEntityLike = {
id: string;
type: 'Form';
entityType: string;
mode?: FormMode;
title?: string;
description?: string;
/** Relation to `FormField` entities — resolved by the caller. */
fields: FormFieldEntityLike[];
};
//# sourceMappingURL=types.d.ts.map