Advanced

Events

Handle NuGrid events for user interactions.

NuGrid emits events for various user interactions, allowing you to respond to clicks, edits, navigation, and state changes.

Event Categories

Click Events

EventPayloadDescription
cell-clickedNuGridCellClickEventCell was clicked
cell-double-clickedNuGridCellClickEventCell was double-clicked
row-clickedNuGridRowClickEventRow was clicked
row-double-clickedNuGridRowClickEventRow was double-clicked

Focus Events

EventPayloadDescription
focused-cell-changedNuGridFocusedCellChangedEventFocused cell changed
focused-row-changedNuGridFocusedRowChangedEventFocused row changed

Keyboard Events

EventPayloadDescription
keydownNuGridKeydownEventUnhandled keyboard event (for custom shortcuts)

Editing Events

EventPayloadDescription
cell-editing-startedNuGridCellEditingStartedEventCell editing began
cell-editing-cancelledNuGridCellEditingCancelledEventCell editing was cancelled
cell-value-changedNuGridCellValueChangedEventCell value was saved

State Events

EventPayloadDescription
sort-changedNuGridSortChangedEventSorting changed
filter-changedNuGridFilterChangedEventFilters changed
row-selection-changedRecord<string, boolean>Selection changed

Row Events

EventPayloadDescription
row-add-requestedRow dataNew row submitted
row-draggedDrag eventRow was reordered

Click Events

Cell Click

<script setup lang="ts">
import type { NuGridCellClickEvent } from '#nu-grid/types'

function onCellClicked(event: NuGridCellClickEvent<User>) {
  console.log('Cell clicked:', {
    row: event.row.original,
    column: event.column.id,
    value: event.value,
  })
}
</script>

<template>
  <NuGrid
    :data="data"
    :columns="columns"
    @cell-clicked="onCellClicked"
  />
</template>

Cell Double Click

<script setup lang="ts">
function onCellDoubleClicked(event) {
  // Navigate to detail page on double-click
  navigateTo(`/users/${event.row.original.id}`)
}
</script>

<template>
  <NuGrid
    :data="data"
    :columns="columns"
    @cell-double-clicked="onCellDoubleClicked"
  />
</template>

Row Click

<script setup lang="ts">
function onRowClicked(event) {
  // Select row on click
  selectedUserId.value = event.row.original.id
}
</script>

<template>
  <NuGrid
    :data="data"
    :columns="columns"
    @row-clicked="onRowClicked"
  />
</template>

Focus Events

Focused Cell Changed

<script setup lang="ts">
import type { NuGridFocusedCellChangedEvent } from '#nu-grid/types'

function onFocusedCellChanged(event: NuGridFocusedCellChangedEvent<User>) {
  console.log('Focus moved:', {
    from: `${event.previousRowIndex}:${event.previousColumnIndex}`,
    to: `${event.rowIndex}:${event.columnIndex}`,
    rowId: event.rowId,
    columnId: event.columnId,
  })
}
</script>

<template>
  <NuGrid
    :data="data"
    :columns="columns"
    :focus="{ mode: 'cell' }"
    @focused-cell-changed="onFocusedCellChanged"
  />
</template>

Focused Row Changed

<script setup lang="ts">
function onFocusedRowChanged(event) {
  // Load details for focused row
  loadUserDetails(event.rowId)
}
</script>

<template>
  <NuGrid
    :data="data"
    :columns="columns"
    :focus="{ mode: 'row' }"
    @focused-row-changed="onFocusedRowChanged"
  />
</template>

Keyboard Events

Custom Keyboard Shortcuts

The keydown event fires before NuGrid's internal handlers process the key. This allows you to intercept keys and prevent NuGrid's default behavior by setting event.handled = true. Use this to implement custom shortcuts like Enter-to-navigate:

<script setup lang="ts">
import type { NuGridKeydownEvent } from '#nu-grid/types'

function onKeydown(event: NuGridKeydownEvent<User>) {
  if (!event.rowData) return

  // Navigate to detail page on Enter (prevent NuGrid from starting editing)
  if (event.event.key === 'Enter') {
    event.handled = true // Prevents NuGrid's default Enter behavior
    navigateTo(`/users/${event.rowData.id}`)
  }

  // Delete selected row on Delete key
  if (event.event.key === 'Delete') {
    event.handled = true
    deleteUser(event.rowData.id)
  }
}
</script>

<template>
  <NuGrid
    :data="data"
    :columns="columns"
    :focus="{ mode: 'row' }"
    @keydown="onKeydown"
  />
</template>

Keydown Event Payload

The NuGridKeydownEvent provides context about the focused cell/row:

PropertyTypeDescription
eventKeyboardEventThe original keyboard event
rowRow<T> | nullCurrently focused row
rowDataT | nullThe original row data (convenience for row.original)
rowIdstring | nullCurrent row ID
rowIndexnumberCurrent row index (-1 if none)
columnColumn<T> | nullCurrently focused column (cell mode only)
columnIdstring | nullCurrent column ID
columnNamestring | nullThe column header name
columnIndexnumberCurrent column index (-1 if none)
cellCell<T> | nullCurrently focused cell (cell mode only)
valueunknownThe current cell value
handledbooleanSet to true to prevent NuGrid's internal handling

