ProtoCrudModal
ProtoCrudModal
A modal editor for a single collection item. Features:
- Auto-save draft on every change (to
Y.Map 'draft:{draftKey}'in the document) - Resume/discard draft banner on next open
- 2-column layout for wide modals (
modalSize≥'lg') - Save/Cancel footer with keyboard shortcut support
- Works with all field types including
linked-responses
When to use it
Set editMode: 'modal' on a CollectionSchema to have ProtoCrudList automatically open ProtoCrudModal when a user clicks an item. You rarely need to instantiate ProtoCrudModal directly.
collections: {
interviews: {
label: 'Interviews',
fields: { … },
editMode: 'modal', // triggers ProtoCrudModal
modalSize: '3xl',
}
}
Props (when using directly)
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | required | Controls modal visibility |
schema | CollectionSchema | required | The collection schema |
item | CollectionItem | null | null | Existing item to edit (null = new item) |
doc | Y.Doc | required | The Y.js document (for draft persistence) |
draftKey | string | 'default' | Key suffix for the draft Y.Map entry |
Emits
| Event | Payload | Description |
|---|---|---|
save | CollectionItem | Emitted with merged data when user clicks Save |
cancel | — | Emitted when user cancels or closes |
Draft persistence
When a user edits a form in the modal and then closes it without saving (navigates away, switches tabs, closes the browser), the in-progress edits are stored as a draft.
Storage location: Y.Map 'draft:{draftKey}' inside the shared Y.Doc.
Because drafts live in the Y.js document (backed by IndexedDB), they:
- Survive page refreshes and browser restarts
- Persist even when offline
- Sync to the server when connectivity is restored (so drafts survive across devices if the user has an account)
Draft banner: On next open, if an unsaved draft exists for the item, a banner appears at the top of the modal:
┌─────────────────────────────────────────────────────┐
│ ℹ You have an unsaved draft from 3 hours ago. │
│ [Resume] [Discard] │
└─────────────────────────────────────────────────────┘
Clicking Resume fills the form with the draft data. Clicking Discard clears the draft and fills the form with the saved item data (or empty for a new item).
Layout behavior
modalSize | Layout |
|---|---|
sm, md, lg | Single-column fields |
xl, 2xl, 3xl, 4xl, 5xl | Two-column fields (fields with colSpan: 2 span both columns) |
Tailwind max-width mapping
modalSize | Max width |
|---|---|
sm | max-w-sm |
md | max-w-md |
lg | max-w-lg |
xl | max-w-xl |
2xl | max-w-2xl |
3xl | max-w-3xl |
4xl | max-w-4xl |
5xl | max-w-5xl |
useProtoDraft
The draft composable, used internally by ProtoCrudModal:
function useProtoDraft(
doc: Y.Doc,
draftKey: string,
): ProtoDraftState
interface ProtoDraftState {
draft: Ref<Record<string, any> | null>
hasDraft: ComputedRef<boolean>
draftAge: ComputedRef<string | null> // e.g. '3 hours ago'
saveDraft(data: Record<string, any>): void
clearDraft(): void
}
You can use useProtoDraft directly in your own custom modals:
const { doc } = useProtoDoc('my-tool')
const { draft, hasDraft, draftAge, saveDraft, clearDraft } =
useProtoDraft(doc, `interview-${itemId}`)
// Auto-save as user types
watch(formData, (data) => saveDraft(data), { deep: true })
// On open — check for existing draft
onMounted(() => {
if (hasDraft.value) {
// Show resume/discard UI
}
})
ProtoTool
The top-level tool rendering component. Accepts a schema and an optional doc-key, renders the full tool UI, and handles all Y.js plumbing internally.
Offline First
How protokit works completely offline using Y.js CRDTs and IndexedDB, with automatic conflict-free sync when connectivity is restored.