@cocoar/vue-ui 3.1.0 → 3.2.0-beta.11
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/bin/cli.mjs +53 -0
- 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 +9 -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 +372 -0
- package/skills/cocoar-vue-ui/references/components/calendar/coar-calendar.md +1647 -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 +486 -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 +116 -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,387 @@
|
|
|
1
|
+
<!-- Generated from apps/docs/components/markdown-embeds.md by apps/docs/scripts/sync-skill.mjs. Do not edit; edit the docs page. -->
|
|
2
|
+
|
|
3
|
+
# Custom Embeds (Preview)
|
|
4
|
+
|
|
5
|
+
Embed your own Vue components into markdown with a `:::key{props}` directive. The
|
|
6
|
+
**same** registry drives the shared viewer (`<CoarMarkdown>`) and the editor
|
|
7
|
+
(`<CoarMarkdownEditor>`): a registered component renders read-only when reading
|
|
8
|
+
and editable when writing, and the directive round-trips losslessly to text.
|
|
9
|
+
|
|
10
|
+
The embedded component has **zero dependency on markdown** — it's a plain
|
|
11
|
+
component with normal props that a consumer registers from the outside. The
|
|
12
|
+
markdown packages never depend on it; the registry is the only meeting point.
|
|
13
|
+
|
|
14
|
+
> **Info: Spans three packages**
|
|
15
|
+
>
|
|
16
|
+
> The directive parser lives in `@cocoar/vue-markdown-core` (Vue-free); the registry
|
|
17
|
+
> and renderer in `@cocoar/vue-markdown`; the editor NodeView + insert affordance in
|
|
18
|
+
> `@cocoar/vue-markdown-editor`. Registering an embed once lights up all three.
|
|
19
|
+
|
|
20
|
+
**Demo — `markdown-embeds/demos/MarkdownEmbeds.vue`**
|
|
21
|
+
|
|
22
|
+
```vue
|
|
23
|
+
<script setup lang="ts">
|
|
24
|
+
/**
|
|
25
|
+
* End-to-end embed demo: the SAME markdown drives the editor (left) and the
|
|
26
|
+
* viewer (right), both handed the SAME registry. The editor folds `:::stat{…}`
|
|
27
|
+
* into a live, editable NodeView (configure it, or insert a new one from the
|
|
28
|
+
* Insert ▾ flyout in the left rail); the viewer renders it read-only. Edits
|
|
29
|
+
* round-trip through the markdown.
|
|
30
|
+
*/
|
|
31
|
+
import { computed, markRaw, onMounted, ref, shallowRef, type Component } from 'vue';
|
|
32
|
+
import { CoarMarkdown, type EmbedRegistry } from '@cocoar/vue-markdown';
|
|
33
|
+
import { parse } from '@cocoar/vue-markdown-core';
|
|
34
|
+
import type { CoarMarkdownEditorToolEntry } from '@cocoar/vue-markdown-editor';
|
|
35
|
+
import DocStat from './DocStat.vue';
|
|
36
|
+
import DocStatConfig from './DocStatConfig.vue';
|
|
37
|
+
|
|
38
|
+
// key → { viewer (read-only), editor (editable), insert (toolbar item) }.
|
|
39
|
+
const embeds: EmbedRegistry = {
|
|
40
|
+
stat: {
|
|
41
|
+
viewer: markRaw(DocStat),
|
|
42
|
+
editor: markRaw(DocStatConfig),
|
|
43
|
+
insert: { label: 'Stat card', icon: 'layout-grid' },
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Custom toolbar: array order = position; the Insert flyout carries `embed:stat`.
|
|
48
|
+
const tools: CoarMarkdownEditorToolEntry[] = [
|
|
49
|
+
'bold', 'italic', 'headings',
|
|
50
|
+
'divider',
|
|
51
|
+
{ flyout: ['embed:stat'], label: 'Insert', icon: 'plus' },
|
|
52
|
+
'divider',
|
|
53
|
+
'undo', 'redo',
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const value = ref(`# Quarterly report
|
|
57
|
+
|
|
58
|
+
Edit the metric in the editor — the viewer updates from the same markdown.
|
|
59
|
+
|
|
60
|
+
:::stat{label="Revenue" value="1,284" trend="+12.4%" tone=positive}
|
|
61
|
+
|
|
62
|
+
Regular markdown keeps working around the embed.
|
|
63
|
+
`);
|
|
64
|
+
|
|
65
|
+
const doc = computed(() => parse(value.value, { gfm: true }));
|
|
66
|
+
|
|
67
|
+
const Editor = shallowRef<Component | null>(null);
|
|
68
|
+
onMounted(async () => {
|
|
69
|
+
const mod = await import('@cocoar/vue-markdown-editor');
|
|
70
|
+
Editor.value = mod.CoarMarkdownEditor;
|
|
71
|
+
});
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<template>
|
|
75
|
+
<ClientOnly>
|
|
76
|
+
<div class="emb-demo">
|
|
77
|
+
<div class="emb-demo__col">
|
|
78
|
+
<div class="emb-demo__label">Editor</div>
|
|
79
|
+
<div class="emb-demo__frame">
|
|
80
|
+
<component
|
|
81
|
+
:is="Editor"
|
|
82
|
+
v-if="Editor"
|
|
83
|
+
v-model="value"
|
|
84
|
+
flavor="cocoar"
|
|
85
|
+
:embeds="embeds"
|
|
86
|
+
:tools="tools"
|
|
87
|
+
toolbar-mode="both"
|
|
88
|
+
/>
|
|
89
|
+
<div v-else class="emb-demo__loading">Loading editor…</div>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
<div class="emb-demo__col">
|
|
93
|
+
<div class="emb-demo__label">Viewer</div>
|
|
94
|
+
<div class="emb-demo__frame emb-demo__frame--viewer">
|
|
95
|
+
<CoarMarkdown :doc="doc" :embeds="embeds" />
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</ClientOnly>
|
|
100
|
+
</template>
|
|
101
|
+
|
|
102
|
+
<style scoped>
|
|
103
|
+
.emb-demo {
|
|
104
|
+
display: grid;
|
|
105
|
+
grid-template-columns: 1fr 1fr;
|
|
106
|
+
gap: 12px;
|
|
107
|
+
}
|
|
108
|
+
@media (max-width: 720px) {
|
|
109
|
+
.emb-demo { grid-template-columns: 1fr; }
|
|
110
|
+
}
|
|
111
|
+
.emb-demo__col {
|
|
112
|
+
display: flex;
|
|
113
|
+
flex-direction: column;
|
|
114
|
+
gap: 6px;
|
|
115
|
+
min-width: 0;
|
|
116
|
+
}
|
|
117
|
+
.emb-demo__label {
|
|
118
|
+
font-size: 11px;
|
|
119
|
+
font-weight: 600;
|
|
120
|
+
text-transform: uppercase;
|
|
121
|
+
letter-spacing: 0.04em;
|
|
122
|
+
color: var(--coar-text-neutral-tertiary, #999);
|
|
123
|
+
}
|
|
124
|
+
.emb-demo__frame {
|
|
125
|
+
height: 360px;
|
|
126
|
+
border: 1px solid var(--coar-border-neutral, #e2e2e2);
|
|
127
|
+
border-radius: var(--coar-radius-xl, 12px);
|
|
128
|
+
overflow: hidden;
|
|
129
|
+
display: flex;
|
|
130
|
+
flex-direction: column;
|
|
131
|
+
}
|
|
132
|
+
.emb-demo__frame--viewer {
|
|
133
|
+
overflow: auto;
|
|
134
|
+
padding: 12px 16px;
|
|
135
|
+
background: var(--coar-background-neutral-primary, #fff);
|
|
136
|
+
}
|
|
137
|
+
.emb-demo__loading {
|
|
138
|
+
padding: 24px;
|
|
139
|
+
text-align: center;
|
|
140
|
+
color: var(--coar-text-neutral-tertiary, #999);
|
|
141
|
+
font-size: 13px;
|
|
142
|
+
}
|
|
143
|
+
</style>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The demo registers one embed under the key `stat`. Edit the metric inline in the
|
|
147
|
+
editor, or insert a fresh one from the **Insert ▾** flyout in the left rail — the
|
|
148
|
+
viewer (right) updates from the same markdown.
|
|
149
|
+
|
|
150
|
+
## The directive
|
|
151
|
+
|
|
152
|
+
A custom embed is a standalone block on its own line:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
:::stat{label="Revenue" value="1,284" trend="+12.4%" tone=positive}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
- `stat` is the **key** — it selects the registered component.
|
|
159
|
+
- The `{…}` attributes become the component's props. Values are strings;
|
|
160
|
+
bareword values may be unquoted (`tone=positive`, `id=2f1c0b9e-…`), values with
|
|
161
|
+
spaces or specials are quoted (`label="Revenue"`). A valueless attribute
|
|
162
|
+
(`{interactive}`) is an empty string.
|
|
163
|
+
|
|
164
|
+
The parser is **registry-agnostic**: any `:::key{…}` parses to a generic `embed`
|
|
165
|
+
node and round-trips, even when no component is registered for that key (it then
|
|
166
|
+
renders as a labelled placeholder — see [Unknown keys](#unknown-keys)).
|
|
167
|
+
|
|
168
|
+
> **Tip: Lossless round-trip**
|
|
169
|
+
>
|
|
170
|
+
> `parse → serialize → parse` is a fixed point. Canonical forms like
|
|
171
|
+
> `:::map{id=<guid>}` are byte-stable; non-canonical input (extra quoting) is
|
|
172
|
+
> normalized on the first serialize and stable thereafter.
|
|
173
|
+
|
|
174
|
+
## Registering an embed
|
|
175
|
+
|
|
176
|
+
The registry maps a key to a definition. Pass it to the viewer and/or editor via
|
|
177
|
+
the `embeds` prop:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
import type { EmbedRegistry } from '@cocoar/vue-markdown';
|
|
181
|
+
import StatCard from './StatCard.vue'; // your read-only component
|
|
182
|
+
import StatCardConfig from './StatCardConfig.vue'; // your editable component
|
|
183
|
+
|
|
184
|
+
const embeds: EmbedRegistry = {
|
|
185
|
+
stat: {
|
|
186
|
+
viewer: StatCard, // required — read-only render (viewer + editor fallback)
|
|
187
|
+
editor: StatCardConfig, // optional — editable variant in the editor
|
|
188
|
+
insert: { // optional — toolbar insert affordance
|
|
189
|
+
label: 'Stat card',
|
|
190
|
+
icon: 'layout-grid',
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
| Field | Type | Description |
|
|
197
|
+
|---|---|---|
|
|
198
|
+
| `viewer` | `Component` | **Required.** Read-only render for this key. Used by the viewer, and as the editor's fallback when no `editor` is supplied. Receives the directive attributes as props. |
|
|
199
|
+
| `editor` | `Component` | _Optional._ Editable variant mounted in the editor. Receives a single `controller` prop — see [The editor contract](#the-editor-contract). Falls back to `viewer` when omitted. |
|
|
200
|
+
| `insert` | `EmbedInsertIntegration` | _Optional._ Toolbar insert affordance — `{ label?, icon?, pick? }`. See [Toolbar insert](#toolbar-insert). |
|
|
201
|
+
|
|
202
|
+
> **Tip: `markRaw` your components**
|
|
203
|
+
>
|
|
204
|
+
> Wrap component definitions in `markRaw(...)` when building the registry so Vue
|
|
205
|
+
> doesn't make them reactive: `{ viewer: markRaw(StatCard) }`.
|
|
206
|
+
|
|
207
|
+
## Viewer
|
|
208
|
+
|
|
209
|
+
Pass `embeds` to `<CoarMarkdown>`. The directive renders through the registered
|
|
210
|
+
`viewer`, read-only:
|
|
211
|
+
|
|
212
|
+
```vue
|
|
213
|
+
<template>
|
|
214
|
+
<CoarMarkdown :doc="doc" :embeds="embeds" />
|
|
215
|
+
</template>
|
|
216
|
+
|
|
217
|
+
<script setup lang="ts">
|
|
218
|
+
import { parse } from '@cocoar/vue-markdown-core';
|
|
219
|
+
import { CoarMarkdown } from '@cocoar/vue-markdown';
|
|
220
|
+
import { embeds } from './embeds';
|
|
221
|
+
|
|
222
|
+
const doc = parse(':::stat{label="Revenue" value="1,284"}', { gfm: true });
|
|
223
|
+
</script>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
An app-wide default works too — `app.provide(MARKDOWN_EMBEDS_KEY, embeds)`. A
|
|
227
|
+
per-instance `embeds` prop wins over the provided value.
|
|
228
|
+
|
|
229
|
+
## Editor
|
|
230
|
+
|
|
231
|
+
Custom embeds are **non-portable**, so they're gated behind the `cocoar`
|
|
232
|
+
[flavor](./markdown-editor.md#flavors-portability) (or an explicit
|
|
233
|
+
`{ embeds: true }` capability). Pass the same registry via `embeds`:
|
|
234
|
+
|
|
235
|
+
```vue
|
|
236
|
+
<CoarMarkdownEditor
|
|
237
|
+
v-model="value"
|
|
238
|
+
flavor="cocoar"
|
|
239
|
+
:embeds="embeds"
|
|
240
|
+
/>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
The editor folds a `:::key{…}` line into an **atomic block** rendered by a live
|
|
244
|
+
NodeView. When the registry entry provides an `editor` component it's mounted
|
|
245
|
+
editable; otherwise the read-only `viewer` is shown. Either way the directive
|
|
246
|
+
round-trips on save.
|
|
247
|
+
|
|
248
|
+
### The editor contract
|
|
249
|
+
|
|
250
|
+
An editor component receives a single, typed `controller` prop — there is no
|
|
251
|
+
`v-model` emit string to remember. The controller carries the current attributes
|
|
252
|
+
and the write-back methods:
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
interface EmbedEditorController<T extends Record<string, string> = Record<string, string>> {
|
|
256
|
+
readonly props: Readonly<T>; // current directive attributes
|
|
257
|
+
update(next: T): void; // replace the whole bag → writes to markdown
|
|
258
|
+
patch(partial: Partial<T>): void; // merge a partial patch (the common case)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
interface EmbedEditorProps<T extends Record<string, string> = Record<string, string>> {
|
|
262
|
+
controller: EmbedEditorController<T>;
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Type your component with `EmbedEditorProps` and call `controller.patch(...)` to
|
|
267
|
+
write changes back. Those writes flow into the ProseMirror node and round-trip to
|
|
268
|
+
the `:::key{props}` markdown — which is the **only** difference between an editor
|
|
269
|
+
and the viewer: the viewer renders an immutable parsed document and cannot write.
|
|
270
|
+
|
|
271
|
+
```vue
|
|
272
|
+
<script setup lang="ts">
|
|
273
|
+
import type { EmbedEditorProps } from '@cocoar/vue-markdown';
|
|
274
|
+
import StatCard from './StatCard.vue';
|
|
275
|
+
|
|
276
|
+
const props = defineProps<EmbedEditorProps>();
|
|
277
|
+
</script>
|
|
278
|
+
|
|
279
|
+
<template>
|
|
280
|
+
<div>
|
|
281
|
+
<input
|
|
282
|
+
:value="props.controller.props.label ?? ''"
|
|
283
|
+
@input="props.controller.patch({ label: ($event.target as HTMLInputElement).value })"
|
|
284
|
+
/>
|
|
285
|
+
<StatCard v-bind="props.controller.props" />
|
|
286
|
+
</div>
|
|
287
|
+
</template>
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
> **Tip: Any edit UX — inline, modal, picker**
|
|
291
|
+
>
|
|
292
|
+
> The editor component is yours. It can edit inline (as above), or render the
|
|
293
|
+
> read-only preview plus an **Edit** button that opens a `useDialog()` modal and
|
|
294
|
+
> calls `controller.update(next)` on save. The library only provides the write
|
|
295
|
+
> channel; the configuration UI is the embed's to design.
|
|
296
|
+
|
|
297
|
+
> **Info: Generic attribute typing**
|
|
298
|
+
>
|
|
299
|
+
> `EmbedEditorProps<T>` is generic, so a specific embed can type its own bag —
|
|
300
|
+
> `defineProps<EmbedEditorProps<{ id: string; zoom: string }>>()` — instead of the
|
|
301
|
+
> default `Record<string, string>`.
|
|
302
|
+
|
|
303
|
+
## Toolbar insert
|
|
304
|
+
|
|
305
|
+
Give a registry entry an `insert` and it can be placed in the toolbar. The
|
|
306
|
+
**registry** defines the *item* (icon, label, behaviour); the editor's
|
|
307
|
+
[`tools`](./markdown-editor.md#toolbar-layout-tools) layout decides
|
|
308
|
+
*where* it appears — referenced by `embed:<key>`:
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
const tools: CoarMarkdownEditorToolEntry[] = [
|
|
312
|
+
'bold', 'italic', 'headings',
|
|
313
|
+
'divider',
|
|
314
|
+
{ flyout: ['embed:stat'], label: 'Insert', icon: 'plus' }, // submenu, builtin + embed mixable
|
|
315
|
+
'divider',
|
|
316
|
+
'undo', 'redo',
|
|
317
|
+
];
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
```vue
|
|
321
|
+
<CoarMarkdownEditor v-model="value" flavor="cocoar" :embeds="embeds" :tools="tools" toolbar-mode="both" />
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
> **Warning: Insert lives in the sidebar**
|
|
325
|
+
>
|
|
326
|
+
> Like the table / image / code-block buttons, the insert item is a **sidebar**
|
|
327
|
+
> tool — use `toolbar-mode="fixed"` or `"both"` so the rail is visible. Embeds are
|
|
328
|
+
> block inserts, not text formatting, so they never appear in the floating toolbar.
|
|
329
|
+
|
|
330
|
+
`EmbedInsertIntegration`:
|
|
331
|
+
|
|
332
|
+
| Field | Type | Description |
|
|
333
|
+
|---|---|---|
|
|
334
|
+
| `label` | `string` | Item label / tooltip. Defaults to the key. |
|
|
335
|
+
| `icon` | `string` | `CoarIcon` name. Defaults to `layout-grid`. |
|
|
336
|
+
| `pick` | `() => Promise<Record<string,string> \| null> \| Record<string,string> \| null` | _Optional._ Resolve the **start attributes** for a new embed — e.g. open a picker dialog. Return `null` to cancel. When omitted, a bare `:::key` is inserted. |
|
|
337
|
+
|
|
338
|
+
A `pick` callback is where you'd open a chooser (which map? which chart?) and
|
|
339
|
+
return its props, e.g. `() => ({ id: chosenGuid })` → inserts `:::map{id=…}`.
|
|
340
|
+
|
|
341
|
+
## Unknown keys
|
|
342
|
+
|
|
343
|
+
A `:::key{…}` whose key isn't registered still parses and round-trips — it just
|
|
344
|
+
renders as a labelled placeholder (`🧩 Unknown embed: :::key`) in both the viewer
|
|
345
|
+
and the editor, so the author sees that an embed is there instead of a blank gap.
|
|
346
|
+
This also means a document moved to an app that hasn't registered the embed
|
|
347
|
+
degrades gracefully rather than losing content.
|
|
348
|
+
|
|
349
|
+
## Security
|
|
350
|
+
|
|
351
|
+
In the JS renderer, attribute values are bound as **Vue props / text**, never via
|
|
352
|
+
`innerHTML` — so untrusted author text (a label like `</script><img onerror=…>`)
|
|
353
|
+
is inert by construction; no manual HTML escaping is needed. (A server-side
|
|
354
|
+
string-lowering renderer in another language must escape on its own.)
|
|
355
|
+
|
|
356
|
+
## API reference
|
|
357
|
+
|
|
358
|
+
### `@cocoar/vue-markdown-core`
|
|
359
|
+
|
|
360
|
+
| Export | Description |
|
|
361
|
+
|---|---|
|
|
362
|
+
| `parseEmbedDirective(line)` | Parse a single line into `{ key, props } \| null`. |
|
|
363
|
+
| `serializeEmbedDirective({ key, props })` | Serialize back to the canonical `:::key{props}` form. |
|
|
364
|
+
| `toEmbedProps(value)` | Coerce an unknown value into a clean `Record<string, string>`. |
|
|
365
|
+
| `'embed'` node type | Added to `MarkdownNodeType`; `attrs` carry `{ key, props }`. |
|
|
366
|
+
|
|
367
|
+
### `@cocoar/vue-markdown`
|
|
368
|
+
|
|
369
|
+
| Export | Description |
|
|
370
|
+
|---|---|
|
|
371
|
+
| `EmbedRegistry` | `Record<string, EmbedDefinition>` — the key → definition map. |
|
|
372
|
+
| `EmbedDefinition` | `{ viewer, editor?, insert? }`. |
|
|
373
|
+
| `EmbedEditorProps` / `EmbedEditorController` | The editor component's `controller` contract. |
|
|
374
|
+
| `EmbedInsertIntegration` | `{ label?, icon?, pick? }`. |
|
|
375
|
+
| `MARKDOWN_EMBEDS_KEY` | Inject key for an app-wide registry. |
|
|
376
|
+
| `EmbedRenderer` | The shared resolve-and-render component (used internally by viewer + editor). |
|
|
377
|
+
|
|
378
|
+
### `@cocoar/vue-markdown-editor`
|
|
379
|
+
|
|
380
|
+
| Export | Description |
|
|
381
|
+
|---|---|
|
|
382
|
+
| `CoarMarkdownEditorToolEntry` | A `tools` entry: a ref, a `{ flyout }` group, or `'divider'`. |
|
|
383
|
+
| `CoarMarkdownEditorToolRef` | `CoarMarkdownEditorTool \| ` `` `embed:${string}` ``. |
|
|
384
|
+
| `CoarMarkdownEditorToolFlyout` | `{ flyout, label?, icon? }`. |
|
|
385
|
+
|
|
386
|
+
All embed types are re-exported from `@cocoar/vue-markdown-editor`, so a consumer
|
|
387
|
+
can import everything from one place.
|