@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,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.