A simple todo list demonstrating automatic component registration and data synchronization.
todo-list/
├── index.ts # Shared types and injection key
├── TodoList.vue # Parent component with desk
└── TodoItem.vue # Child component with auto check-in
import type { InjectionKey } from 'vue';
import type { DeskCore } from '#vue-airport/composables/useCheckIn';
export interface TodoItem {
label: string;
done: boolean;
}
export const TODO_DESK_KEY: InjectionKey<DeskCore<TodoItem>> = Symbol('todoDesk');
export { default as TodoList } from './TodoList.vue';
export { default as TodoItem } from './TodoItem.vue';
Creates the desk and manages the todo list state:
<script setup lang="ts">
import { useCheckIn } from '#vue-airport/composables/useCheckIn';
import TodoItem from './TodoItem.vue';
import { type TodoItem as TodoItemData, TODO_DESK_KEY } from '.';
// Create a desk to manage todo items
const { createDesk } = useCheckIn<TodoItemData>();
const { desk } = createDesk(TODO_DESK_KEY, {
debug: false,
onCheckIn: (id, data) => {
console.log(`✅ Item added: ${id}`, data);
},
onCheckOut: (id) => {
console.log(`❌ Item removed: ${id}`);
},
});
// Local state for managing todos
const todos = ref<Array<{
id: number;
label: string;
done: boolean;
}>>([]);
// Add a new todo item
const addItem = () => {
const id = Date.now();
todos.value.push({
id,
label: `Task ${id}`,
done: false,
});
};
// Toggle the done state
const toggleItem = (id: string | number) => {
const todo = todos.value.find(t => t.id === id);
if (todo) {
todo.done = !todo.done;
}
};
// Remove a todo item
const removeItem = (id: string | number) => {
const index = todos.value.findIndex(t => t.id === id);
if (index !== -1) {
todos.value.splice(index, 1);
}
};
// Clear all todos
const clearAll = () => {
todos.value = [];
desk.clear();
};
</script>
<template>
<div>
<div class="controls">
<UButton icon="i-heroicons-plus" @click="addItem">
Add Task
</UButton>
<UButton
color="error"
variant="soft"
icon="i-heroicons-trash"
:disabled="desk.size.value === 0"
@click="clearAll"
>
Clear All
</UButton>
<UBadge color="primary" variant="subtle">
{{ desk.size.value }} item(s)
</UBadge>
</div>
<ul v-if="todos.length > 0" class="item-list">
<TodoItem
v-for="todo in todos"
:key="todo.id"
:id="todo.id"
:label="todo.label"
:done="todo.done"
@toggle="toggleItem"
@remove="removeItem"
/>
</ul>
<div v-else class="empty-state">
<p>No tasks. Click "Add Task" to get started.</p>
</div>
</div>
</template>
Automatically checks in and synchronizes data:
<script setup lang="ts">
import { useCheckIn } from '#vue-airport/composables/useCheckIn';
import { type TodoItem, TODO_DESK_KEY } from '.';
const props = defineProps<{
id: string | number;
label: string;
done: boolean;
}>();
const emit = defineEmits<{
toggle: [id: string | number];
remove: [id: string | number];
}>();
// Auto check-in with data watching enabled
// The component will:
// 1. Check in automatically when mounted
// 2. Watch props changes and update the desk
// 3. Check out automatically when unmounted
useCheckIn<TodoItem>().checkIn(TODO_DESK_KEY, {
id: props.id,
autoCheckIn: true,
watchData: true,
data: () => ({
label: props.label,
done: props.done,
}),
});
</script>
<template>
<li class="item">
<UCheckbox
:model-value="props.done"
@update:model-value="emit('toggle', props.id)"
/>
<span :class="{ done: props.done }">
{{ props.label }}
</span>
<UButton
size="xs"
color="error"
variant="ghost"
icon="i-heroicons-x-mark"
@click="emit('remove', props.id)"
/>
</li>
</template>
Auto Check-In: Child components automatically register when mounted with autoCheckIn: true.
Watch Data: The desk registry stays synchronized with component props when watchData: true.
Lifecycle Hooks: Parent components can react to check-in/check-out events via onCheckIn and onCheckOut callbacks.
autoCheckIn: true)watchData: true)<template>
<TodoList />
</template>