@getdom/studio 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 (213) hide show
  1. package/LICENSE.md +109 -0
  2. package/README.md +44 -0
  3. package/SKILL.md +83 -0
  4. package/package.json +73 -0
  5. package/public/llms.txt +57 -0
  6. package/public/skills.sh +11 -0
  7. package/src/pages/_layout/componentCatalog.generated.js +4514 -0
  8. package/src/pages/_layout/componentInspector.js +60 -0
  9. package/src/pages/_layout/componentManager.js +192 -0
  10. package/src/pages/_layout/componentRuntimeRecords.js +49 -0
  11. package/src/pages/_layout/inspector/componentRegistry.js +639 -0
  12. package/src/pages/_layout/inspector/serialize.js +134 -0
  13. package/src/pages/_layout/inspector/useInspector.js +293 -0
  14. package/src/pages/components/accordion/DomAccordion.vue +39 -0
  15. package/src/pages/components/breadcrumbs/DomBreadcrumbs.vue +347 -0
  16. package/src/pages/components/button/DomButton.vue +132 -0
  17. package/src/pages/components/command-palette/DomCommandPalette.vue +482 -0
  18. package/src/pages/components/context-menu/DomContextMenu.vue +178 -0
  19. package/src/pages/components/dialog/DomDialog.vue +271 -0
  20. package/src/pages/components/dialog/DomDialogSchemaForm.vue +72 -0
  21. package/src/pages/components/dialog/DomDialogStack.vue +289 -0
  22. package/src/pages/components/dialog/useDialogs.js +253 -0
  23. package/src/pages/components/drawer/DomDrawer.vue +159 -0
  24. package/src/pages/components/dropdown/DomDropdown.vue +261 -0
  25. package/src/pages/components/icon-button/DomIconButton.vue +155 -0
  26. package/src/pages/components/lightbox/DomLightbox.vue +1561 -0
  27. package/src/pages/components/menu/DomMenu.vue +359 -0
  28. package/src/pages/components/menu/DomMenuItem.vue +233 -0
  29. package/src/pages/components/menu/DomMenuLabel.vue +19 -0
  30. package/src/pages/components/menu/DomMenuSeparator.vue +14 -0
  31. package/src/pages/components/menu/menuContext.js +11 -0
  32. package/src/pages/components/menu/menuPlacement.js +26 -0
  33. package/src/pages/components/menu/submenu.js +58 -0
  34. package/src/pages/components/pagination/DomPagination.vue +265 -0
  35. package/src/pages/components/playground/DomRenderer.vue +48 -0
  36. package/src/pages/components/popover/DomPopover.vue +244 -0
  37. package/src/pages/components/profile-menu/DomProfileMenu.vue +408 -0
  38. package/src/pages/components/profile-menu/DomProfileMenuItemContent.vue +45 -0
  39. package/src/pages/components/scroll-menu/DomScrollMenu.vue +430 -0
  40. package/src/pages/components/scroll-menu/DomScrollMenuItem.vue +212 -0
  41. package/src/pages/components/scroll-menu/scrollMenuContext.js +22 -0
  42. package/src/pages/components/splitter-panel/DomSplitterPanel.vue +283 -0
  43. package/src/pages/components/stepper/DomStepper.vue +386 -0
  44. package/src/pages/components/tabs/DomTabs.vue +149 -0
  45. package/src/pages/components/theme-switcher/DomThemeSwitcher.vue +226 -0
  46. package/src/pages/components/toast/DomToastItem.vue +52 -0
  47. package/src/pages/components/toast/DomToastStack.vue +768 -0
  48. package/src/pages/components/toast/useToasts.js +237 -0
  49. package/src/pages/components/toolbar/DomToolbar.vue +158 -0
  50. package/src/pages/components/tooltip/DomTooltip.vue +69 -0
  51. package/src/pages/components/tooltip/vDomTooltip.js +106 -0
  52. package/src/pages/components/tree-view/DomTreeView.vue +961 -0
  53. package/src/pages/components/tree-view/treeSchemas.js +129 -0
  54. package/src/pages/components/tree-view/treeUtils.js +228 -0
  55. package/src/pages/data/_shared/calculateVirtualWindow.js +101 -0
  56. package/src/pages/data/_shared/resourceRecord.js +29 -0
  57. package/src/pages/data/activity-timeline/DomActivityTimeline.vue +417 -0
  58. package/src/pages/data/agent-chat/DomAgentChatShell.vue +565 -0
  59. package/src/pages/data/agent-chat/DomAgentConversation.vue +737 -0
  60. package/src/pages/data/agent-chat/DomAgentConversationList.vue +319 -0
  61. package/src/pages/data/agent-chat/examples/agentChatMockApi.js +620 -0
  62. package/src/pages/data/agent-chat/examples/useAgentChatDemoStore.js +730 -0
  63. package/src/pages/data/data-grid/DataGridVirtualRows.vue +220 -0
  64. package/src/pages/data/data-grid/DomDataGrid.vue +1643 -0
  65. package/src/pages/data/document-editor/DomDocumentEditor.vue +3030 -0
  66. package/src/pages/data/document-editor/documentEditorCodeBlock.js +282 -0
  67. package/src/pages/data/document-editor/examples/documentEditorMockApi.js +272 -0
  68. package/src/pages/data/document-editor/examples/useDocumentEditorDemoStore.js +284 -0
  69. package/src/pages/data/filter-bar/DomFilterBar.vue +533 -0
  70. package/src/pages/data/kanban/DomKanban.vue +1703 -0
  71. package/src/pages/data/kanban/kanbanApi.js +80 -0
  72. package/src/pages/data/media-browser/DomMediaBrowser.vue +2344 -0
  73. package/src/pages/data/media-browser/examples/mediaBrowserMockApi.js +653 -0
  74. package/src/pages/data/media-browser/examples/useMediaBrowserDemoStore.js +1157 -0
  75. package/src/pages/data/month-calendar/DomMonthCalendar.vue +1718 -0
  76. package/src/pages/data/week-planner/DomWeekPlanner.vue +2308 -0
  77. package/src/pages/dev/code-block/DomCodeBlock.vue +263 -0
  78. package/src/pages/dev/json-viewer/DomJsonViewer.vue +962 -0
  79. package/src/pages/forms/_shared/DomFieldLoadingSpinner.vue +12 -0
  80. package/src/pages/forms/_shared/options.js +61 -0
  81. package/src/pages/forms/_shared/useOptionMenu.js +59 -0
  82. package/src/pages/forms/autocomplete/DomAutocomplete.vue +180 -0
  83. package/src/pages/forms/calendar/DomCalendar.vue +442 -0
  84. package/src/pages/forms/checkbox/DomCheckbox.vue +69 -0
  85. package/src/pages/forms/class-toggle-input/DomClassToggleInput.vue +316 -0
  86. package/src/pages/forms/code-input/DomCodeInput.vue +202 -0
  87. package/src/pages/forms/color-input/DomColorInput.vue +118 -0
  88. package/src/pages/forms/combobox/DomCombobox.vue +237 -0
  89. package/src/pages/forms/confirmation-code-input/DomConfirmationCodeInput.vue +345 -0
  90. package/src/pages/forms/date-picker/DomDatePicker.vue +299 -0
  91. package/src/pages/forms/date-time-picker/DomDateTimePicker.vue +630 -0
  92. package/src/pages/forms/email-input/DomEmailInput.vue +55 -0
  93. package/src/pages/forms/field/DomField.vue +267 -0
  94. package/src/pages/forms/field/fieldLayout.js +1 -0
  95. package/src/pages/forms/field/fieldProps.js +114 -0
  96. package/src/pages/forms/field/layouts/DomGridFieldLayout.vue +38 -0
  97. package/src/pages/forms/field/useField.js +343 -0
  98. package/src/pages/forms/field/validators.js +364 -0
  99. package/src/pages/forms/file-upload/DomFileUpload.vue +361 -0
  100. package/src/pages/forms/form/DomForm.vue +985 -0
  101. package/src/pages/forms/form/adapters/fieldBuilder.js +45 -0
  102. package/src/pages/forms/form/adapters/jsonSchema.js +264 -0
  103. package/src/pages/forms/form/adapters/shared.js +53 -0
  104. package/src/pages/forms/form/adapters/zod.js +235 -0
  105. package/src/pages/forms/form/formApi.js +38 -0
  106. package/src/pages/forms/form/formDefinition.js +392 -0
  107. package/src/pages/forms/form/schemaAdapter.js +54 -0
  108. package/src/pages/forms/icon-selector/DomIconSelector.vue +460 -0
  109. package/src/pages/forms/icon-selector/iconSelectorItems.js +490 -0
  110. package/src/pages/forms/input-group/DomInputGroup.vue +232 -0
  111. package/src/pages/forms/json-input/DomJsonInput.vue +227 -0
  112. package/src/pages/forms/json-list-input/DomJsonListInput.vue +206 -0
  113. package/src/pages/forms/listbox/DomListbox.vue +105 -0
  114. package/src/pages/forms/money-input/DomMoneyInput.vue +147 -0
  115. package/src/pages/forms/native-select/DomNativeSelect.vue +67 -0
  116. package/src/pages/forms/number-input/DomNumberInput.vue +47 -0
  117. package/src/pages/forms/password-input/DomPasswordInput.vue +218 -0
  118. package/src/pages/forms/password-input/pwnedPasswords.js +47 -0
  119. package/src/pages/forms/password-strength/DomPasswordStrength.vue +128 -0
  120. package/src/pages/forms/password-strength/passwordStrength.js +92 -0
  121. package/src/pages/forms/position-input/DomPositionInput.vue +83 -0
  122. package/src/pages/forms/radio-group/DomRadioGroup.vue +102 -0
  123. package/src/pages/forms/range-input/DomRangeInput.vue +296 -0
  124. package/src/pages/forms/select/DomSelect.vue +479 -0
  125. package/src/pages/forms/select-input/DomSelectInput.vue +155 -0
  126. package/src/pages/forms/tag-combobox/DomTagCombobox.vue +358 -0
  127. package/src/pages/forms/text-input/DomTextInput.vue +48 -0
  128. package/src/pages/forms/textarea-composer/DomTextareaComposer.vue +234 -0
  129. package/src/pages/forms/textarea-input/DomTextareaInput.vue +86 -0
  130. package/src/pages/forms/textarea-input/useTextareaAutosize.js +147 -0
  131. package/src/pages/forms/time-selector/DomTimeSelector.vue +389 -0
  132. package/src/pages/forms/time-selector/timeUtils.js +194 -0
  133. package/src/pages/forms/toggle/DomToggle.vue +158 -0
  134. package/src/pages/forms/toggle-button/DomToggleButton.vue +88 -0
  135. package/src/pages/forms/toggle-button-group/DomToggleButtonGroup.vue +340 -0
  136. package/src/pages/forms/url-input/DomUrlInput.vue +55 -0
  137. package/src/pages/forms/wysiwyg/DomWysiwyg.vue +204 -0
  138. package/src/pages/lib/css/color.js +245 -0
  139. package/src/pages/lib/headless/accordion.d.ts +7 -0
  140. package/src/pages/lib/headless/accordion.js +246 -0
  141. package/src/pages/lib/headless/autocomplete.d.ts +4 -0
  142. package/src/pages/lib/headless/autocomplete.js +33 -0
  143. package/src/pages/lib/headless/base.d.ts +25 -0
  144. package/src/pages/lib/headless/base.js +127 -0
  145. package/src/pages/lib/headless/checkbox.d.ts +19 -0
  146. package/src/pages/lib/headless/checkbox.js +99 -0
  147. package/src/pages/lib/headless/combobox.d.ts +7 -0
  148. package/src/pages/lib/headless/combobox.js +488 -0
  149. package/src/pages/lib/headless/dialog.d.ts +23 -0
  150. package/src/pages/lib/headless/dialog.js +384 -0
  151. package/src/pages/lib/headless/drawer.d.ts +21 -0
  152. package/src/pages/lib/headless/drawer.js +682 -0
  153. package/src/pages/lib/headless/dropdown.d.ts +19 -0
  154. package/src/pages/lib/headless/dropdown.js +412 -0
  155. package/src/pages/lib/headless/floating-effect.js +118 -0
  156. package/src/pages/lib/headless/floating.d.ts +34 -0
  157. package/src/pages/lib/headless/floating.js +382 -0
  158. package/src/pages/lib/headless/index.d.ts +19 -0
  159. package/src/pages/lib/headless/index.js +19 -0
  160. package/src/pages/lib/headless/listbox.d.ts +19 -0
  161. package/src/pages/lib/headless/listbox.js +126 -0
  162. package/src/pages/lib/headless/menu.d.ts +18 -0
  163. package/src/pages/lib/headless/menu.js +117 -0
  164. package/src/pages/lib/headless/popover-panel.d.ts +18 -0
  165. package/src/pages/lib/headless/popover-panel.js +309 -0
  166. package/src/pages/lib/headless/popover.d.ts +19 -0
  167. package/src/pages/lib/headless/popover.js +391 -0
  168. package/src/pages/lib/headless/radio-group.d.ts +19 -0
  169. package/src/pages/lib/headless/radio-group.js +100 -0
  170. package/src/pages/lib/headless/tabs.d.ts +18 -0
  171. package/src/pages/lib/headless/tabs.js +119 -0
  172. package/src/pages/lib/headless/toast.d.ts +13 -0
  173. package/src/pages/lib/headless/toast.js +65 -0
  174. package/src/pages/lib/headless/toggle.d.ts +19 -0
  175. package/src/pages/lib/headless/toggle.js +162 -0
  176. package/src/pages/lib/headless/tooltip-controller.d.ts +22 -0
  177. package/src/pages/lib/headless/tooltip-controller.js +549 -0
  178. package/src/pages/lib/headless/tooltip.d.ts +19 -0
  179. package/src/pages/lib/headless/tooltip.js +153 -0
  180. package/src/pages/lib/headless/transitions.d.ts +5 -0
  181. package/src/pages/lib/headless/transitions.js +89 -0
  182. package/src/pages/lib/vue/_internal/DomLiquidGlassSurface.vue +310 -0
  183. package/src/pages/lib/vue/_internal/liquidGlass.js +112 -0
  184. package/src/pages/lib/vue/_internal/liquidGlassConfig.js +10 -0
  185. package/src/pages/lib/vue/codeHighlighter.js +132 -0
  186. package/src/pages/lib/vue/floatingTeleportTarget.js +23 -0
  187. package/src/pages/lib/vue/index.d.ts +6806 -0
  188. package/src/pages/lib/vue/index.js +218 -0
  189. package/src/pages/lib/vue/useTheme.js +116 -0
  190. package/src/pages/mobile/action-sheet/DomActionSheet.vue +186 -0
  191. package/src/pages/mobile/bottom-nav/DomAppBottomNav.vue +94 -0
  192. package/src/pages/mobile/list-item/DomAppListItem.vue +90 -0
  193. package/src/pages/mobile/safe-area/DomSafeArea.vue +39 -0
  194. package/src/pages/mobile/shell/DomAppShell.vue +51 -0
  195. package/src/pages/mobile/stack/AppStackScreen.vue +63 -0
  196. package/src/pages/mobile/stack/DomAppStack.vue +711 -0
  197. package/src/pages/mobile/top-bar/DomAppTopBar.vue +67 -0
  198. package/src/pages/visual/alert/DomAlert.vue +363 -0
  199. package/src/pages/visual/avatar/DomAvatar.vue +110 -0
  200. package/src/pages/visual/avatar-stack/DomAvatarStack.vue +89 -0
  201. package/src/pages/visual/badge/DomBadge.vue +105 -0
  202. package/src/pages/visual/card/DomCard.vue +65 -0
  203. package/src/pages/visual/card-rail/DomCardRail.vue +99 -0
  204. package/src/pages/visual/empty-state/DomEmptyState.vue +88 -0
  205. package/src/pages/visual/progress/DomProgress.vue +212 -0
  206. package/src/pages/visual/progress/DomRadialProgress.vue +186 -0
  207. package/src/pages/visual/progress/DomSegmentedProgress.vue +350 -0
  208. package/src/pages/visual/skeleton/DomSkeleton.vue +174 -0
  209. package/src/pages/visual/spinner/DomSpinner.vue +122 -0
  210. package/src/pages/visual/status-pill/DomStatusPill.vue +75 -0
  211. package/src/pages/visual/text-diff/DomTextDiff.vue +577 -0
  212. package/src/pages/visual/text-diff/textDiff.js +300 -0
  213. package/src/style.css +446 -0
