Data & Editing

Cell Editing

Enable inline cell editing in NuGrid.

NuGrid provides powerful inline cell editing with support for various data types, validation, and custom editors.

Double-click a cell to edit. Press Enter to save, Escape to cancel.

ID
Task
Status
Priority
1
Design mockups
completed
1
2
Implement API
in-progress
2
3
Write tests
pending
3
4
Deploy to staging
pending
4
5
Code review
in-progress
2
<script setup lang="ts">
import type { NuGridColumn } from '#nu-grid/types'

interface Task {
  id: number
  name: string
  status: string
  priority: number
}

const data = ref<Task[]>([
  { id: 1, name: 'Design mockups', status: 'completed', priority: 1 },
  { id: 2, name: 'Implement API', status: 'in-progress', priority: 2 },
  { id: 3, name: 'Write tests', status: 'pending', priority: 3 },
  { id: 4, name: 'Deploy to staging', status: 'pending', priority: 4 },
  { id: 5, name: 'Code review', status: 'in-progress', priority: 2 },
])

const columns: NuGridColumn<Task>[] = [
  { accessorKey: 'id', header: 'ID', size: 60, enableEditing: false },
  { accessorKey: 'name', header: 'Task', size: 180, cellDataType: 'text' },
  { accessorKey: 'status', header: 'Status', size: 120, cellDataType: 'text' },
  { accessorKey: 'priority', header: 'Priority', size: 100, cellDataType: 'number' },
]

const toast = useToast()

function onCellValueChanged(event: { row: any; column: any; oldValue: any; newValue: any }) {
  toast.add({
    title: 'Cell Updated',
    description: `${event.column.id}: "${event.oldValue}" → "${event.newValue}"`,
    color: 'success',
  })
}
</script>

<template>
  <div class="w-full">
    <p class="mb-3 text-sm text-muted">
      Double-click a cell to edit. Press Enter to save, Escape to cancel.
    </p>
    <NuGrid
      :data="data"
      :columns="columns"
      :editing="{ enabled: true, startClicks: 'double' }"
      :ui="{
        base: 'w-full border-separate border-spacing-0',
        thead: '[&>tr]:bg-elevated/50',
        th: 'py-2 border-y border-default first:border-l last:border-r first:rounded-l-lg last:rounded-r-lg',
        td: 'border-b border-default',
      }"
      @cell-value-changed="onCellValueChanged"
    />
  </div>
</template>

Enabling Editing

Enable editing with the editing prop:

<template>
  <!-- Simple enable -->
  <NuGrid :data="data" :columns="columns" :editing="{ enabled: true }" />

  <!-- With options -->
  <NuGrid
    :data="data"
    :columns="columns"
    :editing="{
      enabled: true,
      startClicks: 'double',
      startKeys: ['enter', 'f2'],
    }"
  />
</template>

Editing Options

Configure editing behavior:

interface NuGridEditingOptions {
  enabled: boolean
  startClicks: 'none' | 'single' | 'double'
  startKeys: ('enter' | 'f2' | 'bs' | 'alpha' | 'numeric')[]
}

Options Reference

OptionTypeDefaultDescription
enabledbooleanfalseEnable editing
startClicks'none' | 'single' | 'double''double'Click to start editing
startKeysarray['enter', 'f2', 'bs', 'alpha', 'numeric']Keys that start editing

Start Keys

KeyDescription
enterEnter key starts editing
f2F2 key starts editing
bsBackspace/Delete clears and starts editing
alphaAny letter key replaces and starts editing
numericAny number key replaces and starts editing

Per-Column Editing

Enable or disable editing per column:

const columns: NuGridColumn<Product>[] = [
  {
    accessorKey: 'id',
    header: 'ID',
    enableEditing: false,  // ID is not editable
  },
  {
    accessorKey: 'name',
    header: 'Name',
    enableEditing: true,   // Explicitly enable
  },
  {
    accessorKey: 'price',
    header: 'Price',
    // Editing enabled by default when grid editing is on
  },
]

Cell Data Types

Use cell data types for appropriate editors:

