@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.
- package/dist/components/checkbox/CoarCheckbox.vue.d.ts +1 -1
- package/dist/components/checkbox/CoarCheckbox.vue.d.ts.map +1 -1
- package/dist/components/code-block/CoarCodeBlock.vue.d.ts +1 -1
- package/dist/components/code-block/CoarCodeBlock.vue.d.ts.map +1 -1
- package/dist/components/data-list/CoarDataList.vue.d.ts +158 -0
- package/dist/components/data-list/CoarDataList.vue.d.ts.map +1 -0
- package/dist/components/data-list/CoarDataListToolbar.vue.d.ts +30 -0
- package/dist/components/data-list/CoarDataListToolbar.vue.d.ts.map +1 -0
- package/dist/components/data-list/data-list-builder.d.ts +288 -0
- package/dist/components/data-list/data-list-builder.d.ts.map +1 -0
- package/dist/components/data-list/index.d.ts +12 -0
- package/dist/components/data-list/index.d.ts.map +1 -0
- package/dist/components/data-list/internal/compare.d.ts +14 -0
- package/dist/components/data-list/internal/compare.d.ts.map +1 -0
- package/dist/components/data-list/internal/reorder-core.d.ts +46 -0
- package/dist/components/data-list/internal/reorder-core.d.ts.map +1 -0
- package/dist/components/data-list/internal/search.d.ts +16 -0
- package/dist/components/data-list/internal/search.d.ts.map +1 -0
- package/dist/components/data-list/internal/useDataListLines.d.ts +92 -0
- package/dist/components/data-list/internal/useDataListLines.d.ts.map +1 -0
- package/dist/components/data-list/internal/useDataListReorder.d.ts +68 -0
- package/dist/components/data-list/internal/useDataListReorder.d.ts.map +1 -0
- package/dist/components/data-list/internal/useSearchHighlight.d.ts +19 -0
- package/dist/components/data-list/internal/useSearchHighlight.d.ts.map +1 -0
- package/dist/components/data-list/types.d.ts +185 -0
- package/dist/components/data-list/types.d.ts.map +1 -0
- package/dist/components/data-list/useDataListModel.d.ts +79 -0
- package/dist/components/data-list/useDataListModel.d.ts.map +1 -0
- package/dist/components/date-time/_shared/maskito-config.d.ts +7 -2
- package/dist/components/date-time/_shared/maskito-config.d.ts.map +1 -1
- package/dist/components/date-time/_shared/time-helpers.d.ts +11 -4
- package/dist/components/date-time/_shared/time-helpers.d.ts.map +1 -1
- package/dist/components/date-time/_shared/use-date-picker-base.d.ts +1 -1
- package/dist/components/date-time/plain-date-picker/CoarPlainDatePicker.vue.d.ts +1 -1
- package/dist/components/date-time/plain-date-picker/CoarPlainDatePicker.vue.d.ts.map +1 -1
- package/dist/components/date-time/plain-date-time-picker/CoarPlainDateTimePicker.vue.d.ts +1 -1
- package/dist/components/date-time/plain-date-time-picker/CoarPlainDateTimePicker.vue.d.ts.map +1 -1
- package/dist/components/date-time/zoned-date-time-picker/CoarZonedDateTimePicker.vue.d.ts +1 -1
- package/dist/components/date-time/zoned-date-time-picker/CoarZonedDateTimePicker.vue.d.ts.map +1 -1
- package/dist/components/dual-listbox/CoarDualListbox.vue.d.ts +3 -0
- package/dist/components/dual-listbox/CoarDualListbox.vue.d.ts.map +1 -1
- package/dist/components/form-field/CoarFormField.vue.d.ts +1 -1
- package/dist/components/form-field/CoarFormField.vue.d.ts.map +1 -1
- package/dist/components/icon/CoarIcon.vue.d.ts +2 -2
- package/dist/components/icon/CoarIcon.vue.d.ts.map +1 -1
- package/dist/components/listbox/CoarListbox.vue.d.ts +7 -0
- package/dist/components/listbox/CoarListbox.vue.d.ts.map +1 -1
- package/dist/components/number-input/CoarNumberInput.vue.d.ts +1 -1
- package/dist/components/number-input/CoarNumberInput.vue.d.ts.map +1 -1
- package/dist/components/otp-input/CoarOtpInput.vue.d.ts +1 -1
- package/dist/components/otp-input/CoarOtpInput.vue.d.ts.map +1 -1
- package/dist/components/password-input/CoarPasswordInput.vue.d.ts +1 -1
- package/dist/components/password-input/CoarPasswordInput.vue.d.ts.map +1 -1
- package/dist/components/switch/CoarSwitch.vue.d.ts +1 -1
- package/dist/components/switch/CoarSwitch.vue.d.ts.map +1 -1
- package/dist/components/text-input/CoarTextInput.vue.d.ts +1 -1
- package/dist/components/text-input/CoarTextInput.vue.d.ts.map +1 -1
- package/dist/composables/dragRegistry.d.ts +12 -0
- package/dist/composables/dragRegistry.d.ts.map +1 -1
- package/dist/composables/index.d.ts +2 -2
- package/dist/composables/index.d.ts.map +1 -1
- package/dist/composables/useDragDrop.d.ts +62 -6
- package/dist/composables/useDragDrop.d.ts.map +1 -1
- package/dist/composables/useVirtualList.d.ts +21 -1
- package/dist/composables/useVirtualList.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4508 -2039
- package/package.json +5 -3
- package/skills/cocoar-vue-ui/SKILL.md +244 -0
- package/skills/cocoar-vue-ui/references/components/avatar.md +178 -0
- package/skills/cocoar-vue-ui/references/components/badge.md +290 -0
- package/skills/cocoar-vue-ui/references/components/breadcrumb.md +238 -0
- package/skills/cocoar-vue-ui/references/components/button.md +252 -0
- package/skills/cocoar-vue-ui/references/components/calendar/agenda-view.md +353 -0
- package/skills/cocoar-vue-ui/references/components/calendar/coar-calendar.md +1645 -0
- package/skills/cocoar-vue-ui/references/components/calendar/day-view.md +361 -0
- package/skills/cocoar-vue-ui/references/components/calendar/index.md +312 -0
- package/skills/cocoar-vue-ui/references/components/calendar/month-view.md +475 -0
- package/skills/cocoar-vue-ui/references/components/calendar/performance.md +186 -0
- package/skills/cocoar-vue-ui/references/components/calendar/timeline-view.md +301 -0
- package/skills/cocoar-vue-ui/references/components/calendar/week-view.md +382 -0
- package/skills/cocoar-vue-ui/references/components/calendar/work-week-view.md +145 -0
- package/skills/cocoar-vue-ui/references/components/calendar/year-view.md +63 -0
- package/skills/cocoar-vue-ui/references/components/card.md +151 -0
- package/skills/cocoar-vue-ui/references/components/checkbox-group.md +103 -0
- package/skills/cocoar-vue-ui/references/components/checkbox.md +279 -0
- package/skills/cocoar-vue-ui/references/components/code-block.md +241 -0
- package/skills/cocoar-vue-ui/references/components/context-menu.md +355 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/checkbox.md +213 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/date-columns.md +272 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/editing.md +236 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/multi-select.md +193 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/number.md +147 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/select.md +202 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/text.md +127 -0
- package/skills/cocoar-vue-ui/references/components/data-grid.md +1225 -0
- package/skills/cocoar-vue-ui/references/components/data-list.md +2121 -0
- package/skills/cocoar-vue-ui/references/components/date-or-time-picker.md +200 -0
- package/skills/cocoar-vue-ui/references/components/date-picker.md +203 -0
- package/skills/cocoar-vue-ui/references/components/date-time-picker.md +154 -0
- package/skills/cocoar-vue-ui/references/components/date-views.md +212 -0
- package/skills/cocoar-vue-ui/references/components/dialog.md +148 -0
- package/skills/cocoar-vue-ui/references/components/divider.md +157 -0
- package/skills/cocoar-vue-ui/references/components/document-viewer/annotations.md +400 -0
- package/skills/cocoar-vue-ui/references/components/document-viewer/coar-document-viewer.md +327 -0
- package/skills/cocoar-vue-ui/references/components/document-viewer/index.md +234 -0
- package/skills/cocoar-vue-ui/references/components/document-viewer/toolbar.md +221 -0
- package/skills/cocoar-vue-ui/references/components/drag-drop.md +267 -0
- package/skills/cocoar-vue-ui/references/components/dual-listbox.md +449 -0
- package/skills/cocoar-vue-ui/references/components/file-explorer/asset-store.md +327 -0
- package/skills/cocoar-vue-ui/references/components/file-explorer/in-memory-store.md +176 -0
- package/skills/cocoar-vue-ui/references/components/file-explorer/index.md +311 -0
- package/skills/cocoar-vue-ui/references/components/file-explorer/use-file-explorer.md +300 -0
- package/skills/cocoar-vue-ui/references/components/form-field.md +1025 -0
- package/skills/cocoar-vue-ui/references/components/fragment-parser.md +298 -0
- package/skills/cocoar-vue-ui/references/components/link.md +235 -0
- package/skills/cocoar-vue-ui/references/components/listbox.md +575 -0
- package/skills/cocoar-vue-ui/references/components/map/editor.md +448 -0
- package/skills/cocoar-vue-ui/references/components/map/index.md +351 -0
- package/skills/cocoar-vue-ui/references/components/markdown-diagrams.md +210 -0
- package/skills/cocoar-vue-ui/references/components/markdown-editor.md +1478 -0
- package/skills/cocoar-vue-ui/references/components/markdown-embeds.md +387 -0
- package/skills/cocoar-vue-ui/references/components/markdown-form.md +447 -0
- package/skills/cocoar-vue-ui/references/components/markdown.md +276 -0
- package/skills/cocoar-vue-ui/references/components/menu.md +380 -0
- package/skills/cocoar-vue-ui/references/components/mermaid.md +172 -0
- package/skills/cocoar-vue-ui/references/components/navbar.md +147 -0
- package/skills/cocoar-vue-ui/references/components/note.md +96 -0
- package/skills/cocoar-vue-ui/references/components/notice.md +131 -0
- package/skills/cocoar-vue-ui/references/components/number-input.md +215 -0
- package/skills/cocoar-vue-ui/references/components/otp-input.md +324 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/authoring-contract.md +271 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/coar-page-builder.md +476 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/coar-page-renderer.md +677 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/custom-elements.md +441 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/idp-integration.md +122 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/index.md +573 -0
- package/skills/cocoar-vue-ui/references/components/pagination.md +150 -0
- package/skills/cocoar-vue-ui/references/components/panel-layout.md +372 -0
- package/skills/cocoar-vue-ui/references/components/password-input.md +175 -0
- package/skills/cocoar-vue-ui/references/components/popconfirm.md +186 -0
- package/skills/cocoar-vue-ui/references/components/popover.md +173 -0
- package/skills/cocoar-vue-ui/references/components/progress-bar.md +178 -0
- package/skills/cocoar-vue-ui/references/components/radio-group.md +225 -0
- package/skills/cocoar-vue-ui/references/components/script-editor.md +1271 -0
- package/skills/cocoar-vue-ui/references/components/segmented-control.md +238 -0
- package/skills/cocoar-vue-ui/references/components/select.md +463 -0
- package/skills/cocoar-vue-ui/references/components/sidebar.md +421 -0
- package/skills/cocoar-vue-ui/references/components/spinner.md +132 -0
- package/skills/cocoar-vue-ui/references/components/switch.md +195 -0
- package/skills/cocoar-vue-ui/references/components/table.md +170 -0
- package/skills/cocoar-vue-ui/references/components/tabs.md +382 -0
- package/skills/cocoar-vue-ui/references/components/tag.md +178 -0
- package/skills/cocoar-vue-ui/references/components/text-input.md +256 -0
- package/skills/cocoar-vue-ui/references/components/toast.md +160 -0
- package/skills/cocoar-vue-ui/references/components/tooltip.md +121 -0
- package/skills/cocoar-vue-ui/references/components/transitions.md +193 -0
- package/skills/cocoar-vue-ui/references/components/tree.md +2388 -0
- package/skills/cocoar-vue-ui/references/components/virtual-list.md +212 -0
- package/skills/cocoar-vue-ui/references/components/wizard.md +251 -0
- package/skills/cocoar-vue-ui/references/components/zoned-date-time-picker.md +177 -0
- package/skills/cocoar-vue-ui/references/foundations/colors.md +708 -0
- package/skills/cocoar-vue-ui/references/foundations/design-principles.md +115 -0
- package/skills/cocoar-vue-ui/references/foundations/icons.md +381 -0
- package/skills/cocoar-vue-ui/references/foundations/localization/formatting.md +530 -0
- package/skills/cocoar-vue-ui/references/foundations/localization/setup.md +86 -0
- package/skills/cocoar-vue-ui/references/foundations/localization/timezones.md +182 -0
- package/skills/cocoar-vue-ui/references/foundations/localization/translations.md +305 -0
- package/skills/cocoar-vue-ui/references/foundations/motion.md +549 -0
- package/skills/cocoar-vue-ui/references/foundations/spacing.md +330 -0
- package/skills/cocoar-vue-ui/references/foundations/theming.md +140 -0
- package/skills/cocoar-vue-ui/references/foundations/typography.md +206 -0
- package/skills/cocoar-vue-ui/references/guide/error-handling.md +162 -0
- package/skills/cocoar-vue-ui/references/guide/getting-started.md +119 -0
- package/skills/cocoar-vue-ui/references/guide/migration-page-builder-3.md +207 -0
- package/skills/cocoar-vue-ui/references/guide/migration.md +140 -0
- 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`. |
|