Customization

The Grid components provide several ways to customize behavior and appearance.

Custom Move Handle

By default, the entire item is draggable. Use the moveHandle snippet to restrict dragging to a specific element:

<GridItem x={0} y={0}>
	{#snippet moveHandle({ moveStart })}
		<div class="drag-handle cursor-move bg-gray-200 p-2" onpointerdown={moveStart}>⠿ Drag here</div>
	{/snippet}

	{#snippet children()}
		<div class="p-4">This content is not draggable directly. Use the handle above.</div>
	{/snippet}
</GridItem>

Custom Resize Handle

Replace the default resize handle with your own:

<GridItem x={0} y={0}>
	{#snippet resizeHandle({ resizeStart })}
		<button
			class="absolute bottom-0 right-0 w-8 h-8 bg-blue-500 text-white rounded-tl-lg cursor-se-resize"
			onpointerdown={resizeStart}
		>
			↘
		</button>
	{/snippet}

	{#snippet children()}
		<div class="p-4 pr-10 pb-10">Content</div>
	{/snippet}
</GridItem>

Combined Custom Handles

<script lang="ts">
	import Grid, { GridItem } from '@appulsauce/svelte-grid';

	let items = $state([{ id: '1', x: 0, y: 0, w: 4, h: 3 }]);

	const itemSize = { height: 50 };
</script>

<Grid {itemSize} cols={10} collision="push">
	{#each items as item (item.id)}
		<GridItem id={item.id} bind:x={item.x} bind:y={item.y} bind:w={item.w} bind:h={item.h}>
			{#snippet moveHandle({ moveStart })}
				<div
					class="flex items-center gap-2 p-2 bg-gray-100 border-b cursor-move"
					onpointerdown={moveStart}
				>
					<span class="text-gray-400">⠿</span>
					<span class="font-medium">Widget Header</span>
				</div>
			{/snippet}

			{#snippet resizeHandle({ resizeStart })}
				<div class="absolute bottom-1 right-1 w-4 h-4 cursor-se-resize" onpointerdown={resizeStart}>
					<svg viewBox="0 0 24 24" class="w-full h-full text-gray-400">
						<path
							fill="currentColor"
							d="M22,22H20V20H22V22M22,18H20V16H22V18M18,22H16V20H18V22M18,18H16V16H18V18M14,22H12V20H14V22M22,14H20V12H22V14Z"
						/>
					</svg>
				</div>
			{/snippet}

			{#snippet children()}
				<div class="p-4">
					<p class="text-gray-600">Widget content goes here</p>
				</div>
			{/snippet}
		</GridItem>
	{/each}
</Grid>

Disabling Interactions

Read-only Grid

Disable all interactions on the entire grid:

<Grid cols={10} rows={10} readOnly>
	<GridItem x={0} y={0}>
		{#snippet children()}
			<div>Can't move or resize</div>
		{/snippet}
	</GridItem>
</Grid>

Per-item Control

Disable move and/or resize on specific items:

<Grid cols={10} rows={10}>
	<!-- Fixed position, can resize -->
	<GridItem x={0} y={0} movable={false}>
		{#snippet children()}
			<div>Fixed position</div>
		{/snippet}
	</GridItem>

	<!-- Can move, fixed size -->
	<GridItem x={2} y={0} resizable={false}>
		{#snippet children()}
			<div>Fixed size</div>
		{/snippet}
	</GridItem>

	<!-- Completely static -->
	<GridItem x={4} y={0} movable={false} resizable={false}>
		{#snippet children()}
			<div>Static</div>
		{/snippet}
	</GridItem>
</Grid>

Size Constraints

Set minimum and maximum sizes for items:

<GridItem x={0} y={0} w={3} h={2} min={{ w: 2, h: 1 }} max={{ w: 6, h: 4 }}>
	{#snippet children()}
		<div>Constrained: 2-6 wide, 1-4 tall</div>
	{/snippet}
</GridItem>

Custom Attributes

Pass additional attributes to items using the spread operator:

<script lang="ts">
	const items = [
		{ id: '1', x: 0, y: 0, w: 2, h: 2, data: { title: 'Widget A', color: 'blue' } },
		{ id: '2', x: 2, y: 0, w: 2, h: 2, data: { title: 'Widget B', color: 'green' } }
	];
</script>

<Grid cols={10}>
	{#each items as item (item.id)}
		<GridItem x={item.x} y={item.y} w={item.w} h={item.h} data-color={item.data.color}>
			{#snippet children()}
				<div class="p-2">
					<h3>{item.data.title}</h3>
				</div>
			{/snippet}
		</GridItem>
	{/each}
</Grid>

Pattern Matching with ts-pattern

Use ts-pattern for complex item rendering logic:

<script lang="ts">
	import Grid, { GridItem } from '@appulsauce/svelte-grid';
	import { match } from 'ts-pattern';

	type WidgetType = 'chart' | 'table' | 'metric';

	const items: Array<{ id: string; x: number; y: number; w: number; h: number; type: WidgetType }> =
		[
			{ id: '1', x: 0, y: 0, w: 4, h: 3, type: 'chart' },
			{ id: '2', x: 4, y: 0, w: 4, h: 2, type: 'metric' },
			{ id: '3', x: 0, y: 3, w: 8, h: 3, type: 'table' }
		];

	function getWidgetClass(type: WidgetType): string {
		return match(type)
			.with('chart', () => 'bg-blue-50 border-blue-200')
			.with('table', () => 'bg-green-50 border-green-200')
			.with('metric', () => 'bg-purple-50 border-purple-200')
			.exhaustive();
	}
</script>

<Grid cols={8}>
	{#each items as item (item.id)}
		<GridItem
			x={item.x}
			y={item.y}
			w={item.w}
			h={item.h}
			class={`border ${getWidgetClass(item.type)}`}
		>
			{#snippet children()}
				{#if item.type === 'chart'}
					<div>Chart Widget</div>
				{:else if item.type === 'table'}
					<div>Table Widget</div>
				{:else}
					<div>Metric Widget</div>
				{/if}
			{/snippet}
		</GridItem>
	{/each}
</Grid>