@wireweave/core 1.0.0-beta.20260107130839 → 1.0.0-beta.20260107133417

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.
@@ -1,392 +0,0 @@
1
- /**
2
- * Layout Renderer for wireweave
3
- *
4
- * Dedicated renderers for layout and grid components:
5
- * - Row/Col grid system with responsive breakpoints
6
- * - Semantic layout elements (header, main, footer, sidebar, section)
7
- * - Flexbox utilities
8
- */
9
-
10
- import type {
11
- AnyNode,
12
- RowNode,
13
- ColNode,
14
- HeaderNode,
15
- MainNode,
16
- FooterNode,
17
- SidebarNode,
18
- SectionNode,
19
- CommonProps,
20
- WidthValue,
21
- HeightValue,
22
- } from '../../ast/types';
23
- import type { RenderContext, ThemeConfig } from '../types';
24
-
25
- /**
26
- * Type for node renderer callback
27
- */
28
- export type NodeRenderer = (node: AnyNode) => string;
29
-
30
- /**
31
- * Type for children renderer callback
32
- */
33
- export type ChildrenRenderer = (children: AnyNode[]) => string;
34
-
35
- /**
36
- * Layout node types
37
- */
38
- export type LayoutNodeType = 'Row' | 'Col' | 'Header' | 'Main' | 'Footer' | 'Sidebar' | 'Section';
39
-
40
- /**
41
- * Check if a node type is a layout node
42
- */
43
- export function isLayoutNodeType(type: string): type is LayoutNodeType {
44
- return ['Row', 'Col', 'Header', 'Main', 'Footer', 'Sidebar', 'Section'].includes(type);
45
- }
46
-
47
- /**
48
- * Render a layout node
49
- */
50
- export function renderLayoutNode(
51
- node: AnyNode,
52
- context: RenderContext,
53
- renderChildren: ChildrenRenderer,
54
- escapeHtml: (text: string) => string
55
- ): string {
56
- switch (node.type) {
57
- case 'Row':
58
- return renderRow(node as RowNode, context, renderChildren);
59
- case 'Col':
60
- return renderCol(node as ColNode, context, renderChildren);
61
- case 'Header':
62
- return renderHeader(node as HeaderNode, context, renderChildren);
63
- case 'Main':
64
- return renderMain(node as MainNode, context, renderChildren);
65
- case 'Footer':
66
- return renderFooter(node as FooterNode, context, renderChildren);
67
- case 'Sidebar':
68
- return renderSidebar(node as SidebarNode, context, renderChildren);
69
- case 'Section':
70
- return renderSection(node as SectionNode, context, renderChildren, escapeHtml);
71
- default:
72
- return '';
73
- }
74
- }
75
-
76
- // ===========================================
77
- // Row Renderer
78
- // ===========================================
79
-
80
- function renderRow(
81
- node: RowNode,
82
- context: RenderContext,
83
- renderChildren: ChildrenRenderer
84
- ): string {
85
- const prefix = context.options.classPrefix;
86
- const classes = buildRowClasses(node, prefix);
87
- const styles = buildRowStyles(node, context.theme);
88
- const children = renderChildren(node.children);
89
-
90
- const styleAttr = styles ? ` style="${styles}"` : '';
91
- return `<div class="${classes}"${styleAttr}>\n${children}\n</div>`;
92
- }
93
-
94
- function buildRowClasses(node: RowNode, prefix: string): string {
95
- const classes: string[] = [`${prefix}-row`];
96
-
97
- // Flex properties
98
- if (node.flex === true) classes.push(`${prefix}-flex`);
99
- if (typeof node.flex === 'number') classes.push(`${prefix}-flex-${node.flex}`);
100
- if (node.direction === 'row') classes.push(`${prefix}-flex-row`);
101
- if (node.direction === 'column') classes.push(`${prefix}-flex-col`);
102
- if (node.direction === 'row-reverse') classes.push(`${prefix}-flex-row-reverse`);
103
- if (node.direction === 'column-reverse') classes.push(`${prefix}-flex-col-reverse`);
104
- if (node.justify) classes.push(`${prefix}-justify-${node.justify}`);
105
- if (node.align) classes.push(`${prefix}-align-${node.align}`);
106
- if (node.wrap === true) classes.push(`${prefix}-flex-wrap`);
107
- if (node.wrap === 'nowrap') classes.push(`${prefix}-flex-nowrap`);
108
- if (node.gap !== undefined) classes.push(`${prefix}-gap-${node.gap}`);
109
-
110
- // Spacing classes
111
- addSpacingClasses(classes, node, prefix);
112
-
113
- return classes.join(' ');
114
- }
115
-
116
- function buildRowStyles(_node: RowNode, _theme: ThemeConfig): string {
117
- const styles: string[] = [];
118
-
119
- // Custom gap as inline style (for non-standard values)
120
- // Standard gap values are handled by classes
121
-
122
- return styles.join('; ');
123
- }
124
-
125
- // ===========================================
126
- // Col Renderer
127
- // ===========================================
128
-
129
- function renderCol(
130
- node: ColNode,
131
- context: RenderContext,
132
- renderChildren: ChildrenRenderer
133
- ): string {
134
- const prefix = context.options.classPrefix;
135
- const classes = buildColClasses(node, prefix);
136
- const styles = buildColStyles(node, context.theme);
137
- const children = renderChildren(node.children);
138
-
139
- const styleAttr = styles ? ` style="${styles}"` : '';
140
- return `<div class="${classes}"${styleAttr}>\n${children}\n</div>`;
141
- }
142
-
143
- function buildColClasses(node: ColNode, prefix: string): string {
144
- const classes: string[] = [`${prefix}-col`];
145
-
146
- // Grid span classes
147
- if (node.span !== undefined) classes.push(`${prefix}-col-${node.span}`);
148
-
149
- // Responsive breakpoint classes
150
- if (node.sm !== undefined) classes.push(`${prefix}-col-sm-${node.sm}`);
151
- if (node.md !== undefined) classes.push(`${prefix}-col-md-${node.md}`);
152
- if (node.lg !== undefined) classes.push(`${prefix}-col-lg-${node.lg}`);
153
- if (node.xl !== undefined) classes.push(`${prefix}-col-xl-${node.xl}`);
154
-
155
- // Flex properties
156
- if (node.flex === true) classes.push(`${prefix}-flex`);
157
- if (typeof node.flex === 'number') classes.push(`${prefix}-flex-${node.flex}`);
158
- if (node.direction === 'row') classes.push(`${prefix}-flex-row`);
159
- if (node.direction === 'column') classes.push(`${prefix}-flex-col`);
160
- if (node.justify) classes.push(`${prefix}-justify-${node.justify}`);
161
- if (node.align) classes.push(`${prefix}-align-${node.align}`);
162
-
163
- // Size classes (for predefined values)
164
- if (node.w === 'full') classes.push(`${prefix}-w-full`);
165
- if (node.w === 'auto') classes.push(`${prefix}-w-auto`);
166
- if (node.w === 'fit') classes.push(`${prefix}-w-fit`);
167
- if (node.h === 'full') classes.push(`${prefix}-h-full`);
168
- if (node.h === 'auto') classes.push(`${prefix}-h-auto`);
169
- if (node.h === 'screen') classes.push(`${prefix}-h-screen`);
170
-
171
- // Spacing classes
172
- addSpacingClasses(classes, node, prefix);
173
-
174
- return classes.join(' ');
175
- }
176
-
177
- function buildColStyles(node: ColNode, _theme: ThemeConfig): string {
178
- const styles: string[] = [];
179
-
180
- // Width (numeric values as inline style)
181
- if (typeof node.w === 'number') {
182
- styles.push(`width: ${node.w}px`);
183
- } else if (typeof node.w === 'string' && !['full', 'auto', 'fit', 'screen'].includes(node.w)) {
184
- styles.push(`width: ${resolveSizeValue(node.w)}`);
185
- }
186
-
187
- // Height (numeric values as inline style)
188
- if (typeof node.h === 'number') {
189
- styles.push(`height: ${node.h}px`);
190
- } else if (typeof node.h === 'string' && !['full', 'auto', 'screen'].includes(node.h)) {
191
- styles.push(`height: ${resolveSizeValue(node.h)}`);
192
- }
193
-
194
- // Order
195
- if (node.order !== undefined) {
196
- styles.push(`order: ${node.order}`);
197
- }
198
-
199
- return styles.join('; ');
200
- }
201
-
202
- // ===========================================
203
- // Semantic Layout Renderers
204
- // ===========================================
205
-
206
- function renderHeader(
207
- node: HeaderNode,
208
- context: RenderContext,
209
- renderChildren: ChildrenRenderer
210
- ): string {
211
- const prefix = context.options.classPrefix;
212
- const classes = buildSemanticClasses('header', node, prefix);
213
- const styles = buildSemanticStyles(node, context.theme);
214
- const children = renderChildren(node.children);
215
-
216
- // Add no-border class if border is false
217
- const borderClass = node.border === false ? ` ${prefix}-no-border` : '';
218
- const styleAttr = styles ? ` style="${styles}"` : '';
219
-
220
- return `<header class="${classes}${borderClass}"${styleAttr}>\n${children}\n</header>`;
221
- }
222
-
223
- function renderMain(
224
- node: MainNode,
225
- context: RenderContext,
226
- renderChildren: ChildrenRenderer
227
- ): string {
228
- const prefix = context.options.classPrefix;
229
- const classes = buildSemanticClasses('main', node, prefix);
230
- const styles = buildSemanticStyles(node, context.theme);
231
- const children = renderChildren(node.children);
232
-
233
- const styleAttr = styles ? ` style="${styles}"` : '';
234
- return `<main class="${classes}"${styleAttr}>\n${children}\n</main>`;
235
- }
236
-
237
- function renderFooter(
238
- node: FooterNode,
239
- context: RenderContext,
240
- renderChildren: ChildrenRenderer
241
- ): string {
242
- const prefix = context.options.classPrefix;
243
- const classes = buildSemanticClasses('footer', node, prefix);
244
- const styles = buildSemanticStyles(node, context.theme);
245
- const children = renderChildren(node.children);
246
-
247
- // Add no-border class if border is false
248
- const borderClass = node.border === false ? ` ${prefix}-no-border` : '';
249
- const styleAttr = styles ? ` style="${styles}"` : '';
250
-
251
- return `<footer class="${classes}${borderClass}"${styleAttr}>\n${children}\n</footer>`;
252
- }
253
-
254
- function renderSidebar(
255
- node: SidebarNode,
256
- context: RenderContext,
257
- renderChildren: ChildrenRenderer
258
- ): string {
259
- const prefix = context.options.classPrefix;
260
- const classes: string[] = [`${prefix}-sidebar`];
261
-
262
- // Position class
263
- if (node.position === 'right') {
264
- classes.push(`${prefix}-sidebar-right`);
265
- }
266
-
267
- // Span width (using col classes for width)
268
- if (node.span !== undefined) {
269
- classes.push(`${prefix}-col-${node.span}`);
270
- }
271
-
272
- // Flex properties
273
- if (node.flex === true) classes.push(`${prefix}-flex`);
274
- if (typeof node.flex === 'number') classes.push(`${prefix}-flex-${node.flex}`);
275
- if (node.direction) classes.push(`${prefix}-flex-${node.direction}`);
276
- if (node.justify) classes.push(`${prefix}-justify-${node.justify}`);
277
- if (node.align) classes.push(`${prefix}-align-${node.align}`);
278
-
279
- // Spacing classes
280
- addSpacingClasses(classes, node, prefix);
281
-
282
- const styles = buildSemanticStyles(node, context.theme);
283
- const children = renderChildren(node.children);
284
- const styleAttr = styles ? ` style="${styles}"` : '';
285
-
286
- return `<aside class="${classes.join(' ')}"${styleAttr}>\n${children}\n</aside>`;
287
- }
288
-
289
- function renderSection(
290
- node: SectionNode,
291
- context: RenderContext,
292
- renderChildren: ChildrenRenderer,
293
- escapeHtml: (text: string) => string
294
- ): string {
295
- const prefix = context.options.classPrefix;
296
- const classes = buildSemanticClasses('section', node, prefix);
297
- const styles = buildSemanticStyles(node, context.theme);
298
- const children = renderChildren(node.children);
299
-
300
- const title = node.title
301
- ? `<h2 class="${prefix}-title">${escapeHtml(node.title)}</h2>\n`
302
- : '';
303
- const styleAttr = styles ? ` style="${styles}"` : '';
304
-
305
- return `<section class="${classes}"${styleAttr}>\n${title}${children}\n</section>`;
306
- }
307
-
308
- // ===========================================
309
- // Utility Functions
310
- // ===========================================
311
-
312
- function buildSemanticClasses(tag: string, node: CommonProps, prefix: string): string {
313
- const classes: string[] = [`${prefix}-${tag}`];
314
-
315
- // Flex properties
316
- if (node.flex === true) classes.push(`${prefix}-flex`);
317
- if (typeof node.flex === 'number') classes.push(`${prefix}-flex-${node.flex}`);
318
- if (node.direction === 'row') classes.push(`${prefix}-flex-row`);
319
- if (node.direction === 'column') classes.push(`${prefix}-flex-col`);
320
- if (node.justify) classes.push(`${prefix}-justify-${node.justify}`);
321
- if (node.align) classes.push(`${prefix}-align-${node.align}`);
322
- if (node.gap !== undefined) classes.push(`${prefix}-gap-${node.gap}`);
323
-
324
- // Spacing classes
325
- addSpacingClasses(classes, node, prefix);
326
-
327
- return classes.join(' ');
328
- }
329
-
330
- function buildSemanticStyles(node: CommonProps, _theme: ThemeConfig): string {
331
- const styles: string[] = [];
332
-
333
- // Width
334
- if (typeof node.w === 'number') {
335
- styles.push(`width: ${node.w}px`);
336
- }
337
-
338
- // Height
339
- if (typeof node.h === 'number') {
340
- styles.push(`height: ${node.h}px`);
341
- }
342
-
343
- return styles.join('; ');
344
- }
345
-
346
- function addSpacingClasses(classes: string[], node: CommonProps, prefix: string): void {
347
- // Padding
348
- if (node.p !== undefined) classes.push(`${prefix}-p-${node.p}`);
349
- if (node.px !== undefined) classes.push(`${prefix}-px-${node.px}`);
350
- if (node.py !== undefined) classes.push(`${prefix}-py-${node.py}`);
351
- if (node.pt !== undefined) classes.push(`${prefix}-pt-${node.pt}`);
352
- if (node.pr !== undefined) classes.push(`${prefix}-pr-${node.pr}`);
353
- if (node.pb !== undefined) classes.push(`${prefix}-pb-${node.pb}`);
354
- if (node.pl !== undefined) classes.push(`${prefix}-pl-${node.pl}`);
355
-
356
- // Margin
357
- if (node.m !== undefined) classes.push(`${prefix}-m-${node.m}`);
358
- if (node.mx !== undefined) classes.push(`${prefix}-mx-${node.mx}`);
359
- if (node.my !== undefined) classes.push(`${prefix}-my-${node.my}`);
360
- if (node.mt !== undefined) classes.push(`${prefix}-mt-${node.mt}`);
361
- if (node.mr !== undefined) classes.push(`${prefix}-mr-${node.mr}`);
362
- if (node.mb !== undefined) classes.push(`${prefix}-mb-${node.mb}`);
363
- if (node.ml !== undefined) classes.push(`${prefix}-ml-${node.ml}`);
364
- }
365
-
366
- /**
367
- * Resolve size value to CSS
368
- */
369
- function resolveSizeValue(value: WidthValue | HeightValue): string {
370
- if (typeof value === 'number') {
371
- return `${value}px`;
372
- }
373
-
374
- // ValueWithUnit object: direct CSS value
375
- if (typeof value === 'object' && 'value' in value && 'unit' in value) {
376
- return `${value.value}${value.unit}`;
377
- }
378
-
379
- switch (value) {
380
- case 'full':
381
- return '100%';
382
- case 'auto':
383
- return 'auto';
384
- case 'screen':
385
- return '100vh';
386
- case 'fit':
387
- return 'fit-content';
388
- default:
389
- // Exhaustive check - this should never be reached
390
- return value as string;
391
- }
392
- }
@@ -1,143 +0,0 @@
1
- /**
2
- * Renderer module for wireweave
3
- *
4
- * Provides render functions to convert AST to HTML/CSS and SVG
5
- */
6
-
7
- import type { WireframeDocument } from '../ast/types';
8
- import { createHtmlRenderer } from './html';
9
- import { renderToSvg as renderSvg } from './svg';
10
- import type { RenderOptions, RenderResult, SvgRenderOptions, SvgRenderResult } from './types';
11
- import { resolveViewport } from '../viewport';
12
-
13
- // Re-export types
14
- export * from './types';
15
- export { HtmlRenderer, createHtmlRenderer } from './html';
16
- export { SvgRenderer, createSvgRenderer } from './svg';
17
- export { generateStyles } from './styles';
18
- export { generateComponentStyles } from './styles-components';
19
-
20
- // Re-export icons (ensures they're bundled with renderer)
21
- export { getIconData, renderIconSvg, lucideIcons } from '../icons/lucide-icons';
22
- export type { IconData, IconElement } from '../icons/lucide-icons';
23
-
24
- /**
25
- * Render AST to HTML and CSS
26
- *
27
- * @param document - Parsed wireframe document
28
- * @param options - Render options
29
- * @returns Object containing HTML and CSS strings
30
- */
31
- export function render(document: WireframeDocument, options: RenderOptions = {}): RenderResult {
32
- const renderer = createHtmlRenderer(options);
33
- return renderer.render(document);
34
- }
35
-
36
- /**
37
- * Render AST to standalone HTML with embedded CSS
38
- *
39
- * @param document - Parsed wireframe document
40
- * @param options - Render options
41
- * @returns Complete HTML document string
42
- */
43
- export function renderToHtml(document: WireframeDocument, options: RenderOptions = {}): string {
44
- const { html, css } = render(document, options);
45
-
46
- return `<!DOCTYPE html>
47
- <html lang="en">
48
- <head>
49
- <meta charset="UTF-8">
50
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
51
- <title>Wireframe</title>
52
- <style>
53
- /* Browser centering styles */
54
- html, body {
55
- margin: 0;
56
- padding: 0;
57
- min-height: 100vh;
58
- background: #f4f4f5;
59
- }
60
- body {
61
- display: flex;
62
- justify-content: center;
63
- align-items: center;
64
- padding: 24px;
65
- box-sizing: border-box;
66
- }
67
- ${css}
68
- </style>
69
- </head>
70
- <body>
71
- ${html}
72
- </body>
73
- </html>`;
74
- }
75
-
76
- /**
77
- * Render AST to SVG using foreignObject with HTML+CSS
78
- *
79
- * This approach embeds HTML+CSS inside SVG using foreignObject,
80
- * which allows CSS flexbox/grid layouts to work properly.
81
- *
82
- * @param document - Parsed wireframe document
83
- * @param options - SVG render options
84
- * @returns Object containing SVG string and dimensions
85
- */
86
- export function renderToSvg(
87
- document: WireframeDocument,
88
- options: SvgRenderOptions = {}
89
- ): SvgRenderResult {
90
- // Check for viewport settings in the first page
91
- const firstPage = document.children[0];
92
- let width = options.width ?? 800;
93
- let height = options.height ?? 600;
94
-
95
- // Use page viewport/device if set and no explicit options provided
96
- if (firstPage && options.width === undefined && options.height === undefined) {
97
- if (firstPage.viewport !== undefined || firstPage.device !== undefined) {
98
- const viewport = resolveViewport(firstPage.viewport, firstPage.device);
99
- width = viewport.width;
100
- height = viewport.height;
101
- }
102
- }
103
-
104
- const padding = options.padding ?? 20;
105
- const background = options.background ?? '#ffffff';
106
-
107
- // Use HTML renderer to generate content
108
- const { html, css } = render(document, { theme: 'light' });
109
-
110
- // Build SVG with foreignObject containing HTML+CSS
111
- const svg = `<?xml version="1.0" encoding="UTF-8"?>
112
- <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
113
- viewBox="0 0 ${width} ${height}" width="${width}" height="${height}">
114
- <rect width="100%" height="100%" fill="${background}"/>
115
- <foreignObject x="${padding}" y="${padding}" width="${width - padding * 2}" height="${height - padding * 2}">
116
- <div xmlns="http://www.w3.org/1999/xhtml" style="width: 100%; height: 100%; overflow: hidden;">
117
- <style type="text/css">
118
- ${css}
119
- </style>
120
- ${html}
121
- </div>
122
- </foreignObject>
123
- </svg>`;
124
-
125
- return { svg, width, height };
126
- }
127
-
128
- /**
129
- * Render AST to pure SVG (without foreignObject)
130
- *
131
- * This uses the original SVG renderer with manual layout calculations.
132
- * Use this when foreignObject is not supported.
133
- *
134
- * @param document - Parsed wireframe document
135
- * @param options - SVG render options
136
- * @returns Object containing SVG string and dimensions
137
- */
138
- export function renderToPureSvg(
139
- document: WireframeDocument,
140
- options: SvgRenderOptions = {}
141
- ): SvgRenderResult {
142
- return renderSvg(document, options);
143
- }