@@ -0,0 +1,263 @@
1
+ <script setup>
2
+ import { Comment, Fragment, Text, computed, onMounted, ref, useSlots, watch } from 'vue';
3
+ import { highlight } from '../../lib/vue/codeHighlighter.js';
4
+
5
+ const themeModeValues = ['inherit', 'light', 'dark'];
6
+
7
+ defineOptions({
8
+ __doc: {
9
+ name: 'Code block',
10
+ tag: '<DomCodeBlock>',
11
+ description: 'A syntax-highlighted code block for examples, API snippets, and generated source previews.',
12
+ icon: 'M8 9l-4 3 4 3M16 9l4 3-4 3M14 5l-4 14',
13
+ playground: {
14
+ initial: {
15
+ code: `import { DomCodeBlock } from '@getdom/studio/vue';
16
+
17
+ const source = '<DomButton>Save</DomButton>';`,
18
+ lang: 'js',
19
+ framed: true,
20
+ previewLines: 0,
21
+ },
22
+ },
23
+ },
24
+ });
25
+
26
+ const props = defineProps({
27
+ lang: {
28
+ type: String,
29
+ default: 'vue',
30
+ _edit: {
31
+ options: ['vue', 'html', 'js', 'ts', 'css', 'json', 'md', 'python', 'bash', 'sh', 'sql', 'txt'],
32
+ description: 'Language passed to the syntax highlighter.',
33
+ },
34
+ },
35
+ code: {
36
+ type: String,
37
+ default: '',
38
+ _edit: {
39
+ component: 'DomCodeInput',
40
+ props: { rows: 8, editor: false },
41
+ description: 'Code to render. If empty, default slot content is serialized.',
42
+ },
43
+ },
44
+ filename: {
45
+ type: String,
46
+ default: '',
47
+ _edit: { description: 'Optional source filename for labels and wrappers.' },
48
+ },
49
+ previewLines: {
50
+ type: Number,
51
+ default: 0,
52
+ _edit: { description: 'Maximum visible lines before the block scrolls. Use 0 for no cap.' },
53
+ },
54
+ framed: {
55
+ type: Boolean,
56
+ default: true,
57
+ _edit: { description: 'Render the standard border, radius, and canvas background.' },
58
+ },
59
+ themeMode: {
60
+ type: String,
61
+ default: 'inherit',
62
+ _edit: {
63
+ options: ['inherit', 'light', 'dark'],
64
+ description: 'Force a light or dark code theme, or inherit the surrounding application theme.',
65
+ },
66
+ },
67
+ });
68
+
69
+ const slots = useSlots();
70
+ const displayCode = computed(() => props.code || serializeNodes(slots.default?.() || []).trim());
71
+ const resolvedThemeMode = computed(resolveThemeMode);
72
+ const highlighted = ref(null);
73
+ const codeStyle = computed(() => {
74
+ if (!props.previewLines) return {};
75
+ return { maxHeight: `${Math.round((props.previewLines * 20.625) + 32)}px` };
76
+ });
77
+
78
+ /**
79
+ * Refreshes highlighted HTML when the source text or language changes.
80
+ *
81
+ * @returns {Promise<void>} Resolves after the highlighted output is stored.
82
+ */
83
+ async function refreshHighlight() {
84
+ highlighted.value = await highlight(displayCode.value, props.lang, resolvedThemeMode.value);
85
+ }
86
+
87
+ onMounted(refreshHighlight);
88
+ watch(() => [displayCode.value, props.lang, resolvedThemeMode.value], refreshHighlight);
89
+
90
+ /**
91
+ * Normalizes the requested local theme mode.
92
+ *
93
+ * @returns {'inherit' | 'light' | 'dark'} Supported local theme mode.
94
+ */
95
+ function resolveThemeMode() {
96
+ return themeModeValues.includes(props.themeMode) ? props.themeMode : 'inherit';
97
+ }
98
+
99
+ /**
100
+ * Escapes text content for safe HTML serialization.
101
+ *
102
+ * @param {unknown} value Raw text-like value.
103
+ * @returns {string} Escaped text.
104
+ */
105
+ function escapeText(value) {
106
+ return String(value)
107
+ .replace(/&/g, '&amp;')
108
+ .replace(/</g, '&lt;')
109
+ .replace(/>/g, '&gt;');
110
+ }
111
+
112
+ /**
113
+ * Escapes an attribute value for safe HTML serialization.
114
+ *
115
+ * @param {unknown} value Raw attribute value.
116
+ * @returns {string} Escaped attribute value.
117
+ */
118
+ function escapeAttribute(value) {
119
+ return escapeText(value).replace(/"/g, '&quot;');
120
+ }
121
+
122
+ /**
123
+ * Normalizes Vue class bindings into a static class string.
124
+ *
125
+ * @param {string|Array|object|unknown} value Vue class binding value.
126
+ * @returns {string} Serialized class list.
127
+ */
128
+ function normalizeClass(value) {
129
+ if (!value) return '';
130
+ if (typeof value === 'string') return value;
131
+ if (Array.isArray(value)) return value.map(normalizeClass).filter(Boolean).join(' ');
132
+ if (typeof value === 'object') {
133
+ return Object.entries(value)
134
+ .filter(([, active]) => active)
135
+ .map(([name]) => name)
136
+ .join(' ');
137
+ }
138
+ return String(value);
139
+ }
140
+
141
+ /**
142
+ * Normalizes Vue style bindings into a static style string.
143
+ *
144
+ * @param {string|object|unknown} value Vue style binding value.
145
+ * @returns {string} Serialized inline style.
146
+ */
147
+ function normalizeStyle(value) {
148
+ if (!value) return '';
149
+ if (typeof value === 'string') return value;
150
+ if (typeof value === 'object') {
151
+ return Object.entries(value)
152
+ .map(([name, styleValue]) => `${name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}: ${styleValue}`)
153
+ .join('; ');
154
+ }
155
+ return String(value);
156
+ }
157
+
158
+ /**
159
+ * Serializes Vue vnode props into HTML attributes.
160
+ *
161
+ * @param {Record<string, unknown>} props Raw vnode props.
162
+ * @returns {string} Serialized attributes.
163
+ */
164
+ function serializeProps(props = {}) {
165
+ return Object.entries(props)
166
+ .filter(([name, value]) => (
167
+ value !== false
168
+ && value != null
169
+ && typeof value !== 'function'
170
+ && !['key', 'ref', 'ref_for', 'ref_key'].includes(name)
171
+ && !name.startsWith('onVnode')
172
+ ))
173
+ .map(([name, value]) => {
174
+ if (name === 'class') value = normalizeClass(value);
175
+ if (name === 'style') value = normalizeStyle(value);
176
+ if (value === true || value === '') return name;
177
+ return `${name}="${escapeAttribute(value)}"`;
178
+ })
179
+ .filter(Boolean)
180
+ .join(' ');
181
+ }
182
+
183
+ /**
184
+ * Resolves a Vue vnode type into a readable tag name.
185
+ *
186
+ * @param {string|object} type Vue vnode type.
187
+ * @returns {string} Serialized tag name.
188
+ */
189
+ function serializeType(type) {
190
+ if (typeof type === 'string') return type;
191
+ return type?.name || type?.__name || 'component';
192
+ }
193
+
194
+ /**
195
+ * Serializes vnode children into an HTML string.
196
+ *
197
+ * @param {string|Array|object} children Raw vnode children.
198
+ * @param {number} depth Current indentation depth.
199
+ * @returns {string} Serialized child content.
200
+ */
201
+ function serializeChildren(children, depth) {
202
+ if (typeof children === 'string') return escapeText(children);
203
+ if (Array.isArray(children)) return serializeNodes(children, depth);
204
+ if (typeof children === 'object' && children?.default) return serializeNodes(children.default(), depth);
205
+ return '';
206
+ }
207
+
208
+ /**
209
+ * Serializes a Vue vnode into a readable HTML snippet.
210
+ *
211
+ * @param {object|string} node Vue vnode or text node.
212
+ * @param {number} depth Current indentation depth.
213
+ * @returns {string} Serialized node markup.
214
+ */
215
+ function serializeNode(node, depth = 0) {
216
+ if (!node) return '';
217
+ if (typeof node === 'string') return `${'\t'.repeat(depth)}${escapeText(node)}`;
218
+ if (node.type === Text) return `${'\t'.repeat(depth)}${escapeText(node.children || '')}`;
219
+ if (node.type === Comment) return '';
220
+ if (node.type === Fragment) return serializeNodes(node.children || [], depth);
221
+
222
+ const tag = serializeType(node.type);
223
+ const serializedProps = serializeProps(node.props);
224
+ const open = serializedProps ? `<${tag} ${serializedProps}>` : `<${tag}>`;
225
+ const children = serializeChildren(node.children, depth + 1);
226
+ const indent = '\t'.repeat(depth);
227
+
228
+ if (!children) return `${indent}${open}</${tag}>`;
229
+ if (!children.includes('\n') && !children.trim().startsWith('<')) return `${indent}${open}${children}</${tag}>`;
230
+ return `${indent}${open}\n${children}\n${indent}</${tag}>`;
231
+ }
232
+
233
+ /**
234
+ * Serializes a list of Vue vnodes into a readable HTML snippet.
235
+ *
236
+ * @param {Array} nodes Vue vnodes.
237
+ * @param {number} depth Current indentation depth.
238
+ * @returns {string} Serialized node list.
239
+ */
240
+ function serializeNodes(nodes, depth = 0) {
241
+ return nodes
242
+ .map((node) => serializeNode(node, depth))
243
+ .filter(Boolean)
244
+ .join('\n');
245
+ }
246
+ </script>
247
+
248
+ <template>
249
+ <div
250
+ class="dom-code relative w-full min-w-0 max-w-full overflow-auto"
251
+ :class="framed ? 'rounded-2xl border border-border bg-canvas' : ''"
252
+ :style="codeStyle"
253
+ :data-theme="resolvedThemeMode === 'inherit' ? undefined : resolvedThemeMode"
254
+ :data-language="lang || undefined"
255
+ :data-filename="filename || undefined"
256
+ >
257
+ <span class="pointer-events-none absolute right-3 top-1 z-10 font-mono text-[9px] font-medium uppercase leading-none tracking-wider text-muted-fg">
258
+ {{ lang || 'text' }}
259
+ </span>
260
+ <div v-if="highlighted" class="dom-shiki" v-html="highlighted"></div>
261
+ <pre v-else class="min-w-0 max-w-full overflow-auto bg-canvas p-4 font-mono text-[12.5px] leading-relaxed text-canvas-fg"><code>{{ displayCode }}</code></pre>
262
+ </div>
263
+ </template>