const columns: NuGridColumn<Product>[] = [
  {
    accessorKey: 'name',
    header: 'Name',
    cellDataType: 'text',      // Text input editor
  },
  {
    accessorKey: 'price',
    header: 'Price',
    cellDataType: 'number',    // Number input editor
  },
  {
    accessorKey: 'active',
    header: 'Active',
    cellDataType: 'boolean',   // Checkbox editor
  },
  {
    accessorKey: 'category',
    header: 'Category',
    cellDataType: 'selection', // Dropdown editor
    cellDataTypeOptions: {
      options: ['Electronics', 'Clothing', 'Food'],
    },
  },
]

Handling Value Changes

Listen to cell value changes:

<script setup lang="ts">
function onCellValueChanged(event) {
  console.log('Changed:', {
    rowId: event.row.id,
    column: event.column.id,
    oldValue: event.oldValue,
    newValue: event.newValue,
  })

  // Persist to server
  await updateRecord(event.row.original.id, {
    [event.column.id]: event.newValue,
  })
}
</script>

<template>
  <NuGrid
    :data="data"
    :columns="columns"
    :editing="{ enabled: true }"
    @cell-value-changed="onCellValueChanged"
  />
</template>

Editing Events

NuGrid emits several editing-related events:

<script setup lang="ts">
function onCellEditingStarted(event) {
  console.log('Started editing:', event.row.id, event.column.id)
}

function onCellEditingCancelled(event) {
  console.log('Cancelled editing:', event.row.id, event.column.id)
}

function onCellValueChanged(event) {
  console.log('Value changed:', event.oldValue, '->', event.newValue)
}
</script>

<template>
  <NuGrid
    :data="data"
    :columns="columns"
    :editing="{ enabled: true }"
    @cell-editing-started="onCellEditingStarted"
    @cell-editing-cancelled="onCellEditingCancelled"
    @cell-value-changed="onCellValueChanged"
  />
</template>

Custom Cell Editors

Create custom editors for special data types:

<script setup lang="ts">
// Register a custom editor component
const columns: NuGridColumn<Product>[] = [
  {
    accessorKey: 'rating',
    header: 'Rating',
    cellEditor: defineComponent({
      props: ['value', 'onSave', 'onCancel'],
      setup(props) {
        const localValue = ref(props.value)

        return () => h('div', { class: 'flex gap-1' }, [
          ...Array.from({ length: 5 }, (_, i) =>
            h('button', {
              class: i < localValue.value ? 'text-yellow-400' : 'text-gray-300',
              onClick: () => {
                localValue.value = i + 1
                props.onSave(localValue.value)
              },
            }, '')
          ),
        ])
      },
    }),
  },
]
</script>

Keyboard Navigation During Edit

KeyAction
EnterSave and move down
TabSave and move right
Shift + TabSave and move left
EscapeCancel editing
Arrow keysSave and navigate (configurable)

Preventing Edit

Cancel editing programmatically by returning false from a handler:

<script setup lang="ts">
function onCellEditingStarted(event) {
  // Prevent editing locked rows
  if (event.row.original.locked) {
    return false
  }
}
</script>

<template>
  <NuGrid
    :data="data"
    :columns="columns"
    :editing="{ enabled: true }"
    @cell-editing-started="onCellEditingStarted"
  />
</template>

Example: Editable Grid with Auto-Save

<script setup lang="ts">
const toast = useToast()
const saving = ref(false)

async function onCellValueChanged(event) {
  saving.value = true

  try {
    await api.updateItem(event.row.original.id, {
      [event.column.id]: event.newValue,
    })

    toast.add({
      title: 'Saved',
      description: `Updated ${event.column.id}`,
      color: 'success',
    })
  } catch (error) {
    toast.add({
      title: 'Error',
      description: 'Failed to save changes',
      color: 'error',
    })

    // Revert the change
    event.row.original[event.column.id] = event.oldValue
  } finally {
    saving.value = false
  }
}
</script>

<template>
  <NuGrid
    :data="data"
    :columns="columns"
    :editing="{ enabled: true, startClicks: 'double' }"
    :loading="saving"
    @cell-value-changed="onCellValueChanged"
  />
</template>

Next Steps

Add New Rows

Enable adding new rows to the grid.

Validation

Validate cell values with schemas.