LyteNyte Grid lets you edit cell values directly in the grid. Editing is configured per cell.
To enable editing:
editCellMode
to "cell"
in grid state. (Default is "readonly"
.)editable: true
.Control how editing starts with editClickActivator
. Supported values:
"single-click"
"double-click"
"none"
The example below shows a basic setup where columnBase: { editable: true }
makes all columns editable.
"use client";
import { useClientRowDataSource, Grid } from "@1771technologies/lytenyte-pro";
import "@1771technologies/lytenyte-pro/grid.css";
import type { Column } from "@1771technologies/lytenyte-pro/types";
import { bankDataSmall } from "@1771technologies/sample-data/bank-data-smaller";
import { useId } from "react";
type BankData = (typeof bankDataSmall)[number];
const columns: Column<BankData>[] = [
{ id: "age", width: 100, type: "number" },
{ id: "job", width: 160 },
{ id: "balance", width: 160, type: "number" },
{ id: "education", width: 160 },
{ id: "marital" },
];
export default function CellEditing() {
const ds = useClientRowDataSource({
data: bankDataSmall,
});
const grid = Grid.useLyteNyte({
gridId: useId(),
rowDataSource: ds,
columns,
columnBase: {
editable: true,
widthFlex: 1,
},
editCellMode: "cell",
editClickActivator: "single",
});
const view = grid.view.useValue();
return (
<div className="lng-grid" style={{ height: 500 }}>
<Grid.Root grid={grid}>
<Grid.Viewport>
<Grid.Header>
{view.header.layout.map((row, i) => {
return (
<Grid.HeaderRow key={i} headerRowIndex={i}>
{row.map((c) => {
if (c.kind === "group") return null;
return (
<Grid.HeaderCell
key={c.id}
cell={c}
className="flex w-full h-full capitalize px-2 items-center"
/>
);
})}
</Grid.HeaderRow>
);
})}
</Grid.Header>
<Grid.RowsContainer>
<Grid.RowsCenter>
{view.rows.center.map((row) => {
if (row.kind === "full-width") return null;
return (
<Grid.Row row={row} key={row.id}>
{row.cells.map((c) => {
return (
<Grid.Cell
key={c.id}
cell={c}
className="text-sm flex items-center px-2 h-full w-full"
/>
);
})}
</Grid.Row>
);
})}
</Grid.RowsCenter>
</Grid.RowsContainer>
</Grid.Viewport>
</Grid.Root>
</div>
);
}
Set the editRenderer
property on a column to define its cell editor. This can be:
Register edit providers with the editRenderers
property in grid state.
"use client";
import { useClientRowDataSource, Grid } from "@1771technologies/lytenyte-pro";
import "@1771technologies/lytenyte-pro/grid.css";
import type { Column } from "@1771technologies/lytenyte-pro/types";
import { bankDataSmall } from "@1771technologies/sample-data/bank-data-smaller";
import { useId } from "react";
type BankData = (typeof bankDataSmall)[number];
const columns: Column<BankData>[] = [
{ id: "age", width: 100, type: "number" },
{ id: "job", width: 160 },
{ id: "balance", width: 160, type: "number" },
{ id: "education", width: 160 },
{
id: "marital",
editable: true,
editRenderer: (p) => {
return (
<select
style={{ width: "100%", height: "100%", boxSizing: "border-box" }}
value={p.value as string}
onChange={(e) => p.onChange(e.target.value)}
>
<option>married</option>
<option>divorced</option>
<option>single</option>
</select>
);
},
},
];
export default function CellEditingProvider() {
const ds = useClientRowDataSource({
data: bankDataSmall,
});
const grid = Grid.useLyteNyte({
gridId: useId(),
rowDataSource: ds,
columns,
columnBase: {
widthFlex: 1,
editable: true,
editRenderer: ({ column, onChange, value }) => {
const type =
column.type === "datetime" || column.type === "date"
? "date"
: column.type === "number"
? "number"
: undefined;
return (
<input
className="w-full h-full text-sm bg-black text-white px-2"
type={type}
value={`${value}`}
onChange={(ev) => {
if (type === "number")
onChange(Number.parseFloat(ev.target.value));
else onChange(ev.target.value);
}}
/>
);
},
},
editCellMode: "cell",
editClickActivator: "single",
});
const view = grid.view.useValue();
return (
<div className="lng-grid" style={{ height: 500 }}>
<Grid.Root grid={grid}>
<Grid.Viewport>
<Grid.Header>
{view.header.layout.map((row, i) => {
return (
<Grid.HeaderRow key={i} headerRowIndex={i}>
{row.map((c) => {
if (c.kind === "group") return null;
return (
<Grid.HeaderCell
key={c.id}
cell={c}
className="flex w-full h-full capitalize px-2 items-center"
/>
);
})}
</Grid.HeaderRow>
);
})}
</Grid.Header>
<Grid.RowsContainer>
<Grid.RowsCenter>
{view.rows.center.map((row) => {
if (row.kind === "full-width") return null;
return (
<Grid.Row row={row} key={row.id}>
{row.cells.map((c) => {
return (
<Grid.Cell
key={c.id}
cell={c}
className="text-sm flex items-center px-2 h-full w-full"
/>
);
})}
</Grid.Row>
);
})}
</Grid.RowsCenter>
</Grid.RowsContainer>
</Grid.Viewport>
</Grid.Root>
</div>
);
}
When using a custom editor, LyteNyte Grid passes:
value
- The current cell value.onChange
- A function to update the value.Editing is controlled: Enter accepts changes, Escape cancels.
editable
can be:
Example: In the Job
column, only "management"
values are editable.
"use client";
import { useClientRowDataSource, Grid } from "@1771technologies/lytenyte-pro";
import "@1771technologies/lytenyte-pro/grid.css";
import type { Column } from "@1771technologies/lytenyte-pro/types";
import { bankDataSmall } from "@1771technologies/sample-data/bank-data-smaller";
import { useId } from "react";
type BankData = (typeof bankDataSmall)[number];
const columns: Column<BankData>[] = [
{ id: "age", width: 100, type: "number" },
{ id: "job", width: 160, editable: (p) => p.row.data?.job === "management" },
{ id: "balance", width: 160, type: "number" },
{ id: "education", width: 160 },
{ id: "marital" },
];
export default function CellEditingSelectively() {
const ds = useClientRowDataSource({
data: bankDataSmall,
});
const grid = Grid.useLyteNyte({
gridId: useId(),
rowDataSource: ds,
columns,
columnBase: {
editable: true,
widthFlex: 1,
},
editCellMode: "cell",
editClickActivator: "single",
});
const view = grid.view.useValue();
return (
<div className="lng-grid" style={{ height: 500 }}>
<Grid.Root grid={grid}>
<Grid.Viewport>
<Grid.Header>
{view.header.layout.map((row, i) => {
return (
<Grid.HeaderRow key={i} headerRowIndex={i}>
{row.map((c) => {
if (c.kind === "group") return null;
return (
<Grid.HeaderCell
key={c.id}
cell={c}
className="flex w-full h-full capitalize px-2 items-center"
/>
);
})}
</Grid.HeaderRow>
);
})}
</Grid.Header>
<Grid.RowsContainer>
<Grid.RowsCenter>
{view.rows.center.map((row) => {
if (row.kind === "full-width") return null;
return (
<Grid.Row row={row} key={row.id}>
{row.cells.map((c) => {
return (
<Grid.Cell
key={c.id}
cell={c}
className="text-sm flex items-center px-2 h-full w-full"
/>
);
})}
</Grid.Row>
);
})}
</Grid.RowsCenter>
</Grid.RowsContainer>
</Grid.Viewport>
</Grid.Root>
</div>
);
}
Use editSetter
to control how updated values are written to row data. This is useful when a cell's
value comes from a derived or nested field. The field property determines the
displayed value.
When editing:
editSetter
(or via field
for string
/number
fields)."use client";
import { Grid, useClientRowDataSource } from "@1771technologies/lytenyte-pro";
import "@1771technologies/lytenyte-pro/grid.css";
import type { Column } from "@1771technologies/lytenyte-pro/types";
import { stockData } from "@1771technologies/sample-data/stock-data-smaller";
import { useId } from "react";
type Stock = (typeof stockData)[number];
const columns: Column<Stock>[] = [
{ field: 0, width: 150, id: "ticker" },
{ field: 1, width: 150, id: "full", widthFlex: 1 },
{ field: 2, width: 150, id: "analyst-rating" },
{
width: 150,
field: ({ data }) => {
if (data.kind === "branch" || !data.data) return null;
const value = data.data[3] as number;
return value;
},
cellRenderer: (p) => {
const field = p.grid.api.columnField(p.column, p.row);
return (
<div className="w-full h-full flex items-center justify-end px-2 tabular-nums">
{`${field}`} USD
</div>
);
},
id: "price",
},
{
width: 150,
field: ({ data }) => {
if (data.kind === "branch" || !data.data) return null;
const value = data.data[3] as number;
return Math.round(value * 1.28);
},
cellRenderer: (p) => {
const field = p.grid.api.columnField(p.column, p.row) as number;
return (
<div className="w-full h-full flex items-center justify-end px-2 tabular-nums">
{`${field.toFixed(2)}`} GBP
</div>
);
},
editSetter: ({ data, value }) => {
const usdValue = Math.round(value / 1.28);
data[3] = usdValue;
return data;
},
id: "Price (GBP @ 1.28)",
},
];
export default function CellEditingEditSetter() {
const ds = useClientRowDataSource({ data: stockData });
const grid = Grid.useLyteNyte({
gridId: useId(),
rowDataSource: ds,
columns,
editCellMode: "cell",
columnBase: {
editable: true,
widthFlex: 1,
},
});
const view = grid.view.useValue();
return (
<div className="lng-grid" style={{ height: 500 }}>
<Grid.Root grid={grid}>
<Grid.Viewport>
<Grid.Header>
{view.header.layout.map((row, i) => {
return (
<Grid.HeaderRow key={i} headerRowIndex={i}>
{row.map((c) => {
if (c.kind === "group") return null;
return (
<Grid.HeaderCell
key={c.id}
cell={c}
className="flex w-full h-full capitalize px-2 items-center"
/>
);
})}
</Grid.HeaderRow>
);
})}
</Grid.Header>
<Grid.RowsContainer>
<Grid.RowsCenter>
{view.rows.center.map((row) => {
if (row.kind === "full-width") return null;
return (
<Grid.Row row={row} key={row.id}>
{row.cells.map((c) => {
return (
<Grid.Cell
key={c.id}
cell={c}
className="text-sm flex items-center px-2 h-full w-full text-nowrap overflow-hidden text-ellipsis"
/>
);
})}
</Grid.Row>
);
})}
</Grid.RowsCenter>
</Grid.RowsContainer>
</Grid.Viewport>
</Grid.Root>
</div>
);
}
Edits are applied at the row level-updating one cell updates the row's entire data object.
If defined, editRowValidatorFn
runs before updates are applied. It can:
true
- Validation passes.false
- Validation fails.If validation fails, the editError
event is fired. Use this event to inform the user.
Example: Prevent negative values in the Age
column.
"use client";
import { useClientRowDataSource, Grid } from "@1771technologies/lytenyte-pro";
import "@1771technologies/lytenyte-pro/grid.css";
import type { Column } from "@1771technologies/lytenyte-pro/types";
import { bankDataSmall } from "@1771technologies/sample-data/bank-data-smaller";
import { useId, useState } from "react";
type BankData = (typeof bankDataSmall)[number];
const columns: Column<BankData>[] = [
{ id: "age", width: 100, type: "number" },
{ id: "job", width: 160 },
{ id: "balance", width: 160, type: "number" },
{ id: "education", width: 160 },
{ id: "marital" },
];
export default function CellEditingValidation() {
const ds = useClientRowDataSource({
data: bankDataSmall,
});
const grid = Grid.useLyteNyte({
gridId: useId(),
rowDataSource: ds,
columns,
columnBase: {
editable: true,
widthFlex: 1,
},
editRowValidatorFn: (p) => {
const data = p.data;
console.log("i ran", p, data.age < 0);
if (data.age < 0) return { reason: "Age cannot be less than 0." };
return true;
},
editCellMode: "cell",
editClickActivator: "single",
});
const view = grid.view.useValue();
const [error, setError] = useState<string>();
return (
<div>
{error && <div className="px-2 py-1 text-red-500">Error: {error}</div>}
<div className="lng-grid" style={{ height: 500 }}>
<Grid.Root
grid={grid}
onEditError={(e) => {
if (typeof e.validation === "object") setError(e.validation.reason);
}}
onEditEnd={() => {
setError("");
}}
>
<Grid.Viewport>
<Grid.Header>
{view.header.layout.map((row, i) => {
return (
<Grid.HeaderRow key={i} headerRowIndex={i}>
{row.map((c) => {
if (c.kind === "group") return null;
return (
<Grid.HeaderCell
key={c.id}
cell={c}
className="flex w-full h-full capitalize px-2 items-center"
/>
);
})}
</Grid.HeaderRow>
);
})}
</Grid.Header>
<Grid.RowsContainer>
<Grid.RowsCenter>
{view.rows.center.map((row) => {
if (row.kind === "full-width") return null;
return (
<Grid.Row row={row} key={row.id}>
{row.cells.map((c) => {
return (
<Grid.Cell
key={c.id}
cell={c}
className="text-sm flex items-center px-2 h-full w-full"
/>
);
})}
</Grid.Row>
);
})}
</Grid.RowsCenter>
</Grid.RowsContainer>
</Grid.Viewport>
</Grid.Root>
</div>
</div>
);
}
Use grid.api.editUpdate
to update a cell without user interaction. This method:
editSetter
.LyteNyte Grid emits these events during editing:
editBegin
- Fired before editing starts; call preventDefault()
to block it.editEnd
- Fired after updates are applied.editError
- Fired when validation fails or an error occurs.editCancel
- Fired when editing is cancelled (e.g., pressing Escape).