@wire-dsl/engine 0.0.3 → 0.2.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/dist/index.d.cts CHANGED
@@ -1,40 +1,182 @@
1
+ /**
2
+ * SourceMap Types for Wire DSL
3
+ *
4
+ * Provides bidirectional mapping between code positions and AST nodes
5
+ * for features like code-canvas selection, visual editing, and error reporting.
6
+ */
7
+
8
+ /**
9
+ * Position in source code (1-based line, 0-based column)
10
+ */
11
+ interface Position {
12
+ line: number;
13
+ column: number;
14
+ offset?: number;
15
+ }
16
+ /**
17
+ * Range in source code
18
+ */
19
+ interface CodeRange {
20
+ start: Position;
21
+ end: Position;
22
+ }
23
+ /**
24
+ * SourceMap for a single property
25
+ * Captures ranges for precise property editing
26
+ */
27
+ interface PropertySourceMap {
28
+ name: string;
29
+ value: any;
30
+ range: CodeRange;
31
+ nameRange: CodeRange;
32
+ valueRange: CodeRange;
33
+ }
34
+ /**
35
+ * Types of nodes in Wire DSL
36
+ */
37
+ type SourceMapNodeType = 'project' | 'screen' | 'layout' | 'component' | 'component-definition' | 'cell' | 'style' | 'mocks' | 'colors';
38
+ /**
39
+ * Main SourceMap entry - represents one node in the AST
40
+ */
41
+ interface SourceMapEntry {
42
+ nodeId: string;
43
+ type: SourceMapNodeType;
44
+ range: CodeRange;
45
+ filePath: string;
46
+ parentId: string | null;
47
+ name?: string;
48
+ layoutType?: string;
49
+ componentType?: string;
50
+ indexInParent?: number;
51
+ isUserDefined?: boolean;
52
+ keywordRange?: CodeRange;
53
+ nameRange?: CodeRange;
54
+ bodyRange?: CodeRange;
55
+ properties?: Record<string, PropertySourceMap>;
56
+ insertionPoint?: InsertionPoint;
57
+ }
58
+ /**
59
+ * Insertion point for new children (FASE 3)
60
+ */
61
+ interface InsertionPoint {
62
+ line: number;
63
+ column: number;
64
+ indentation: string;
65
+ after?: string;
66
+ }
67
+ /**
68
+ * Parse result with SourceMap
69
+ */
70
+ interface ParseResult {
71
+ ast: AST;
72
+ sourceMap: SourceMapEntry[];
73
+ errors: ParseError[];
74
+ }
75
+ /**
76
+ * Parse error with optional nodeId reference
77
+ */
78
+ interface ParseError {
79
+ message: string;
80
+ range: CodeRange;
81
+ severity: 'error' | 'warning';
82
+ nodeId?: string;
83
+ }
84
+ /**
85
+ * Captured tokens from Chevrotain parser (internal use)
86
+ * These are NOT stored in AST, only used temporarily during SourceMap building
87
+ */
88
+ interface CapturedTokens {
89
+ keyword?: any;
90
+ name?: any;
91
+ paramList?: any;
92
+ properties?: any[];
93
+ body?: any;
94
+ children?: any[];
95
+ }
96
+
1
97
  interface AST {
2
98
  type: 'project';
3
99
  name: string;
4
- theme: Record<string, string>;
100
+ style: Record<string, string>;
5
101
  mocks: Record<string, string>;
6
102
  colors: Record<string, string>;
7
103
  definedComponents: ASTDefinedComponent[];
8
104
  screens: ASTScreen[];
105
+ _meta?: {
106
+ nodeId: string;
107
+ };
9
108
  }
10
109
  interface ASTDefinedComponent {
11
110
  type: 'definedComponent';
12
111
  name: string;
13
112
  body: ASTLayout | ASTComponent;
113
+ _meta?: {
114
+ nodeId: string;
115
+ };
14
116
  }
15
117
  interface ASTScreen {
16
118
  type: 'screen';
17
119
  name: string;
18
120
  params: Record<string, string | number>;
19
121
  layout: ASTLayout;
122
+ _meta?: {
123
+ nodeId: string;
124
+ };
20
125
  }
21
126
  interface ASTLayout {
22
127
  type: 'layout';
23
128
  layoutType: string;
24
129
  params: Record<string, string | number>;
25
130
  children: (ASTComponent | ASTLayout | ASTCell)[];
131
+ _meta?: {
132
+ nodeId: string;
133
+ };
26
134
  }
27
135
  interface ASTCell {
28
136
  type: 'cell';
29
137
  props: Record<string, string | number>;
30
138
  children: (ASTComponent | ASTLayout)[];
139
+ _meta?: {
140
+ nodeId: string;
141
+ };
31
142
  }
32
143
  interface ASTComponent {
33
144
  type: 'component';
34
145
  componentType: string;
35
146
  props: Record<string, string | number>;
147
+ _meta?: {
148
+ nodeId: string;
149
+ };
36
150
  }
37
151
  declare function parseWireDSL(input: string): AST;
