@mdxui/terminal 2.0.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.
- package/README.md +571 -0
- package/dist/ansi-css-Sk5mWtdK.d.ts +119 -0
- package/dist/ansi-css-V6JIHGsM.d.ts +119 -0
- package/dist/ansi-css-_3eSEU9d.d.ts +119 -0
- package/dist/chunk-3EFDH7PK.js +5235 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-3X5IR6WE.js +884 -0
- package/dist/chunk-4FV5ZDCE.js +5236 -0
- package/dist/chunk-4OVMSF2J.js +243 -0
- package/dist/chunk-63FEETIS.js +4048 -0
- package/dist/chunk-B43KP7XJ.js +884 -0
- package/dist/chunk-BMTJXWUV.js +655 -0
- package/dist/chunk-C3SVH4N7.js +882 -0
- package/dist/chunk-EVWR7Y47.js +874 -0
- package/dist/chunk-F6A5VWUC.js +1285 -0
- package/dist/chunk-FD7KW7GE.js +882 -0
- package/dist/chunk-GBQ6UD6I.js +655 -0
- package/dist/chunk-GMDD3M6U.js +5227 -0
- package/dist/chunk-JBHRXOXM.js +1058 -0
- package/dist/chunk-JFOO3EYO.js +1182 -0
- package/dist/chunk-JQ5H3WXL.js +1291 -0
- package/dist/chunk-JQD5NASE.js +234 -0
- package/dist/chunk-KRHJP5R7.js +592 -0
- package/dist/chunk-KWF6WVJE.js +962 -0
- package/dist/chunk-LHYQVN3H.js +1038 -0
- package/dist/chunk-M3TLQLGC.js +1032 -0
- package/dist/chunk-MVW4Q5OP.js +240 -0
- package/dist/chunk-NXCZSWLU.js +1294 -0
- package/dist/chunk-O25TNRO6.js +607 -0
- package/dist/chunk-PNECDA2I.js +884 -0
- package/dist/chunk-QIHWRLJR.js +962 -0
- package/dist/chunk-QW5YMQ7K.js +882 -0
- package/dist/chunk-R5U7XKVJ.js +16 -0
- package/dist/chunk-RP2MVQLR.js +962 -0
- package/dist/chunk-TP6RXGXA.js +1087 -0
- package/dist/chunk-TQQSTITZ.js +655 -0
- package/dist/chunk-X24GWXQV.js +1281 -0
- package/dist/components/index.d.ts +802 -0
- package/dist/components/index.js +149 -0
- package/dist/data/index.d.ts +2554 -0
- package/dist/data/index.js +51 -0
- package/dist/forms/index.d.ts +1596 -0
- package/dist/forms/index.js +464 -0
- package/dist/index-CQRFZntR.d.ts +867 -0
- package/dist/index.d.ts +579 -0
- package/dist/index.js +786 -0
- package/dist/interactive-D0JkWosD.d.ts +217 -0
- package/dist/keyboard/index.d.ts +2 -0
- package/dist/keyboard/index.js +43 -0
- package/dist/renderers/index.d.ts +546 -0
- package/dist/renderers/index.js +2157 -0
- package/dist/storybook/index.d.ts +396 -0
- package/dist/storybook/index.js +641 -0
- package/dist/theme/index.d.ts +1339 -0
- package/dist/theme/index.js +123 -0
- package/dist/types-Bxu5PAgA.d.ts +710 -0
- package/dist/types-CIlop5Ji.d.ts +701 -0
- package/dist/types-Ca8p_p5X.d.ts +710 -0
- package/package.json +90 -0
- package/src/__tests__/components/data/card.test.ts +458 -0
- package/src/__tests__/components/data/list.test.ts +473 -0
- package/src/__tests__/components/data/metrics.test.ts +541 -0
- package/src/__tests__/components/data/table.test.ts +448 -0
- package/src/__tests__/components/input/field.test.ts +555 -0
- package/src/__tests__/components/input/form.test.ts +870 -0
- package/src/__tests__/components/input/search.test.ts +1238 -0
- package/src/__tests__/components/input/select.test.ts +658 -0
- package/src/__tests__/components/navigation/breadcrumb.test.ts +923 -0
- package/src/__tests__/components/navigation/command-palette.test.ts +1095 -0
- package/src/__tests__/components/navigation/sidebar.test.ts +1018 -0
- package/src/__tests__/components/navigation/tabs.test.ts +995 -0
- package/src/__tests__/components.test.tsx +1197 -0
- package/src/__tests__/core/compiler.test.ts +986 -0
- package/src/__tests__/core/parser.test.ts +785 -0
- package/src/__tests__/core/tier-switcher.test.ts +1103 -0
- package/src/__tests__/core/types.test.ts +1398 -0
- package/src/__tests__/data/collections.test.ts +1337 -0
- package/src/__tests__/data/db.test.ts +1265 -0
- package/src/__tests__/data/reactive.test.ts +1010 -0
- package/src/__tests__/data/sync.test.ts +1614 -0
- package/src/__tests__/errors.test.ts +660 -0
- package/src/__tests__/forms/integration.test.ts +444 -0
- package/src/__tests__/integration.test.ts +905 -0
- package/src/__tests__/keyboard.test.ts +1791 -0
- package/src/__tests__/renderer.test.ts +489 -0
- package/src/__tests__/renderers/ansi-css.test.ts +948 -0
- package/src/__tests__/renderers/ansi.test.ts +1366 -0
- package/src/__tests__/renderers/ascii.test.ts +1360 -0
- package/src/__tests__/renderers/interactive.test.ts +2353 -0
- package/src/__tests__/renderers/markdown.test.ts +1483 -0
- package/src/__tests__/renderers/text.test.ts +1369 -0
- package/src/__tests__/renderers/unicode.test.ts +1307 -0
- package/src/__tests__/theme.test.ts +639 -0
- package/src/__tests__/utils/assertions.ts +685 -0
- package/src/__tests__/utils/index.ts +115 -0
- package/src/__tests__/utils/test-renderer.ts +381 -0
- package/src/__tests__/utils/utils.test.ts +560 -0
- package/src/components/containers/card.ts +56 -0
- package/src/components/containers/dialog.ts +53 -0
- package/src/components/containers/index.ts +9 -0
- package/src/components/containers/panel.ts +59 -0
- package/src/components/feedback/badge.ts +40 -0
- package/src/components/feedback/index.ts +8 -0
- package/src/components/feedback/spinner.ts +23 -0
- package/src/components/helpers.ts +81 -0
- package/src/components/index.ts +153 -0
- package/src/components/layout/breadcrumb.ts +31 -0
- package/src/components/layout/index.ts +10 -0
- package/src/components/layout/list.ts +29 -0
- package/src/components/layout/sidebar.ts +79 -0
- package/src/components/layout/table.ts +62 -0
- package/src/components/primitives/box.ts +95 -0
- package/src/components/primitives/button.ts +54 -0
- package/src/components/primitives/index.ts +11 -0
- package/src/components/primitives/input.ts +88 -0
- package/src/components/primitives/select.ts +97 -0
- package/src/components/primitives/text.ts +60 -0
- package/src/components/render.ts +155 -0
- package/src/components/templates/app.ts +43 -0
- package/src/components/templates/index.ts +8 -0
- package/src/components/templates/site.ts +54 -0
- package/src/components/types.ts +777 -0
- package/src/core/compiler.ts +718 -0
- package/src/core/parser.ts +127 -0
- package/src/core/tier-switcher.ts +607 -0
- package/src/core/types.ts +672 -0
- package/src/data/collection.ts +316 -0
- package/src/data/collections.ts +50 -0
- package/src/data/context.tsx +174 -0
- package/src/data/db.ts +127 -0
- package/src/data/hooks.ts +532 -0
- package/src/data/index.ts +138 -0
- package/src/data/reactive.ts +1225 -0
- package/src/data/saas-collections.ts +375 -0
- package/src/data/sync.ts +1213 -0
- package/src/data/types.ts +660 -0
- package/src/forms/converters.ts +512 -0
- package/src/forms/index.ts +133 -0
- package/src/forms/schemas.ts +403 -0
- package/src/forms/types.ts +476 -0
- package/src/index.ts +542 -0
- package/src/keyboard/focus.ts +748 -0
- package/src/keyboard/index.ts +96 -0
- package/src/keyboard/integration.ts +371 -0
- package/src/keyboard/manager.ts +377 -0
- package/src/keyboard/presets.ts +90 -0
- package/src/renderers/ansi-css.ts +576 -0
- package/src/renderers/ansi.ts +802 -0
- package/src/renderers/ascii.ts +680 -0
- package/src/renderers/breadcrumb.ts +480 -0
- package/src/renderers/command-palette.ts +802 -0
- package/src/renderers/components/field.ts +210 -0
- package/src/renderers/components/form.ts +327 -0
- package/src/renderers/components/index.ts +21 -0
- package/src/renderers/components/search.ts +449 -0
- package/src/renderers/components/select.ts +222 -0
- package/src/renderers/index.ts +101 -0
- package/src/renderers/interactive/component-handlers.ts +622 -0
- package/src/renderers/interactive/cursor-manager.ts +147 -0
- package/src/renderers/interactive/focus-manager.ts +279 -0
- package/src/renderers/interactive/index.ts +661 -0
- package/src/renderers/interactive/input-handler.ts +164 -0
- package/src/renderers/interactive/keyboard-handler.ts +212 -0
- package/src/renderers/interactive/mouse-handler.ts +167 -0
- package/src/renderers/interactive/state-manager.ts +109 -0
- package/src/renderers/interactive/types.ts +338 -0
- package/src/renderers/interactive-string.ts +299 -0
- package/src/renderers/interactive.ts +59 -0
- package/src/renderers/markdown.ts +950 -0
- package/src/renderers/sidebar.ts +549 -0
- package/src/renderers/tabs.ts +682 -0
- package/src/renderers/text.ts +791 -0
- package/src/renderers/unicode.ts +917 -0
- package/src/renderers/utils.ts +942 -0
- package/src/router/adapters.ts +383 -0
- package/src/router/types.ts +140 -0
- package/src/router/utils.ts +452 -0
- package/src/schemas.ts +205 -0
- package/src/storybook/index.ts +91 -0
- package/src/storybook/interactive-decorator.tsx +659 -0
- package/src/storybook/keyboard-simulator.ts +501 -0
- package/src/theme/ansi-codes.ts +80 -0
- package/src/theme/box-drawing.ts +132 -0
- package/src/theme/color-convert.ts +254 -0
- package/src/theme/color-support.ts +321 -0
- package/src/theme/index.ts +134 -0
- package/src/theme/strip-ansi.ts +50 -0
- package/src/theme/tailwind-map.ts +469 -0
- package/src/theme/text-styles.ts +206 -0
- package/src/theme/theme-system.ts +568 -0
- package/src/types.ts +103 -0
|
@@ -0,0 +1,791 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text Renderer - Plain text output without formatting
|
|
3
|
+
*
|
|
4
|
+
* The TEXT renderer is the first tier in the Universal Terminal UI's
|
|
5
|
+
* multi-tier rendering architecture. It outputs plain text without any
|
|
6
|
+
* formatting markers, colors, or special characters.
|
|
7
|
+
*
|
|
8
|
+
* Text Tier (tier 1 of 6):
|
|
9
|
+
* - Simple content only, no bold/italic/code markers
|
|
10
|
+
* - Tables rendered as key=value lines (one per line)
|
|
11
|
+
* - Lists rendered as bullet points (- item)
|
|
12
|
+
* - Metrics rendered as "Label: value" format
|
|
13
|
+
* - Nested structures indented (2 spaces per level)
|
|
14
|
+
* - Dashboard layout as labeled sections with separators
|
|
15
|
+
* - All special characters and formatting stripped
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { UINode } from '../core/types'
|
|
19
|
+
import { getIndentStr, extractStringArray, extractHeaders, extractRowValues, joinParts, getProp } from './utils'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Text render options
|
|
23
|
+
*/
|
|
24
|
+
export interface TextRenderOptions {
|
|
25
|
+
/** Current indentation level */
|
|
26
|
+
indent?: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Renders a UINode tree to a plain text string.
|
|
31
|
+
*
|
|
32
|
+
* @param node - The UINode tree to render
|
|
33
|
+
* @param options - Optional rendering configuration
|
|
34
|
+
* @returns Plain text string
|
|
35
|
+
*/
|
|
36
|
+
export function renderText(node: UINode, options: TextRenderOptions = {}): string {
|
|
37
|
+
const indent = options.indent ?? 0
|
|
38
|
+
const indentStr = getIndentStr(indent)
|
|
39
|
+
|
|
40
|
+
const type = node.type
|
|
41
|
+
const props = node.props || {}
|
|
42
|
+
// Normalize children: string becomes a text node array
|
|
43
|
+
const rawChildren = node.children
|
|
44
|
+
const children: UINode[] = typeof rawChildren === 'string'
|
|
45
|
+
? [{ type: 'text', props: { content: rawChildren } }]
|
|
46
|
+
: rawChildren || []
|
|
47
|
+
|
|
48
|
+
switch (type) {
|
|
49
|
+
case 'text':
|
|
50
|
+
return renderTextNode(props)
|
|
51
|
+
|
|
52
|
+
case 'header':
|
|
53
|
+
return renderHeader(props)
|
|
54
|
+
|
|
55
|
+
case 'list':
|
|
56
|
+
return renderList(props, children, indent)
|
|
57
|
+
|
|
58
|
+
case 'table':
|
|
59
|
+
return renderTable(props, node.data)
|
|
60
|
+
|
|
61
|
+
case 'code':
|
|
62
|
+
return renderCode(props)
|
|
63
|
+
|
|
64
|
+
case 'link':
|
|
65
|
+
return renderLink(props)
|
|
66
|
+
|
|
67
|
+
case 'button':
|
|
68
|
+
return renderButton(props)
|
|
69
|
+
|
|
70
|
+
case 'box':
|
|
71
|
+
return renderBox(props, children, indent)
|
|
72
|
+
|
|
73
|
+
case 'panel':
|
|
74
|
+
return renderPanel(props, children, indent)
|
|
75
|
+
|
|
76
|
+
case 'card':
|
|
77
|
+
return renderCard(props, children, indent)
|
|
78
|
+
|
|
79
|
+
case 'metrics':
|
|
80
|
+
return renderMetrics(props)
|
|
81
|
+
|
|
82
|
+
case 'metric':
|
|
83
|
+
return renderSingleMetric(props)
|
|
84
|
+
|
|
85
|
+
case 'sidebar':
|
|
86
|
+
return renderSidebar(props, indent)
|
|
87
|
+
|
|
88
|
+
case 'breadcrumb':
|
|
89
|
+
return renderBreadcrumb(props)
|
|
90
|
+
|
|
91
|
+
case 'badge':
|
|
92
|
+
return renderBadge(props)
|
|
93
|
+
|
|
94
|
+
case 'dialog':
|
|
95
|
+
return renderDialog(props, children, indent)
|
|
96
|
+
|
|
97
|
+
case 'spinner':
|
|
98
|
+
return renderSpinner(props)
|
|
99
|
+
|
|
100
|
+
case 'dashboard':
|
|
101
|
+
return renderDashboard(props, children, indent)
|
|
102
|
+
|
|
103
|
+
case 'settings':
|
|
104
|
+
return renderSettings(props, children, indent)
|
|
105
|
+
|
|
106
|
+
case 'input':
|
|
107
|
+
return renderInput(props)
|
|
108
|
+
|
|
109
|
+
case 'select':
|
|
110
|
+
return renderSelect(props)
|
|
111
|
+
|
|
112
|
+
case 'hero':
|
|
113
|
+
return renderHero(props)
|
|
114
|
+
|
|
115
|
+
case 'features':
|
|
116
|
+
return renderFeatures(props)
|
|
117
|
+
|
|
118
|
+
case 'pricing':
|
|
119
|
+
return renderPricing(props)
|
|
120
|
+
|
|
121
|
+
case 'faq':
|
|
122
|
+
return renderFAQ(props)
|
|
123
|
+
|
|
124
|
+
case 'footer':
|
|
125
|
+
return renderFooter(props)
|
|
126
|
+
|
|
127
|
+
default:
|
|
128
|
+
// Unknown types: render children only
|
|
129
|
+
return renderChildren(children, indent)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function renderTextNode(props: Record<string, unknown>): string {
|
|
134
|
+
const content = props.content
|
|
135
|
+
if (content == null) return ''
|
|
136
|
+
return String(content)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function renderHeader(props: Record<string, unknown>): string {
|
|
140
|
+
const content = props.content
|
|
141
|
+
const nav = props.nav as Array<{ label: string; href?: string }> | undefined
|
|
142
|
+
const callToAction = props.callToAction as string | undefined
|
|
143
|
+
const breadcrumbs = props.breadcrumbs as Array<{ label: string; href?: string }> | undefined
|
|
144
|
+
|
|
145
|
+
// If it has content (simple heading), return it
|
|
146
|
+
if (content != null) {
|
|
147
|
+
return String(content)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Otherwise handle as site header with nav/cta/breadcrumbs
|
|
151
|
+
const parts: string[] = []
|
|
152
|
+
|
|
153
|
+
if (nav && Array.isArray(nav) && nav.length > 0) {
|
|
154
|
+
const navLabels = nav.map((item) => item.label).join(' | ')
|
|
155
|
+
parts.push(navLabels)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (breadcrumbs && Array.isArray(breadcrumbs) && breadcrumbs.length > 0) {
|
|
159
|
+
const crumbLabels = breadcrumbs.map((item) => item.label).join(' > ')
|
|
160
|
+
parts.push(crumbLabels)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (callToAction) {
|
|
164
|
+
parts.push(callToAction)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return parts.join('\n')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function renderList(
|
|
171
|
+
props: Record<string, unknown>,
|
|
172
|
+
children: UINode[],
|
|
173
|
+
indent: number
|
|
174
|
+
): string {
|
|
175
|
+
const items = props.items as unknown[] | undefined
|
|
176
|
+
const numbered = getProp(props, 'numbered', false)
|
|
177
|
+
const taskList = getProp(props, 'taskList', false)
|
|
178
|
+
const indentStr = getIndentStr(indent)
|
|
179
|
+
const lines: string[] = []
|
|
180
|
+
|
|
181
|
+
// Handle items prop (simple items or task items)
|
|
182
|
+
if (items && Array.isArray(items)) {
|
|
183
|
+
items.forEach((item, index) => {
|
|
184
|
+
let text: string
|
|
185
|
+
let prefix: string
|
|
186
|
+
|
|
187
|
+
if (typeof item === 'string') {
|
|
188
|
+
text = item
|
|
189
|
+
prefix = numbered ? `${index + 1}. ` : '- '
|
|
190
|
+
} else if (typeof item === 'object' && item !== null) {
|
|
191
|
+
const itemObj = item as { text?: string; checked?: boolean }
|
|
192
|
+
text = itemObj.text ?? String(item)
|
|
193
|
+
if (taskList && 'checked' in itemObj) {
|
|
194
|
+
prefix = itemObj.checked ? '[x] ' : '[ ] '
|
|
195
|
+
} else {
|
|
196
|
+
prefix = numbered ? `${index + 1}. ` : '- '
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
text = String(item)
|
|
200
|
+
prefix = numbered ? `${index + 1}. ` : '- '
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
lines.push(`${indentStr}${prefix}${text}`)
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Handle list-item children (TDD test format)
|
|
208
|
+
const nestedChildren = (children && children.length > 0)
|
|
209
|
+
? children
|
|
210
|
+
: (props.children as UINode[] | undefined)
|
|
211
|
+
|
|
212
|
+
if (nestedChildren && nestedChildren.length > 0) {
|
|
213
|
+
nestedChildren.forEach((child, index) => {
|
|
214
|
+
if (child.type === 'list-item') {
|
|
215
|
+
const content = child.props?.content as string ?? ''
|
|
216
|
+
const prefix = numbered ? `${index + 1}. ` : '- '
|
|
217
|
+
lines.push(`${indentStr}${prefix}${content}`)
|
|
218
|
+
|
|
219
|
+
// Handle nested children of list-item
|
|
220
|
+
const childChildren = child.children
|
|
221
|
+
if (childChildren && Array.isArray(childChildren) && childChildren.length > 0) {
|
|
222
|
+
childChildren.forEach((nestedChild: UINode) => {
|
|
223
|
+
const nestedOutput = renderText(nestedChild, { indent: indent + 1 })
|
|
224
|
+
if (nestedOutput) {
|
|
225
|
+
lines.push(nestedOutput)
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
const childOutput = renderText(child, { indent: indent + 1 })
|
|
231
|
+
if (childOutput) {
|
|
232
|
+
lines.push(childOutput)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return lines.join('\n')
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function renderTable(props: Record<string, unknown>, nodeData?: unknown): string {
|
|
242
|
+
const columns = props.columns as Array<{ key: string; header: string }> | undefined
|
|
243
|
+
// Support data from node.data (TDD tests) or props.data (legacy)
|
|
244
|
+
const data = (nodeData as Array<Record<string, unknown>> | undefined) ??
|
|
245
|
+
(props.data as Array<Record<string, unknown>> | undefined)
|
|
246
|
+
const lines: string[] = []
|
|
247
|
+
|
|
248
|
+
if (!columns || !Array.isArray(columns)) return ''
|
|
249
|
+
|
|
250
|
+
// Add headers using shared utility
|
|
251
|
+
const headers = extractHeaders(columns)
|
|
252
|
+
lines.push(headers.join(', '))
|
|
253
|
+
|
|
254
|
+
// Add data rows using shared utility
|
|
255
|
+
if (data && Array.isArray(data)) {
|
|
256
|
+
data.forEach((row) => {
|
|
257
|
+
const values = extractRowValues(row, columns)
|
|
258
|
+
lines.push(values.join(', '))
|
|
259
|
+
})
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return lines.join('\n')
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function renderCode(props: Record<string, unknown>): string {
|
|
266
|
+
const code = props.code
|
|
267
|
+
if (code == null) return ''
|
|
268
|
+
return String(code)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function renderLink(props: Record<string, unknown>): string {
|
|
272
|
+
const text = props.text
|
|
273
|
+
if (text == null) return ''
|
|
274
|
+
return String(text)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function renderButton(props: Record<string, unknown>): string {
|
|
278
|
+
const label = props.label as string | undefined
|
|
279
|
+
const hotkey = props.hotkey as string | undefined
|
|
280
|
+
|
|
281
|
+
if (!label) return ''
|
|
282
|
+
|
|
283
|
+
if (hotkey) {
|
|
284
|
+
return `${label} (${hotkey})`
|
|
285
|
+
}
|
|
286
|
+
return label
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function renderBox(
|
|
290
|
+
props: Record<string, unknown>,
|
|
291
|
+
children: UINode[],
|
|
292
|
+
indent: number
|
|
293
|
+
): string {
|
|
294
|
+
const title = props.title as string | undefined
|
|
295
|
+
const parts: string[] = []
|
|
296
|
+
|
|
297
|
+
if (title) {
|
|
298
|
+
parts.push(title)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const childOutput = renderChildren(children, indent)
|
|
302
|
+
if (childOutput) {
|
|
303
|
+
parts.push(childOutput)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return parts.join('\n\n')
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function renderPanel(
|
|
310
|
+
props: Record<string, unknown>,
|
|
311
|
+
children: UINode[],
|
|
312
|
+
indent: number
|
|
313
|
+
): string {
|
|
314
|
+
const title = props.title as string | undefined
|
|
315
|
+
const collapsed = props.collapsed as boolean | undefined
|
|
316
|
+
const parts: string[] = []
|
|
317
|
+
|
|
318
|
+
if (title) {
|
|
319
|
+
parts.push(title)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// If collapsed, still show title but skip content for now
|
|
323
|
+
// Based on tests, collapsed panels still show title
|
|
324
|
+
const childOutput = renderChildren(children, indent)
|
|
325
|
+
if (childOutput) {
|
|
326
|
+
parts.push(childOutput)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return parts.join('\n\n')
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function renderCard(
|
|
333
|
+
props: Record<string, unknown>,
|
|
334
|
+
children: UINode[],
|
|
335
|
+
indent: number
|
|
336
|
+
): string {
|
|
337
|
+
const title = props.title as string | undefined
|
|
338
|
+
const subtitle = props.subtitle as string | undefined
|
|
339
|
+
const badge = props.badge as { content: string; variant?: string } | undefined
|
|
340
|
+
const titleAction = props.titleAction as { label: string; action?: string } | undefined
|
|
341
|
+
const pairs = props.pairs as Array<{ key: string; value: unknown }> | undefined
|
|
342
|
+
const actions = props.actions as Array<{ label: string; action?: string }> | undefined
|
|
343
|
+
const parts: string[] = []
|
|
344
|
+
|
|
345
|
+
// Title section
|
|
346
|
+
if (title) {
|
|
347
|
+
let titleLine = title
|
|
348
|
+
if (badge) {
|
|
349
|
+
titleLine += ` ${badge.content}`
|
|
350
|
+
}
|
|
351
|
+
if (titleAction) {
|
|
352
|
+
titleLine += ` ${titleAction.label}`
|
|
353
|
+
}
|
|
354
|
+
parts.push(titleLine)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (subtitle) {
|
|
358
|
+
parts.push(subtitle)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Key-value pairs
|
|
362
|
+
if (pairs && Array.isArray(pairs)) {
|
|
363
|
+
const pairLines = pairs.map((p) => {
|
|
364
|
+
const val = p.value != null ? String(p.value) : ''
|
|
365
|
+
return `${p.key}: ${val}`
|
|
366
|
+
})
|
|
367
|
+
if (pairLines.length > 0) {
|
|
368
|
+
parts.push(pairLines.join('\n'))
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Children content
|
|
373
|
+
const childOutput = renderChildren(children, indent)
|
|
374
|
+
if (childOutput) {
|
|
375
|
+
parts.push(childOutput)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Actions
|
|
379
|
+
if (actions && Array.isArray(actions)) {
|
|
380
|
+
const actionLabels = actions.map((a) => a.label).join(' | ')
|
|
381
|
+
if (actionLabels) {
|
|
382
|
+
parts.push(actionLabels)
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return parts.join('\n\n')
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function renderMetrics(props: Record<string, unknown>): string {
|
|
390
|
+
const metrics = props.metrics as Array<{
|
|
391
|
+
label: string
|
|
392
|
+
value: unknown
|
|
393
|
+
format?: string
|
|
394
|
+
unit?: string
|
|
395
|
+
trend?: string
|
|
396
|
+
}> | undefined
|
|
397
|
+
|
|
398
|
+
if (!metrics || !Array.isArray(metrics) || metrics.length === 0) {
|
|
399
|
+
return ''
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return metrics.map((m) => {
|
|
403
|
+
const val = m.value != null ? String(m.value) : ''
|
|
404
|
+
let formatted = val
|
|
405
|
+
if (m.format === 'percentage' && !val.includes('%')) {
|
|
406
|
+
formatted = `${val}%`
|
|
407
|
+
}
|
|
408
|
+
if (m.unit) {
|
|
409
|
+
formatted = `${formatted} ${m.unit}`
|
|
410
|
+
}
|
|
411
|
+
return `${m.label}: ${formatted}`
|
|
412
|
+
}).join('\n')
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function renderSingleMetric(props: Record<string, unknown>): string {
|
|
416
|
+
const label = props.label as string | undefined
|
|
417
|
+
const value = props.value
|
|
418
|
+
const format = props.format as string | undefined
|
|
419
|
+
const unit = props.unit as string | undefined
|
|
420
|
+
|
|
421
|
+
if (!label) return ''
|
|
422
|
+
|
|
423
|
+
const val = value != null ? String(value) : ''
|
|
424
|
+
let formatted = val
|
|
425
|
+
if (format === 'percentage' && !val.includes('%')) {
|
|
426
|
+
formatted = `${val}%`
|
|
427
|
+
}
|
|
428
|
+
if (unit) {
|
|
429
|
+
formatted = `${formatted} ${unit}`
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return `${label}: ${formatted}`
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function renderSidebar(props: Record<string, unknown>, indent: number): string {
|
|
436
|
+
const nav = props.nav as Array<{
|
|
437
|
+
label: string
|
|
438
|
+
href?: string
|
|
439
|
+
active?: boolean
|
|
440
|
+
children?: Array<{ label: string; href?: string }>
|
|
441
|
+
}> | undefined
|
|
442
|
+
const sections = props.sections as Array<{
|
|
443
|
+
title: string
|
|
444
|
+
items: Array<{ label: string; href?: string }>
|
|
445
|
+
}> | undefined
|
|
446
|
+
|
|
447
|
+
const lines: string[] = []
|
|
448
|
+
const indentStr = getIndentStr(indent)
|
|
449
|
+
|
|
450
|
+
if (nav && Array.isArray(nav)) {
|
|
451
|
+
nav.forEach((item) => {
|
|
452
|
+
lines.push(`${indentStr}- ${item.label}`)
|
|
453
|
+
if (item.children && Array.isArray(item.children)) {
|
|
454
|
+
item.children.forEach((child) => {
|
|
455
|
+
lines.push(`${indentStr} - ${child.label}`)
|
|
456
|
+
})
|
|
457
|
+
}
|
|
458
|
+
})
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (sections && Array.isArray(sections)) {
|
|
462
|
+
sections.forEach((section) => {
|
|
463
|
+
lines.push(`${indentStr}${section.title}`)
|
|
464
|
+
if (section.items && Array.isArray(section.items)) {
|
|
465
|
+
section.items.forEach((item) => {
|
|
466
|
+
lines.push(`${indentStr} - ${item.label}`)
|
|
467
|
+
})
|
|
468
|
+
}
|
|
469
|
+
})
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return lines.join('\n')
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function renderBreadcrumb(props: Record<string, unknown>): string {
|
|
476
|
+
const items = props.items as Array<{ label: string; path?: string }> | undefined
|
|
477
|
+
const separator = (props.separator as string) || '>'
|
|
478
|
+
|
|
479
|
+
if (!items || !Array.isArray(items)) return ''
|
|
480
|
+
|
|
481
|
+
return items.map((item) => item.label).join(` ${separator} `)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function renderBadge(props: Record<string, unknown>): string {
|
|
485
|
+
const children = props.children
|
|
486
|
+
if (children == null) return ''
|
|
487
|
+
return String(children)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function renderDialog(
|
|
491
|
+
props: Record<string, unknown>,
|
|
492
|
+
children: UINode[],
|
|
493
|
+
indent: number
|
|
494
|
+
): string {
|
|
495
|
+
const open = props.open as boolean | undefined
|
|
496
|
+
const title = props.title as string | undefined
|
|
497
|
+
const actions = props.actions as Array<{ label: string; action: string }> | undefined
|
|
498
|
+
|
|
499
|
+
// Skip closed dialogs
|
|
500
|
+
if (open === false) return ''
|
|
501
|
+
|
|
502
|
+
const parts: string[] = []
|
|
503
|
+
|
|
504
|
+
if (title) {
|
|
505
|
+
parts.push(title)
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const childOutput = renderChildren(children, indent)
|
|
509
|
+
if (childOutput) {
|
|
510
|
+
parts.push(childOutput)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (actions && Array.isArray(actions)) {
|
|
514
|
+
const actionLabels = actions.map((a) => a.label).join(' | ')
|
|
515
|
+
parts.push(actionLabels)
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return parts.join('\n\n')
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function renderSpinner(props: Record<string, unknown>): string {
|
|
522
|
+
const label = props.label as string | undefined
|
|
523
|
+
return label || 'Loading...'
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function renderDashboard(
|
|
527
|
+
props: Record<string, unknown>,
|
|
528
|
+
children: UINode[],
|
|
529
|
+
indent: number
|
|
530
|
+
): string {
|
|
531
|
+
const title = props.title as string | undefined
|
|
532
|
+
const metrics = props.metrics as Array<{
|
|
533
|
+
label: string
|
|
534
|
+
value: string | number
|
|
535
|
+
trend?: string
|
|
536
|
+
}> | undefined
|
|
537
|
+
|
|
538
|
+
const parts: string[] = []
|
|
539
|
+
|
|
540
|
+
if (title) {
|
|
541
|
+
parts.push(title)
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (metrics && Array.isArray(metrics) && metrics.length > 0) {
|
|
545
|
+
const metricsOutput = metrics.map((m) => `${m.label}: ${m.value}`).join('\n')
|
|
546
|
+
parts.push(metricsOutput)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const childOutput = renderChildren(children, indent)
|
|
550
|
+
if (childOutput) {
|
|
551
|
+
parts.push(childOutput)
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return parts.join('\n\n')
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function renderSettings(
|
|
558
|
+
props: Record<string, unknown>,
|
|
559
|
+
children: UINode[],
|
|
560
|
+
indent: number
|
|
561
|
+
): string {
|
|
562
|
+
const sections = props.sections as string[] | undefined
|
|
563
|
+
const parts: string[] = []
|
|
564
|
+
|
|
565
|
+
if (sections && Array.isArray(sections)) {
|
|
566
|
+
sections.forEach((section) => {
|
|
567
|
+
parts.push(section)
|
|
568
|
+
})
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const childOutput = renderChildren(children, indent)
|
|
572
|
+
if (childOutput) {
|
|
573
|
+
parts.push(childOutput)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return parts.join('\n')
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function renderInput(props: Record<string, unknown>): string {
|
|
580
|
+
const label = props.label as string | undefined
|
|
581
|
+
const value = props.value as string | undefined
|
|
582
|
+
const placeholder = props.placeholder as string | undefined
|
|
583
|
+
|
|
584
|
+
const parts: string[] = []
|
|
585
|
+
|
|
586
|
+
if (label) {
|
|
587
|
+
parts.push(label)
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (value != null) {
|
|
591
|
+
parts.push(String(value))
|
|
592
|
+
} else if (placeholder) {
|
|
593
|
+
parts.push(placeholder)
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
return parts.join(': ')
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function renderSelect(props: Record<string, unknown>): string {
|
|
600
|
+
const label = props.label as string | undefined
|
|
601
|
+
const value = props.value as string | undefined
|
|
602
|
+
const options = props.options as Array<{ label: string; value: string }> | undefined
|
|
603
|
+
|
|
604
|
+
const parts: string[] = []
|
|
605
|
+
|
|
606
|
+
if (label) {
|
|
607
|
+
parts.push(label)
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Show current selection if there is one
|
|
611
|
+
if (value && options) {
|
|
612
|
+
const selected = options.find((o) => o.value === value)
|
|
613
|
+
if (selected) {
|
|
614
|
+
parts.push(`Selected: ${selected.label}`)
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// List all options
|
|
619
|
+
if (options && Array.isArray(options)) {
|
|
620
|
+
options.forEach((opt) => {
|
|
621
|
+
parts.push(`- ${opt.label}`)
|
|
622
|
+
})
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
return parts.join('\n')
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function renderHero(props: Record<string, unknown>): string {
|
|
629
|
+
const title = props.title as string | undefined
|
|
630
|
+
const subtitle = props.subtitle as string | undefined
|
|
631
|
+
const callToAction = props.callToAction as string | undefined
|
|
632
|
+
const secondaryCallToAction = props.secondaryCallToAction as string | undefined
|
|
633
|
+
const badge = props.badge as string | undefined
|
|
634
|
+
|
|
635
|
+
const parts: string[] = []
|
|
636
|
+
|
|
637
|
+
if (badge) {
|
|
638
|
+
parts.push(badge)
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if (title) {
|
|
642
|
+
parts.push(title)
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (subtitle) {
|
|
646
|
+
parts.push(subtitle)
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const ctas: string[] = []
|
|
650
|
+
if (callToAction) {
|
|
651
|
+
ctas.push(callToAction)
|
|
652
|
+
}
|
|
653
|
+
if (secondaryCallToAction) {
|
|
654
|
+
ctas.push(secondaryCallToAction)
|
|
655
|
+
}
|
|
656
|
+
if (ctas.length > 0) {
|
|
657
|
+
parts.push(ctas.join(' | '))
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
return parts.join('\n\n')
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function renderFeatures(props: Record<string, unknown>): string {
|
|
664
|
+
const title = props.title as string | undefined
|
|
665
|
+
const features = props.features as Array<{
|
|
666
|
+
title: string
|
|
667
|
+
description?: string
|
|
668
|
+
icon?: string
|
|
669
|
+
}> | undefined
|
|
670
|
+
|
|
671
|
+
const parts: string[] = []
|
|
672
|
+
|
|
673
|
+
if (title) {
|
|
674
|
+
parts.push(title)
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (features && Array.isArray(features)) {
|
|
678
|
+
features.forEach((feature) => {
|
|
679
|
+
if (feature.description) {
|
|
680
|
+
parts.push(`${feature.title}: ${feature.description}`)
|
|
681
|
+
} else {
|
|
682
|
+
parts.push(feature.title)
|
|
683
|
+
}
|
|
684
|
+
})
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
return parts.join('\n\n')
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function renderPricing(props: Record<string, unknown>): string {
|
|
691
|
+
const tiers = props.tiers as Array<{
|
|
692
|
+
name: string
|
|
693
|
+
price: string
|
|
694
|
+
features: string[]
|
|
695
|
+
callToAction?: string
|
|
696
|
+
}> | undefined
|
|
697
|
+
|
|
698
|
+
if (!tiers || !Array.isArray(tiers)) return ''
|
|
699
|
+
|
|
700
|
+
const parts: string[] = []
|
|
701
|
+
|
|
702
|
+
tiers.forEach((tier) => {
|
|
703
|
+
const tierParts: string[] = []
|
|
704
|
+
tierParts.push(`${tier.name} - ${tier.price}`)
|
|
705
|
+
|
|
706
|
+
if (tier.features && Array.isArray(tier.features)) {
|
|
707
|
+
tier.features.forEach((feature) => {
|
|
708
|
+
tierParts.push(` - ${feature}`)
|
|
709
|
+
})
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (tier.callToAction) {
|
|
713
|
+
tierParts.push(` ${tier.callToAction}`)
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
parts.push(tierParts.join('\n'))
|
|
717
|
+
})
|
|
718
|
+
|
|
719
|
+
return parts.join('\n\n')
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function renderFAQ(props: Record<string, unknown>): string {
|
|
723
|
+
const title = props.title as string | undefined
|
|
724
|
+
const items = props.items as Array<{
|
|
725
|
+
question: string
|
|
726
|
+
answer: string
|
|
727
|
+
}> | undefined
|
|
728
|
+
|
|
729
|
+
const parts: string[] = []
|
|
730
|
+
|
|
731
|
+
if (title) {
|
|
732
|
+
parts.push(title)
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if (items && Array.isArray(items)) {
|
|
736
|
+
items.forEach((item) => {
|
|
737
|
+
parts.push(`Q: ${item.question}`)
|
|
738
|
+
parts.push(`A: ${item.answer}`)
|
|
739
|
+
})
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return parts.join('\n\n')
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function renderFooter(props: Record<string, unknown>): string {
|
|
746
|
+
const links = props.links as Array<{
|
|
747
|
+
title: string
|
|
748
|
+
links: Array<{ label: string; href: string }>
|
|
749
|
+
}> | undefined
|
|
750
|
+
const copyright = props.copyright as string | undefined
|
|
751
|
+
const social = props.social as Array<{ platform: string; href: string }> | undefined
|
|
752
|
+
|
|
753
|
+
const parts: string[] = []
|
|
754
|
+
|
|
755
|
+
if (links && Array.isArray(links)) {
|
|
756
|
+
links.forEach((section) => {
|
|
757
|
+
parts.push(section.title)
|
|
758
|
+
if (section.links && Array.isArray(section.links)) {
|
|
759
|
+
section.links.forEach((link) => {
|
|
760
|
+
parts.push(` - ${link.label}`)
|
|
761
|
+
})
|
|
762
|
+
}
|
|
763
|
+
})
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
if (social && Array.isArray(social)) {
|
|
767
|
+
const socialParts = social.map((s) => s.platform).join(' | ')
|
|
768
|
+
parts.push(socialParts)
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
if (copyright) {
|
|
772
|
+
parts.push(copyright)
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
return parts.join('\n')
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function renderChildren(children: UINode[], indent: number): string {
|
|
779
|
+
if (!children || children.length === 0) return ''
|
|
780
|
+
|
|
781
|
+
const parts: string[] = []
|
|
782
|
+
|
|
783
|
+
children.forEach((child) => {
|
|
784
|
+
const output = renderText(child, { indent })
|
|
785
|
+
if (output) {
|
|
786
|
+
parts.push(output)
|
|
787
|
+
}
|
|
788
|
+
})
|
|
789
|
+
|
|
790
|
+
return parts.join('\n\n')
|
|
791
|
+
}
|