Context menu

Published at Oct 15, 2024

To provide more flexibility, I decided against a simple “context menu cell” option. Instead, you can fully customize the your cell behavior with just a few extra lines of code.

Name
On stock
Price
Sprouts - Onion
972
261.51
Beer - True North Lager
38
191.32
Milk - Condensed
714
826.1
Brandy Cherry - Mcguinness
212
555.07
Olive - Spread Tapenade
509
506.45
Tea - Black Currant
848
750.65
Coconut Milk - Unsweetened
149
658.56
Nut - Walnut, Pieces
147
763.42
Apple - Northern Spy
950
81.35
Orange - Canned, Mandarin
200
939.59
Veal - Brisket, Provimi,bnls
226
353.49
Wine - Ice Wine
618
867.85
Table Cloth 54x72 White
770
703.86
Wine - Cotes Du Rhone Parallele
435
967.56
Wine - Duboeuf Beaujolais
105
247.42
Carrots - Jumbo
350
880.11
Wine - Touraine Azay - Le - Rideau
290
949.68
Schnappes Peppermint - Walker
69
879.93
Wine - Champagne Brut Veuve
201
496.79
Appetizer - Smoked Salmon / Dill
856
274.6

Implementation

Make custom cell

It is basically default cell, but with added builder from shadcn-svelte. More explictly - from melt-ui. Datagrid will support headless mode and because of that context menu is not implement into cell as default.

For shadcn-svelte users I prepared a component

I advise build own one. It may seem complicated, but you can simply copy and paste it. Even better, grab the latest version directly from your codebase, as I might forget to update this example in the future. To prevent error described above I will just show you what to add to custom context menu cell.

<script lang="ts">
	let { builder }: { builder?: any } = $props();
</script>

{#if column.visible !== false}
	<div use:builder.action {...builder}>...rest</div>
{/if}

Render it

{#each datagrid.columns as column, columnIndex}
	{@const props = { row, rowIndex, column, columnIndex }}
	<ContextMenu.Root>
		<ContextMenu.Trigger asChild let:builder>
			<CellWithContextMenu
				{builder}
				{...props}
				class={{ data: 'overflow-hidden text-ellipsis text-nowrap' }}
			/>
		</ContextMenu.Trigger>
		<ContextMenu.Content>
			<ContextMenu.Item>{getNestedValue(row, column.id)}</ContextMenu.Item>
		</ContextMenu.Content>
	</ContextMenu.Root>
{/each}

Code

Column definitions

import type { BaseColumn } from "$lib/datagrid/types";
import type { InventoryDataRow } from "$lib/data/inventory";

export const columns = [
    {
        id: 'product.name',
        title: 'Name',
        width: '300px',
        
    },
    {
        id: 'quantity',
        title: 'On stock',
        align: 'end',
    },
    {
        id: 'price',
        title: 'Price',
        align: 'end',
        grow: true,
    }

] satisfies BaseColumn<InventoryDataRow>[]

Datagrid component

<script lang="ts">
	import { setContext } from 'svelte';
	import { columns } from './columns.svelte';
	import { inventoryData as data } from '$lib/data/inventory';
	import { TzezarDatagrid } from '$lib/datagrid/tzezar-datagrid.svelte';
	import * as Datagrid from '$lib/datagrid';
	import * as ContextMenu from '$lib/components/ui/context-menu';
	import { getNestedValue } from '$lib/datagrid/fns/get-nested-value';
	import CellWithContextMenu from '$lib/datagrid/shadcn/cell-with-context-menu.svelte';

	let datagrid = setContext(
		"datagrid",
		new TzezarDatagrid({
			data: data.slice(0, 20),
			columns,
			options: {
				pagination: { display: false },
			}
		})
	);
</script>

<Datagrid.Datagrid>
	{#snippet head()}
		{#each datagrid.columns as column (column.id)}
			<Datagrid.Header {column} />
		{/each}
	{/snippet}
	{#snippet body()}
		{#each datagrid.data as row, rowIndex}
			<Datagrid.Row {rowIndex}>
				{#each datagrid.columns as column, columnIndex}
					{@const props = { row, rowIndex, column, columnIndex }}
					<ContextMenu.Root>
						<ContextMenu.Trigger asChild let:builder>
							<CellWithContextMenu
								{builder}
								{...props}
								class={{ data: 'overflow-hidden text-ellipsis text-nowrap' }}
							/>
						</ContextMenu.Trigger>
						<ContextMenu.Content>
							<ContextMenu.Item>Is not it awesome?</ContextMenu.Item>
							<ContextMenu.Item>Cell content: {getNestedValue(row, column.id)}</ContextMenu.Item>
						</ContextMenu.Content>
					</ContextMenu.Root>
				{/each}
			</Datagrid.Row>
		{/each}
	{/snippet}
</Datagrid.Datagrid>
In the coming days, the grid will officially launch its version 1.0. I recommend waiting until the release before incorporating it into your projects, as we've completely rewritten and simplified the internal logic, making it more efficient. Version 1.0 will also introduce exciting new features such as row grouping and aggregation. If you have any questions or feedback, feel free to reach out! If you find this project valuable, please consider giving it a star on GitHub. Thank you for your support! ❤️