@cocoar/vue-ui 3.1.0 → 3.2.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/dist/components/checkbox/CoarCheckbox.vue.d.ts +1 -1
  2. package/dist/components/checkbox/CoarCheckbox.vue.d.ts.map +1 -1
  3. package/dist/components/code-block/CoarCodeBlock.vue.d.ts +1 -1
  4. package/dist/components/code-block/CoarCodeBlock.vue.d.ts.map +1 -1
  5. package/dist/components/data-list/CoarDataList.vue.d.ts +158 -0
  6. package/dist/components/data-list/CoarDataList.vue.d.ts.map +1 -0
  7. package/dist/components/data-list/CoarDataListToolbar.vue.d.ts +30 -0
  8. package/dist/components/data-list/CoarDataListToolbar.vue.d.ts.map +1 -0
  9. package/dist/components/data-list/data-list-builder.d.ts +288 -0
  10. package/dist/components/data-list/data-list-builder.d.ts.map +1 -0
  11. package/dist/components/data-list/index.d.ts +12 -0
  12. package/dist/components/data-list/index.d.ts.map +1 -0
  13. package/dist/components/data-list/internal/compare.d.ts +14 -0
  14. package/dist/components/data-list/internal/compare.d.ts.map +1 -0
  15. package/dist/components/data-list/internal/reorder-core.d.ts +46 -0
  16. package/dist/components/data-list/internal/reorder-core.d.ts.map +1 -0
  17. package/dist/components/data-list/internal/search.d.ts +16 -0
  18. package/dist/components/data-list/internal/search.d.ts.map +1 -0
  19. package/dist/components/data-list/internal/useDataListLines.d.ts +92 -0
  20. package/dist/components/data-list/internal/useDataListLines.d.ts.map +1 -0
  21. package/dist/components/data-list/internal/useDataListReorder.d.ts +68 -0
  22. package/dist/components/data-list/internal/useDataListReorder.d.ts.map +1 -0
  23. package/dist/components/data-list/internal/useSearchHighlight.d.ts +19 -0
  24. package/dist/components/data-list/internal/useSearchHighlight.d.ts.map +1 -0
  25. package/dist/components/data-list/types.d.ts +185 -0
  26. package/dist/components/data-list/types.d.ts.map +1 -0
  27. package/dist/components/data-list/useDataListModel.d.ts +79 -0
  28. package/dist/components/data-list/useDataListModel.d.ts.map +1 -0
  29. package/dist/components/date-time/_shared/maskito-config.d.ts +7 -2
  30. package/dist/components/date-time/_shared/maskito-config.d.ts.map +1 -1
  31. package/dist/components/date-time/_shared/time-helpers.d.ts +11 -4
  32. package/dist/components/date-time/_shared/time-helpers.d.ts.map +1 -1
  33. package/dist/components/date-time/_shared/use-date-picker-base.d.ts +1 -1
  34. package/dist/components/date-time/plain-date-picker/CoarPlainDatePicker.vue.d.ts +1 -1
  35. package/dist/components/date-time/plain-date-picker/CoarPlainDatePicker.vue.d.ts.map +1 -1
  36. package/dist/components/date-time/plain-date-time-picker/CoarPlainDateTimePicker.vue.d.ts +1 -1
  37. package/dist/components/date-time/plain-date-time-picker/CoarPlainDateTimePicker.vue.d.ts.map +1 -1
  38. package/dist/components/date-time/zoned-date-time-picker/CoarZonedDateTimePicker.vue.d.ts +1 -1
  39. package/dist/components/date-time/zoned-date-time-picker/CoarZonedDateTimePicker.vue.d.ts.map +1 -1
  40. package/dist/components/dual-listbox/CoarDualListbox.vue.d.ts +3 -0
  41. package/dist/components/dual-listbox/CoarDualListbox.vue.d.ts.map +1 -1
  42. package/dist/components/form-field/CoarFormField.vue.d.ts +1 -1
  43. package/dist/components/form-field/CoarFormField.vue.d.ts.map +1 -1
  44. package/dist/components/icon/CoarIcon.vue.d.ts +2 -2
  45. package/dist/components/icon/CoarIcon.vue.d.ts.map +1 -1
  46. package/dist/components/listbox/CoarListbox.vue.d.ts +7 -0
  47. package/dist/components/listbox/CoarListbox.vue.d.ts.map +1 -1
  48. package/dist/components/number-input/CoarNumberInput.vue.d.ts +1 -1
  49. package/dist/components/number-input/CoarNumberInput.vue.d.ts.map +1 -1
  50. package/dist/components/otp-input/CoarOtpInput.vue.d.ts +1 -1
  51. package/dist/components/otp-input/CoarOtpInput.vue.d.ts.map +1 -1
  52. package/dist/components/password-input/CoarPasswordInput.vue.d.ts +1 -1
  53. package/dist/components/password-input/CoarPasswordInput.vue.d.ts.map +1 -1
  54. package/dist/components/switch/CoarSwitch.vue.d.ts +1 -1
  55. package/dist/components/switch/CoarSwitch.vue.d.ts.map +1 -1
  56. package/dist/components/text-input/CoarTextInput.vue.d.ts +1 -1
  57. package/dist/components/text-input/CoarTextInput.vue.d.ts.map +1 -1
  58. package/dist/composables/dragRegistry.d.ts +12 -0
  59. package/dist/composables/dragRegistry.d.ts.map +1 -1
  60. package/dist/composables/index.d.ts +2 -2
  61. package/dist/composables/index.d.ts.map +1 -1
  62. package/dist/composables/useDragDrop.d.ts +62 -6
  63. package/dist/composables/useDragDrop.d.ts.map +1 -1
  64. package/dist/composables/useVirtualList.d.ts +21 -1
  65. package/dist/composables/useVirtualList.d.ts.map +1 -1
  66. package/dist/index.css +1 -1
  67. package/dist/index.d.ts +4 -2
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +4508 -2039
  70. package/package.json +5 -3
  71. package/skills/cocoar-vue-ui/SKILL.md +244 -0
  72. package/skills/cocoar-vue-ui/references/components/avatar.md +178 -0
  73. package/skills/cocoar-vue-ui/references/components/badge.md +290 -0
  74. package/skills/cocoar-vue-ui/references/components/breadcrumb.md +238 -0
  75. package/skills/cocoar-vue-ui/references/components/button.md +252 -0
  76. package/skills/cocoar-vue-ui/references/components/calendar/agenda-view.md +353 -0
  77. package/skills/cocoar-vue-ui/references/components/calendar/coar-calendar.md +1645 -0
  78. package/skills/cocoar-vue-ui/references/components/calendar/day-view.md +361 -0
  79. package/skills/cocoar-vue-ui/references/components/calendar/index.md +312 -0
  80. package/skills/cocoar-vue-ui/references/components/calendar/month-view.md +475 -0
  81. package/skills/cocoar-vue-ui/references/components/calendar/performance.md +186 -0
  82. package/skills/cocoar-vue-ui/references/components/calendar/timeline-view.md +301 -0
  83. package/skills/cocoar-vue-ui/references/components/calendar/week-view.md +382 -0
  84. package/skills/cocoar-vue-ui/references/components/calendar/work-week-view.md +145 -0
  85. package/skills/cocoar-vue-ui/references/components/calendar/year-view.md +63 -0
  86. package/skills/cocoar-vue-ui/references/components/card.md +151 -0
  87. package/skills/cocoar-vue-ui/references/components/checkbox-group.md +103 -0
  88. package/skills/cocoar-vue-ui/references/components/checkbox.md +279 -0
  89. package/skills/cocoar-vue-ui/references/components/code-block.md +241 -0
  90. package/skills/cocoar-vue-ui/references/components/context-menu.md +355 -0
  91. package/skills/cocoar-vue-ui/references/components/data-grid/checkbox.md +213 -0
  92. package/skills/cocoar-vue-ui/references/components/data-grid/date-columns.md +272 -0
  93. package/skills/cocoar-vue-ui/references/components/data-grid/editing.md +236 -0
  94. package/skills/cocoar-vue-ui/references/components/data-grid/multi-select.md +193 -0
  95. package/skills/cocoar-vue-ui/references/components/data-grid/number.md +147 -0
  96. package/skills/cocoar-vue-ui/references/components/data-grid/select.md +202 -0
  97. package/skills/cocoar-vue-ui/references/components/data-grid/text.md +127 -0
  98. package/skills/cocoar-vue-ui/references/components/data-grid.md +1225 -0
  99. package/skills/cocoar-vue-ui/references/components/data-list.md +2121 -0
  100. package/skills/cocoar-vue-ui/references/components/date-or-time-picker.md +200 -0
  101. package/skills/cocoar-vue-ui/references/components/date-picker.md +203 -0
  102. package/skills/cocoar-vue-ui/references/components/date-time-picker.md +154 -0
  103. package/skills/cocoar-vue-ui/references/components/date-views.md +212 -0
  104. package/skills/cocoar-vue-ui/references/components/dialog.md +148 -0
  105. package/skills/cocoar-vue-ui/references/components/divider.md +157 -0
  106. package/skills/cocoar-vue-ui/references/components/document-viewer/annotations.md +400 -0
  107. package/skills/cocoar-vue-ui/references/components/document-viewer/coar-document-viewer.md +327 -0
  108. package/skills/cocoar-vue-ui/references/components/document-viewer/index.md +234 -0
  109. package/skills/cocoar-vue-ui/references/components/document-viewer/toolbar.md +221 -0
  110. package/skills/cocoar-vue-ui/references/components/drag-drop.md +267 -0
  111. package/skills/cocoar-vue-ui/references/components/dual-listbox.md +449 -0
  112. package/skills/cocoar-vue-ui/references/components/file-explorer/asset-store.md +327 -0
  113. package/skills/cocoar-vue-ui/references/components/file-explorer/in-memory-store.md +176 -0
  114. package/skills/cocoar-vue-ui/references/components/file-explorer/index.md +311 -0
  115. package/skills/cocoar-vue-ui/references/components/file-explorer/use-file-explorer.md +300 -0
  116. package/skills/cocoar-vue-ui/references/components/form-field.md +1025 -0
  117. package/skills/cocoar-vue-ui/references/components/fragment-parser.md +298 -0
  118. package/skills/cocoar-vue-ui/references/components/link.md +235 -0
  119. package/skills/cocoar-vue-ui/references/components/listbox.md +575 -0
  120. package/skills/cocoar-vue-ui/references/components/map/editor.md +448 -0
  121. package/skills/cocoar-vue-ui/references/components/map/index.md +351 -0
  122. package/skills/cocoar-vue-ui/references/components/markdown-diagrams.md +210 -0
  123. package/skills/cocoar-vue-ui/references/components/markdown-editor.md +1478 -0
  124. package/skills/cocoar-vue-ui/references/components/markdown-embeds.md +387 -0
  125. package/skills/cocoar-vue-ui/references/components/markdown-form.md +447 -0
  126. package/skills/cocoar-vue-ui/references/components/markdown.md +276 -0
  127. package/skills/cocoar-vue-ui/references/components/menu.md +380 -0
  128. package/skills/cocoar-vue-ui/references/components/mermaid.md +172 -0
  129. package/skills/cocoar-vue-ui/references/components/navbar.md +147 -0
  130. package/skills/cocoar-vue-ui/references/components/note.md +96 -0
  131. package/skills/cocoar-vue-ui/references/components/notice.md +131 -0
  132. package/skills/cocoar-vue-ui/references/components/number-input.md +215 -0
  133. package/skills/cocoar-vue-ui/references/components/otp-input.md +324 -0
  134. package/skills/cocoar-vue-ui/references/components/page-builder/authoring-contract.md +271 -0
  135. package/skills/cocoar-vue-ui/references/components/page-builder/coar-page-builder.md +476 -0
  136. package/skills/cocoar-vue-ui/references/components/page-builder/coar-page-renderer.md +677 -0
  137. package/skills/cocoar-vue-ui/references/components/page-builder/custom-elements.md +441 -0
  138. package/skills/cocoar-vue-ui/references/components/page-builder/idp-integration.md +122 -0
  139. package/skills/cocoar-vue-ui/references/components/page-builder/index.md +573 -0
  140. package/skills/cocoar-vue-ui/references/components/pagination.md +150 -0
  141. package/skills/cocoar-vue-ui/references/components/panel-layout.md +372 -0
  142. package/skills/cocoar-vue-ui/references/components/password-input.md +175 -0
  143. package/skills/cocoar-vue-ui/references/components/popconfirm.md +186 -0
  144. package/skills/cocoar-vue-ui/references/components/popover.md +173 -0
  145. package/skills/cocoar-vue-ui/references/components/progress-bar.md +178 -0
  146. package/skills/cocoar-vue-ui/references/components/radio-group.md +225 -0
  147. package/skills/cocoar-vue-ui/references/components/script-editor.md +1271 -0
  148. package/skills/cocoar-vue-ui/references/components/segmented-control.md +238 -0
  149. package/skills/cocoar-vue-ui/references/components/select.md +463 -0
  150. package/skills/cocoar-vue-ui/references/components/sidebar.md +421 -0
  151. package/skills/cocoar-vue-ui/references/components/spinner.md +132 -0
  152. package/skills/cocoar-vue-ui/references/components/switch.md +195 -0
  153. package/skills/cocoar-vue-ui/references/components/table.md +170 -0
  154. package/skills/cocoar-vue-ui/references/components/tabs.md +382 -0
  155. package/skills/cocoar-vue-ui/references/components/tag.md +178 -0
  156. package/skills/cocoar-vue-ui/references/components/text-input.md +256 -0
  157. package/skills/cocoar-vue-ui/references/components/toast.md +160 -0
  158. package/skills/cocoar-vue-ui/references/components/tooltip.md +121 -0
  159. package/skills/cocoar-vue-ui/references/components/transitions.md +193 -0
  160. package/skills/cocoar-vue-ui/references/components/tree.md +2388 -0
  161. package/skills/cocoar-vue-ui/references/components/virtual-list.md +212 -0
  162. package/skills/cocoar-vue-ui/references/components/wizard.md +251 -0
  163. package/skills/cocoar-vue-ui/references/components/zoned-date-time-picker.md +177 -0
  164. package/skills/cocoar-vue-ui/references/foundations/colors.md +708 -0
  165. package/skills/cocoar-vue-ui/references/foundations/design-principles.md +115 -0
  166. package/skills/cocoar-vue-ui/references/foundations/icons.md +381 -0
  167. package/skills/cocoar-vue-ui/references/foundations/localization/formatting.md +530 -0
  168. package/skills/cocoar-vue-ui/references/foundations/localization/setup.md +86 -0
  169. package/skills/cocoar-vue-ui/references/foundations/localization/timezones.md +182 -0
  170. package/skills/cocoar-vue-ui/references/foundations/localization/translations.md +305 -0
  171. package/skills/cocoar-vue-ui/references/foundations/motion.md +549 -0
  172. package/skills/cocoar-vue-ui/references/foundations/spacing.md +330 -0
  173. package/skills/cocoar-vue-ui/references/foundations/theming.md +140 -0
  174. package/skills/cocoar-vue-ui/references/foundations/typography.md +206 -0
  175. package/skills/cocoar-vue-ui/references/guide/error-handling.md +162 -0
  176. package/skills/cocoar-vue-ui/references/guide/getting-started.md +119 -0
  177. package/skills/cocoar-vue-ui/references/guide/migration-page-builder-3.md +207 -0
  178. package/skills/cocoar-vue-ui/references/guide/migration.md +140 -0
  179. package/skills/cocoar-vue-ui/references/guide/theming.md +98 -0
