Skip to main content

Type Alias: Field<TValue>

Field<TValue> = { column?: Omit<GridColumnBase, "field" | "columnRenderer" | "headerRenderer" | "toggle" | "hidden"> & GridColumnSizing; editable?: FieldEditable | boolean; inputType?: "text" | "number" | "email" | "tel" | "url" | "checkbox"; key: FieldKey; rollUp?: AggregatorName; type?: FieldTypeName | FieldType<TValue>; compare?: number; equals?: boolean; formatValue?: string; parseValue?: TValue | undefined; } | { column?: Omit<GridColumnBase, "field" | "columnRenderer" | "headerRenderer" | "toggle" | "hidden"> & GridColumnSizing; editable?: never; equals?: never; inputType?: never; key: FieldKey; parseValue?: never; rollUp?: never; type?: FieldTypeName | FieldType<TValue>; compare?: number; compute: TValue | undefined; formatValue?: string; }

Defined in: model/field.ts:171

TValue checks equals/compare/formatValue/parseValue against each other only where a Field is declared — FieldRegistry, DatasetOptions.fields and FieldLookup all hold bare Field (Field<unknown>), so nothing downstream of declaration re-checks it (ADR 0005, #141 item #4). This is deliberate, not a gap: the registry is heterogeneous and string-keyed by design, and closing it over a compile-time schema would be a different library.

The union is exclusive (ADR 0011): a stored Field may roll up and may be edited; a compute Field may do neither, and runs on every row, a rolling-up parent included. compute is the discriminant — 'compute' in field is the one test the registry's hasSomewhereToWrite and the write resolver both ask. Declaring a key does not create it: carrying a value is free, and a Field exists only because the library has a job to do with it — a sort, a format, a rollup, an editor, a column.

Type Parameters​

TValue​

TValue = unknown

Union Members​

Type Literal​

{ column?: Omit<GridColumnBase, "field" | "columnRenderer" | "headerRenderer" | "toggle" | "hidden"> & GridColumnSizing; editable?: FieldEditable | boolean; inputType?: "text" | "number" | "email" | "tel" | "url" | "checkbox"; key: FieldKey; rollUp?: AggregatorName; type?: FieldTypeName | FieldType<TValue>; compare?: number; equals?: boolean; formatValue?: string; parseValue?: TValue | undefined; }

column?​

optional column?: Omit<GridColumnBase, "field" | "columnRenderer" | "headerRenderer" | "toggle" | "hidden"> & GridColumnSizing

columnRenderer, headerRenderer and toggle sit on the Gantt's GridColumn, never here — data/ never holds a renderer or a callback, so this default set excludes them. hidden is excluded for a different reason. A Field default of hidden: true would make a Gantt that names the column show nothing. Which columns a view shows is the Gantt's question, never the Field's. Omit<GridColumn, …> would flatten the sizing union and let a Field default name both width and flex (#249) — so this type is built from GridColumnBase directly, joined back to GridColumnSizing, the same exclusive pair GridColumn itself carries.

editable?​

optional editable?: FieldEditable | boolean

Where this Field's value may change (ADR 0015, amended by ADR 0033). One key, two thresholds — the grid writes it only at 'anywhere', and entries.update() writes it at anything but 'never'. That is I14: the inline cell editor (S5.8), bar drag-resize for start/end (#142) and the API door all read this one key, and never disagree about it.

  • 'anywhere' — the cell editor opens, a drag writes it, and update() writes it.
  • 'api' — update() writes it; no gesture does. capabilities.edit and a variant's own edit only narrow an 'anywhere' Field, so neither reopens this one.
  • 'never' — a lock. update() throws FieldNotEditableError.

Absent means 'anywhere'. true and false are input-only aliases for 'anywhere' and 'never'; after ingest the stored Field holds the enum, so dataset.fields.all reads it back as one.

A lock is not a wall around the data. Create, ingest and History replay still write a 'never' Field — it names what a caller may write, not what the library may.

A core Field (start, name, ...) is declared by the library and cannot be redeclared, so a consumer overrides only this key on one through DatasetOptions.fields or a plugin's own fields — field-registry.ts's CORE_FIELD_OVERRIDABLE_KEYS names the keys merge accepts; naming any other key on a core Field's key throws (IllegalCoreFieldOverrideError). dataset.setFieldEditable(key, editable) changes it after setup; nothing else may.

inputType?​

optional inputType?: "text" | "number" | "email" | "tel" | "url" | "checkbox"

S5.8+: the generic inline editor's <input type> attribute. Default 'text'. A native HTML affordance only (a number stepper, a numeric mobile keyboard, tel/email validation) — it does not change how a value is read back; pair it with parseValue when the stored value is not itself a string (a 'number' input's .value is still a string). 'checkbox' is the one exception: the editor reads and writes its .checked state instead of .value, so a boolean Field takes no parseValue. Has no effect on a type: 'date' Field — that never reaches the generic editor, routing through the dateInput seam instead. For a full widget swap, not just the native input type, veto with beforeEntryEdit and mount your own control.

