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.
ProtoCalendar
Day, week and infinite month calendar with drag to create, move and resize, an inline event form, quick natural-language events, and Y.js CRDT persistence.