@protomarkdown/parser 1.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.
@@ -0,0 +1,820 @@
1
+ class MarkdownParser {
2
+ constructor(options = {}) {
3
+ this.options = {
4
+ strict: false,
5
+ preserveWhitespace: false,
6
+ ...options,
7
+ };
8
+ }
9
+ parse(markdown) {
10
+ const lines = markdown.split("\n");
11
+ const nodes = [];
12
+ const errors = [];
13
+ let i = 0;
14
+ while (i < lines.length) {
15
+ const line = this.options.preserveWhitespace ? lines[i] : lines[i].trim();
16
+ if (!line) {
17
+ i++;
18
+ continue;
19
+ }
20
+ // Check for table (line with pipes)
21
+ if (line.includes("|") && line.trim().startsWith("|")) {
22
+ const headers = line
23
+ .split("|")
24
+ .map((h) => h.trim())
25
+ .filter((h) => h.length > 0);
26
+ i++;
27
+ // Skip separator line (|---|---|)
28
+ if (i < lines.length) {
29
+ const separatorLine = this.options.preserveWhitespace
30
+ ? lines[i]
31
+ : lines[i].trim();
32
+ if (separatorLine.includes("-") && separatorLine.includes("|")) {
33
+ i++;
34
+ }
35
+ }
36
+ // Parse table rows
37
+ const rows = [];
38
+ while (i < lines.length) {
39
+ const rowLine = this.options.preserveWhitespace
40
+ ? lines[i]
41
+ : lines[i].trim();
42
+ if (!rowLine || !rowLine.includes("|")) {
43
+ break;
44
+ }
45
+ const cells = rowLine
46
+ .split("|")
47
+ .map((c) => c.trim())
48
+ .filter((c) => c.length > 0);
49
+ rows.push(cells);
50
+ i++;
51
+ }
52
+ nodes.push({
53
+ type: "table",
54
+ headers,
55
+ rows,
56
+ });
57
+ continue;
58
+ }
59
+ // Check for card start
60
+ const cardMatch = line.match(/^\[--\s*(.*)$/);
61
+ if (cardMatch) {
62
+ const result = this.parseCard(lines, i, cardMatch[1] || undefined);
63
+ nodes.push(result.node);
64
+ i = result.nextIndex;
65
+ continue;
66
+ }
67
+ // Check for grid start ([grid cols-2 gap-4)
68
+ const gridMatch = line.match(/^\[grid\s+(.*)$/);
69
+ if (gridMatch) {
70
+ const result = this.parseContainer(lines, i, 'grid', gridMatch[1]);
71
+ nodes.push(result.node);
72
+ i = result.nextIndex;
73
+ continue;
74
+ }
75
+ // Check for plain div start ([ or [ class-name)
76
+ const divMatch = line.match(/^\[\s*(.*)$/);
77
+ if (divMatch && !line.includes("]")) {
78
+ const result = this.parseContainer(lines, i, 'div', divMatch[1] || '');
79
+ nodes.push(result.node);
80
+ i = result.nextIndex;
81
+ continue;
82
+ }
83
+ const node = this.parseLine(line);
84
+ if (node) {
85
+ nodes.push(node);
86
+ }
87
+ else if (this.options.strict) {
88
+ errors.push(`Line ${i + 1}: Unable to parse "${line}"`);
89
+ }
90
+ i++;
91
+ }
92
+ return { nodes, errors: errors.length > 0 ? errors : undefined };
93
+ }
94
+ parseCard(lines, startIndex, title) {
95
+ const cardChildren = [];
96
+ let i = startIndex + 1;
97
+ let depth = 1;
98
+ // Parse card content until we find the matching closing --]
99
+ while (i < lines.length && depth > 0) {
100
+ const cardLine = this.options.preserveWhitespace ? lines[i] : lines[i].trim();
101
+ // Check for card closing
102
+ if (cardLine === "--]") {
103
+ depth--;
104
+ if (depth === 0) {
105
+ break;
106
+ }
107
+ }
108
+ // Check for nested card opening (must be before div check)
109
+ if (cardLine.match(/^\[--\s*(.*)$/)) {
110
+ const nestedTitle = cardLine.match(/^\[--\s*(.*)$/)?.[1] || undefined;
111
+ const result = this.parseCard(lines, i, nestedTitle);
112
+ cardChildren.push(result.node);
113
+ i = result.nextIndex;
114
+ continue;
115
+ }
116
+ // Check for nested grid opening
117
+ if (cardLine.match(/^\[grid\s+(.*)$/)) {
118
+ const nestedConfig = cardLine.match(/^\[grid\s+(.*)$/)?.[1] || '';
119
+ const result = this.parseContainer(lines, i, 'grid', nestedConfig);
120
+ cardChildren.push(result.node);
121
+ i = result.nextIndex;
122
+ continue;
123
+ }
124
+ // Check for nested div opening
125
+ if (cardLine.match(/^\[\s*(.*)$/) && !cardLine.includes("]")) {
126
+ const nestedConfig = cardLine.match(/^\[\s*(.*)$/)?.[1] || '';
127
+ const result = this.parseContainer(lines, i, 'div', nestedConfig);
128
+ cardChildren.push(result.node);
129
+ i = result.nextIndex;
130
+ continue;
131
+ }
132
+ if (cardLine) {
133
+ const childNode = this.parseLine(cardLine);
134
+ if (childNode) {
135
+ cardChildren.push(childNode);
136
+ }
137
+ }
138
+ i++;
139
+ }
140
+ return {
141
+ node: {
142
+ type: "card",
143
+ titleChildren: title ? this.parseInlineEmphasis(title) : undefined,
144
+ children: cardChildren,
145
+ },
146
+ nextIndex: i + 1,
147
+ };
148
+ }
149
+ parseContainer(lines, startIndex, type, config) {
150
+ const containerChildren = [];
151
+ let i = startIndex + 1;
152
+ let depth = 1;
153
+ // Parse container content until we find the matching closing ]
154
+ while (i < lines.length && depth > 0) {
155
+ const containerLine = this.options.preserveWhitespace ? lines[i] : lines[i].trim();
156
+ // Check for container closing
157
+ if (containerLine === "]") {
158
+ depth--;
159
+ if (depth === 0) {
160
+ break;
161
+ }
162
+ }
163
+ // Check for nested card opening (must be before div check)
164
+ if (containerLine.match(/^\[--\s*(.*)$/)) {
165
+ const nestedTitle = containerLine.match(/^\[--\s*(.*)$/)?.[1] || undefined;
166
+ const result = this.parseCard(lines, i, nestedTitle);
167
+ containerChildren.push(result.node);
168
+ i = result.nextIndex;
169
+ continue;
170
+ }
171
+ // Check for nested grid opening
172
+ if (containerLine.match(/^\[grid\s+(.*)$/)) {
173
+ const nestedConfig = containerLine.match(/^\[grid\s+(.*)$/)?.[1] || '';
174
+ const result = this.parseContainer(lines, i, 'grid', nestedConfig);
175
+ containerChildren.push(result.node);
176
+ i = result.nextIndex;
177
+ continue;
178
+ }
179
+ // Check for nested div opening
180
+ if (containerLine.match(/^\[\s*(.*)$/) && !containerLine.includes("]")) {
181
+ const nestedConfig = containerLine.match(/^\[\s*(.*)$/)?.[1] || '';
182
+ const result = this.parseContainer(lines, i, 'div', nestedConfig);
183
+ containerChildren.push(result.node);
184
+ i = result.nextIndex;
185
+ continue;
186
+ }
187
+ if (containerLine) {
188
+ const childNode = this.parseLine(containerLine);
189
+ if (childNode) {
190
+ containerChildren.push(childNode);
191
+ }
192
+ }
193
+ i++;
194
+ }
195
+ const node = {
196
+ type,
197
+ children: containerChildren,
198
+ };
199
+ if (type === 'grid') {
200
+ node.gridConfig = config;
201
+ }
202
+ else if (type === 'div' && config) {
203
+ node.className = config;
204
+ }
205
+ return {
206
+ node,
207
+ nextIndex: i + 1,
208
+ };
209
+ }
210
+ parseLine(line) {
211
+ // Parse headers (# H1, ## H2, etc.)
212
+ const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
213
+ if (headerMatch) {
214
+ return {
215
+ type: "header",
216
+ level: headerMatch[1].length,
217
+ children: this.parseInlineEmphasis(headerMatch[2]),
218
+ };
219
+ }
220
+ // Parse multiple form fields on one line (inputs, textareas, dropdowns, checkboxes)
221
+ // This must be checked BEFORE single field patterns
222
+ const fieldPattern = /(.+?)\s+(___|\|___\||__\*|__>(?:\s*\[[^\]]+\])?|__\[\])/g;
223
+ const fieldMatches = [...line.matchAll(fieldPattern)];
224
+ if (fieldMatches.length > 1) {
225
+ return {
226
+ type: "container",
227
+ children: fieldMatches.map((match) => {
228
+ const label = match[1].trim();
229
+ const marker = match[2];
230
+ if (marker === '__*') {
231
+ return {
232
+ type: "input",
233
+ label,
234
+ inputType: "password",
235
+ };
236
+ }
237
+ else if (marker === '|___|') {
238
+ return {
239
+ type: "textarea",
240
+ label,
241
+ };
242
+ }
243
+ else if (marker === '__[]') {
244
+ return {
245
+ type: "checkbox",
246
+ label,
247
+ };
248
+ }
249
+ else if (marker.startsWith('__>')) {
250
+ // Handle dropdown with or without options
251
+ const optionsMatch = marker.match(/\[([^\]]+)\]/);
252
+ if (optionsMatch) {
253
+ const options = optionsMatch[1]
254
+ .split(",")
255
+ .map((opt) => opt.trim())
256
+ .filter((opt) => opt.length > 0);
257
+ return {
258
+ type: "dropdown",
259
+ label,
260
+ options,
261
+ };
262
+ }
263
+ else {
264
+ return {
265
+ type: "dropdown",
266
+ label,
267
+ };
268
+ }
269
+ }
270
+ else { // ___
271
+ return {
272
+ type: "input",
273
+ label,
274
+ inputType: "text",
275
+ };
276
+ }
277
+ }),
278
+ };
279
+ }
280
+ // Parse password inputs (Label __*)
281
+ const passwordMatch = line.match(/^(.+?)\s+__\*$/);
282
+ if (passwordMatch) {
283
+ return {
284
+ type: "input",
285
+ label: passwordMatch[1],
286
+ inputType: "password",
287
+ };
288
+ }
289
+ // Parse textarea (Label |___|)
290
+ const textareaMatch = line.match(/^(.+?)\s+\|___\|$/);
291
+ if (textareaMatch) {
292
+ return {
293
+ type: "textarea",
294
+ label: textareaMatch[1],
295
+ };
296
+ }
297
+ // Parse text inputs (Label ___)
298
+ const inputMatch = line.match(/^(.+?)\s+___$/);
299
+ if (inputMatch) {
300
+ return {
301
+ type: "input",
302
+ label: inputMatch[1],
303
+ inputType: "text",
304
+ };
305
+ }
306
+ // Parse checkbox (Label __[])
307
+ const checkboxMatch = line.match(/^(.+?)\s+__\[\]$/);
308
+ if (checkboxMatch) {
309
+ return {
310
+ type: "checkbox",
311
+ label: checkboxMatch[1],
312
+ };
313
+ }
314
+ // Parse radio group (Label __() [option1, option2, option3])
315
+ const radioGroupMatch = line.match(/^(.+?)\s+__\(\)\s+\[(.+?)\]$/);
316
+ if (radioGroupMatch) {
317
+ const options = radioGroupMatch[2]
318
+ .split(",")
319
+ .map((opt) => opt.trim())
320
+ .filter((opt) => opt.length > 0);
321
+ return {
322
+ type: "radiogroup",
323
+ label: radioGroupMatch[1],
324
+ options,
325
+ };
326
+ }
327
+ // Parse dropdowns with options (Label __> [option1, option2, option3])
328
+ const dropdownWithOptionsMatch = line.match(/^(.+?)\s+__>\s+\[(.+?)\]$/);
329
+ if (dropdownWithOptionsMatch) {
330
+ const options = dropdownWithOptionsMatch[2]
331
+ .split(",")
332
+ .map((opt) => opt.trim())
333
+ .filter((opt) => opt.length > 0);
334
+ return {
335
+ type: "dropdown",
336
+ label: dropdownWithOptionsMatch[1],
337
+ options,
338
+ };
339
+ }
340
+ // Parse dropdowns without options (Label __>)
341
+ const dropdownMatch = line.match(/^(.+?)\s+__>$/);
342
+ if (dropdownMatch) {
343
+ return {
344
+ type: "dropdown",
345
+ label: dropdownMatch[1],
346
+ };
347
+ }
348
+ // Parse image (![alt text](url))
349
+ const imageMatch = line.match(/^!\[([^\]]*)\]\(([^)]+)\)$/);
350
+ if (imageMatch) {
351
+ return {
352
+ type: "image",
353
+ alt: imageMatch[1],
354
+ src: imageMatch[2],
355
+ };
356
+ }
357
+ // Parse multiple buttons on one line ([btn1][(btn2)])
358
+ const multiButtonMatch = line.match(/^(\[\(?[^\[\]|]+\)?\]\s*)+$/);
359
+ if (multiButtonMatch) {
360
+ const buttons = line.match(/\[(\(?)[^\[\]|]+?(\)?)\]/g);
361
+ if (buttons && buttons.length > 1) {
362
+ return {
363
+ type: "container",
364
+ children: buttons.map((btn) => {
365
+ const innerMatch = btn.match(/\[(\(?)(.+?)(\)?)\]/);
366
+ if (innerMatch) {
367
+ const isDefault = innerMatch[1] === "(" && innerMatch[3] === ")";
368
+ const content = innerMatch[2];
369
+ return {
370
+ type: "button",
371
+ content,
372
+ variant: isDefault ? "default" : "outline",
373
+ };
374
+ }
375
+ return {
376
+ type: "button",
377
+ content: btn.slice(1, -1),
378
+ variant: "outline",
379
+ };
380
+ }),
381
+ };
382
+ }
383
+ }
384
+ // Parse default button [(button text)] or [(button text) | classes]
385
+ const defaultButtonMatch = line.match(/^\[\((.+?)\)(?:\s*\|\s*(.+))?\]$/);
386
+ if (defaultButtonMatch) {
387
+ const content = defaultButtonMatch[1];
388
+ const className = defaultButtonMatch[2]?.trim();
389
+ return {
390
+ type: "button",
391
+ content,
392
+ variant: "default",
393
+ ...(className && { className }),
394
+ };
395
+ }
396
+ // Parse outline button [button text] or [button text | classes]
397
+ const buttonMatch = line.match(/^\[([^|]+?)(?:\s*\|\s*(.+))?\]$/);
398
+ if (buttonMatch) {
399
+ const content = buttonMatch[1].trim();
400
+ const className = buttonMatch[2]?.trim();
401
+ return {
402
+ type: "button",
403
+ content,
404
+ variant: "outline",
405
+ ...(className && { className }),
406
+ };
407
+ }
408
+ // Plain text
409
+ return {
410
+ type: "text",
411
+ children: this.parseInlineEmphasis(line),
412
+ };
413
+ }
414
+ parseInlineEmphasis(text) {
415
+ const nodes = [];
416
+ let remaining = text;
417
+ let position = 0;
418
+ while (position < remaining.length) {
419
+ // Try to match bold-italic (_*text*_)
420
+ const boldItalicMatch = remaining
421
+ .slice(position)
422
+ .match(/^_\*(.+?)\*_/);
423
+ if (boldItalicMatch) {
424
+ const content = boldItalicMatch[1];
425
+ nodes.push({
426
+ type: "bold",
427
+ children: [{ type: "italic", content }],
428
+ });
429
+ position += boldItalicMatch[0].length;
430
+ continue;
431
+ }
432
+ // Try to match bold (*text*)
433
+ const boldMatch = remaining.slice(position).match(/^\*(.+?)\*/);
434
+ if (boldMatch) {
435
+ nodes.push({
436
+ type: "bold",
437
+ content: boldMatch[1],
438
+ });
439
+ position += boldMatch[0].length;
440
+ continue;
441
+ }
442
+ // Try to match italic (_text_)
443
+ const italicMatch = remaining.slice(position).match(/^_(.+?)_/);
444
+ if (italicMatch) {
445
+ nodes.push({
446
+ type: "italic",
447
+ content: italicMatch[1],
448
+ });
449
+ position += italicMatch[0].length;
450
+ continue;
451
+ }
452
+ // No emphasis found, consume until next emphasis marker or end of string
453
+ const nextAsterisk = remaining.slice(position).indexOf("*");
454
+ const nextUnderscore = remaining.slice(position).indexOf("_");
455
+ // Find the nearest marker
456
+ let nextMarker = -1;
457
+ if (nextAsterisk !== -1 && nextUnderscore !== -1) {
458
+ nextMarker = Math.min(nextAsterisk, nextUnderscore);
459
+ }
460
+ else if (nextAsterisk !== -1) {
461
+ nextMarker = nextAsterisk;
462
+ }
463
+ else if (nextUnderscore !== -1) {
464
+ nextMarker = nextUnderscore;
465
+ }
466
+ const textEnd = nextMarker === -1
467
+ ? remaining.length
468
+ : position + nextMarker;
469
+ if (textEnd > position) {
470
+ nodes.push({
471
+ type: "text",
472
+ content: remaining.slice(position, textEnd),
473
+ });
474
+ position = textEnd;
475
+ }
476
+ else {
477
+ // Edge case: marker that doesn't form emphasis
478
+ nodes.push({
479
+ type: "text",
480
+ content: remaining.slice(position, position + 1),
481
+ });
482
+ position++;
483
+ }
484
+ }
485
+ return nodes;
486
+ }
487
+ }
488
+
489
+ /**
490
+ * Generates React component code from a Proto Markdown AST using Shadcn UI components
491
+ */
492
+ class ShadcnCodeGenerator {
493
+ constructor() {
494
+ this.indentLevel = 0;
495
+ this.indentSize = 2;
496
+ this.requiredImports = new Set();
497
+ }
498
+ /**
499
+ * Generate complete React component code from markdown AST
500
+ */
501
+ generate(nodes) {
502
+ this.indentLevel = 0;
503
+ this.requiredImports.clear();
504
+ // Generate component body
505
+ const componentBody = this.generateNodes(nodes);
506
+ // Add base indentation (6 spaces = 3 levels for proper JSX nesting)
507
+ const indentedBody = componentBody
508
+ .split('\n')
509
+ .map(line => line ? ` ${line}` : line)
510
+ .join('\n');
511
+ // Collect imports
512
+ const imports = this.generateImports();
513
+ return `${imports}
514
+
515
+ export function GeneratedComponent() {
516
+ return (
517
+ <div className="space-y-2">
518
+ ${indentedBody}
519
+ </div>
520
+ );
521
+ }
522
+ `;
523
+ }
524
+ /**
525
+ * Generate imports based on used components
526
+ */
527
+ generateImports() {
528
+ const imports = [];
529
+ if (this.requiredImports.has("Button")) {
530
+ imports.push('import { Button } from "@/components/ui/button";');
531
+ }
532
+ if (this.requiredImports.has("Input")) {
533
+ imports.push('import { Input } from "@/components/ui/input";');
534
+ }
535
+ if (this.requiredImports.has("Textarea")) {
536
+ imports.push('import { Textarea } from "@/components/ui/textarea";');
537
+ }
538
+ if (this.requiredImports.has("Card")) {
539
+ imports.push('import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";');
540
+ }
541
+ if (this.requiredImports.has("Checkbox")) {
542
+ imports.push('import { Checkbox } from "@/components/ui/checkbox";');
543
+ }
544
+ if (this.requiredImports.has("RadioGroup")) {
545
+ imports.push('import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";');
546
+ }
547
+ if (this.requiredImports.has("Select")) {
548
+ imports.push('import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";');
549
+ }
550
+ if (this.requiredImports.has("Table")) {
551
+ imports.push('import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from "@/components/ui/table";');
552
+ }
553
+ if (this.requiredImports.has("Label")) {
554
+ imports.push('import { Label } from "@/components/ui/label";');
555
+ }
556
+ return imports.join("\n");
557
+ }
558
+ /**
559
+ * Generate code for multiple nodes
560
+ */
561
+ generateNodes(nodes) {
562
+ return nodes.map((node, index) => this.generateNode(node, index)).join("\n");
563
+ }
564
+ /**
565
+ * Generate code for a single node
566
+ */
567
+ generateNode(node, index) {
568
+ switch (node.type) {
569
+ case "header":
570
+ return this.generateHeader(node, index);
571
+ case "input":
572
+ return this.generateInput(node, index);
573
+ case "textarea":
574
+ return this.generateTextarea(node, index);
575
+ case "dropdown":
576
+ return this.generateDropdown(node, index);
577
+ case "checkbox":
578
+ return this.generateCheckbox(node, index);
579
+ case "radiogroup":
580
+ return this.generateRadioGroup(node, index);
581
+ case "button":
582
+ return this.generateButton(node, index);
583
+ case "container":
584
+ return this.generateContainer(node, index);
585
+ case "card":
586
+ return this.generateCard(node, index);
587
+ case "table":
588
+ return this.generateTable(node, index);
589
+ case "grid":
590
+ return this.generateGrid(node, index);
591
+ case "div":
592
+ return this.generateDiv(node, index);
593
+ case "text":
594
+ return this.generateText(node, index);
595
+ case "bold":
596
+ return this.generateBold(node, index);
597
+ case "italic":
598
+ return this.generateItalic(node, index);
599
+ case "image":
600
+ return this.generateImage(node, index);
601
+ default:
602
+ return "";
603
+ }
604
+ }
605
+ indent() {
606
+ return " ".repeat(this.indentLevel * this.indentSize);
607
+ }
608
+ escapeJSX(text) {
609
+ return text
610
+ .replace(/&/g, "&amp;")
611
+ .replace(/</g, "&lt;")
612
+ .replace(/>/g, "&gt;")
613
+ .replace(/"/g, "&quot;")
614
+ .replace(/{/g, "&#123;")
615
+ .replace(/}/g, "&#125;");
616
+ }
617
+ generateHeader(node, index) {
618
+ const Tag = `h${node.level}`;
619
+ const className = `text-${node.level === 1 ? "4xl" : node.level === 2 ? "3xl" : node.level === 3 ? "2xl" : node.level === 4 ? "xl" : node.level === 5 ? "lg" : "base"} font-bold`;
620
+ let content;
621
+ if (node.children && node.children.length > 0) {
622
+ // Inline children (emphasis)
623
+ content = node.children.map((child, i) => this.generateInlineNode(child, i)).join("");
624
+ }
625
+ else {
626
+ content = this.escapeJSX(node.content || "");
627
+ }
628
+ return `${this.indent()}<${Tag} key={${index}} className="${className}">${content}</${Tag}>`;
629
+ }
630
+ generateInlineNode(node, index) {
631
+ switch (node.type) {
632
+ case "bold":
633
+ const boldContent = node.children?.map((child, i) => this.generateInlineNode(child, i)).join("") || this.escapeJSX(node.content || "");
634
+ return `<strong key={${index}}>${boldContent}</strong>`;
635
+ case "italic":
636
+ const italicContent = node.children?.map((child, i) => this.generateInlineNode(child, i)).join("") || this.escapeJSX(node.content || "");
637
+ return `<em key={${index}}>${italicContent}</em>`;
638
+ case "text":
639
+ if (node.children && node.children.length > 0) {
640
+ return node.children.map((child, i) => this.generateInlineNode(child, i)).join("");
641
+ }
642
+ return this.escapeJSX(node.content || "");
643
+ default:
644
+ return this.escapeJSX(node.content || "");
645
+ }
646
+ }
647
+ generateInput(node, index) {
648
+ this.requiredImports.add("Input");
649
+ this.requiredImports.add("Label");
650
+ const id = node.id || `input-${index}`;
651
+ const type = node.inputType || "text";
652
+ return `${this.indent()}<div key={${index}} className="space-y-2">
653
+ ${this.indent()} <Label htmlFor="${id}">${this.escapeJSX(node.label || "")}</Label>
654
+ ${this.indent()} <Input id="${id}" type="${type}" />
655
+ ${this.indent()}</div>`;
656
+ }
657
+ generateTextarea(node, index) {
658
+ this.requiredImports.add("Textarea");
659
+ this.requiredImports.add("Label");
660
+ const id = node.id || `textarea-${index}`;
661
+ return `${this.indent()}<div key={${index}} className="space-y-2">
662
+ ${this.indent()} <Label htmlFor="${id}">${this.escapeJSX(node.label || "")}</Label>
663
+ ${this.indent()} <Textarea id="${id}" />
664
+ ${this.indent()}</div>`;
665
+ }
666
+ generateDropdown(node, index) {
667
+ this.requiredImports.add("Select");
668
+ this.requiredImports.add("Label");
669
+ const id = node.id || `select-${index}`;
670
+ const options = node.options || [];
671
+ return `${this.indent()}<div key={${index}} className="space-y-2">
672
+ ${this.indent()} <Label htmlFor="${id}">${this.escapeJSX(node.label || "")}</Label>
673
+ ${this.indent()} <Select>
674
+ ${this.indent()} <SelectTrigger id="${id}">
675
+ ${this.indent()} <SelectValue placeholder="Select an option" />
676
+ ${this.indent()} </SelectTrigger>
677
+ ${this.indent()} <SelectContent>
678
+ ${options.map((opt, i) => `${this.indent()} <SelectItem key={${i}} value="${opt.toLowerCase().replace(/\s+/g, "-")}">${this.escapeJSX(opt)}</SelectItem>`).join("\n")}
679
+ ${this.indent()} </SelectContent>
680
+ ${this.indent()} </Select>
681
+ ${this.indent()}</div>`;
682
+ }
683
+ generateCheckbox(node, index) {
684
+ this.requiredImports.add("Checkbox");
685
+ this.requiredImports.add("Label");
686
+ const id = node.id || `checkbox-${index}`;
687
+ return `${this.indent()}<div key={${index}} className="flex items-center space-x-2">
688
+ ${this.indent()} <Checkbox id="${id}" />
689
+ ${this.indent()} <Label htmlFor="${id}" className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
690
+ ${this.indent()} ${this.escapeJSX(node.label || "")}
691
+ ${this.indent()} </Label>
692
+ ${this.indent()}</div>`;
693
+ }
694
+ generateRadioGroup(node, index) {
695
+ this.requiredImports.add("RadioGroup");
696
+ this.requiredImports.add("Label");
697
+ const options = node.options || [];
698
+ return `${this.indent()}<div key={${index}} className="space-y-2">
699
+ ${this.indent()} <Label>${this.escapeJSX(node.label || "")}</Label>
700
+ ${this.indent()} <RadioGroup>
701
+ ${options.map((opt, i) => {
702
+ const optId = `radio-${index}-${i}`;
703
+ const value = opt.toLowerCase().replace(/\s+/g, "-");
704
+ return `${this.indent()} <div className="flex items-center space-x-2">
705
+ ${this.indent()} <RadioGroupItem id="${optId}" value="${value}" />
706
+ ${this.indent()} <Label htmlFor="${optId}">${this.escapeJSX(opt)}</Label>
707
+ ${this.indent()} </div>`;
708
+ }).join("\n")}
709
+ ${this.indent()} </RadioGroup>
710
+ ${this.indent()}</div>`;
711
+ }
712
+ generateButton(node, index) {
713
+ this.requiredImports.add("Button");
714
+ const variant = node.variant || "default";
715
+ const className = node.className ? ` className="${node.className}"` : "";
716
+ return `${this.indent()}<Button key={${index}} variant="${variant}"${className}>${this.escapeJSX(node.content || "")}</Button>`;
717
+ }
718
+ generateContainer(node, index) {
719
+ this.indentLevel++;
720
+ const children = node.children ? this.generateNodes(node.children) : "";
721
+ this.indentLevel--;
722
+ return `${this.indent()}<div key={${index}} className="flex gap-2">
723
+ ${children}
724
+ ${this.indent()}</div>`;
725
+ }
726
+ generateCard(node, index) {
727
+ this.requiredImports.add("Card");
728
+ let titleContent = "";
729
+ if (node.titleChildren && node.titleChildren.length > 0) {
730
+ titleContent = node.titleChildren.map((child, i) => this.generateInlineNode(child, i)).join("");
731
+ }
732
+ else if (node.title) {
733
+ titleContent = this.escapeJSX(node.title);
734
+ }
735
+ // Increment by 2 to account for Card wrapper + CardContent nesting
736
+ this.indentLevel += 2;
737
+ const children = node.children ? this.generateNodes(node.children) : "";
738
+ this.indentLevel -= 2;
739
+ const cardContent = titleContent
740
+ ? `${this.indent()}<Card key={${index}}>
741
+ ${this.indent()} <CardHeader>
742
+ ${this.indent()} <CardTitle>${titleContent}</CardTitle>
743
+ ${this.indent()} </CardHeader>
744
+ ${this.indent()} <CardContent className="space-y-2">
745
+ ${children}
746
+ ${this.indent()} </CardContent>
747
+ ${this.indent()}</Card>`
748
+ : `${this.indent()}<Card key={${index}}>
749
+ ${this.indent()} <CardContent className="pt-6 space-y-2">
750
+ ${children}
751
+ ${this.indent()} </CardContent>
752
+ ${this.indent()}</Card>`;
753
+ return cardContent;
754
+ }
755
+ generateTable(node, index) {
756
+ this.requiredImports.add("Table");
757
+ const headers = node.headers || [];
758
+ const rows = node.rows || [];
759
+ return `${this.indent()}<Table key={${index}}>
760
+ ${this.indent()} <TableHeader>
761
+ ${this.indent()} <TableRow>
762
+ ${headers.map((header, i) => `${this.indent()} <TableHead key={${i}}>${this.escapeJSX(header)}</TableHead>`).join("\n")}
763
+ ${this.indent()} </TableRow>
764
+ ${this.indent()} </TableHeader>
765
+ ${this.indent()} <TableBody>
766
+ ${rows.map((row, i) => `${this.indent()} <TableRow key={${i}}>
767
+ ${row.map((cell, j) => `${this.indent()} <TableCell key={${j}}>${this.escapeJSX(cell)}</TableCell>`).join("\n")}
768
+ ${this.indent()} </TableRow>`).join("\n")}
769
+ ${this.indent()} </TableBody>
770
+ ${this.indent()}</Table>`;
771
+ }
772
+ generateGrid(node, index) {
773
+ const gridClasses = `grid ${node.gridConfig || ""}`;
774
+ this.indentLevel++;
775
+ const children = node.children ? this.generateNodes(node.children) : "";
776
+ this.indentLevel--;
777
+ return `${this.indent()}<div key={${index}} className="${gridClasses}">
778
+ ${children}
779
+ ${this.indent()}</div>`;
780
+ }
781
+ generateDiv(node, index) {
782
+ const className = node.className || "";
783
+ this.indentLevel++;
784
+ const children = node.children ? this.generateNodes(node.children) : "";
785
+ this.indentLevel--;
786
+ return `${this.indent()}<div key={${index}} className="${className}">
787
+ ${children}
788
+ ${this.indent()}</div>`;
789
+ }
790
+ generateText(node, index) {
791
+ if (node.children && node.children.length > 0) {
792
+ // Text with inline emphasis
793
+ const inlineContent = node.children.map((child, i) => this.generateInlineNode(child, i)).join("");
794
+ return `${this.indent()}<p key={${index}}>${inlineContent}</p>`;
795
+ }
796
+ return `${this.indent()}<p key={${index}}>${this.escapeJSX(node.content || "")}</p>`;
797
+ }
798
+ generateBold(node, index) {
799
+ if (node.children && node.children.length > 0) {
800
+ const content = node.children.map((child, i) => this.generateInlineNode(child, i)).join("");
801
+ return `${this.indent()}<strong key={${index}}>${content}</strong>`;
802
+ }
803
+ return `${this.indent()}<strong key={${index}}>${this.escapeJSX(node.content || "")}</strong>`;
804
+ }
805
+ generateItalic(node, index) {
806
+ if (node.children && node.children.length > 0) {
807
+ const content = node.children.map((child, i) => this.generateInlineNode(child, i)).join("");
808
+ return `${this.indent()}<em key={${index}}>${content}</em>`;
809
+ }
810
+ return `${this.indent()}<em key={${index}}>${this.escapeJSX(node.content || "")}</em>`;
811
+ }
812
+ generateImage(node, index) {
813
+ const src = node.src || "";
814
+ const alt = node.alt || "";
815
+ return `${this.indent()}<img key={${index}} src="${src}" alt="${this.escapeJSX(alt)}" className="max-w-full h-auto" />`;
816
+ }
817
+ }
818
+
819
+ export { MarkdownParser, ShadcnCodeGenerator };
820
+ //# sourceMappingURL=index.esm.js.map