@@ -0,0 +1,400 @@
1
+ <!-- Generated from apps/docs/components/document-viewer/annotations.md by apps/docs/scripts/sync-skill.mjs. Do not edit; edit the docs page. -->
2
+
3
+ # Annotations
4
+
5
+ `CoarDocumentViewer` ships with a built-in annotation layer that supports four types — **marker** highlights, comment **notes**, free-hand **ink**, and **freetext** boxes. Annotations are **controlled**: the consumer owns the data, the viewer emits events, the consumer applies changes.
6
+
7
+ ```ts
8
+ import {
9
+ CoarDocumentViewer,
10
+ type CoarPdfAnnotation,
11
+ type CoarPdfAnnotationMode,
12
+ type CoarPdfAnnotationCreatePayload,
13
+ type CoarPdfAnnotationUpdatePayload,
14
+ } from '@cocoar/vue-document-viewer';
15
+ ```
16
+
17
+ ## Live demo
18
+
19
+ **Demo — `document-viewer/demos/AnnotationsDemo.vue`**
20
+
21
+ ```vue
22
+ <template>
23
+ <ClientOnly>
24
+ <div style="display: flex; flex-direction: column; gap: 8px;">
25
+ <div style="display: flex; gap: 8px; flex-wrap: wrap; font-size: 13px;">
26
+ <span style="color: var(--vp-c-text-2);">Mode: <strong>{{ annotationMode }}</strong></span>
27
+ <span style="color: var(--vp-c-text-2);">Annotations: <strong>{{ annotations.length }}</strong></span>
28
+ <button
29
+ type="button"
30
+ :disabled="annotations.length === 0"
31
+ style="font-size: 12px; padding: 2px 8px; border-radius: 4px; cursor: pointer;"
32
+ @click="annotations = []"
33
+ >Clear all</button>
34
+ </div>
35
+ <div style="height: 520px; border: 1px solid var(--vp-c-divider); border-radius: 8px; overflow: hidden;">
36
+ <component
37
+ :is="Viewer"
38
+ v-if="Viewer && source"
39
+ :source="source"
40
+ v-model:annotation-mode="annotationMode"
41
+ :annotations="annotations"
42
+ :show-annotations-panel="true"
43
+ @annotation:create="onCreate"
44
+ @annotation:update="onUpdate"
45
+ @annotation:delete="onDelete"
46
+ />
47
+ <div v-else class="dv-loading">Loading viewer…</div>
48
+ </div>
49
+ <p style="font-size: 12px; color: var(--vp-c-text-2); margin: 0;">
50
+ Pick a drawing mode (marker / note / draw / text) in the toolbar, then
51
+ interact with the image. The consumer owns the annotations array; the
52
+ viewer emits create/update/delete and the parent assigns IDs.
53
+ </p>
54
+ </div>
55
+ </ClientOnly>
56
+ </template>
57
+
58
+ <script setup lang="ts">
59
+ import { computed, onMounted, ref, shallowRef, type Component } from 'vue';
60
+ import { SINGLE_IMAGE_URL } from './_shared';
61
+
62
+ // Loose runtime types — full types are imported at code-block level on the
63
+ // annotations.md page itself. SSR has to skip the package entirely (it pulls
64
+ // browser-only DOM APIs at module eval time).
65
+ type Anno = { id: string; type: string; pageIndex: number; color: string; createdAt: string; comment?: string } & Record<string, unknown>;
66
+ type CreatePayload = Omit<Anno, 'id' | 'createdAt'>;
67
+ type UpdatePayload = { id: string; patch: Partial<Anno> };
68
+
69
+ const Viewer = shallowRef<Component | null>(null);
70
+ const imageSource = shallowRef<((opts: { url: string }) => unknown) | null>(null);
71
+
72
+ const source = computed(() => imageSource.value?.({ url: SINGLE_IMAGE_URL }) ?? null);
73
+ const annotationMode = ref<'view' | 'select' | 'eraser' | 'marker' | 'comment' | 'ink' | 'freetext'>('view');
74
+ const annotations = ref<Anno[]>([]);
75
+
76
+ let nextId = 1;
77
+ function makeId() {
78
+ return `demo-${Date.now()}-${nextId++}`;
79
+ }
80
+
81
+ function onCreate(payload: CreatePayload) {
82
+ annotations.value = [
83
+ ...annotations.value,
84
+ { ...payload, id: makeId(), createdAt: new Date().toISOString() } as Anno,
85
+ ];
86
+ }
87
+
88
+ function onUpdate(payload: UpdatePayload) {
89
+ annotations.value = annotations.value.map((a) =>
90
+ a.id === payload.id ? ({ ...a, ...payload.patch } as Anno) : a,
91
+ );
92
+ }
93
+
94
+ function onDelete(id: string) {
95
+ annotations.value = annotations.value.filter((a) => a.id !== id);
96
+ }
97
+
98
+ onMounted(async () => {
99
+ const mod = await import('@cocoar/vue-document-viewer');
100
+ await import('@cocoar/vue-document-viewer/styles');
101
+ imageSource.value = mod.imageSource;
102
+ Viewer.value = mod.CoarDocumentViewer;
103
+ });
104
+ </script>
105
+
106
+ <style scoped>
107
+ .dv-loading {
108
+ height: 100%;
109
+ display: flex;
110
+ align-items: center;
111
+ justify-content: center;
112
+ color: var(--coar-text-neutral-tertiary, #6b7280);
113
+ font-size: 13px;
114
+ }
115
+ </style>
116
+ ```
117
+
118
+ Pick a mode in the toolbar (marker / note / draw / text), then interact with the image. The consumer assigns `id` + `createdAt` on creation, merges patches on update, and removes on delete. Try **switch to Select**, then drag an existing annotation, or click the 3-dot menu in the right panel.
119
+
120
+ ## Annotation types
121
+
122
+ Four discriminated-union types, all sharing a `BaseAnnotation` shape:
123
+
124
+ ```ts
125
+ interface BaseAnnotation {
126
+ id: string;
127
+ type: 'marker' | 'comment' | 'ink' | 'freetext';
128
+ pageIndex: number; // 0-based
129
+ color: string; // CSS color
130
+ createdAt: string; // ISO timestamp
131
+ createdBy?: string; // Consumer-provided display string
132
+ comment?: string; // Every type may carry a side comment
133
+ }
134
+ ```
135
+
136
+ ### Marker (highlighter)
137
+
138
+ ```ts
139
+ interface CoarPdfMarkerAnnotation extends BaseAnnotation {
140
+ type: 'marker';
141
+ strokes: CoarPdfPoint[][]; // SVG-style polyline list
142
+ width: number; // Stroke width in CSS px at zoom=1
143
+ }
144
+ ```
145
+
146
+ Drawn like a felt-tip marker, rendered with `mix-blend-mode: multiply` so the underlying text reads through.
147
+
148
+ ### Comment pin
149
+
150
+ ```ts
151
+ interface CoarPdfCommentAnnotation extends BaseAnnotation {
152
+ type: 'comment';
153
+ anchor: CoarPdfPoint; // Pin location in normalized page coords
154
+ comment: string; // REQUIRED for comment annotations
155
+ }
156
+ ```
157
+
158
+ Drops a pin at the click point and opens a popover for the comment body. The comment text is mandatory — empty pins are never created.
159
+
160
+ ### Ink (freehand)
161
+
162
+ ```ts
163
+ interface CoarPdfInkAnnotation extends BaseAnnotation {
164
+ type: 'ink';
165
+ strokes: CoarPdfPoint[][];
166
+ width: number;
167
+ }
168
+ ```
169
+
170
+ Same wire shape as marker but rendered as opaque strokes (no blend mode, thinner default width).
171
+
172
+ ### Freetext
173
+
174
+ ```ts
175
+ interface CoarPdfFreetextAnnotation extends BaseAnnotation {
176
+ type: 'freetext';
177
+ rect: CoarPdfRect; // Box geometry in normalized page coords
178
+ text: string; // The visible label
179
+ fontSize: number; // CSS px at zoom=1
180
+ }
181
+ ```
182
+
183
+ User clicks to drop a text box; the textarea writes to `text` (not `comment`). Optional `comment` still works as a side note.
184
+
185
+ ## Coordinate system
186
+
187
+ All coordinates are **page-relative and normalized to `[0..1]`**:
188
+
189
+ ```ts
190
+ interface CoarPdfPoint { x: number; y: number; }
191
+ interface CoarPdfRect { x: number; y: number; w: number; h: number; }
192
+ ```
193
+
194
+ This means the same annotation renders correctly at any zoom level and rotation — the viewer multiplies by the current viewport at render time. Storage stays portable: an annotation drawn at 100 % zoom plays back identically at 250 %, on a different screen, or after a rotation.
195
+
196
+ ## Lifecycle
197
+
198
+ The viewer never mutates the `annotations` array directly. Instead it emits three events that the consumer applies:
199
+
200
+ ### Create
201
+
202
+ ```ts
203
+ import { v4 as uuid } from 'uuid';
204
+
205
+ function onCreate(payload: CoarPdfAnnotationCreatePayload) {
206
+ annotations.value = [
207
+ ...annotations.value,
208
+ {
209
+ ...payload,
210
+ id: uuid(),
211
+ createdAt: new Date().toISOString(),
212
+ createdBy: currentUser.displayName,
213
+ } as CoarPdfAnnotation,
214
+ ];
215
+ }
216
+ ```
217
+
218
+ `CoarPdfAnnotationCreatePayload` is `CoarPdfAnnotation` with the consumer-owned fields stripped (`'id' | 'createdAt' | 'createdBy'`). The viewer fills in everything else — type, color, geometry, stroke data — based on the active mode and pointer input.
219
+
220
+ ### Update
221
+
222
+ ```ts
223
+ function onUpdate({ id, patch }: CoarPdfAnnotationUpdatePayload) {
224
+ annotations.value = annotations.value.map((a) =>
225
+ a.id === id ? ({ ...a, ...patch } as CoarPdfAnnotation) : a,
226
+ );
227
+ }
228
+ ```
229
+
230
+ Fired when the user edits an annotation through its popover (color, comment, freetext body) or drags it to a new position (anchor / rect / strokes). `patch` is a partial of the relevant annotation shape — apply with a plain spread.
231
+
232
+ ### Delete
233
+
234
+ ```ts
235
+ function onDelete(id: string) {
236
+ annotations.value = annotations.value.filter((a) => a.id !== id);
237
+ }
238
+ ```
239
+
240
+ Fired when the user picks "Delete" from the panel's 3-dot menu, or when the eraser tool removes the last stroke from a marker / ink annotation.
241
+
242
+ ## Modes
243
+
244
+ ```ts
245
+ type CoarPdfAnnotationMode =
246
+ | 'view' // Read-only — existing annotations clickable, no new ones
247
+ | 'select' // Existing annotations clickable AND draggable
248
+ | 'eraser' // Click a stroke on marker/ink to remove it
249
+ | 'marker' // Drawing
250
+ | 'comment' // Drawing — drops a pin
251
+ | 'ink' // Drawing
252
+ | 'freetext'; // Drawing — drops a text box
253
+ ```
254
+
255
+ Mode is two-way bound — the toolbar's mode buttons update it, but the consumer can also drive it from outside (e.g. a keyboard shortcut, a parent's "Start commenting" CTA):
256
+
257
+ ```vue
258
+ <CoarDocumentViewer
259
+ :source="src"
260
+ v-model:annotation-mode="mode"
261
+ :annotations="annotations"
262
+ />
263
+ ```
264
+
265
+ ```ts
266
+ // Keyboard shortcut from anywhere
267
+ useEventListener(window, 'keydown', (e) => {
268
+ if (e.key === 'm' && (e.ctrlKey || e.metaKey)) mode.value = 'marker';
269
+ });
270
+ ```
271
+
272
+ ## Color palette
273
+
274
+ The mode picker shows a color selector with seven defaults:
275
+
276
+ | Color | Hex |
277
+ |---|---|
278
+ | Yellow | `#fde68a` |
279
+ | Pink | `#fca5a5` |
280
+ | Green | `#86efac` |
281
+ | Blue | `#93c5fd` |
282
+ | Purple | `#c4b5fd` |
283
+ | Saturated yellow | `#facc15` |
284
+ | Hot pink | `#ec4899` |
285
+
286
+ Pass `:annotation-colors="[...]"` to override the entire palette. Each color is a CSS string — anything `rgb()`, `hsl()`, `#xxx`, `oklch()` etc. that the browser accepts works.
287
+
288
+ ## Panel + filters
289
+
290
+ `:show-annotations-panel="true"` opens the right rail with:
291
+
292
+ - **Info section** at the top (source metadata — see [Overview](./index.md)).
293
+ - **Annotation list** — every annotation, grouped by page or chronologically. Click an entry to select + scroll-to. 3-dot menu per entry: edit comment, change color, delete.
294
+ - **Filter chips** — toggle each type on/off (marker / note / ink / freetext).
295
+ - **Search input** — substring search over comments and freetext bodies.
296
+ - **Sort toggle** — By page / Chronological.
297
+
298
+ The list is read-only — every interaction goes through the same `annotation:update` / `annotation:delete` event pipeline as the in-page surface, so the consumer-owned state stays canonical.
299
+
300
+ ## Worked example — collaborative review
301
+
302
+ ```vue
303
+ <script setup lang="ts">
304
+ import { ref, computed } from 'vue';
305
+ import {
306
+ CoarDocumentViewer,
307
+ type CoarPdfAnnotation,
308
+ type CoarPdfAnnotationMode,
309
+ type CoarPdfAnnotationCreatePayload,
310
+ type CoarPdfAnnotationUpdatePayload,
311
+ } from '@cocoar/vue-document-viewer';
312
+ import { pdfSource } from '@cocoar/vue-document-viewer/pdf';
313
+ import { v4 as uuid } from 'uuid';
314
+
315
+ const props = defineProps<{
316
+ documentId: string;
317
+ currentUser: { id: string; displayName: string };
318
+ }>();
319
+
320
+ const annotations = ref<CoarPdfAnnotation[]>([]);
321
+ const mode = ref<CoarPdfAnnotationMode>('view');
322
+ const source = computed(() =>
323
+ pdfSource({ url: `/api/files/${props.documentId}/source.pdf`, withCredentials: true }),
324
+ );
325
+
326
+ // Initial load
327
+ onMounted(async () => {
328
+ annotations.value = await api.listAnnotations(props.documentId);
329
+ });
330
+
331
+ async function onCreate(payload: CoarPdfAnnotationCreatePayload) {
332
+ const next: CoarPdfAnnotation = {
333
+ ...payload,
334
+ id: uuid(),
335
+ createdAt: new Date().toISOString(),
336
+ createdBy: props.currentUser.displayName,
337
+ } as CoarPdfAnnotation;
338
+ // Optimistic insert; rollback on failure
339
+ annotations.value = [...annotations.value, next];
340
+ try { await api.createAnnotation(props.documentId, next); }
341
+ catch (err) { annotations.value = annotations.value.filter(a => a.id !== next.id); throw err; }
342
+ }
343
+
344
+ async function onUpdate({ id, patch }: CoarPdfAnnotationUpdatePayload) {
345
+ const before = annotations.value;
346
+ annotations.value = annotations.value.map(a => a.id === id ? { ...a, ...patch } as CoarPdfAnnotation : a);
347
+ try { await api.updateAnnotation(props.documentId, id, patch); }
348
+ catch (err) { annotations.value = before; throw err; }
349
+ }
350
+
351
+ async function onDelete(id: string) {
352
+ const before = annotations.value;
353
+ annotations.value = annotations.value.filter(a => a.id !== id);
354
+ try { await api.deleteAnnotation(props.documentId, id); }
355
+ catch (err) { annotations.value = before; throw err; }
356
+ }
357
+ </script>
358
+
359
+ <template>
360
+ <CoarDocumentViewer
361
+ :source="source"
362
+ v-model:annotation-mode="mode"
363
+ :annotations="annotations"
364
+ :show-annotations-panel="true"
365
+ storage-key="`doc-${documentId}`"
366
+ @annotation:create="onCreate"
367
+ @annotation:update="onUpdate"
368
+ @annotation:delete="onDelete"
369
+ />
370
+ </template>
371
+ ```
372
+
373
+ The controlled pattern makes optimistic updates + rollback trivial: snapshot `annotations.value`, apply locally, and revert on API failure.
374
+
375
+ ## Persistence shape
376
+
377
+ Annotations are plain JSON — no `Date` objects, no class instances, no internal references. Store as-is:
378
+
379
+ ```ts
380
+ // PostgreSQL JSONB column
381
+ CREATE TABLE annotations (
382
+ id UUID PRIMARY KEY,
383
+ document_id UUID NOT NULL REFERENCES documents,
384
+ data JSONB NOT NULL -- one CoarPdfAnnotation
385
+ );
386
+ ```
387
+
388
+ ```ts
389
+ // MongoDB / Firestore — embed directly
390
+ { documentId: "...", annotations: CoarPdfAnnotation[] }
391
+ ```
392
+
393
+ Because coordinates are normalized, annotations migrate across documents only when the page geometry matches; they're fully portable across rendering environments (different screens, zoom levels, rotation).
394
+
395
+ ## Tips
396
+
397
+ - **Always wrap `annotations` mutations in a fresh array** (`[...]`, `.map`, `.filter`) so Vue's reactivity sees the change.
398
+ - **Generate the id on create**, not before — the create event fires only after the user finishes the drawing gesture (releases the mouse / taps "Save"). Pre-creating ids causes orphan entries when the gesture is cancelled.
399
+ - **Touch interactions work the same as mouse** — drawing modes accept both. The viewer normalizes pointer events internally.
400
+ - **Eraser is destructive** — clicking a stroke on a marker/ink annotation deletes that stroke; deleting the last stroke fires `annotation:delete` for the whole annotation. There's no per-stroke undo built in; consumers wanting one should keep a history snapshot ring around their `annotations` array.