key​

key: FieldKey

rollUp?​

optional rollUp?: AggregatorName

Name only — a function does not serialize (ADR 0005).

type?​

optional type?: FieldTypeName | FieldType<TValue>

A string looks up the type table. An object is the bundle — { key: 'cost', type: currency({ code: 'EUR' }) }. After merge, a string name stays; an inline bundle does not leave a function object on type.

compare()?​

optional compare(a, b): number

Parameters​
a​

TValue | undefined

b​

TValue | undefined

Returns​

number

equals()?​

optional equals(a, b): boolean

Parameters​
a​

TValue | undefined

b​

TValue | undefined

Returns​

boolean

formatValue()?​

optional formatValue(value, ctx, entry): string

entry is the row this value came from. FormatContext is built once per resolveColumns and reused for every cell, so a per-entry value cannot live there without rebuilding it per cell — a formatter that needs the Entry declares this third parameter instead; every other formatter still assigns with two, or one (#240). A consumer declaration naming a core Field's key overrides this on any core Field, the same door editable uses (field-registry.ts's CORE_FIELD_OVERRIDABLE_KEYS, #577).

Parameters​
value​

TValue | undefined

ctx​

FormatContext

entry​

Entry

Returns​

string

parseValue()?​

optional parseValue(text, ctx, entry): TValue | undefined

S5.8, issue #137: reads what the user typed into the inline editor's <input> back into a stored value. undefined means the text names no value — the editor stays open in the invalid state and commits nothing. formatValue is not invertible in general (a currency-formatted "€1.234,56" cannot be parsed back without knowing the format that produced it), so the library ships no guessed default: with no parseValue, type: 'text' (or no type at all) reads and writes the raw string, and every other named type refuses to open the editor rather than parse wrong. A type: 'date' Field never reaches this — inlineEditing() routes it through the dateInput seam instead.

Parameters​
text​

string

ctx​

FieldContext

entry​

Entry

Returns​

TValue | undefined


Type Literal​

{ column?: Omit<GridColumnBase, "field" | "columnRenderer" | "headerRenderer" | "toggle" | "hidden"> & GridColumnSizing; editable?: never; equals?: never; inputType?: never; key: FieldKey; parseValue?: never; rollUp?: never; type?: FieldTypeName | FieldType<TValue>; compare?: number; compute: TValue | undefined; formatValue?: string; }

column?​

optional column?: Omit<GridColumnBase, "field" | "columnRenderer" | "headerRenderer" | "toggle" | "hidden"> & GridColumnSizing

editable?​

optional editable?: never

equals?​

optional equals?: never

inputType?​

optional inputType?: never

key​

key: FieldKey

parseValue?​

optional parseValue?: never

rollUp?​

optional rollUp?: never

type?​

optional type?: FieldTypeName | FieldType<TValue>

compare()?​

optional compare(a, b): number

Parameters​
a​

TValue | undefined

b​

TValue | undefined

Returns​

number

compute()​

compute(entry, ctx): TValue | undefined

Runs on every row a read touches, a rolling-up parent included (ADR 0011, decision 10): read a stored value off entry, and read a Field — a core key, or another Field's own compute arm — through ctx.read(key). A computed value may also depend on the tree: ctx.children(entry), ctx.descendants(entry), ctx.leaves(entry) or ctx.hasChildren(entry) (#214, #466). entry is a StoredEntry because the row may be hypothetical — a post-edit row, or a Rollup's effective child. Named compute, not get: get already names three unrelated jobs in this codebase.

Parameters​
entry​

StoredEntry

ctx​

ComputeContext

Returns​

TValue | undefined

formatValue()?​

optional formatValue(value, ctx, entry): string

Parameters​
value​

TValue | undefined

ctx​

FormatContext

entry​

Entry

Returns​

string