152
+ /**
153
+ * Parse Wire DSL with SourceMap generation
154
+ *
155
+ * Returns both AST and SourceMap for bidirectional code-canvas mapping
156
+ * Useful for:
157
+ * - Visual editors (Wire Studio)
158
+ * - Code navigation (click canvas → jump to code)
159
+ * - Error reporting with precise locations
160
+ * - Component inspection and manipulation
161
+ *
162
+ * @param input - Wire DSL source code
163
+ * @param filePath - Optional file path (default: "<input>") - used for stable nodeIds
164
+ * @returns ParseResult with AST, SourceMap, and errors
165
+ *
166
+ * @example
167
+ * ```typescript
168
+ * const { ast, sourceMap } = parseWireDSLWithSourceMap(code, 'screens/Main.wire');
169
+ *
170
+ * // Find node by position
171
+ * const node = sourceMap.find(e =>
172
+ * e.range.start.line === 5 && e.range.start.column === 4
173
+ * );
174
+ *
175
+ * // Access AST node
176
+ * console.log(node.astNode.type); // 'component'
177
+ * ```
178
+ */
179
+ declare function parseWireDSLWithSourceMap(input: string, filePath?: string): ParseResult;
38
180
  interface ParsedWireframe {
39
181
  name: string;
40
182
  components: ParsedComponent[];
@@ -61,19 +203,21 @@ interface IRContract {
61
203
  interface IRProject {
62
204
  id: string;
63
205
  name: string;
64
- theme: IRTheme;
206
+ style: IRStyle;
65
207
  mocks: Record<string, unknown>;
66
208
  colors: Record<string, string>;
67
209
  screens: IRScreen[];
68
210
  nodes: Record<string, IRNode>;
69
211
  }
70
- interface IRTheme {
212
+ interface IRStyle {
71
213
  density: 'compact' | 'normal' | 'comfortable';
72
214
  spacing: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
73
215
  radius: 'none' | 'sm' | 'md' | 'lg' | 'full';
74
216
  stroke: 'thin' | 'normal' | 'thick';
75
217
  font: 'sm' | 'base' | 'lg';
76
218
  background?: string;
219
+ theme?: string;
220
+ device?: string;
77
221
  }
78
222
  interface IRScreen {
79
223
  id: string;
@@ -96,7 +240,7 @@ interface IRContainerNode {
96
240
  children: Array<{
97
241
  ref: string;
98
242
  }>;
99
- style: IRStyle;
243
+ style: IRNodeStyle;
100
244
  meta: IRMeta;
101
245
  }
102
246
  interface IRComponentNode {
@@ -104,10 +248,10 @@ interface IRComponentNode {
104
248
  kind: 'component';
105
249
  componentType: string;
106
250
  props: Record<string, string | number>;
107
- style: IRStyle;
251
+ style: IRNodeStyle;
108
252
  meta: IRMeta;
109
253
  }
110
- interface IRStyle {
254
+ interface IRNodeStyle {
111
255
  padding?: string;
112
256
  gap?: string;
113
257
  align?: 'left' | 'center' | 'right' | 'justify';
@@ -116,6 +260,7 @@ interface IRStyle {
116
260
  }
117
261
  interface IRMeta {
118
262
  source?: string;
263
+ nodeId?: string;
119
264
  }
120
265
  declare class IRGenerator {
121
266
  private idGen;
@@ -124,9 +269,9 @@ declare class IRGenerator {
124
269
  private definedComponentIndices;
125
270
  private undefinedComponentsUsed;
126
271
  private warnings;
127
- private theme;
272
+ private style;
128
273
  generate(ast: AST): IRContract;
129
- private applyTheme;
274
+ private applyStyle;
130
275
  private convertScreen;
131
276
  /**
132
277
  * Validates that component definitions appear before their first usage
@@ -179,6 +324,44 @@ interface IRMetadata {
179
324
  description?: string;
180
325
  }
181
326
 
327
+ /**
328
+ * Device viewport presets for multi-device wireframe rendering
329
+ *
330
+ * Defines standard viewport widths and minimum heights for different device
331
+ * categories. Final render height remains dynamic and grows with content.
332
+ */
333
+ interface DevicePreset {
334
+ name: string;
335
+ width: number;
336
+ minHeight: number;
337
+ category: 'mobile' | 'desktop' | 'tablet' | 'print';
338
+ description: string;
339
+ }
340
+ /**
341
+ * Standard device viewport presets
342
+ *
343
+ * `minHeight` is used as the baseline viewport height. Renderers still
344
+ * expand dynamically when content exceeds this value.
345
+ */
346
+ declare const DEVICE_PRESETS: Record<string, DevicePreset>;
347
+ /**
348
+ * Resolve device preset name to viewport dimensions
349
+ *
350
+ * @param device - Device preset name (case-insensitive)
351
+ * @returns Viewport width and minimum height in pixels
352
+ */
353
+ declare function resolveDevicePreset(device: string): {
354
+ width: number;
355
+ minHeight: number;
356
+ };
357
+ /**
358
+ * Check if a device preset name is valid
359
+ *
360
+ * @param device - Device preset name to validate
361
+ * @returns True if device preset exists
362
+ */
363
+ declare function isValidDevice(device: string): boolean;
364
+
182
365
  /**
183
366
  * Layout Engine
184
367
  *
@@ -196,7 +379,7 @@ interface LayoutResult {
196
379
  }
197
380
  declare class LayoutEngine {
198
381
  private nodes;
199
- private theme;
382
+ private style;
200
383
  private result;
201
384
  private viewport;
202
385
  private ir;
@@ -213,16 +396,119 @@ declare class LayoutEngine {
213
396
  private calculateCard;
214
397
  private calculateComponent;
215
398
  private resolveSpacing;
399
+ private getSeparateSize;
216
400
  private getComponentHeight;
401
+ private getTextMetricsForDensity;
402
+ private getButtonMetricsForDensity;
403
+ private getHeadingMetricsForDensity;
404
+ private wrapTextToLines;
217
405
  private getIntrinsicComponentHeight;
406
+ private getControlLabelOffset;
218
407
  private getIntrinsicComponentWidth;
219
408
  private calculateChildHeight;
220
409
  private calculateChildWidth;
410
+ private estimateTextWidth;
221
411
  private adjustNodeYPositions;
222
412
  }
223
413
  declare function calculateLayout(ir: IRContract): LayoutResult;
224
414
  declare function resolveGridPosition(row: number, col: number, rowSpan?: number, colSpan?: number, gridWidth?: number, gridHeight?: number, gridCols?: number, gridRows?: number): LayoutPosition;
225
415
 
416
+ /**
417
+ * Color Resolver
418
+ *
419
+ * Resolves color references from project colors, named colors, or hex values
420
+ */
421
+ declare class ColorResolver {
422
+ private customColors;
423
+ private namedColors;
424
+ /**
425
+ * Set custom colors from project definition
426
+ */
427
+ setCustomColors(colors: Record<string, string>): void;
428
+ /**
429
+ * Check if a color key/reference is resolvable.
430
+ */
431
+ hasColor(colorRef: string): boolean;
432
+ /**
433
+ * Resolve a color reference to a hex value
434
+ * Priority: custom colors > named colors > hex validation > default
435
+ */
436
+ resolveColor(colorRef: string, defaultColor?: string): string;
437
+ private resolveColorInternal;
438
+ /**
439
+ * Validate hex color (6-character hex code)
440
+ */
441
+ private isValidHex;
442
+ /**
443
+ * Validate and return hex, or undefined if invalid
444
+ */
445
+ private validateHex;
446
+ }
447
+
448
+ /**
449
+ * Design Tokens System
450
+ *
451
+ * Visual properties organized by density levels.
452
+ * These tokens are read from ir.project.style (density, spacing, radius, stroke, font)
453
+ * and used by all renderers to maintain consistent visual styling.
454
+ */
455
+ interface DesignTokens {
456
+ spacing: {
457
+ xs: number;
458
+ sm: number;
459
+ md: number;
460
+ lg: number;
461
+ xl: number;
462
+ };
463
+ button: {
464
+ paddingX: number;
465
+ paddingY: number;
466
+ radius: number;
467
+ fontSize: number;
468
+ fontWeight: number;
469
+ };
470
+ input: {
471
+ paddingX: number;
472
+ paddingY: number;
473
+ radius: number;
474
+ fontSize: number;
475
+ };
476
+ card: {
477
+ padding: number;
478
+ radius: number;
479
+ strokeWidth: number;
480
+ };
481
+ heading: {
482
+ fontSize: number;
483
+ fontWeight: number;
484
+ marginBottom: number;
485
+ };
486
+ text: {
487
+ fontSize: number;
488
+ lineHeight: number;
489
+ };
490
+ badge: {
491
+ paddingX: number;
492
+ paddingY: number;
493
+ radius: number | 'pill';
494
+ fontSize: number;
495
+ };
496
+ table: {
497
+ cellPaddingX: number;
498
+ cellPaddingY: number;
499
+ headerFontWeight: number;
500
+ borderWidth: number;
501
+ };
502
+ }
503
+ /**
504
+ * Token sets for each density level
505
+ */
506
+ declare const DENSITY_TOKENS: Record<'compact' | 'normal' | 'comfortable', DesignTokens>;
507
+ /**
508
+ * Resolve design tokens based on IRStyle
509
+ */
510
+ declare function resolveTokens(style: any): DesignTokens;
511
+
226
512
  /**
227
513
  * SVG Renderer
228
514
  *
@@ -241,14 +527,40 @@ interface SVGComponent {
241
527
  children?: SVGComponent[];
242
528
  text?: string;
243
529
  }
530
+ declare const THEMES: {
531
+ light: {
532
+ bg: string;
533
+ cardBg: string;
534
+ border: string;
535
+ text: string;
536
+ textMuted: string;
537
+ primary: string;
538
+ primaryHover: string;
539
+ primaryLight: string;
540
+ };
541
+ dark: {
542
+ bg: string;
543
+ cardBg: string;
544
+ border: string;
545
+ text: string;
546
+ textMuted: string;
547
+ primary: string;
548
+ primaryHover: string;
549
+ primaryLight: string;
550
+ };
551
+ };
244
552
  declare class SVGRenderer {
245
553
  private ir;
246
554
  private layout;
247
- private options;
248
- private renderTheme;
555
+ protected options: Required<Omit<SVGRenderOptions, 'screenName'>> & {
556
+ screenName?: string;
557
+ };
558
+ protected renderTheme: typeof THEMES.light;
559
+ protected tokens: DesignTokens;
249
560
  private selectedScreenName?;
250
- private renderedNodeIds;
251
- private colorResolver;
561
+ protected renderedNodeIds: Set<string>;
562
+ protected colorResolver: ColorResolver;
563
+ protected fontFamily: string;
252
564
  constructor(ir: IRContract, layout: LayoutResult, options?: SVGRenderOptions);
253
565
  /**
254
566
  * Get list of available screens in the project
@@ -260,53 +572,715 @@ declare class SVGRenderer {
260
572
  /**
261
573
  * Get the currently selected or first screen
262
574
  */
263
- private getSelectedScreen;
575
+ protected getSelectedScreen(): {
576
+ screen: any;
577
+ name: string;
578
+ };
264
579
  render(): string;
265
- private calculateContentHeight;
266
- private renderNode;
267
- private renderComponent;
268
- private renderHeading;
269
- private renderButton;
270
- private renderInput;
271
- private renderTopbar;
272
- private renderPanelBorder;
273
- private renderCardBorder;
274
- private renderTable;
275
- private renderChartPlaceholder;
276
- private renderText;
277
- private renderLabel;
278
- private renderCode;
279
- private renderTextarea;
280
- private renderSelect;
281
- private renderCheckbox;
282
- private renderRadio;
283
- private renderToggle;
284
- private renderSidebar;
285
- private renderTabs;
286
- private renderDivider;
287
- private renderAlert;
288
- private renderBadge;
289
- private renderModal;
290
- private renderList;
291
- private renderGenericComponent;
292
- private renderStatCard;
293
- private renderImage;
294
- private renderBreadcrumbs;
295
- private renderSidebarMenu;
296
- private renderIcon;
297
- private renderIconButton;
580
+ protected calculateContentHeight(): number;
581
+ protected renderNode(nodeId: string, output: string[]): void;
582
+ protected renderComponent(node: IRComponentNode, pos: {
583
+ x: number;
584
+ y: number;
585
+ width: number;
586
+ height: number;
587
+ }): string;
588
+ protected renderHeading(node: IRComponentNode, pos: any): string;
589
+ protected renderButton(node: IRComponentNode, pos: any): string;
590
+ protected renderLink(node: IRComponentNode, pos: any): string;
591
+ protected renderInput(node: IRComponentNode, pos: any): string;
592
+ protected renderTopbar(node: IRComponentNode, pos: any): string;
593
+ protected renderPanelBorder(node: IRNode, pos: any, output: string[]): void;
594
+ protected renderCardBorder(node: IRNode, pos: any, output: string[]): void;
595
+ protected renderTable(node: IRComponentNode, pos: any): string;
596
+ protected renderChartPlaceholder(node: IRComponentNode, pos: any): string;
597
+ protected renderText(node: IRComponentNode, pos: any): string;
598
+ protected renderLabel(node: IRComponentNode, pos: any): string;
599
+ protected renderCode(node: IRComponentNode, pos: any): string;
600
+ protected renderTextarea(node: IRComponentNode, pos: any): string;
601
+ protected renderSelect(node: IRComponentNode, pos: any): string;
602
+ protected renderCheckbox(node: IRComponentNode, pos: any): string;
603
+ protected renderRadio(node: IRComponentNode, pos: any): string;
604
+ protected renderToggle(node: IRComponentNode, pos: any): string;
605
+ protected renderSidebar(node: IRComponentNode, pos: any): string;
606
+ protected renderTabs(node: IRComponentNode, pos: any): string;
607
+ protected renderDivider(node: IRComponentNode, pos: any): string;
608
+ protected renderSeparate(node: IRComponentNode, _pos: any): string;
609
+ protected renderAlert(node: IRComponentNode, pos: any): string;
610
+ protected renderBadge(node: IRComponentNode, pos: any): string;
611
+ protected renderModal(node: IRComponentNode, pos: any): string;
612
+ protected renderList(node: IRComponentNode, pos: any): string;
613
+ protected renderGenericComponent(node: IRComponentNode, pos: any): string;
614
+ protected renderStatCard(node: IRComponentNode, pos: any): string;
615
+ protected renderImage(node: IRComponentNode, pos: any): string;
616
+ protected renderBreadcrumbs(node: IRComponentNode, pos: any): string;
617
+ protected renderSidebarMenu(node: IRComponentNode, pos: any): string;
618
+ protected renderIcon(node: IRComponentNode, pos: any): string;
619
+ protected renderIconButton(node: IRComponentNode, pos: any): string;
298
620
  /**
299
621
  * Extract SVG path/element content from a full SVG string
300
622
  * Removes the outer <svg> tag but keeps the content
301
623
  */
302
- private extractSvgContent;
303
- private resolveSpacing;
304
- private escapeXml;
624
+ protected extractSvgContent(svgString: string): string;
625
+ protected resolveVariantColor(variant: string, fallback: string): string;
626
+ protected resolveAccentColor(): string;
627
+ protected resolveControlColor(): string;
628
+ protected resolveChartColor(): string;
629
+ protected getSemanticVariantColor(variant: string): string | undefined;
630
+ protected hexToRgba(hex: string, alpha: number): string;
631
+ protected getIconSize(size?: string): number;
632
+ protected getIconButtonSize(size?: string): number;
633
+ protected resolveSpacing(spacing?: string): number;
634
+ protected wrapTextToLines(text: string, maxWidth: number, fontSize: number): string[];
635
+ protected clampControlWidth(idealWidth: number, availableWidth: number): number;
636
+ protected truncateTextToWidth(text: string, maxWidth: number, fontSize: number): string;
637
+ protected estimateTextWidth(text: string, fontSize: number): number;
638
+ protected generateUpwardTrendValues(count: number, start: number, end: number): number[];
639
+ protected getControlLabelOffset(label: string): number;
640
+ protected getControlLabelBaselineY(y: number): number;
641
+ protected getHeadingTypography(node: IRComponentNode): {
642
+ fontSize: number;
643
+ fontWeight: number;
644
+ lineHeight: number;
645
+ };
646
+ protected getHeadingFirstLineY(node: IRComponentNode, pos: {
647
+ y: number;
648
+ height: number;
649
+ }, fontSize: number, lineHeightPx: number, lineCount: number): number;
650
+ protected calculateTopbarLayout(node: IRComponentNode, pos: {
651
+ x: number;
652
+ y: number;
653
+ width: number;
654
+ height: number;
655
+ }, title: string, subtitle: string, actions: string, user: string): {
656
+ hasSubtitle: boolean;
657
+ titleY: number;
658
+ subtitleY: number;
659
+ textX: number;
660
+ titleMaxWidth: number;
661
+ visibleTitle: string;
662
+ visibleSubtitle: string;
663
+ leftIcon: null | {
664
+ badgeX: number;
665
+ badgeY: number;
666
+ badgeSize: number;
667
+ badgeRadius: number;
668
+ iconX: number;
669
+ iconY: number;
670
+ iconSize: number;
671
+ iconSvg: string;
672
+ };
673
+ actions: Array<{
674
+ x: number;
675
+ y: number;
676
+ width: number;
677
+ height: number;
678
+ label: string;
679
+ }>;
680
+ userBadge: null | {
681
+ x: number;
682
+ y: number;
683
+ width: number;
684
+ height: number;
685
+ label: string;
686
+ };
687
+ avatar: null | {
688
+ cx: number;
689
+ cy: number;
690
+ r: number;
691
+ };
692
+ };
693
+ protected parseBooleanProp(value: unknown, fallback?: boolean): boolean;
694
+ protected escapeXml(text: string): string;
695
+ /**
696
+ * Get data-node-id attribute string for SVG elements
697
+ * Enables bidirectional selection between code and canvas
698
+ */
699
+ protected getDataNodeId(node: IRComponentNode | IRContainerNode): string;
305
700
  }
306
701
  declare function renderToSVG(ir: IRContract, layout: LayoutResult, options?: SVGRenderOptions): string;
307
702
  declare function createSVGElement(tag: string, attrs: Record<string, string | number>, children?: string[]): string;
308
703
  declare function buildSVG(component: SVGComponent): string;
309
704
 
705
+ /**
706
+ * Skeleton SVG Renderer
707
+ *
708
+ * Renders wireframes in a skeleton/loading state style:
709
+ * - Text/Heading: Gray rectangular blocks instead of text
710
+ * - Buttons: Shape outline only (no text, no fill)
711
+ * - Icons: Hidden completely
712
+ * - All text content: Gray blocks instead of actual text
713
+ *
714
+ * Used for:
715
+ * - Loading states
716
+ * - Content placeholders
717
+ * - Wireframe presentations without actual content
718
+ */
719
+
720
+ declare class SkeletonSVGRenderer extends SVGRenderer {
721
+ /**
722
+ * Render button with same appearance as standard but without text
723
+ */
724
+ protected renderButton(node: IRComponentNode, pos: any): string;
725
+ /**
726
+ * Render link as placeholder block + underline (no text)
727
+ */
728
+ protected renderLink(node: IRComponentNode, pos: any): string;
729
+ /**
730
+ * Render heading as gray block
731
+ */
732
+ protected renderHeading(node: IRComponentNode, pos: any): string;
733
+ /**
734
+ * Render text as gray block
735
+ */
736
+ protected renderText(node: IRComponentNode, pos: any): string;
737
+ /**
738
+ * Render label as gray block
739
+ */
740
+ protected renderLabel(node: IRComponentNode, pos: any): string;
741
+ /**
742
+ * Render badge as shape only (no text)
743
+ */
744
+ protected renderBadge(node: IRComponentNode, pos: any): string;
745
+ /**
746
+ * Render alert as shape with gray block instead of message
747
+ */
748
+ protected renderAlert(node: IRComponentNode, pos: any): string;
749
+ /**
750
+ * Render input with gray block for placeholder text
751
+ */
752
+ protected renderInput(node: IRComponentNode, pos: any): string;
753
+ /**
754
+ * Render textarea with gray block for placeholder text
755
+ */
756
+ protected renderTextarea(node: IRComponentNode, pos: any): string;
757
+ /**
758
+ * Render select as shape only (no placeholder text)
759
+ */
760
+ protected renderSelect(node: IRComponentNode, pos: any): string;
761
+ /**
762
+ * Render checkbox as shape only (no label text)
763
+ */
764
+ protected renderCheckbox(node: IRComponentNode, pos: any): string;
765
+ /**
766
+ * Render radio as shape only (no label text)
767
+ */
768
+ protected renderRadio(node: IRComponentNode, pos: any): string;
769
+ /**
770
+ * Render toggle as shape only (no label text)
771
+ */
772
+ protected renderToggle(node: IRComponentNode, pos: any): string;
773
+ /**
774
+ * Render code as shape with gray block instead of code text
775
+ */
776
+ protected renderCode(node: IRComponentNode, pos: any): string;
777
+ /**
778
+ * Render table with gray blocks instead of text
779
+ */
780
+ protected renderTable(node: IRComponentNode, pos: any): string;
781
+ /**
782
+ * Render topbar with gray blocks instead of text
783
+ */
784
+ protected renderTopbar(node: IRComponentNode, pos: any): string;
785
+ /**
786
+ * Render StatCard with gray blocks instead of values
787
+ */
788
+ protected renderStatCard(node: IRComponentNode, pos: any): string;
789
+ /**
790
+ * Render icon as gray square instead of hiding it
791
+ */
792
+ protected renderIcon(node: IRComponentNode, pos: any): string;
793
+ /**
794
+ * Render IconButton with same appearance as standard but without icon
795
+ */
796
+ protected renderIconButton(node: IRComponentNode, pos: any): string;
797
+ /**
798
+ * Render Sidebar with gray blocks instead of text
799
+ */
800
+ protected renderSidebar(node: IRComponentNode, pos: any): string;
801
+ /**
802
+ * Render SidebarMenu with gray blocks instead of text and no icons
803
+ */
804
+ protected renderSidebarMenu(node: IRComponentNode, pos: any): string;
805
+ /**
806
+ * Private helper: Render text as gray block
807
+ */
808
+ private renderTextBlock;
809
+ private renderWrappedLineBlocks;
810
+ }
811
+
812
+ /**
813
+ * Sketch SVG Renderer
814
+ *
815
+ * Renders wireframes in a hand-drawn/sketch style:
816
+ * - Thicker borders with sketch appearance
817
+ * - For variant elements (Button, Badge): colored border instead of fill
818
+ * - Keeps text and icons visible
819
+ * - Traditional wireframe look
820
+ *
821
+ * Used for:
822
+ * - Low-fidelity wireframes
823
+ * - Traditional hand-drawn wireframe presentations
824
+ * - Early design mockups
825
+ */
826
+
827
+ declare class SketchSVGRenderer extends SVGRenderer {
828
+ /**
829
+ * Override render to add sketch filter definitions
830
+ */
831
+ render(): string;
832
+ /**
833
+ * Render button with colored border instead of fill
834
+ */
835
+ protected renderButton(node: IRComponentNode, pos: any): string;
836
+ /**
837
+ * Render badge with colored border instead of fill
838
+ */
839
+ protected renderBadge(node: IRComponentNode, pos: any): string;
840
+ /**
841
+ * Render IconButton with colored border instead of fill
842
+ */
843
+ protected renderIconButton(node: IRComponentNode, pos: any): string;
844
+ /**
845
+ * Render alert with colored border
846
+ */
847
+ protected renderAlert(node: IRComponentNode, pos: any): string;
848
+ /**
849
+ * Render input with thicker border
850
+ */
851
+ protected renderInput(node: IRComponentNode, pos: any): string;
852
+ /**
853
+ * Render textarea with thicker border
854
+ */
855
+ protected renderTextarea(node: IRComponentNode, pos: any): string;
856
+ /**
857
+ * Render card with thicker border and sketch filter
858
+ */
859
+ protected renderCard(node: IRComponentNode, pos: any): string;
860
+ /**
861
+ * Render panel with thicker border and sketch filter
862
+ */
863
+ protected renderPanel(node: IRComponentNode, pos: any): string;
864
+ /**
865
+ * Render heading with sketch filter and Comic Sans
866
+ */
867
+ protected renderHeading(node: IRComponentNode, pos: any): string;
868
+ /**
869
+ * Render topbar with sketch filter and Comic Sans
870
+ */
871
+ protected renderTopbar(node: IRComponentNode, pos: any): string;
872
+ /**
873
+ * Render table with sketch filter and Comic Sans
874
+ */
875
+ protected renderTable(node: IRComponentNode, pos: any): string;
876
+ /**
877
+ * Render text with Comic Sans
878
+ */
879
+ protected renderText(node: IRComponentNode, pos: any): string;
880
+ /**
881
+ * Render label with Comic Sans
882
+ */
883
+ protected renderLabel(node: IRComponentNode, pos: any): string;
884
+ /**
885
+ * Render code with sketch filter and Comic Sans
886
+ */
887
+ protected renderCode(node: IRComponentNode, pos: any): string;
888
+ /**
889
+ * Render select with sketch filter and Comic Sans
890
+ */
891
+ protected renderSelect(node: IRComponentNode, pos: any): string;
892
+ /**
893
+ * Render checkbox with sketch filter and Comic Sans
894
+ */
895
+ protected renderCheckbox(node: IRComponentNode, pos: any): string;
896
+ /**
897
+ * Render radio with sketch filter and Comic Sans
898
+ */
899
+ protected renderRadio(node: IRComponentNode, pos: any): string;
900
+ /**
901
+ * Render toggle with sketch filter and Comic Sans
902
+ */
903
+ protected renderToggle(node: IRComponentNode, pos: any): string;
904
+ /**
905
+ * Render sidebar with sketch filter and Comic Sans
906
+ */
907
+ protected renderSidebar(node: IRComponentNode, pos: any): string;
908
+ /**
909
+ * Render tabs with sketch filter and Comic Sans
910
+ */
911
+ protected renderTabs(node: IRComponentNode, pos: any): string;
912
+ /**
913
+ * Render divider with sketch filter
914
+ */
915
+ protected renderDivider(node: IRComponentNode, pos: any): string;
916
+ /**
917
+ * Render modal with sketch filter and Comic Sans
918
+ */
919
+ protected renderModal(node: IRComponentNode, pos: any): string;
920
+ /**
921
+ * Render list with sketch filter and Comic Sans
922
+ */
923
+ protected renderList(node: IRComponentNode, pos: any): string;
924
+ /**
925
+ * Render generic component with sketch filter and Comic Sans
926
+ */
927
+ protected renderGenericComponent(node: IRComponentNode, pos: any): string;
928
+ /**
929
+ * Render stat card with sketch filter and Comic Sans
930
+ */
931
+ protected renderStatCard(node: IRComponentNode, pos: any): string;
932
+ /**
933
+ * Render image with sketch filter
934
+ */
935
+ protected renderImage(node: IRComponentNode, pos: any): string;
936
+ /**
937
+ * Render breadcrumbs with Comic Sans
938
+ */
939
+ protected renderBreadcrumbs(node: IRComponentNode, pos: any): string;
940
+ /**
941
+ * Render sidebar menu with sketch filter and Comic Sans
942
+ */
943
+ protected renderSidebarMenu(node: IRComponentNode, pos: any): string;
944
+ /**
945
+ * Render icon (same as base, icons don't need filter)
946
+ */
947
+ protected renderIcon(node: IRComponentNode, pos: any): string;
948
+ /**
949
+ * Render chart placeholder with sketch filter and Comic Sans
950
+ */
951
+ protected renderChartPlaceholder(node: IRComponentNode, pos: any): string;
952
+ /**
953
+ * Helper method to get icon SVG
954
+ */
955
+ private getIconSvg;
956
+ }
957
+
958
+ /**
959
+ * Content-based hash generation for stable NodeIds
960
+ *
961
+ * Browser-safe implementation (no crypto/node dependencies)
962
+ * Generates stable IDs that persist across parses if code hasn't changed
963
+ *
964
+ * **Uniqueness Strategy:**
965
+ * Includes `indexInParent` in the hash to ensure identical components
966
+ * in the same parent get unique IDs. This prevents nodeId collisions for:
967
+ *
968
+ * ```wire
969
+ * layout stack {
970
+ * component Button text: "Click" // index 0
971
+ * component Button text: "Click" // index 1
972
+ * component Button text: "Click" // index 2
973
+ * }
974
+ * ```
975
+ *
976
+ * Each button will have a different nodeId despite being identical.
977
+ */
978
+
979
+ /**
980
+ * Generate a stable, content-based node ID
981
+ *
982
+ * The ID is based on:
983
+ * - File path (distinguishes nodes across files)
984
+ * - Line and column (unique position in file)
985
+ * - Node type (component, layout, screen, etc.)
986
+ * - Index in parent array (distinguishes identical siblings)
987
+ * - Optional name (for named nodes like screens, defined components)
988
+ *
989
+ * Examples:
990
+ * - "node-k7m2p9-component" (Icon component at Main.wire:5:4, index 0)
991
+ * - "node-abc123-screen" (Main screen at Main.wire:2:0, index 0)
992
+ * - "node-xyz789-layout" (stack layout at Main.wire:3:2, index 0)
993
+ *
994
+ * @param type - Type of AST node
995
+ * @param filePath - Source file path
996
+ * @param line - Line number (1-based)
997
+ * @param column - Column number (0-based)
998
+ * @param indexInParent - Index in parent's child array (0-based)
999
+ * @param name - Optional name (for screens, defined components)
1000
+ * @returns Stable node ID
1001
+ */
1002
+ declare function generateStableNodeId(type: SourceMapNodeType, filePath: string, line: number, column: number, indexInParent: number, name?: string): string;
1003
+ /**
1004
+ * Validate if a string looks like a valid node ID
1005
+ *
1006
+ * @param id - String to validate
1007
+ * @returns true if it matches the node ID pattern
1008
+ */
1009
+ declare function isValidNodeId(id: string): boolean;
1010
+ /**
1011
+ * Extract node type from a node ID
1012
+ *
1013
+ * @param nodeId - Node ID to parse
1014
+ * @returns The node type, or null if invalid
1015
+ */
1016
+ declare function getTypeFromNodeId(nodeId: string): SourceMapNodeType | null;
1017
+
1018
+ /**
1019
+ * SourceMapBuilder
1020
+ *
1021
+ * Constructs SourceMap during AST traversal using semantic node IDs.
1022
+ *
1023
+ * **ID Strategy:**
1024
+ * - Uses human-readable, semantic IDs: `{type}-{subtype}-{counter}`
1025
+ * - Counter is per type-subtype to keep indices small
1026
+ * - Examples: `component-button-0`, `layout-stack-1`, `screen-0`
1027
+ *
1028
+ * **Stability:**
1029
+ * - IDs remain stable when editing properties ✅
1030
+ * - IDs change when reordering nodes ⚠️
1031
+ * - Ideal for editor UX where property editing is frequent
1032
+ *
1033
+ * **Generated IDs:**
1034
+ * - `project` - Single project (no counter)
1035
+ * - `screen-{n}` - Screens by index (0-based)
1036
+ * - `component-{type}-{n}` - Components by subtype (button-0, input-0, etc.)
1037
+ * - `layout-{type}-{n}` - Layouts by subtype (stack-0, grid-1, etc.)
1038
+ * - `cell-{n}` - Grid cells by index
1039
+ * - `define-{name}` - Component definitions by name
1040
+ */
1041
+
1042
+ /**
1043
+ * Builder for constructing SourceMap entries during parsing
1044
+ * Uses semantic IDs based on type-subtype-counter (e.g., component-button-0)
1045
+ */
1046
+ declare class SourceMapBuilder {
1047
+ private entries;
1048
+ private filePath;
1049
+ private sourceCode;
1050
+ private parentStack;
1051
+ private counters;
1052
+ constructor(filePath?: string, sourceCode?: string);
1053
+ /**
1054
+ * Add a node to the SourceMap
1055
+ * Generates semantic IDs like: project, screen-0, component-button-1, layout-stack-0
1056
+ *
1057
+ * @param type - Type of AST node
1058
+ * @param tokens - Captured tokens from parser
1059
+ * @param metadata - Optional metadata (name, layoutType, componentType)
1060
+ * @returns Generated nodeId
1061
+ */
1062
+ addNode(type: SourceMapNodeType, tokens: CapturedTokens, metadata?: {
1063
+ name?: string;
1064
+ layoutType?: string;
1065
+ componentType?: string;
1066
+ isUserDefined?: boolean;
1067
+ }): string;
1068
+ /**
1069
+ * Generate semantic node ID based on type and subtype
1070
+ * Format: {type}-{subtype}-{counter} or {type}-{counter}
1071
+ *
1072
+ * Examples:
1073
+ * - project → "project"
1074
+ * - theme → "theme"
1075
+ * - mocks → "mocks"
1076
+ * - colors → "colors"
1077
+ * - screen → "screen-0", "screen-1"
1078
+ * - component Button → "component-button-0", "component-button-1"
1079
+ * - layout stack → "layout-stack-0", "layout-stack-1"
1080
+ * - cell → "cell-0", "cell-1"
1081
+ * - component-definition → "define-MyButton"
1082
+ */
1083
+ private generateNodeId;
1084
+ /**
1085
+ * Add a property to an existing node in the SourceMap
1086
+ * Captures precise ranges for property name and value for surgical editing
1087
+ *
1088
+ * @param nodeId - ID of the node that owns this property
1089
+ * @param propertyName - Name of the property (e.g., "text", "direction")
1090
+ * @param propertyValue - Parsed value of the property
1091
+ * @param tokens - Captured tokens for the property
1092
+ * @returns The PropertySourceMap entry created
1093
+ */
1094
+ addProperty(nodeId: string, propertyName: string, propertyValue: any, tokens: {
1095
+ name?: any;
1096
+ value?: any;
1097
+ separator?: any;
1098
+ full?: any;
1099
+ }): PropertySourceMap;
1100
+ /**
1101
+ * Push a parent onto the stack (when entering a container node)
1102
+ */
1103
+ pushParent(nodeId: string): void;
1104
+ /**
1105
+ * Pop a parent from the stack (when exiting a container node)
1106
+ */
1107
+ popParent(): void;
1108
+ /**
1109
+ * Get the current parent nodeId (or null if at root)
1110
+ */
1111
+ getCurrentParent(): string | null;
1112
+ /**
1113
+ * Build and return the final SourceMap
1114
+ */
1115
+ build(): SourceMapEntry[];
1116
+ /**
1117
+ * Calculate insertionPoints for all container nodes
1118
+ * Container nodes: project, screen, layout, cell, component-definition
1119
+ */
1120
+ private calculateAllInsertionPoints;
1121
+ /**
1122
+ * Calculate CodeRange from captured tokens
1123
+ * Finds the earliest start and latest end among all tokens
1124
+ */
1125
+ private calculateRange;
1126
+ /**
1127
+ * Convert a single token to CodeRange
1128
+ */
1129
+ private tokenToRange;
1130
+ /**
1131
+ * Calculate body range from closing brace token
1132
+ * Body range typically spans from opening brace to closing brace
1133
+ */
1134
+ private calculateBodyRange;
1135
+ /**
1136
+ * Extract the first real token from a CST node (earliest by offset)
1137
+ * Recursively searches through children to find the token with smallest offset
1138
+ */
1139
+ private getFirstToken;
1140
+ /**
1141
+ * Extract the last real token from a CST node (latest by offset)
1142
+ * Recursively searches through children to find the token with largest offset
1143
+ */
1144
+ private getLastToken;
1145
+ /**
1146
+ * Extract start position from a Chevrotain token or CST node
1147
+ */
1148
+ private getTokenStart;
1149
+ /**
1150
+ * Extract end position from a Chevrotain token or CST node
1151
+ */
1152
+ private getTokenEnd;
1153
+ /**
1154
+ * Reset the builder (for reuse)
1155
+ */
1156
+ reset(filePath?: string, sourceCode?: string): void;
1157
+ /**
1158
+ * Calculate insertion point for adding new children to a container node
1159
+ *
1160
+ * Strategy:
1161
+ * - If node has children: insert after last child, preserve indentation
1162
+ * - If node is empty: insert inside body, use parent indentation + 2 spaces
1163
+ *
1164
+ * @param nodeId - ID of the container node
1165
+ * @returns InsertionPoint with line, column, indentation, and optional after
1166
+ */
1167
+ calculateInsertionPoint(nodeId: string): {
1168
+ line: number;
1169
+ column: number;
1170
+ indentation: string;
1171
+ after?: string;
1172
+ } | undefined;
1173
+ /**
1174
+ * Extract indentation (leading whitespace) from a line
1175
+ */
1176
+ private extractIndentation;
1177
+ }
1178
+
1179
+ /**
1180
+ * SourceMapResolver
1181
+ *
1182
+ * Provides query APIs for bidirectional code↔canvas selection
1183
+ *
1184
+ * **Use Cases:**
1185
+ * 1. Canvas → Code: Click SVG element with data-node-id → find code location
1186
+ * 2. Code → Canvas: Click code position → find corresponding node
1187
+ *
1188
+ * **Performance:**
1189
+ * - getNodeById: O(1) with Map index
1190
+ * - getNodeByPosition: O(n) linear search (could be optimized with interval tree)
1191
+ * - getChildren/getParent: O(1) with indexes
1192
+ */
1193
+
1194
+ /**
1195
+ * Query result for position-based lookups
1196
+ */
1197
+ interface PositionQueryResult extends SourceMapEntry {
1198
+ depth: number;
1199
+ }
1200
+ /**
1201
+ * SourceMap query and navigation API
1202
+ */
1203
+ declare class SourceMapResolver {
1204
+ private nodeMap;
1205
+ private childrenMap;
1206
+ private positionIndex;
1207
+ constructor(sourceMap: SourceMapEntry[]);
1208
+ /**
1209
+ * Find node by ID (Canvas → Code)
1210
+ *
1211
+ * @example
1212
+ * // User clicks SVG element with data-node-id="component-button-0"
1213
+ * const node = resolver.getNodeById("component-button-0");
1214
+ * editor.revealRange(node.range); // Jump to code
1215
+ */
1216
+ getNodeById(nodeId: string): SourceMapEntry | null;
1217
+ /**
1218
+ * Find node at position (Code → Canvas)
1219
+ * Returns the most specific (deepest) node containing the position
1220
+ *
1221
+ * @example
1222
+ * // User clicks code at line 5, column 10
1223
+ * const node = resolver.getNodeByPosition(5, 10);
1224
+ * canvas.highlightElement(node.nodeId); // Highlight in canvas
1225
+ */
1226
+ getNodeByPosition(line: number, column: number): SourceMapEntry | null;
1227
+ /**
1228
+ * Get all child nodes of a parent
1229
+ *
1230
+ * @example
1231
+ * const children = resolver.getChildren("layout-stack-0");
1232
+ * // Returns: [component-button-0, component-input-0, ...]
1233
+ */
1234
+ getChildren(nodeId: string): SourceMapEntry[];
1235
+ /**
1236
+ * Get parent node
1237
+ *
1238
+ * @example
1239
+ * const parent = resolver.getParent("component-button-0");
1240
+ * // Returns: layout-stack-0
1241
+ */
1242
+ getParent(nodeId: string): SourceMapEntry | null;
1243
+ /**
1244
+ * Get all nodes in the SourceMap
1245
+ */
1246
+ getAllNodes(): SourceMapEntry[];
1247
+ /**
1248
+ * Get all nodes of a specific type
1249
+ *
1250
+ * @example
1251
+ * const buttons = resolver.getNodesByType("component", "Button");
1252
+ */
1253
+ getNodesByType(type: SourceMapEntry['type'], subtype?: string): SourceMapEntry[];
1254
+ /**
1255
+ * Get siblings of a node (nodes with same parent)
1256
+ */
1257
+ getSiblings(nodeId: string): SourceMapEntry[];
1258
+ /**
1259
+ * Get path from root to node (breadcrumb)
1260
+ *
1261
+ * @example
1262
+ * const path = resolver.getPath("component-button-0");
1263
+ * // Returns: [project, screen-0, layout-stack-0, component-button-0]
1264
+ */
1265
+ getPath(nodeId: string): SourceMapEntry[];
1266
+ /**
1267
+ * Check if a position is within a node's range
1268
+ */
1269
+ private containsPosition;
1270
+ /**
1271
+ * Calculate depth of a node in the tree (0 = root)
1272
+ */
1273
+ private calculateDepth;
1274
+ /**
1275
+ * Get statistics about the SourceMap
1276
+ */
1277
+ getStats(): {
1278
+ totalNodes: number;
1279
+ byType: Record<string, number>;
1280
+ maxDepth: number;
1281
+ };
1282
+ }
1283
+
310
1284
  declare const version = "0.0.1";
311
1285
 
312
- export { type AST, type ASTCell, type ASTComponent, type ASTDefinedComponent, type ASTLayout, type ASTScreen, type IRComponent, type IRComponentNode, type IRContainerNode, type IRContract, IRGenerator, type IRLayout, type IRMeta, type IRMetadata, type IRNode, type IRProject, type IRScreen, type IRStyle, type IRTheme, type IRWireframe, LayoutEngine, type LayoutPosition, type LayoutResult, type ParsedComponent, type ParsedWireframe, type SVGComponent, type SVGRenderOptions, SVGRenderer, buildSVG, calculateLayout, createSVGElement, generateIR, parseWireDSL, renderToSVG, resolveGridPosition, version };
1286
+ export { type AST, type ASTCell, type ASTComponent, type ASTDefinedComponent, type ASTLayout, type ASTScreen, type CapturedTokens, type CodeRange, DENSITY_TOKENS, DEVICE_PRESETS, type DesignTokens, type DevicePreset, type IRComponent, type IRComponentNode, type IRContainerNode, type IRContract, IRGenerator, type IRLayout, type IRMeta, type IRMetadata, type IRNode, type IRNodeStyle, type IRProject, type IRScreen, type IRStyle, type IRWireframe, type InsertionPoint, LayoutEngine, type LayoutPosition, type LayoutResult, type ParseError, type ParseResult, type ParsedComponent, type ParsedWireframe, type Position, type PositionQueryResult, type PropertySourceMap, type SVGComponent, type SVGRenderOptions, SVGRenderer, SkeletonSVGRenderer, SketchSVGRenderer, SourceMapBuilder, type SourceMapEntry, type SourceMapNodeType, SourceMapResolver, buildSVG, calculateLayout, createSVGElement, generateIR, generateStableNodeId, getTypeFromNodeId, isValidDevice, isValidNodeId, parseWireDSL, parseWireDSLWithSourceMap, renderToSVG, resolveDevicePreset, resolveGridPosition, resolveTokens, version };