@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,221 @@
1
+ <!-- Generated from apps/docs/components/document-viewer/toolbar.md by apps/docs/scripts/sync-skill.mjs. Do not edit; edit the docs page. -->
2
+
3
+ # Toolbar customization
4
+
5
+ The toolbar is **order-driven**: the `tools` prop is an array of tool identifiers in the order you want them to render. Want zoom buttons on the left and page navigation on the right? Just reorder the array. Want a custom layout with only the four buttons you actually need? Pass that subset.
6
+
7
+ When `tools` is omitted, the viewer falls back to `COAR_DOCUMENT_VIEWER_ALL_TOOLS` — the canonical 8-group layout with separators at the original group boundaries.
8
+
9
+ ## Minimal toolbar
10
+
11
+ The canonical "nav + zoom" layout — page navigation, separator, zoom controls:
12
+
13
+ **Demo — `document-viewer/demos/MinimalToolbarDemo.vue`**
14
+
15
+ ```vue
16
+ <template>
17
+ <ClientOnly>
18
+ <div style="height: 520px; border: 1px solid var(--vp-c-divider); border-radius: 8px; overflow: hidden;">
19
+ <component
20
+ :is="Viewer"
21
+ v-if="Viewer && source"
22
+ :source="source"
23
+ :tools="MINIMAL_TOOLS"
24
+ />
25
+ <div v-else class="dv-loading">Loading viewer…</div>
26
+ </div>
27
+ </ClientOnly>
28
+ </template>
29
+
30
+ <script setup lang="ts">
31
+ import { computed, onMounted, shallowRef, type Component } from 'vue';
32
+ import { GALLERY_URLS } from './_shared';
33
+
34
+ /**
35
+ * Order-driven `tools` prop. Leading + trailing separators are auto-trimmed
36
+ * and consecutive separators collapse to one — see Toolbar customization.
37
+ */
38
+ const MINIMAL_TOOLS = [
39
+ 'prev-page',
40
+ 'page-input',
41
+ 'next-page',
42
+ 'separator',
43
+ 'zoom-out',
44
+ 'zoom-reset',
45
+ 'zoom-in',
46
+ ] as const;
47
+
48
+ const Viewer = shallowRef<Component | null>(null);
49
+ const galleryFactory = shallowRef<((opts: { urls: readonly string[] }) => unknown) | null>(null);
50
+
51
+ const source = computed(() => galleryFactory.value?.({ urls: GALLERY_URLS }) ?? null);
52
+
53
+ onMounted(async () => {
54
+ const mod = await import('@cocoar/vue-document-viewer');
55
+ await import('@cocoar/vue-document-viewer/styles');
56
+ galleryFactory.value = mod.imageGallerySource;
57
+ Viewer.value = mod.CoarDocumentViewer;
58
+ });
59
+ </script>
60
+
61
+ <style scoped>
62
+ .dv-loading {
63
+ height: 100%;
64
+ display: flex;
65
+ align-items: center;
66
+ justify-content: center;
67
+ color: var(--coar-text-neutral-tertiary, #6b7280);
68
+ font-size: 13px;
69
+ }
70
+ </style>
71
+ ```
72
+
73
+ ```ts
74
+ import type { CoarDocumentViewerTool } from '@cocoar/vue-document-viewer';
75
+
76
+ const MINIMAL_TOOLS: CoarDocumentViewerTool[] = [
77
+ 'prev-page',
78
+ 'page-input',
79
+ 'next-page',
80
+ 'separator',
81
+ 'zoom-out',
82
+ 'zoom-reset',
83
+ 'zoom-in',
84
+ ];
85
+ ```
86
+
87
+ ```vue
88
+ <CoarDocumentViewer :source="source" :tools="MINIMAL_TOOLS" />
89
+ ```
90
+
91
+ ## The `'separator'` pseudo-tool
92
+
93
+ `'separator'` doesn't render an action — it renders a `CoarSidebarDivider` between groups. Place it anywhere in the array to visually break up clusters.
94
+
95
+ Three convenience behaviors save you from edge-case handling:
96
+
97
+ | Input | Output |
98
+ |---|---|
99
+ | Leading `'separator'` | Trimmed |
100
+ | Trailing `'separator'` | Trimmed |
101
+ | Consecutive `'separator'`s | Collapsed to one |
102
+
103
+ This matters because **section toggles** (e.g. `showSearch: false`) filter tools _before_ the trim/collapse. So a `tools` array like `['prev-page', 'separator', 'search', 'separator', 'next-page']` with `showSearch: false` doesn't leave you two adjacent orphan separators — the collapse step turns it into `['prev-page', 'separator', 'next-page']` automatically.
104
+
105
+ ## All available tools
106
+
107
+ The full `CoarDocumentViewerTool` union:
108
+
109
+ | Identifier | What it does | Default group |
110
+ |---|---|---|
111
+ | `sidebar-toggle` | Toggle the left rail (thumbnails / outline) | Panels |
112
+ | `annotations-panel` | Toggle the right rail | Panels |
113
+ | `prev-page` | Previous page | Navigation |
114
+ | `page-input` | "Page N of M" number input | Navigation |
115
+ | `next-page` | Next page | Navigation |
116
+ | `zoom-out` | Zoom out one step | Zoom |
117
+ | `zoom-reset` | Editable zoom-percent readout (click to reset to 100%) | Zoom |
118
+ | `zoom-in` | Zoom in one step | Zoom |
119
+ | `fit-width` | Fit page width to viewport | View |
120
+ | `fit-page` | Fit whole page to viewport | View |
121
+ | `reset-view` | Reset zoom + rotation | View |
122
+ | `rotate-ccw` | Rotate -90° | Rotation |
123
+ | `rotate-cw` | Rotate +90° | Rotation |
124
+ | `pan` | Hand tool — drag the page | Pointer |
125
+ | `select` | Select / move existing annotations | Pointer |
126
+ | `eraser` | Erase strokes from marker / ink annotations | Pointer |
127
+ | `marker` | Draw highlighter strokes | Drawing |
128
+ | `note` | Place comment pin | Drawing |
129
+ | `ink` | Free-hand ink strokes | Drawing |
130
+ | `freetext` | Place free-text box | Drawing |
131
+ | `search` | Open search bar | Actions |
132
+ | `print` | Print document | Actions |
133
+ | `download` | Download source file | Actions |
134
+ | `separator` | Visual divider between groups | _pseudo-tool_ |
135
+
136
+ ## Default layout
137
+
138
+ `COAR_DOCUMENT_VIEWER_ALL_TOOLS` — the canonical 8-group layout, importable so you can derive variants by filtering:
139
+
140
+ ```ts
141
+ import { COAR_DOCUMENT_VIEWER_ALL_TOOLS } from '@cocoar/vue-document-viewer';
142
+
143
+ // Hide just the print + download buttons
144
+ const tools = COAR_DOCUMENT_VIEWER_ALL_TOOLS.filter(
145
+ (t) => t !== 'print' && t !== 'download',
146
+ );
147
+ ```
148
+
149
+ ## Filtering layers
150
+
151
+ Three independent layers decide which buttons render and how:
152
+
153
+ ```
154
+ user's `tools` array (or COAR_DOCUMENT_VIEWER_ALL_TOOLS)
155
+
156
+
157
+ ┌─────────────────────────────────────────────────────┐
158
+ │ 1. Section toggles strip whole categories │
159
+ │ showSearch:false → drop 'search' │
160
+ │ showPrintDownload:false → drop 'print','download' │
161
+ │ showAnnotationModes:false → drop drawing tools │
162
+ │ (no Thumbnails/Outline?) → drop 'sidebar-toggle'│
163
+ │ (no AnnotationsPanel?) → drop 'annotations-panel' │
164
+ └─────────────────────────────────────────────────────┘
165
+
166
+
167
+ ┌─────────────────────────────────────────────────────┐
168
+ │ 2. Separator normalization │
169
+ │ trim leading / trailing → collapse consecutive │
170
+ └─────────────────────────────────────────────────────┘
171
+
172
+
173
+ ┌─────────────────────────────────────────────────────┐
174
+ │ 3. Capability gating (per-tool, runtime) │
175
+ │ source.capabilities.search:false → 'search' disabled │
176
+ │ source.capabilities.multiPage:false → page-nav disabled │
177
+ │ source.capabilities.outline:false → outline tab hidden │
178
+ │ (disabled tools STAY VISIBLE with a tooltip suffix) │
179
+ └─────────────────────────────────────────────────────┘
180
+
181
+
182
+ Final rendered toolbar
183
+ ```
184
+
185
+ Layers 1 and 2 happen in pure-function form (`computeEffectiveTools` in `internal/effective-tools.ts`) and remove items from the array. Layer 3 happens per-button at render time — it never removes anything, just toggles the `disabled` state and appends `notAvailableForSource` to the tooltip.
186
+
187
+ This is the **stable-position rule**: switching sources never makes buttons jump around, because layer 3 doesn't touch positions.
188
+
189
+ ## Section toggles vs `tools` array
190
+
191
+ If you want to drop a whole category, either path works:
192
+
193
+ ```ts
194
+ // A) Shorthand — section toggle
195
+ <CoarDocumentViewer :source="src" :show-print-download="false" />
196
+
197
+ // B) Explicit — omit from tools array
198
+ <CoarDocumentViewer
199
+ :source="src"
200
+ :tools="COAR_DOCUMENT_VIEWER_ALL_TOOLS.filter(t => t !== 'print' && t !== 'download')"
201
+ />
202
+ ```
203
+
204
+ Use the section toggle when you want to drop a category cleanly. Use `tools` when you need precise positional control — e.g. moving search to the start, or placing the page-input between zoom and rotation.
205
+
206
+ ## Toolbar position
207
+
208
+ `toolbarPosition` controls where the toolbar sits relative to the page area:
209
+
210
+ ```vue
211
+ <CoarDocumentViewer :source="src" toolbar-position="left" />
212
+ ```
213
+
214
+ | Value | Layout |
215
+ |---|---|
216
+ | `'top'` (default) | Horizontal bar above the page area |
217
+ | `'bottom'` | Horizontal bar below the page area |
218
+ | `'left'` | Vertical rail to the left of the page area |
219
+ | `'right'` | Vertical rail to the right of the page area |
220
+
221
+ Vertical rails ('left' / 'right') still use the same `tools` array — buttons just stack vertically and the `'separator'` pseudo-tool renders as a horizontal divider instead of a vertical one.
@@ -0,0 +1,267 @@
1
+ <!-- Generated from apps/docs/components/drag-drop.md by apps/docs/scripts/sync-skill.mjs. Do not edit; edit the docs page. -->
2
+
3
+ # Drag & Drop
4
+
5
+ `useDragDrop` is a small, framework-agnostic composable wrapping HTML5 drag-and-drop with the same group / accept / canDrop semantics used by [`CoarListbox`](./listbox.md#drag-drop-between-lists) — but usable from any Vue component. It takes care of the fiddly bits (module-level payload registry, group matching, directional whitelists, source-side cleanup on accept) so your component only needs to wire events.
6
+
7
+ ```ts
8
+ import { useDragDrop } from '@cocoar/vue-ui';
9
+ ```
10
+
11
+ ## Standalone example — custom Kanban board
12
+
13
+ The demo below is built from plain `<div>` columns + cards, using `useDragDrop` directly — **no Listbox involved**. Cards flow **Backlog → In progress → Done**; Backlog accepts no drops (not a target), Done accepts from everywhere, In progress only accepts from Backlog:
14
+
15
+ **Demo — `drag-drop/demos/StandaloneKanban.vue`**
16
+
17
+ ```vue
18
+ <template>
19
+ <div class="board">
20
+ <KanbanColumn
21
+ v-for="col in columns"
22
+ :key="col.id"
23
+ :title="col.title"
24
+ :cards="col.cards"
25
+ :column-id="col.id"
26
+ :drag-accept="col.dragAccept"
27
+ @items-add="(p) => onAdd(col.id, p)"
28
+ @items-remove="(p) => onRemove(col.id, p)"
29
+ />
30
+ </div>
31
+ </template>
32
+
33
+ <script setup lang="ts">
34
+ import { ref } from 'vue';
35
+ import KanbanColumn from './KanbanColumn.vue';
36
+
37
+ export interface Card { id: string; title: string; priority: 'low' | 'med' | 'high' }
38
+
39
+ const columns = ref([
40
+ {
41
+ id: 'backlog',
42
+ title: 'Backlog',
43
+ // Empty whitelist = accept nothing. Keeps Backlog as a source-only column.
44
+ dragAccept: [] as string[] | undefined,
45
+ cards: [
46
+ { id: 'c1', title: 'Research virtualization', priority: 'med' },
47
+ { id: 'c2', title: 'Audit type coverage', priority: 'low' },
48
+ { id: 'c3', title: 'Drop indicator UX', priority: 'med' },
49
+ ] as Card[],
50
+ },
51
+ {
52
+ id: 'doing',
53
+ title: 'In progress',
54
+ dragAccept: ['backlog'],
55
+ cards: [{ id: 'c4', title: 'Release Listbox v2', priority: 'high' }] as Card[],
56
+ },
57
+ {
58
+ id: 'done',
59
+ title: 'Done',
60
+ dragAccept: ['backlog', 'doing'],
61
+ cards: [{ id: 'c5', title: 'Ship Virtual List page', priority: 'med' }] as Card[],
62
+ },
63
+ ]);
64
+
65
+ function onAdd(colId: string, p: { items: readonly Card[] }) {
66
+ const col = columns.value.find((c) => c.id === colId);
67
+ if (!col) return;
68
+ col.cards = [...col.cards, ...p.items];
69
+ }
70
+
71
+ function onRemove(colId: string, p: { items: readonly Card[] }) {
72
+ const col = columns.value.find((c) => c.id === colId);
73
+ if (!col) return;
74
+ const removing = new Set(p.items.map((i) => i.id));
75
+ col.cards = col.cards.filter((c) => !removing.has(c.id));
76
+ }
77
+ </script>
78
+
79
+ <style scoped>
80
+ .board { display: flex; gap: 16px; min-height: 340px; }
81
+ </style>
82
+ ```
83
+
84
+ Each column is one component that wires the composable's `startDrag` / `endDrag` to its cards and `onDragOver` / `onDragLeave` / `onDrop` to itself:
85
+
86
+ ```vue
87
+ <script setup lang="ts">
88
+ import { useDragDrop } from '@cocoar/vue-ui'
89
+
90
+ const dnd = useDragDrop<Card>({
91
+ dragId: () => props.columnId,
92
+ dragGroup: 'kanban',
93
+ dragAccept: () => props.dragAccept, // whitelist of upstream columns
94
+ onDropAccept: ({ items }) => emit('items-add', { items }),
95
+ onItemsRemove: ({ items }) => emit('items-remove', { items }),
96
+ })
97
+ </script>
98
+
99
+ <template>
100
+ <div
101
+ :class="{ 'over': dnd.isDragOver.value }"
102
+ @dragover="dnd.onDragOver"
103
+ @dragleave="dnd.onDragLeave"
104
+ @drop="dnd.onDrop($event)"
105
+ >
106
+ <div
107
+ v-for="card in cards"
108
+ draggable="true"
109
+ @dragstart="dnd.startDrag($event, [card])"
110
+ @dragend="dnd.endDrag($event)"
111
+ >{{ card.title }}</div>
112
+ </div>
113
+ </template>
114
+ ```
115
+
116
+ ## Matching rules
117
+
118
+ A drop is accepted iff **all** of the following pass:
119
+
120
+ 1. **`dragGroup`** on source and target is equal (or both unset). This is the fast coarse-matching layer.
121
+ 2. **`dragAccept`** — if set on the target, the source's `dragId` must be in the list.
122
+ 3. **`canDrop`** — if provided on the target, it must return `true` for the incoming payload.
123
+
124
+ Self-drops (drag within the same surface) bypass the group check but still honour `dragAccept` and `canDrop`. They also skip `onItemsRemove` — the source of truth already holds the item.
125
+
126
+ Visual feedback: when any rule fails during `dragover`, the composable sets `dropEffect = 'none'` (cursor shows "not allowed") and leaves `isDragOver` false — wire that ref to a CSS class for accurate hover highlighting.
127
+
128
+ ## API
129
+
130
+ ### `UseDragDropOptions<T>`
131
+
132
+ | Option | Type | Default | Description |
133
+ |---|---|---|---|
134
+ | `dragId` | `MaybeRefOrGetter<string \| undefined>` | — | Public identifier for this surface. Pair with another surface's `dragAccept` for directional flow. |
135
+ | `dragGroup` | `MaybeRefOrGetter<string \| undefined>` | — | Shared name linking compatible surfaces. Only surfaces sharing a group exchange items. |
136
+ | `dragAccept` | `MaybeRefOrGetter<string[] \| undefined>` | — | Whitelist of source `dragId`s this surface accepts. Unset = accept any source in the same `dragGroup`. |
137
+ | `canDrop` | `(payload) => boolean` | — | Runtime drop validation. `payload = { items, fromId, fromGroup, fromSelf }`. |
138
+ | `onDragStart` | `(items) => void` | — | Called after `startDrag` registers a drag. |
139
+ | `onDragEnd` | `({ items, dropped }) => void` | — | Called on `dragend` — `dropped: true` if a target consumed the payload. |
140
+ | `onDropAccept` | `(payload & { insertIndex }) => void` | — | Called on **this** surface when it accepts a drop. Update your source of truth here. |
141
+ | `onItemsRemove` | `({ items, toGroup }) => void` | — | Called on the **source** surface when another target consumed its payload — fires synchronously inside the target's `drop`. Update your source of truth here. |
142
+
143
+ ### Return value
144
+
145
+ | Field | Type | Description |
146
+ |---|---|---|
147
+ | `instanceId` | `string` | Stable per-instance identifier (auto-generated). |
148
+ | `isDragOver` | `Ref<boolean>` | `true` while a compatible drag is hovering this surface. Wire to a CSS class. |
149
+ | `isDragging` | `Ref<boolean>` | `true` while this surface is the source of an in-flight drag. |
150
+ | `startDrag` | `(event, items) => boolean` | Call from `@dragstart` on a draggable element. Returns `false` for empty payloads. |
151
+ | `endDrag` | `(event) => void` | Call from `@dragend`. Cleans up the session and fires `onDragEnd`. |
152
+ | `onDragOver` | `(event) => void` | Call from the drop container's `@dragover`. |
153
+ | `onDragLeave` | `(event) => void` | Call from `@dragleave`. Ignores events whose relatedTarget is still inside the container. |
154
+ | `onDrop` | `(event, ctx?) => void` | Call from `@drop`. Optional `ctx.insertIndex` is forwarded to `onDropAccept`. |
155
+
156
+ ## Engines
157
+
158
+ The same contract runs on two input engines. Pick one per surface with `engine`; the matching rules, `onDropAccept` / `onItemsRemove` and the registry are shared, so a source on one engine simply cannot reach a target on the other — but everything else stays identical.
159
+
160
+ | Engine | Input | Targets | Use when |
161
+ |---|---|---|---|
162
+ | `'native'` (default) | HTML5 drag events | any HTML5 drop target: other Cocoar components, the OS, other apps | desktop |
163
+ | `'pointer'` | Pointer Events — mouse, pen, touch (long-press) | surfaces using `useDragDrop` with the pointer engine | tablets, touch-first views |
164
+ | `'auto'` | picks `'pointer'` on coarse-pointer devices | — | one setting for both |
165
+
166
+ ```ts
167
+ const dnd = useDragDrop<Row>({
168
+ engine: 'auto',
169
+ dragGroup: 'rows',
170
+ pointer: {
171
+ target: containerRef, // registered as a pointer drop surface
172
+ onHover: (point, payload) => { insertAt.value = indexAt(point) },
173
+ onLeave: () => { insertAt.value = null },
174
+ onDrop: () => insertAt.value, // becomes onDropAccept's insertIndex
175
+ },
176
+ onDropAccept, onItemsRemove,
177
+ })
178
+ ```
179
+
180
+ ```vue
181
+ <!-- source: wire pointerdown next to the native handlers; the engine decides which one acts -->
182
+ <div :draggable="dnd.engine.value === 'native' || undefined"
183
+ @dragstart="dnd.startDrag($event, [row])" @dragend="dnd.endDrag"
184
+ @pointerdown="dnd.onPointerDown($event, [row])">
185
+ ```
186
+
187
+ `onPointerDown` accepts a getter for the items, resolved when the drag actually starts (after the mouse threshold or the touch long-press). Drags never start from interactive children (buttons, inputs, links). A ghost cloned from the source follows the pointer; pass `pointer.ghost` for your own element or `false` for none. `Escape` cancels. Accepting OS files is a plain `drop` listener and is unaffected by the engine. Components exposing this choice: `CoarDataList` (`dragEngine`), `CoarListbox` / `CoarDualListbox` (`dragEngine`).
188
+
189
+ ## Patterns
190
+
191
+ **Two-way exchange** — both surfaces draggable + droppable + same `dragGroup`:
192
+
193
+ ```ts
194
+ useDragDrop({ dragGroup: 'items', onDropAccept, onItemsRemove })
195
+ ```
196
+
197
+ **One-way flow** — give each source a unique `dragId`, whitelist on the target:
198
+
199
+ ```ts
200
+ // source (box1)
201
+ useDragDrop({ dragId: 'box1', dragGroup: 'flow' })
202
+ // target (box2) — only accepts from box1
203
+ useDragDrop({ dragGroup: 'flow', dragAccept: ['box1'], onDropAccept })
204
+ ```
205
+
206
+ **Capacity limits** — reject in `canDrop`:
207
+
208
+ ```ts
209
+ useDragDrop({
210
+ dragGroup: 'roles',
211
+ canDrop: ({ items }) => admins.value.length + items.length <= 5,
212
+ onDropAccept,
213
+ })
214
+ ```
215
+
216
+ **Integration with existing components** — `CoarListbox` uses this composable internally. If you're building a new component that needs the same drag semantics, reach for `useDragDrop` rather than reimplementing the registry.
217
+
218
+ ## Custom drag ghosts
219
+
220
+ The browser's default drag image is a semi-transparent snapshot of the dragged element. That looks fine for small items but turns into a giant faded blob for a large card or a nested tree node. Two tiny helpers attached to the same package give you a styled ghost next to the cursor without the boilerplate of cloning, off-screen mounting, and cleanup.
221
+
222
+ ```ts
223
+ import {
224
+ setCoarDragImageFromElement,
225
+ setCoarDragImageFromHtml,
226
+ } from '@cocoar/vue-ui';
227
+
228
+ function onDragStart(event: DragEvent) {
229
+ setCoarDragImageFromElement(event, event.currentTarget as HTMLElement);
230
+ }
231
+ ```
232
+
233
+ The helper clones the source element, sizes the clone to match the source's bounding box, mounts it off-screen (horizontally, because Chromium skips rendering elements that are entirely outside the viewport — and an unrendered ghost captures as an empty bitmap), calls `dataTransfer.setDragImage`, and removes the clone on the next macrotask so the browser has time to rasterise it.
234
+
235
+ For a free-form ghost that doesn't mirror an existing element, use the HTML variant:
236
+
237
+ ```ts
238
+ setCoarDragImageFromHtml(event, `
239
+ <div style="padding: 6px 10px; font-size: 12px;">
240
+ Moving 3 items
241
+ </div>
242
+ `);
243
+ ```
244
+
245
+ ### API
246
+
247
+ #### `setCoarDragImageFromElement(event, source, options?)`
248
+
249
+ | Argument | Type | Description |
250
+ |---|---|---|
251
+ | `event` | `DragEvent` | The `dragstart` event. Called synchronously inside the handler. |
252
+ | `source` | `HTMLElement` | Element to clone as the ghost. The live element is not visually disturbed. |
253
+ | `options` | `CoarDragImageOptions` | Optional styling overrides. See below. |
254
+
255
+ #### `setCoarDragImageFromHtml(event, html, options?)`
256
+
257
+ Same contract, but builds the ghost from a raw HTML string instead of cloning an element. Useful for "drag summary" previews (e.g. "Moving 3 items") that don't correspond to a single DOM node.
258
+
259
+ #### `CoarDragImageOptions`
260
+
261
+ | Option | Type | Default | Description |
262
+ |---|---|---|---|
263
+ | `offsetX` | `number` | `12` | Cursor offset within the ghost, in px. |
264
+ | `offsetY` | `number` | `12` | Cursor offset within the ghost, in px. |
265
+ | `className` | `string` | — | CSS class applied to the generated wrapper so consumers can theme the ghost. |
266
+ | `style` | `Partial<CSSStyleDeclaration>` | — | Inline styles merged onto the wrapper. Prefer `className` when possible. |
267
+ | `applyDefaultStyle` | `boolean` | `true` | Apply the default rounded-corner, drop-shadow, 0.9 opacity treatment. Set to `false` when the caller handles all styling via `className`. |