Example: Quick Actions

<script setup lang="ts">
function onKeydown(event: NuGridKeydownEvent<Task>) {
  const task = event.rowData
  if (!task) return

  switch (event.event.key) {
    case 'Enter':
      // Open task details instead of editing
      event.handled = true
      openTaskModal(task.id)
      break
    case 'e':
      // Quick edit (only if no modifier keys)
      if (!event.event.ctrlKey && !event.event.metaKey) {
        event.handled = true
        startEditing(task.id)
      }
      break
    case 'c':
      // Mark complete (only if no modifier keys)
      if (!event.event.ctrlKey && !event.event.metaKey) {
        event.handled = true
        toggleComplete(task.id)
      }
      break
    case 'Delete':
    case 'Backspace':
      // Delete with confirmation
      event.handled = true
      confirmDelete(task.id)
      break
  }
}
</script>

<template>
  <NuGrid
    :data="tasks"
    :columns="columns"
    :focus="{ mode: 'row', autoFocus: true }"
    @keydown="onKeydown"
  />
</template>

Editing Events

Cell Editing Started

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

  // Prevent editing for locked rows
  if (event.row.original.locked) {
    return false  // Cancels the edit
  }
}
</script>

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

Cell Value Changed

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

  // Persist to server
  try {
    await api.updateUser(event.row.original.id, {
      [event.column.id]: event.newValue,
    })
    toast.add({ title: 'Saved', color: 'success' })
  } catch (error) {
    toast.add({ title: 'Failed to save', color: 'error' })
    // Optionally revert
    event.row.original[event.column.id] = event.oldValue
  }
}
</script>

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

Cell Editing Cancelled

<script setup lang="ts">
function onCellEditingCancelled(event) {
  console.log('Editing cancelled:', event.column.id)
}
</script>

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

State Events

Sort Changed

<script setup lang="ts">
function onSortChanged(event) {
  console.log('Sorting:', event.sorting)
  // [{ id: 'name', desc: false }, ...]
}
</script>

<template>
  <NuGrid
    :data="data"
    :columns="columns"
    @sort-changed="onSortChanged"
  />
</template>

Filter Changed

<script setup lang="ts">
function onFilterChanged(event) {
  console.log('Filters:', event.columnFilters)
  // [{ id: 'status', value: 'active' }, ...]
}
</script>

<template>
  <NuGrid
    :data="data"
    :columns="columns"
    @filter-changed="onFilterChanged"
  />
</template>

Row Selection Changed

<script setup lang="ts">
function onRowSelectionChanged(selection: Record<string, boolean>) {
  const selectedIds = Object.entries(selection)
    .filter(([, selected]) => selected)
    .map(([id]) => id)

  console.log('Selected:', selectedIds)
}
</script>

<template>
  <NuGrid
    :data="data"
    :columns="columns"
    row-selection="multi"
    @row-selection-changed="onRowSelectionChanged"
  />
</template>

Example: Event Logger

<script setup lang="ts">
interface LogEntry {
  id: number
  event: string
  time: string
  payload: string
}

const log = ref<LogEntry[]>([])
let counter = 0

function logEvent(name: string, payload: any) {
  log.value.unshift({
    id: ++counter,
    event: name,
    time: new Date().toLocaleTimeString(),
    payload: JSON.stringify(payload, null, 2),
  })

  if (log.value.length > 50) {
    log.value.pop()
  }
}
</script>

<template>
  <div class="flex gap-4">
    <NuGrid
      :data="data"
      :columns="columns"
      :editing="{ enabled: true }"
      :focus="{ mode: 'cell' }"
      row-selection="multi"
      class="flex-1"
      @cell-clicked="e => logEvent('cell-clicked', { row: e.row.id, col: e.column.id })"
      @cell-double-clicked="e => logEvent('cell-double-clicked', { row: e.row.id, col: e.column.id })"
      @focused-cell-changed="e => logEvent('focused-cell-changed', { row: e.rowIndex, col: e.columnIndex })"
      @cell-editing-started="e => logEvent('cell-editing-started', { col: e.column.id })"
      @cell-value-changed="e => logEvent('cell-value-changed', { old: e.oldValue, new: e.newValue })"
      @sort-changed="e => logEvent('sort-changed', e.sorting)"
      @row-selection-changed="e => logEvent('row-selection-changed', Object.keys(e).length)"
    />

    <div class="w-80 rounded-lg border p-4">
      <h3 class="mb-2 font-semibold">Event Log</h3>
      <div class="max-h-96 space-y-2 overflow-auto text-xs">
        <div v-for="entry in log" :key="entry.id" class="rounded bg-gray-50 p-2 dark:bg-gray-800">
          <div class="flex justify-between">
            <span class="font-medium text-primary">{{ entry.event }}</span>
            <span class="text-gray-400">{{ entry.time }}</span>
          </div>
          <pre class="mt-1 text-gray-600 dark:text-gray-400">{{ entry.payload }}</pre>
        </div>
      </div>
    </div>
  </div>
</template>

Next Steps

Row Actions

Add action menus to rows.

Excel Export

Export data to Excel.