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?
optionalcolumn?: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?
optionaleditable?: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, andupdate()writes it.'api'—update()writes it; no gesture does.capabilities.editand a variant's owneditonly narrow an'anywhere'Field, so neither reopens this one.'never'— a lock.update()throwsFieldNotEditableError.
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?
optionalinputType?:"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?
optionalrollUp?:AggregatorName
Name only — a function does not serialize (ADR 0005).
type?
optionaltype?: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()?
optionalcompare(a,b):number
Parameters
a
TValue | undefined
b
TValue | undefined
Returns
number
equals()?
optionalequals(a,b):boolean
Parameters
a
TValue | undefined
b
TValue | undefined
Returns
boolean
formatValue()?
optionalformatValue(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
entry
Returns
string
parseValue()?
optionalparseValue(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
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?
optionalcolumn?:Omit<GridColumnBase,"field"|"columnRenderer"|"headerRenderer"|"toggle"|"hidden"> &GridColumnSizing
editable?
optionaleditable?:never
equals?
optionalequals?:never
inputType?
optionalinputType?:never
key
key:
FieldKey
parseValue?
optionalparseValue?:never
rollUp?
optionalrollUp?:never
type?
optionaltype?:FieldTypeName|FieldType<TValue>
compare()?
optionalcompare(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
ctx
Returns
TValue | undefined
formatValue()?
optionalformatValue(value,ctx,entry):string
Parameters
value
TValue | undefined
ctx
entry
Returns
string