Data grid / foldkit-data-grid
Editing
A column with edit is editable. The grid owns the edit session (which
cell, the draft, an error); the application owns the value, and hears of a
commit as the grid's OutMessage. The column's schema says what its text
means, from the text typed to the value: a draft is committed only when it
decodes, and the failure's message is the cell's error. A schema that
decodes to a date (Schema.DateFromString) is CellEditor.Date and edits in
a date field. The draft the field opens on is the local calendar day,
YYYY-MM-DD. toISOString is the UTC day, so a local midnight used to open
the field on the wrong one. A column draft that returns a string still
wins. Committing decodes the field's text through the schema.
ts
// Dollars as typed, with at most two decimals, and the number they mean.
const Price = Schema.String.check(
Schema.isPattern(/^\d+(\.\d{1,2})?$/, { message: 'A price, like 4.99' }),
).pipe(
Schema.decodeTo(Schema.Number, {
decode: SchemaGetter.transform(text => Number(text)),
encode: SchemaGetter.transform(price => price.toFixed(2)),
}),
)
const priced = Columns.define<Product>()({
sku: { header: 'SKU', value: product => product.sku },
price: {
header: 'Price',
value: product => product.price,
edit: { schema: Price, draft: product => Schema.encodeSync(Price)(product.price) },
},
})
const Prices = DataGrid.make({ id: 'prices', columns: priced })
// Edited({ row, column, text }) | Pasted({ accepted, refused }) | Filled({ source, to })
// | UndoRequested | RedoRequested
type Out = typeof Prices.Out.Type
// In `onOut`: the committed text read back as its column's value, a number here.
const repriced = (cell: Extract<Out, { _tag: 'Edited' }>) =>
Prices.matchEdit(cell, { price: ({ row, value }) => ({ row, price: value }) })EditStarted({ address, draft })opens an edit on an editable cell;EditChangedkeeps the draft;EditCancelleddrops it.EditCommitted({ next, reveal })decodes the draft with the column'sschema(any text, without one). A draft it refuses keeps the edit with the error, and nothing is reported. One it accepts ends the edit, moves focus tonext, and the update returns the OutMessageOut.Edited({ row, column, text }), unless it is unchanged: the text the edit began from, or text that decodes to the same value (09.0for9.00). An unchanged edit closes as a changed one does and reports nothing. It is judged against the text the edit began from, not the cell as it is now, so leaving an editor as it opened never writes over a change another device made meanwhile.Grid.matchEdit(cell, handlers)reads a reported cell's value back: one handler per editable column, each given its column's decoded type, so the application parses nothing itself. The OutMessage stays text, which a parent can store or send; a cell that does not decode, or names a column that does not edit, was not the grid's, and throws.- A key typed on the grid is
EditTyped({ address, text, from }): it starts the edit fromfrom, the text the cell showed, or adds to the one open on that cell, so keys faster than the editor opens all count. On a choice column it finds the next option the key starts instead.EditStepped({ by })moves a choice's draft through its options (next,previous,first,last,pageNext,pagePrevious), andEditChosen({ draft })keeps one a pointer chose. The grid's Messages carry what the user did andupdateworks out what it means, for the reason in Messages carry intent. - Place the grid with
onOut. Every placement handles the OutMessage, as any Bundle's must; a grid that edits nothing passesonOut: Bundle.ignore. - A click on another cell commits first; a refused draft keeps the edit and the click waits. Hiding the edited column ends the edit.