@blogmcp/mdx-render-vue 0.1.0

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 (249) hide show
  1. package/dist/_virtual/_plugin-vue_export-helper.js +10 -0
  2. package/dist/_virtual/constants.js +4 -0
  3. package/dist/_virtual/index.js +5 -0
  4. package/dist/_virtual/index2.js +4 -0
  5. package/dist/composables/index.d.ts +11 -0
  6. package/dist/composables/index.js +24 -0
  7. package/dist/composables/useAudioPlayer.d.ts +22 -0
  8. package/dist/composables/useAudioPlayer.js +58 -0
  9. package/dist/composables/useCallout.d.ts +43 -0
  10. package/dist/composables/useCallout.js +71 -0
  11. package/dist/composables/useCodeBlock.d.ts +23 -0
  12. package/dist/composables/useCodeBlock.js +67 -0
  13. package/dist/composables/useGallery.d.ts +37 -0
  14. package/dist/composables/useGallery.js +84 -0
  15. package/dist/composables/useMedia.d.ts +18 -0
  16. package/dist/composables/useMedia.js +9 -0
  17. package/dist/composables/useYouTube.d.ts +31 -0
  18. package/dist/composables/useYouTube.js +32 -0
  19. package/dist/edit/AlertEdit.vue.d.ts +12 -0
  20. package/dist/edit/AlertEdit.vue.js +4 -0
  21. package/dist/edit/AlertEdit.vue2.js +18 -0
  22. package/dist/edit/AudioPlayerEdit.vue.d.ts +3 -0
  23. package/dist/edit/AudioPlayerEdit.vue.js +117 -0
  24. package/dist/edit/AudioPlayerEdit.vue2.js +4 -0
  25. package/dist/edit/BadgeEdit.vue.d.ts +2 -0
  26. package/dist/edit/BadgeEdit.vue.js +4 -0
  27. package/dist/edit/BadgeEdit.vue2.js +13 -0
  28. package/dist/edit/ButtonEdit.vue.d.ts +2 -0
  29. package/dist/edit/ButtonEdit.vue.js +4 -0
  30. package/dist/edit/ButtonEdit.vue2.js +13 -0
  31. package/dist/edit/CalloutEdit.vue.d.ts +13 -0
  32. package/dist/edit/CalloutEdit.vue.js +85 -0
  33. package/dist/edit/CalloutEdit.vue2.js +4 -0
  34. package/dist/edit/CardEdit.vue.d.ts +12 -0
  35. package/dist/edit/CardEdit.vue.js +4 -0
  36. package/dist/edit/CardEdit.vue2.js +18 -0
  37. package/dist/edit/CodeBlockEdit.vue.d.ts +12 -0
  38. package/dist/edit/CodeBlockEdit.vue.js +7 -0
  39. package/dist/edit/CodeBlockEdit.vue2.js +89 -0
  40. package/dist/edit/GalleryEdit.vue.d.ts +3 -0
  41. package/dist/edit/GalleryEdit.vue.js +64 -0
  42. package/dist/edit/GalleryEdit.vue2.js +4 -0
  43. package/dist/edit/IconEdit.vue.d.ts +2 -0
  44. package/dist/edit/IconEdit.vue.js +4 -0
  45. package/dist/edit/IconEdit.vue2.js +13 -0
  46. package/dist/edit/TooltipEdit.vue.d.ts +16 -0
  47. package/dist/edit/TooltipEdit.vue.js +4 -0
  48. package/dist/edit/TooltipEdit.vue2.js +24 -0
  49. package/dist/edit/YouTubeEdit.vue.d.ts +3 -0
  50. package/dist/edit/YouTubeEdit.vue.js +41 -0
  51. package/dist/edit/YouTubeEdit.vue2.js +4 -0
  52. package/dist/edit/index.d.ts +29 -0
  53. package/dist/edit/index.js +35 -0
  54. package/dist/index.d.ts +12 -0
  55. package/dist/index.js +75 -0
  56. package/dist/mdx-render-vue.css +31 -0
  57. package/dist/node_modules/@ark-ui/vue/dist/_virtual/_rolldown/runtime.js +13 -0
  58. package/dist/node_modules/@ark-ui/vue/dist/components/factory.js +35 -0
  59. package/dist/node_modules/@ark-ui/vue/dist/components/presence/use-presence-context.js +6 -0
  60. package/dist/node_modules/@ark-ui/vue/dist/components/presence/use-presence.js +40 -0
  61. package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-arrow-tip.js +5 -0
  62. package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-arrow-tip.vue_vue_type_script_setup_true_lang.js +21 -0
  63. package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-arrow.js +5 -0
  64. package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-arrow.vue_vue_type_script_setup_true_lang.js +21 -0
  65. package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-content.js +5 -0
  66. package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-content.vue_vue_type_script_setup_true_lang.js +25 -0
  67. package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-context.js +5 -0
  68. package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-context.vue_vue_type_script_setup_true_lang.js +14 -0
  69. package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-positioner.js +5 -0
  70. package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-positioner.vue_vue_type_script_setup_true_lang.js +23 -0
  71. package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-root-provider.js +5 -0
  72. package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-root-provider.vue_vue_type_script_setup_true_lang.js +38 -0
  73. package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-root.js +5 -0
  74. package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-root.vue_vue_type_script_setup_true_lang.js +68 -0
  75. package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-trigger.js +5 -0
  76. package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip-trigger.vue_vue_type_script_setup_true_lang.js +25 -0
  77. package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/tooltip.js +22 -0
  78. package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/use-tooltip-context.js +6 -0
  79. package/dist/node_modules/@ark-ui/vue/dist/components/tooltip/use-tooltip.js +36 -0
  80. package/dist/node_modules/@ark-ui/vue/dist/providers/environment/use-environment-context.js +13 -0
  81. package/dist/node_modules/@ark-ui/vue/dist/providers/locale/use-locale-context.js +12 -0
  82. package/dist/node_modules/@ark-ui/vue/dist/utils/clean-props.js +8 -0
  83. package/dist/node_modules/@ark-ui/vue/dist/utils/create-context.js +14 -0
  84. package/dist/node_modules/@ark-ui/vue/dist/utils/dynamic.js +34 -0
  85. package/dist/node_modules/@ark-ui/vue/dist/utils/unref-element.js +8 -0
  86. package/dist/node_modules/@ark-ui/vue/dist/utils/use-forward-expose.js +46 -0
  87. package/dist/node_modules/@ark-ui/vue/dist/utils/use-render-strategy.js +5 -0
  88. package/dist/node_modules/@braintree/sanitize-url/dist/constants.js +20 -0
  89. package/dist/node_modules/@braintree/sanitize-url/dist/index.js +75 -0
  90. package/dist/node_modules/@floating-ui/core/dist/floating-ui.core.js +728 -0
  91. package/dist/node_modules/@floating-ui/dom/dist/floating-ui.dom.js +629 -0
  92. package/dist/node_modules/@floating-ui/utils/dist/floating-ui.utils.dom.js +176 -0
  93. package/dist/node_modules/@floating-ui/utils/dist/floating-ui.utils.js +151 -0
  94. package/dist/node_modules/@iconify/vue/dist/iconify.js +1691 -0
  95. package/dist/node_modules/@zag-js/anatomy/dist/create-anatomy.js +29 -0
  96. package/dist/node_modules/@zag-js/core/dist/create-machine.js +28 -0
  97. package/dist/node_modules/@zag-js/core/dist/merge-props.js +55 -0
  98. package/dist/node_modules/@zag-js/core/dist/scope.js +20 -0
  99. package/dist/node_modules/@zag-js/core/dist/state.js +176 -0
  100. package/dist/node_modules/@zag-js/core/dist/types.js +11 -0
  101. package/dist/node_modules/@zag-js/dom-query/dist/chunk-QZ7TP4HQ.js +6 -0
  102. package/dist/node_modules/@zag-js/dom-query/dist/computed-style.js +11 -0
  103. package/dist/node_modules/@zag-js/dom-query/dist/event.js +37 -0
  104. package/dist/node_modules/@zag-js/dom-query/dist/node.js +64 -0
  105. package/dist/node_modules/@zag-js/dom-query/dist/overflow.js +28 -0
  106. package/dist/node_modules/@zag-js/dom-query/dist/platform.js +23 -0
  107. package/dist/node_modules/@zag-js/dom-query/dist/query.js +6 -0
  108. package/dist/node_modules/@zag-js/dom-query/dist/raf.js +52 -0
  109. package/dist/node_modules/@zag-js/dom-query/dist/set.js +22 -0
  110. package/dist/node_modules/@zag-js/dom-query/dist/shared.js +8 -0
  111. package/dist/node_modules/@zag-js/focus-visible/dist/index.js +159 -0
  112. package/dist/node_modules/@zag-js/popper/dist/get-anchor.js +39 -0
  113. package/dist/node_modules/@zag-js/popper/dist/get-placement.js +296 -0
  114. package/dist/node_modules/@zag-js/popper/dist/get-styles.js +47 -0
  115. package/dist/node_modules/@zag-js/popper/dist/middleware.js +79 -0
  116. package/dist/node_modules/@zag-js/popper/dist/placement.js +7 -0
  117. package/dist/node_modules/@zag-js/presence/dist/presence.connect.js +18 -0
  118. package/dist/node_modules/@zag-js/presence/dist/presence.machine.js +175 -0
  119. package/dist/node_modules/@zag-js/tooltip/dist/tooltip.anatomy.js +7 -0
  120. package/dist/node_modules/@zag-js/tooltip/dist/tooltip.connect.js +155 -0
  121. package/dist/node_modules/@zag-js/tooltip/dist/tooltip.dom.js +24 -0
  122. package/dist/node_modules/@zag-js/tooltip/dist/tooltip.machine.js +416 -0
  123. package/dist/node_modules/@zag-js/tooltip/dist/tooltip.store.js +9 -0
  124. package/dist/node_modules/@zag-js/types/dist/prop-types.js +14 -0
  125. package/dist/node_modules/@zag-js/utils/dist/array.js +7 -0
  126. package/dist/node_modules/@zag-js/utils/dist/equal.js +36 -0
  127. package/dist/node_modules/@zag-js/utils/dist/functions.js +11 -0
  128. package/dist/node_modules/@zag-js/utils/dist/guard.js +26 -0
  129. package/dist/node_modules/@zag-js/utils/dist/object.js +16 -0
  130. package/dist/node_modules/@zag-js/utils/dist/store.js +44 -0
  131. package/dist/node_modules/@zag-js/utils/dist/warning.js +31 -0
  132. package/dist/node_modules/@zag-js/vue/dist/bindable.js +48 -0
  133. package/dist/node_modules/@zag-js/vue/dist/machine.js +258 -0
  134. package/dist/node_modules/@zag-js/vue/dist/normalize-props.js +42 -0
  135. package/dist/node_modules/@zag-js/vue/dist/refs.js +14 -0
  136. package/dist/node_modules/@zag-js/vue/dist/track.js +22 -0
  137. package/dist/node_modules/dompurify/dist/purify.es.js +1190 -0
  138. package/dist/registry.d.ts +76 -0
  139. package/dist/registry.js +164 -0
  140. package/dist/render/alert/Alert.vue.d.ts +18 -0
  141. package/dist/render/alert/Alert.vue.js +4 -0
  142. package/dist/render/alert/Alert.vue2.js +43 -0
  143. package/dist/render/alert/index.d.ts +2 -0
  144. package/dist/render/audioplayer/AudioPlayerDefault.vue.d.ts +3 -0
  145. package/dist/render/audioplayer/AudioPlayerDefault.vue.js +116 -0
  146. package/dist/render/audioplayer/AudioPlayerDefault.vue2.js +4 -0
  147. package/dist/render/audioplayer/index.d.ts +5 -0
  148. package/dist/render/audioplayer/index.js +5 -0
  149. package/dist/render/badge/Badge.vue.d.ts +10 -0
  150. package/dist/render/badge/Badge.vue.js +4 -0
  151. package/dist/render/badge/Badge.vue2.js +28 -0
  152. package/dist/render/badge/index.d.ts +2 -0
  153. package/dist/render/button/Button.vue.d.ts +13 -0
  154. package/dist/render/button/Button.vue.js +4 -0
  155. package/dist/render/button/Button.vue2.js +42 -0
  156. package/dist/render/button/index.d.ts +2 -0
  157. package/dist/render/callout/CalloutDefault.vue.d.ts +13 -0
  158. package/dist/render/callout/CalloutDefault.vue.js +86 -0
  159. package/dist/render/callout/CalloutDefault.vue2.js +4 -0
  160. package/dist/render/callout/CalloutMinimal.vue.d.ts +13 -0
  161. package/dist/render/callout/CalloutMinimal.vue.js +27 -0
  162. package/dist/render/callout/CalloutMinimal.vue2.js +4 -0
  163. package/dist/render/callout/index.d.ts +6 -0
  164. package/dist/render/callout/index.js +7 -0
  165. package/dist/render/card/Card.vue.d.ts +15 -0
  166. package/dist/render/card/Card.vue.js +4 -0
  167. package/dist/render/card/Card.vue2.js +29 -0
  168. package/dist/render/card/index.d.ts +2 -0
  169. package/dist/render/codeblock/CodeBlockDefault.vue.d.ts +12 -0
  170. package/dist/render/codeblock/CodeBlockDefault.vue.js +7 -0
  171. package/dist/render/codeblock/CodeBlockDefault.vue2.js +92 -0
  172. package/dist/render/codeblock/index.d.ts +5 -0
  173. package/dist/render/codeblock/index.js +5 -0
  174. package/dist/render/gallery/GalleryGrid.vue.d.ts +3 -0
  175. package/dist/render/gallery/GalleryGrid.vue.js +125 -0
  176. package/dist/render/gallery/GalleryGrid.vue2.js +4 -0
  177. package/dist/render/gallery/GalleryLightbox.vue.d.ts +16 -0
  178. package/dist/render/gallery/GalleryLightbox.vue.js +7 -0
  179. package/dist/render/gallery/GalleryLightbox.vue2.js +170 -0
  180. package/dist/render/gallery/index.d.ts +6 -0
  181. package/dist/render/gallery/index.js +7 -0
  182. package/dist/render/icon/Icon.vue.d.ts +9 -0
  183. package/dist/render/icon/Icon.vue.js +4 -0
  184. package/dist/render/icon/Icon.vue2.js +25 -0
  185. package/dist/render/icon/index.d.ts +2 -0
  186. package/dist/render/index.d.ts +40 -0
  187. package/dist/render/index.js +63 -0
  188. package/dist/render/tooltip/Tooltip.vue.d.ts +18 -0
  189. package/dist/render/tooltip/Tooltip.vue.js +4 -0
  190. package/dist/render/tooltip/Tooltip.vue2.js +58 -0
  191. package/dist/render/tooltip/index.d.ts +2 -0
  192. package/dist/render/youtube/YouTubeDefault.vue.d.ts +3 -0
  193. package/dist/render/youtube/YouTubeDefault.vue.js +79 -0
  194. package/dist/render/youtube/YouTubeDefault.vue2.js +4 -0
  195. package/dist/render/youtube/index.d.ts +5 -0
  196. package/dist/render/youtube/index.js +5 -0
  197. package/dist/useAudioPlayer.d.ts +22 -0
  198. package/dist/useCallout.d.ts +46 -0
  199. package/dist/useCodeBlock.d.ts +25 -0
  200. package/dist/useGallery.d.ts +44 -0
  201. package/dist/useMedia.d.ts +16 -0
  202. package/dist/useYouTube.d.ts +32 -0
  203. package/package.json +125 -0
  204. package/src/composables/index.ts +16 -0
  205. package/src/composables/useAudioPlayer.ts +84 -0
  206. package/src/composables/useCallout.ts +103 -0
  207. package/src/composables/useCodeBlock.ts +90 -0
  208. package/src/composables/useGallery.ts +143 -0
  209. package/src/composables/useMedia.ts +23 -0
  210. package/src/composables/useYouTube.ts +58 -0
  211. package/src/edit/AlertEdit.vue +9 -0
  212. package/src/edit/AudioPlayerEdit.vue +92 -0
  213. package/src/edit/BadgeEdit.vue +7 -0
  214. package/src/edit/ButtonEdit.vue +10 -0
  215. package/src/edit/CalloutEdit.vue +107 -0
  216. package/src/edit/CardEdit.vue +9 -0
  217. package/src/edit/CodeBlockEdit.vue +76 -0
  218. package/src/edit/GalleryEdit.vue +65 -0
  219. package/src/edit/IconEdit.vue +7 -0
  220. package/src/edit/TooltipEdit.vue +15 -0
  221. package/src/edit/YouTubeEdit.vue +33 -0
  222. package/src/edit/index.ts +32 -0
  223. package/src/index.ts +62 -0
  224. package/src/registry.ts +272 -0
  225. package/src/render/alert/Alert.vue +32 -0
  226. package/src/render/alert/index.ts +2 -0
  227. package/src/render/audioplayer/AudioPlayerDefault.vue +89 -0
  228. package/src/render/audioplayer/index.ts +8 -0
  229. package/src/render/badge/Badge.vue +25 -0
  230. package/src/render/badge/index.ts +2 -0
  231. package/src/render/button/Button.vue +41 -0
  232. package/src/render/button/index.ts +2 -0
  233. package/src/render/callout/CalloutDefault.vue +101 -0
  234. package/src/render/callout/CalloutMinimal.vue +31 -0
  235. package/src/render/callout/index.ts +9 -0
  236. package/src/render/card/Card.vue +15 -0
  237. package/src/render/card/index.ts +2 -0
  238. package/src/render/codeblock/CodeBlockDefault.vue +76 -0
  239. package/src/render/codeblock/index.ts +8 -0
  240. package/src/render/gallery/GalleryGrid.vue +162 -0
  241. package/src/render/gallery/GalleryLightbox.vue +162 -0
  242. package/src/render/gallery/index.ts +9 -0
  243. package/src/render/icon/Icon.vue +22 -0
  244. package/src/render/icon/index.ts +2 -0
  245. package/src/render/index.ts +76 -0
  246. package/src/render/tooltip/Tooltip.vue +35 -0
  247. package/src/render/tooltip/index.ts +2 -0
  248. package/src/render/youtube/YouTubeDefault.vue +72 -0
  249. package/src/render/youtube/index.ts +8 -0
@@ -0,0 +1,272 @@
1
+ /**
2
+ * MDX Component Registry (Vue)
3
+ *
4
+ * Attaches Vue-specific render/edit loaders to the framework-agnostic metadata
5
+ * published by @blogmcp/mdx-schema. Schema is the single source of truth for
6
+ * component descriptions, prop shapes, and MCP-facing examples — this file
7
+ * adds only the Vue wiring (dynamic imports of SFCs + editor theming).
8
+ */
9
+
10
+ import { defineAsyncComponent, defineComponent, h, type Component } from 'vue'
11
+ import {
12
+ componentMetadata,
13
+ getComponentMetadata,
14
+ getComponentNames,
15
+ type ComponentMetadata,
16
+ } from '@blogmcp/mdx-schema/metadata'
17
+ import { prepareProps } from '@blogmcp/mdx-schema'
18
+
19
+ // Re-export schema helpers so callers that already have the registry can reach
20
+ // metadata without a second import.
21
+ export { componentMetadata, getComponentMetadata, getComponentNames }
22
+
23
+ /**
24
+ * Sentinel property read by the per-renderer "prepareProps is wired"
25
+ * integration test. Wrapped components carry this with the component's
26
+ * schema name so the test can confirm wrapping end-to-end (vs the
27
+ * historical regex-grep on registry source, which was easily defeated
28
+ * by commented-out code and unused imports).
29
+ */
30
+ export const MDX_PREPARED_SENTINEL = '__mdxPreparePropsAppliedFor' as const
31
+
32
+ export interface EditorConfig {
33
+ /** SVG icon name used in MdxBlockView header */
34
+ icon: string
35
+ /** Primary text/accent color */
36
+ color: string
37
+ /** Border color */
38
+ borderColor: string
39
+ /** Background color (typically rgba) */
40
+ bgColor: string
41
+ }
42
+
43
+ export interface ComponentRegistration {
44
+ metadata: ComponentMetadata
45
+ editor: EditorConfig
46
+ /** Render variants: variant name → async loader. Must include 'default'. */
47
+ render: Record<string, () => Promise<{ default: Component }>>
48
+ /** Edit component (dark theme) for md-editor preview */
49
+ edit: () => Promise<{ default: Component }>
50
+ /** Optional custom editor view for complex inline editing (Gallery drag-and-drop, CodeBlock textarea) */
51
+ editorView?: () => Promise<{ default: Component }>
52
+ }
53
+
54
+ /**
55
+ * Per-component Vue wiring. Metadata is pulled from the schema package by name
56
+ * so Phase 1b additions (Button, Card, etc.) only require schema changes +
57
+ * a new entry here.
58
+ */
59
+ interface VueWiring {
60
+ editor: EditorConfig
61
+ render: Record<string, () => Promise<{ default: Component }>>
62
+ edit: () => Promise<{ default: Component }>
63
+ }
64
+
65
+ const vueWiring: Record<string, VueWiring> = {
66
+ Callout: {
67
+ editor: { icon: 'info', color: '#569cd6', borderColor: '#569cd6', bgColor: 'rgba(86, 156, 214, 0.08)' },
68
+ render: {
69
+ default: () => import('./render/callout/CalloutDefault.vue'),
70
+ minimal: () => import('./render/callout/CalloutMinimal.vue'),
71
+ },
72
+ edit: () => import('./edit/CalloutEdit.vue'),
73
+ },
74
+ YouTube: {
75
+ editor: { icon: 'play', color: '#ff4444', borderColor: '#ff4444', bgColor: 'rgba(255, 68, 68, 0.08)' },
76
+ render: {
77
+ default: () => import('./render/youtube/YouTubeDefault.vue'),
78
+ },
79
+ edit: () => import('./edit/YouTubeEdit.vue'),
80
+ },
81
+ Gallery: {
82
+ editor: { icon: 'image', color: '#4ec9b0', borderColor: '#4ec9b0', bgColor: 'rgba(78, 201, 176, 0.08)' },
83
+ render: {
84
+ default: () => import('./render/gallery/GalleryGrid.vue'),
85
+ grid: () => import('./render/gallery/GalleryGrid.vue'),
86
+ },
87
+ edit: () => import('./edit/GalleryEdit.vue'),
88
+ },
89
+ CodeBlock: {
90
+ editor: { icon: 'code', color: '#ce9178', borderColor: '#ce9178', bgColor: 'rgba(206, 145, 120, 0.08)' },
91
+ render: {
92
+ default: () => import('./render/codeblock/CodeBlockDefault.vue'),
93
+ },
94
+ edit: () => import('./edit/CodeBlockEdit.vue'),
95
+ },
96
+ AudioPlayer: {
97
+ editor: { icon: 'audio', color: '#b48ead', borderColor: '#b48ead', bgColor: 'rgba(180, 142, 173, 0.08)' },
98
+ render: {
99
+ default: () => import('./render/audioplayer/AudioPlayerDefault.vue'),
100
+ },
101
+ edit: () => import('./edit/AudioPlayerEdit.vue'),
102
+ },
103
+ Button: {
104
+ editor: { icon: 'button', color: '#2563eb', borderColor: '#2563eb', bgColor: 'rgba(37, 99, 235, 0.08)' },
105
+ render: {
106
+ default: () => import('./render/button/Button.vue'),
107
+ },
108
+ edit: () => import('./edit/ButtonEdit.vue'),
109
+ },
110
+ Card: {
111
+ editor: { icon: 'card', color: '#6b7280', borderColor: '#6b7280', bgColor: 'rgba(107, 114, 128, 0.08)' },
112
+ render: {
113
+ default: () => import('./render/card/Card.vue'),
114
+ },
115
+ edit: () => import('./edit/CardEdit.vue'),
116
+ },
117
+ Alert: {
118
+ editor: { icon: 'alert', color: '#d97706', borderColor: '#d97706', bgColor: 'rgba(217, 119, 6, 0.08)' },
119
+ render: {
120
+ default: () => import('./render/alert/Alert.vue'),
121
+ },
122
+ edit: () => import('./edit/AlertEdit.vue'),
123
+ },
124
+ Badge: {
125
+ editor: { icon: 'badge', color: '#059669', borderColor: '#059669', bgColor: 'rgba(5, 150, 105, 0.08)' },
126
+ render: {
127
+ default: () => import('./render/badge/Badge.vue'),
128
+ },
129
+ edit: () => import('./edit/BadgeEdit.vue'),
130
+ },
131
+ Icon: {
132
+ editor: { icon: 'icon', color: '#7c3aed', borderColor: '#7c3aed', bgColor: 'rgba(124, 58, 237, 0.08)' },
133
+ render: {
134
+ default: () => import('./render/icon/Icon.vue'),
135
+ },
136
+ edit: () => import('./edit/IconEdit.vue'),
137
+ },
138
+ Tooltip: {
139
+ editor: { icon: 'tooltip', color: '#0891b2', borderColor: '#0891b2', bgColor: 'rgba(8, 145, 178, 0.08)' },
140
+ render: {
141
+ default: () => import('./render/tooltip/Tooltip.vue'),
142
+ },
143
+ edit: () => import('./edit/TooltipEdit.vue'),
144
+ },
145
+ }
146
+
147
+ /** Merged registry: schema metadata + Vue wiring, keyed by component name. */
148
+ export const componentRegistry: Record<string, ComponentRegistration> = Object.fromEntries(
149
+ componentMetadata.map(metadata => {
150
+ const wiring = vueWiring[metadata.name]
151
+ if (!wiring) {
152
+ throw new Error(
153
+ `[mdx-components] No Vue wiring registered for component "${metadata.name}". ` +
154
+ `Add an entry to vueWiring in registry.ts or remove the schema entry.`,
155
+ )
156
+ }
157
+ return [metadata.name, { metadata, ...wiring }] as const
158
+ }),
159
+ )
160
+
161
+ /** Get available render variant names for a component */
162
+ export function getComponentVariants(name: string): string[] {
163
+ const reg = componentRegistry[name]
164
+ return reg ? Object.keys(reg.render) : []
165
+ }
166
+
167
+ /** Async resolver — loads only the requested render component (raw, no shim). */
168
+ export async function getRenderComponentAsync(
169
+ name: string,
170
+ variant: string = 'default',
171
+ ): Promise<Component | undefined> {
172
+ const reg = componentRegistry[name]
173
+ if (!reg) return undefined
174
+ const loader = reg.render[variant] || reg.render.default
175
+ if (!loader) return undefined
176
+ const mod = await loader()
177
+ return mod.default
178
+ }
179
+
180
+ // ─── Wrapped (prepareProps-aware) component resolution ──────────────────
181
+ //
182
+ // `getWrappedRenderComponent` is what blog consumers SHOULD use to render
183
+ // MDX-authored components. It returns a Vue component whose render runs
184
+ // every incoming attribute through `prepareProps(attrs, metadata)` before
185
+ // forwarding to the real render SFC. That makes the schema's render-time
186
+ // shim impossible to forget at any call site: `h(Comp, { ...item.props })`
187
+ // automatically gets normalized props, including in recursively-rendered
188
+ // nested MDX children.
189
+ //
190
+ // The wrapper is built around `defineAsyncComponent` so the underlying
191
+ // SFC stays lazily loaded — the wrapper itself is sync and cheap to
192
+ // create. Results are cached per (name, variant) so the wrapper has a
193
+ // stable identity across re-renders (important for Vue's keyed-vdom
194
+ // optimizations).
195
+
196
+ /**
197
+ * Wraps an already-resolved Vue component with a `prepareProps`-aware
198
+ * shim. Returned component is sync; works with both sync `Inner` and
199
+ * `defineAsyncComponent(...)` results. Exported (vs inlined into
200
+ * `getWrappedRenderComponent`) so unit tests can pass a spy component
201
+ * and assert prop normalization without touching the real registry.
202
+ */
203
+ export function wrapVueComponentForMdx(
204
+ Inner: Component,
205
+ metadata: ComponentMetadata,
206
+ variant: string = 'default',
207
+ ): Component {
208
+ const wrapped = defineComponent({
209
+ name: `MdxPrepared${metadata.name}${variant === 'default' ? '' : `_${variant}`}`,
210
+ inheritAttrs: false,
211
+ setup(_props, { attrs, slots }) {
212
+ return () => h(
213
+ Inner,
214
+ prepareProps(attrs as Record<string, unknown>, metadata),
215
+ slots,
216
+ )
217
+ },
218
+ })
219
+ ;(wrapped as unknown as Record<string, unknown>)[MDX_PREPARED_SENTINEL] = metadata.name
220
+ return wrapped
221
+ }
222
+
223
+ const wrappedRenderCache: Map<string, Component> = new Map()
224
+
225
+ /**
226
+ * Returns a Vue component that wraps the lazy-loaded render SFC for
227
+ * `name`/`variant` with a `prepareProps`-aware shim. Returns `undefined`
228
+ * if the component isn't registered.
229
+ *
230
+ * Wrapped components carry `MDX_PREPARED_SENTINEL` on themselves, set
231
+ * to the schema component name — used by the per-renderer integration
232
+ * test to confirm wrapping was applied (instead of the historical
233
+ * brittle regex-grep approach).
234
+ */
235
+ export function getWrappedRenderComponent(
236
+ name: string,
237
+ variant: string = 'default',
238
+ ): Component | undefined {
239
+ const cacheKey = `${name}:${variant}`
240
+ const cached = wrappedRenderCache.get(cacheKey)
241
+ if (cached) return cached
242
+
243
+ const reg = componentRegistry[name]
244
+ if (!reg) return undefined
245
+ const loader = reg.render[variant] || reg.render.default
246
+ if (!loader) return undefined
247
+
248
+ const Inner = defineAsyncComponent(() => loader().then(m => m.default))
249
+ const wrapped = wrapVueComponentForMdx(Inner, reg.metadata, variant)
250
+ wrappedRenderCache.set(cacheKey, wrapped)
251
+ return wrapped
252
+ }
253
+
254
+ /** Test-only — clears the wrapped-component cache between specs. */
255
+ export function _resetWrappedRenderCacheForTests(): void {
256
+ wrappedRenderCache.clear()
257
+ }
258
+
259
+ /** Async resolver for edit components */
260
+ export async function getEditComponentAsync(
261
+ name: string,
262
+ ): Promise<Component | undefined> {
263
+ const reg = componentRegistry[name]
264
+ if (!reg) return undefined
265
+ const mod = await reg.edit()
266
+ return mod.default
267
+ }
268
+
269
+ /** Get editor config for a component (icon, colors) */
270
+ export function getEditorConfig(name: string): EditorConfig | undefined {
271
+ return componentRegistry[name]?.editor
272
+ }
@@ -0,0 +1,32 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import { Icon } from '@iconify/vue'
4
+ import { alertStyles } from '@blogmcp/mdx-schema/styles'
5
+
6
+ interface Props {
7
+ severity?: 'info' | 'success' | 'warning' | 'error'
8
+ title?: string
9
+ }
10
+
11
+ const props = withDefaults(defineProps<Props>(), { severity: 'info' })
12
+
13
+ // severity → wrap classes + iconify icon + icon color shared with the React
14
+ // renderer via @blogmcp/mdx-schema/styles.
15
+ const cfg = computed(() => alertStyles.severity[props.severity])
16
+ </script>
17
+
18
+ <template>
19
+ <div
20
+ :class="[alertStyles.base, cfg.wrap]"
21
+ role="alert"
22
+ data-mdx-component="Alert"
23
+ >
24
+ <Icon :icon="cfg.icon" :class="['mt-0.5 shrink-0', cfg.iconClass]" :width="18" :height="18" />
25
+ <div class="min-w-0 flex-1">
26
+ <div v-if="title" class="mb-0.5 font-semibold">{{ title }}</div>
27
+ <div class="prose-sm">
28
+ <slot />
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </template>
@@ -0,0 +1,2 @@
1
+ export { default as Alert } from './Alert.vue'
2
+ export { default } from './Alert.vue'
@@ -0,0 +1,89 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * AudioPlayer Default Render Component
4
+ *
5
+ * Light theme with HTML5 audio, title, and collapsible transcript.
6
+ */
7
+
8
+ import { useAudioPlayer } from '../../composables/useAudioPlayer'
9
+ import type { AudioPlayerProps } from '@blogmcp/mdx-schema'
10
+
11
+ const props = defineProps<AudioPlayerProps>()
12
+
13
+ const {
14
+ audioUrl,
15
+ displayTitle,
16
+ hasTitle,
17
+ showTranscription,
18
+ transcriptionText,
19
+ transcriptionSummary,
20
+ transcriptionLoading,
21
+ transcriptionExpanded,
22
+ toggleTranscription,
23
+ } = useAudioPlayer(props)
24
+ </script>
25
+
26
+ <template>
27
+ <div class="audio-player my-6 rounded-lg bg-gray-50 p-5 border border-gray-200" data-mdx-component="AudioPlayer">
28
+ <!-- Title -->
29
+ <div class="flex items-center gap-3 mb-3">
30
+ <svg
31
+ class="w-5 h-5 text-gray-500 flex-shrink-0"
32
+ fill="none"
33
+ stroke="currentColor"
34
+ viewBox="0 0 24 24"
35
+ >
36
+ <path
37
+ stroke-linecap="round"
38
+ stroke-linejoin="round"
39
+ stroke-width="2"
40
+ d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z"
41
+ />
42
+ </svg>
43
+ <h3 v-if="hasTitle" class="text-base font-semibold text-gray-800 m-0">
44
+ {{ displayTitle }}
45
+ </h3>
46
+ </div>
47
+
48
+ <!-- Audio player -->
49
+ <audio
50
+ :src="audioUrl"
51
+ controls
52
+ preload="metadata"
53
+ class="w-full"
54
+ />
55
+
56
+ <!-- Collapsible transcription -->
57
+ <div v-if="showTranscription" class="mt-4">
58
+ <button
59
+ class="flex items-center gap-2 text-sm text-gray-600 hover:text-gray-900 transition-colors cursor-pointer bg-transparent border-0 p-0"
60
+ @click="toggleTranscription"
61
+ >
62
+ <svg
63
+ class="w-4 h-4 transition-transform"
64
+ :class="{ 'rotate-90': transcriptionExpanded }"
65
+ fill="none"
66
+ stroke="currentColor"
67
+ viewBox="0 0 24 24"
68
+ >
69
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
70
+ </svg>
71
+ Transcription
72
+ </button>
73
+ <div
74
+ v-if="transcriptionExpanded"
75
+ class="mt-2 p-3 bg-white rounded border border-gray-200 text-sm text-gray-700 leading-relaxed max-h-64 overflow-y-auto"
76
+ >
77
+ <p v-if="transcriptionLoading" class="text-gray-400 italic">Loading transcription...</p>
78
+ <p v-else-if="!transcriptionText" class="text-gray-400 italic">No transcription available.</p>
79
+ <template v-else>
80
+ <div v-if="transcriptionSummary" class="mb-3 pb-3 border-b border-gray-200">
81
+ <div class="text-xs uppercase tracking-wide text-gray-400 mb-1">Summary</div>
82
+ <div class="text-gray-800">{{ transcriptionSummary }}</div>
83
+ </div>
84
+ <div class="whitespace-pre-wrap">{{ transcriptionText }}</div>
85
+ </template>
86
+ </div>
87
+ </div>
88
+ </div>
89
+ </template>
@@ -0,0 +1,8 @@
1
+ /**
2
+ * AudioPlayer Render Component Variants
3
+ */
4
+
5
+ export { default as AudioPlayerDefault } from './AudioPlayerDefault.vue'
6
+
7
+ // Default export for convenience
8
+ export { default } from './AudioPlayerDefault.vue'
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import { badgeStyles } from '@blogmcp/mdx-schema/styles'
4
+
5
+ interface Props {
6
+ label: string
7
+ color?: 'primary' | 'neutral' | 'success' | 'warning' | 'error'
8
+ variant?: 'solid' | 'soft' | 'outline'
9
+ }
10
+
11
+ const props = withDefaults(defineProps<Props>(), { color: 'neutral', variant: 'soft' })
12
+
13
+ // Variant × color class table lives in @blogmcp/mdx-schema/styles (shared with
14
+ // the React renderer). Explicit entries so Tailwind JIT sees every class.
15
+ const classes = computed(() =>
16
+ [
17
+ badgeStyles.base,
18
+ badgeStyles.variantColor[props.variant][props.color],
19
+ ].join(' '),
20
+ )
21
+ </script>
22
+
23
+ <template>
24
+ <span :class="classes" data-mdx-component="Badge">{{ label }}</span>
25
+ </template>
@@ -0,0 +1,2 @@
1
+ export { default as Badge } from './Badge.vue'
2
+ export { default } from './Badge.vue'
@@ -0,0 +1,41 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import { buttonStyles } from '@blogmcp/mdx-schema/styles'
4
+ // URL sanitizer — returns 'about:blank' for javascript:/data:text/html/etc.
5
+ // Component-level prop sanitization: DOMPurify only sees HTML output, never
6
+ // JSX props consumed by render code. See:
7
+ // competitor-monitor/docs/internal/sanitization-trust-contract.md
8
+ import { sanitizeUrl } from '@blogmcp/sanitize'
9
+
10
+ interface Props {
11
+ label: string
12
+ to?: string
13
+ variant?: 'solid' | 'outline' | 'soft' | 'ghost' | 'link'
14
+ color?: 'primary' | 'neutral' | 'success' | 'warning' | 'error'
15
+ size?: 'sm' | 'md' | 'lg'
16
+ }
17
+
18
+ const props = withDefaults(defineProps<Props>(), {
19
+ variant: 'solid',
20
+ color: 'primary',
21
+ size: 'md',
22
+ })
23
+
24
+ const safeHref = computed(() => (props.to ? sanitizeUrl(props.to) : undefined))
25
+
26
+ // Variant × color × size class tables live in @blogmcp/mdx-schema/styles so
27
+ // the Vue and React renderers compose identical strings. Explicit (non-
28
+ // interpolated) entries so Tailwind's JIT sees every class at build time.
29
+ const classes = computed(() =>
30
+ [
31
+ buttonStyles.base,
32
+ buttonStyles.size[props.size],
33
+ buttonStyles.variantColor[props.variant][props.color],
34
+ ].join(' '),
35
+ )
36
+ </script>
37
+
38
+ <template>
39
+ <a v-if="safeHref" :href="safeHref" :class="classes" data-mdx-component="Button">{{ label }}</a>
40
+ <button v-else type="button" :class="classes" data-mdx-component="Button">{{ label }}</button>
41
+ </template>
@@ -0,0 +1,2 @@
1
+ export { default as Button } from './Button.vue'
2
+ export { default } from './Button.vue'
@@ -0,0 +1,101 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Callout Default Render Component
4
+ *
5
+ * Standard callout styling for blog display.
6
+ * Light theme optimized with accessible colors.
7
+ */
8
+
9
+ import { computed } from 'vue'
10
+ import type { CalloutProps, CalloutType } from '@blogmcp/mdx-schema'
11
+ import { calloutStyles } from '@blogmcp/mdx-schema/styles'
12
+
13
+ const props = defineProps<CalloutProps>()
14
+
15
+ const effectiveType = computed<CalloutType>(() => props.type || 'info')
16
+
17
+ // Per-type color table shared with the React renderer via
18
+ // @blogmcp/mdx-schema/styles (calloutStyles.default).
19
+ const colorClasses = computed(() => calloutStyles.default.type[effectiveType.value])
20
+ </script>
21
+
22
+ <template>
23
+ <div
24
+ :class="[calloutStyles.default.base, colorClasses.bg, colorClasses.border, colorClasses.text]"
25
+ data-mdx-component="Callout"
26
+ >
27
+ <div class="flex items-start gap-3">
28
+ <!-- Icon -->
29
+ <div class="flex-shrink-0 mt-0.5">
30
+ <!-- Info icon -->
31
+ <svg
32
+ v-if="effectiveType === 'info'"
33
+ class="w-5 h-5"
34
+ :class="colorClasses.iconColor"
35
+ fill="none"
36
+ stroke="currentColor"
37
+ viewBox="0 0 24 24"
38
+ >
39
+ <path
40
+ stroke-linecap="round"
41
+ stroke-linejoin="round"
42
+ stroke-width="2"
43
+ d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
44
+ />
45
+ </svg>
46
+ <!-- Warning icon -->
47
+ <svg
48
+ v-else-if="effectiveType === 'warning'"
49
+ class="w-5 h-5"
50
+ :class="colorClasses.iconColor"
51
+ fill="none"
52
+ stroke="currentColor"
53
+ viewBox="0 0 24 24"
54
+ >
55
+ <path
56
+ stroke-linecap="round"
57
+ stroke-linejoin="round"
58
+ stroke-width="2"
59
+ d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
60
+ />
61
+ </svg>
62
+ <!-- Tip icon -->
63
+ <svg
64
+ v-else-if="effectiveType === 'tip'"
65
+ class="w-5 h-5"
66
+ :class="colorClasses.iconColor"
67
+ fill="none"
68
+ stroke="currentColor"
69
+ viewBox="0 0 24 24"
70
+ >
71
+ <path
72
+ stroke-linecap="round"
73
+ stroke-linejoin="round"
74
+ stroke-width="2"
75
+ d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"
76
+ />
77
+ </svg>
78
+ <!-- Danger icon -->
79
+ <svg
80
+ v-else-if="effectiveType === 'danger'"
81
+ class="w-5 h-5"
82
+ :class="colorClasses.iconColor"
83
+ fill="none"
84
+ stroke="currentColor"
85
+ viewBox="0 0 24 24"
86
+ >
87
+ <path
88
+ stroke-linecap="round"
89
+ stroke-linejoin="round"
90
+ stroke-width="2"
91
+ d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
92
+ />
93
+ </svg>
94
+ </div>
95
+ <!-- Content -->
96
+ <div class="flex-1 text-sm leading-relaxed">
97
+ <slot />
98
+ </div>
99
+ </div>
100
+ </div>
101
+ </template>
@@ -0,0 +1,31 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Callout Minimal Render Component
4
+ *
5
+ * Simple callout styling without icons.
6
+ * Clean, minimal design for content-focused blogs.
7
+ */
8
+
9
+ import { computed } from 'vue'
10
+ import type { CalloutProps, CalloutType } from '@blogmcp/mdx-schema'
11
+ import { calloutStyles } from '@blogmcp/mdx-schema/styles'
12
+
13
+ const props = defineProps<CalloutProps>()
14
+
15
+ const effectiveType = computed<CalloutType>(() => props.type || 'info')
16
+
17
+ // Per-type color table shared with the React renderer via
18
+ // @blogmcp/mdx-schema/styles (calloutStyles.minimal).
19
+ const colorClasses = computed(() => calloutStyles.minimal.type[effectiveType.value])
20
+ </script>
21
+
22
+ <template>
23
+ <div
24
+ :class="[calloutStyles.minimal.base, colorClasses.bg, colorClasses.border, colorClasses.text]"
25
+ data-mdx-component="Callout"
26
+ >
27
+ <div class="text-sm leading-relaxed">
28
+ <slot />
29
+ </div>
30
+ </div>
31
+ </template>
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Callout Render Component Variants
3
+ */
4
+
5
+ export { default as CalloutDefault } from './CalloutDefault.vue'
6
+ export { default as CalloutMinimal } from './CalloutMinimal.vue'
7
+
8
+ // Default export for convenience
9
+ export { default } from './CalloutDefault.vue'
@@ -0,0 +1,15 @@
1
+ <script setup lang="ts">
2
+ interface Props {
3
+ title?: string
4
+ }
5
+ defineProps<Props>()
6
+ </script>
7
+
8
+ <template>
9
+ <section class="mdx-card rounded-lg border border-gray-200 bg-white p-5 shadow-sm" data-mdx-component="Card">
10
+ <h3 v-if="title" class="mb-3 text-lg font-semibold text-gray-900">{{ title }}</h3>
11
+ <div class="text-gray-700">
12
+ <slot />
13
+ </div>
14
+ </section>
15
+ </template>
@@ -0,0 +1,2 @@
1
+ export { default as Card } from './Card.vue'
2
+ export { default } from './Card.vue'