@kernlang/react 3.1.6 → 3.1.8
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/artifact-utils.d.ts +1 -1
- package/dist/artifact-utils.js +6 -8
- package/dist/artifact-utils.js.map +1 -1
- package/dist/codegen-react.js +35 -17
- package/dist/codegen-react.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/nextjs-assembler.d.ts +33 -0
- package/dist/nextjs-assembler.js +233 -0
- package/dist/nextjs-assembler.js.map +1 -0
- package/dist/nextjs-imports.d.ts +9 -0
- package/dist/nextjs-imports.js +61 -0
- package/dist/nextjs-imports.js.map +1 -0
- package/dist/nextjs-renderers.d.ts +4 -0
- package/dist/nextjs-renderers.js +628 -0
- package/dist/nextjs-renderers.js.map +1 -0
- package/dist/nextjs-style.d.ts +8 -0
- package/dist/nextjs-style.js +135 -0
- package/dist/nextjs-style.js.map +1 -0
- package/dist/nextjs-types.d.ts +39 -0
- package/dist/nextjs-types.js +2 -0
- package/dist/nextjs-types.js.map +1 -0
- package/dist/structure.d.ts +1 -1
- package/dist/structure.js +56 -22
- package/dist/structure.js.map +1 -1
- package/dist/transpiler-nextjs.d.ts +4 -7
- package/dist/transpiler-nextjs.js +20 -1173
- package/dist/transpiler-nextjs.js.map +1 -1
- package/dist/transpiler-tailwind.d.ts +1 -1
- package/dist/transpiler-tailwind.js +83 -51
- package/dist/transpiler-tailwind.js.map +1 -1
- package/dist/transpiler-web.d.ts +1 -1
- package/dist/transpiler-web.js +33 -19
- package/dist/transpiler-web.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nextjs-imports.js","sourceRoot":"","sources":["../src/nextjs-imports.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
import { colorToTw, escapeJsxAttr, escapeJsxText, getProps, getStyles } from '@kernlang/core';
|
|
2
|
+
import { addDefaultImport, addNamedImport, exprCode, isExpr } from './nextjs-imports.js';
|
|
3
|
+
import { htmlAttrsToJsx, SVG_ICONS, TEXT_TAG_MAP, twClasses } from './nextjs-style.js';
|
|
4
|
+
// ── Node renderers ──────────────────────────────────────────────────────
|
|
5
|
+
export function renderNode(node, ctx, indent) {
|
|
6
|
+
const p = getProps(node);
|
|
7
|
+
ctx.sourceMap.push({
|
|
8
|
+
irLine: node.loc?.line || 0,
|
|
9
|
+
irCol: node.loc?.col || 1,
|
|
10
|
+
outLine: ctx.lines.length + 1,
|
|
11
|
+
outCol: 1,
|
|
12
|
+
});
|
|
13
|
+
switch (node.type) {
|
|
14
|
+
case 'page':
|
|
15
|
+
case 'screen':
|
|
16
|
+
renderPage(node, ctx, indent);
|
|
17
|
+
break;
|
|
18
|
+
case 'layout':
|
|
19
|
+
renderLayout(node, ctx, indent);
|
|
20
|
+
break;
|
|
21
|
+
case 'loading':
|
|
22
|
+
renderLoading(node, ctx, indent);
|
|
23
|
+
break;
|
|
24
|
+
case 'error':
|
|
25
|
+
renderError(node, ctx, indent);
|
|
26
|
+
break;
|
|
27
|
+
case 'metadata':
|
|
28
|
+
renderMetadata(node, ctx);
|
|
29
|
+
break;
|
|
30
|
+
case 'section':
|
|
31
|
+
renderSection(node, ctx, indent);
|
|
32
|
+
break;
|
|
33
|
+
case 'card':
|
|
34
|
+
renderCard(node, ctx, indent);
|
|
35
|
+
break;
|
|
36
|
+
case 'row':
|
|
37
|
+
ctx.lines.push(`${indent}<div${twClasses(node, ctx, 'flex')}>`);
|
|
38
|
+
renderChildren(node, ctx, indent);
|
|
39
|
+
ctx.lines.push(`${indent}</div>`);
|
|
40
|
+
break;
|
|
41
|
+
case 'col':
|
|
42
|
+
ctx.lines.push(`${indent}<div${twClasses(node, ctx, 'flex flex-col')}>`);
|
|
43
|
+
renderChildren(node, ctx, indent);
|
|
44
|
+
ctx.lines.push(`${indent}</div>`);
|
|
45
|
+
break;
|
|
46
|
+
case 'text':
|
|
47
|
+
renderText(node, ctx, indent);
|
|
48
|
+
break;
|
|
49
|
+
case 'divider':
|
|
50
|
+
ctx.lines.push(`${indent}<div${twClasses(node, ctx, 'h-px')} />`);
|
|
51
|
+
break;
|
|
52
|
+
case 'button':
|
|
53
|
+
renderButton(node, ctx, indent);
|
|
54
|
+
break;
|
|
55
|
+
case 'link':
|
|
56
|
+
renderLink(node, ctx, indent);
|
|
57
|
+
break;
|
|
58
|
+
case 'image':
|
|
59
|
+
renderImage(node, ctx, indent);
|
|
60
|
+
break;
|
|
61
|
+
case 'codeblock':
|
|
62
|
+
renderCodeBlock(node, ctx, indent);
|
|
63
|
+
break;
|
|
64
|
+
case 'input':
|
|
65
|
+
case 'textarea':
|
|
66
|
+
renderInput(node, ctx, indent);
|
|
67
|
+
break;
|
|
68
|
+
case 'slider':
|
|
69
|
+
renderSlider(node, ctx, indent);
|
|
70
|
+
break;
|
|
71
|
+
case 'toggle':
|
|
72
|
+
renderToggle(node, ctx, indent);
|
|
73
|
+
break;
|
|
74
|
+
case 'grid':
|
|
75
|
+
renderGrid(node, ctx, indent);
|
|
76
|
+
break;
|
|
77
|
+
case 'conditional':
|
|
78
|
+
renderConditional(node, ctx, indent);
|
|
79
|
+
break;
|
|
80
|
+
case 'component':
|
|
81
|
+
renderComponent(node, ctx, indent);
|
|
82
|
+
break;
|
|
83
|
+
case 'icon':
|
|
84
|
+
ctx.componentImports.add('Icon');
|
|
85
|
+
ctx.lines.push(`${indent}<Icon name="${p.name}" size="sm"${twClasses(node, ctx)} />`);
|
|
86
|
+
break;
|
|
87
|
+
case 'svg':
|
|
88
|
+
renderSvg(node, ctx, indent);
|
|
89
|
+
break;
|
|
90
|
+
case 'form':
|
|
91
|
+
ctx.lines.push(`${indent}<form${twClasses(node, ctx)}>`);
|
|
92
|
+
renderChildren(node, ctx, indent);
|
|
93
|
+
ctx.lines.push(`${indent}</form>`);
|
|
94
|
+
break;
|
|
95
|
+
case 'list':
|
|
96
|
+
ctx.lines.push(`${indent}<div${twClasses(node, ctx, 'space-y-2')}>`);
|
|
97
|
+
renderChildren(node, ctx, indent);
|
|
98
|
+
ctx.lines.push(`${indent}</div>`);
|
|
99
|
+
break;
|
|
100
|
+
case 'item':
|
|
101
|
+
ctx.lines.push(`${indent}<div${twClasses(node, ctx)}>`);
|
|
102
|
+
renderChildren(node, ctx, indent);
|
|
103
|
+
ctx.lines.push(`${indent}</div>`);
|
|
104
|
+
break;
|
|
105
|
+
case 'progress':
|
|
106
|
+
renderProgress(node, ctx, indent);
|
|
107
|
+
break;
|
|
108
|
+
case 'tabs':
|
|
109
|
+
ctx.lines.push(`${indent}<nav${twClasses(node, ctx, 'flex')}>`);
|
|
110
|
+
renderChildren(node, ctx, indent);
|
|
111
|
+
ctx.lines.push(`${indent}</nav>`);
|
|
112
|
+
break;
|
|
113
|
+
case 'tab':
|
|
114
|
+
ctx.lines.push(`${indent}<button${twClasses(node, ctx)}>${escapeJsxText(String(p.label || ''))}</button>`);
|
|
115
|
+
break;
|
|
116
|
+
case 'table':
|
|
117
|
+
ctx.lines.push(`${indent}<table${twClasses(node, ctx)}>`);
|
|
118
|
+
renderChildren(node, ctx, indent);
|
|
119
|
+
ctx.lines.push(`${indent}</table>`);
|
|
120
|
+
break;
|
|
121
|
+
case 'thead':
|
|
122
|
+
ctx.lines.push(`${indent}<thead>`);
|
|
123
|
+
renderChildren(node, ctx, indent);
|
|
124
|
+
ctx.lines.push(`${indent}</thead>`);
|
|
125
|
+
break;
|
|
126
|
+
case 'tbody':
|
|
127
|
+
ctx.lines.push(`${indent}<tbody>`);
|
|
128
|
+
renderChildren(node, ctx, indent);
|
|
129
|
+
ctx.lines.push(`${indent}</tbody>`);
|
|
130
|
+
break;
|
|
131
|
+
case 'tr':
|
|
132
|
+
ctx.lines.push(`${indent}<tr${twClasses(node, ctx)}>`);
|
|
133
|
+
renderChildren(node, ctx, indent);
|
|
134
|
+
ctx.lines.push(`${indent}</tr>`);
|
|
135
|
+
break;
|
|
136
|
+
case 'th':
|
|
137
|
+
renderTableCell(node, ctx, indent, 'th');
|
|
138
|
+
break;
|
|
139
|
+
case 'td':
|
|
140
|
+
renderTableCell(node, ctx, indent, 'td');
|
|
141
|
+
break;
|
|
142
|
+
case 'generateMetadata':
|
|
143
|
+
renderGenerateMetadata(node, ctx);
|
|
144
|
+
break;
|
|
145
|
+
case 'notFound':
|
|
146
|
+
renderNotFound(node, ctx, indent);
|
|
147
|
+
break;
|
|
148
|
+
case 'redirect':
|
|
149
|
+
renderRedirect(node, ctx, indent);
|
|
150
|
+
break;
|
|
151
|
+
case 'import':
|
|
152
|
+
renderImport(node, ctx);
|
|
153
|
+
break;
|
|
154
|
+
case 'fetch':
|
|
155
|
+
renderFetchNode(node, ctx);
|
|
156
|
+
break;
|
|
157
|
+
case 'on':
|
|
158
|
+
renderOnHandler(node, ctx);
|
|
159
|
+
return;
|
|
160
|
+
case 'state':
|
|
161
|
+
ctx.stateDecls.push({ name: String(p.name || ''), initial: String(p.initial ?? '') });
|
|
162
|
+
ctx.isClient = true; // state requires 'use client'
|
|
163
|
+
return;
|
|
164
|
+
case 'logic':
|
|
165
|
+
if (p.code)
|
|
166
|
+
ctx.logicBlocks.push(String(p.code));
|
|
167
|
+
else if (node.children) {
|
|
168
|
+
const handlerChild = node.children.find((c) => c.type === 'handler');
|
|
169
|
+
if (handlerChild?.props?.code)
|
|
170
|
+
ctx.logicBlocks.push(String(handlerChild.props.code));
|
|
171
|
+
}
|
|
172
|
+
ctx.isClient = true;
|
|
173
|
+
return;
|
|
174
|
+
case 'theme':
|
|
175
|
+
break;
|
|
176
|
+
default:
|
|
177
|
+
ctx.lines.push(`${indent}<div${twClasses(node, ctx)}>`);
|
|
178
|
+
renderChildren(node, ctx, indent);
|
|
179
|
+
ctx.lines.push(`${indent}</div>`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
export function renderChildren(node, ctx, indent) {
|
|
183
|
+
if (node.children)
|
|
184
|
+
for (const child of node.children)
|
|
185
|
+
renderNode(child, ctx, `${indent} `);
|
|
186
|
+
}
|
|
187
|
+
function renderPage(node, ctx, indent) {
|
|
188
|
+
const p = getProps(node);
|
|
189
|
+
if (p.client === 'true' || p.client === true)
|
|
190
|
+
ctx.isClient = true;
|
|
191
|
+
if (p.async === 'true' || p.async === true)
|
|
192
|
+
ctx.isAsync = true;
|
|
193
|
+
ctx.lines.push(`${indent}<div${twClasses(node, ctx)}>`);
|
|
194
|
+
renderChildren(node, ctx, indent);
|
|
195
|
+
ctx.lines.push(`${indent}</div>`);
|
|
196
|
+
}
|
|
197
|
+
function renderLayout(node, ctx, indent) {
|
|
198
|
+
const p = getProps(node);
|
|
199
|
+
ctx.lines.push(`${indent}<html lang="${p.lang || 'en'}">`);
|
|
200
|
+
ctx.lines.push(`${indent} <body${twClasses(node, ctx)}>`);
|
|
201
|
+
ctx.lines.push(`${indent} {children}`);
|
|
202
|
+
renderChildren(node, ctx, `${indent} `);
|
|
203
|
+
ctx.lines.push(`${indent} </body>`);
|
|
204
|
+
ctx.lines.push(`${indent}</html>`);
|
|
205
|
+
}
|
|
206
|
+
function renderLoading(node, ctx, indent) {
|
|
207
|
+
ctx.lines.push(`${indent}<div${twClasses(node, ctx, 'animate-pulse')}>`);
|
|
208
|
+
renderChildren(node, ctx, indent);
|
|
209
|
+
ctx.lines.push(`${indent}</div>`);
|
|
210
|
+
}
|
|
211
|
+
function renderError(node, ctx, indent) {
|
|
212
|
+
ctx.isClient = true;
|
|
213
|
+
ctx.lines.push(`${indent}<div${twClasses(node, ctx)}>`);
|
|
214
|
+
ctx.lines.push(`${indent} <h2>Something went wrong!</h2>`);
|
|
215
|
+
ctx.lines.push(`${indent} <button onClick={() => reset()}>Try again</button>`);
|
|
216
|
+
renderChildren(node, ctx, indent);
|
|
217
|
+
ctx.lines.push(`${indent}</div>`);
|
|
218
|
+
}
|
|
219
|
+
function renderMetadata(node, ctx) {
|
|
220
|
+
const p = getProps(node);
|
|
221
|
+
ctx.metadata = {};
|
|
222
|
+
if (p.title)
|
|
223
|
+
ctx.metadata.title = p.title;
|
|
224
|
+
if (p.description)
|
|
225
|
+
ctx.metadata.description = p.description;
|
|
226
|
+
if (p.keywords)
|
|
227
|
+
ctx.metadata.keywords = p.keywords;
|
|
228
|
+
if (p.ogImage)
|
|
229
|
+
ctx.metadata.ogImage = p.ogImage;
|
|
230
|
+
}
|
|
231
|
+
function renderSection(node, ctx, indent) {
|
|
232
|
+
const p = getProps(node);
|
|
233
|
+
const title = p.title || '';
|
|
234
|
+
const id = p.id;
|
|
235
|
+
const idAttr = id ? ` id="${id}"` : '';
|
|
236
|
+
const tw = twClasses(node, ctx);
|
|
237
|
+
ctx.lines.push(`${indent}<section${idAttr}${tw}>`);
|
|
238
|
+
if (title) {
|
|
239
|
+
ctx.lines.push(`${indent} <h2 className="text-lg font-semibold mb-4">${escapeJsxText(title)}</h2>`);
|
|
240
|
+
}
|
|
241
|
+
renderChildren(node, ctx, indent);
|
|
242
|
+
ctx.lines.push(`${indent}</section>`);
|
|
243
|
+
}
|
|
244
|
+
function renderCodeBlock(node, ctx, indent) {
|
|
245
|
+
const p = getProps(node);
|
|
246
|
+
const lang = p.lang || '';
|
|
247
|
+
const langClass = lang ? ` language-${lang}` : '';
|
|
248
|
+
const hasCustomStyle = getStyles(node).className || getStyles(node).background;
|
|
249
|
+
const preAttrs = hasCustomStyle ? twClasses(node, ctx) : ` className="bg-zinc-900 rounded-lg p-4 overflow-x-auto"`;
|
|
250
|
+
const codeClass = hasCustomStyle
|
|
251
|
+
? `className="${langClass.trim()}"` +
|
|
252
|
+
(getStyles(node).fontFamily ? ` style={{ fontFamily: '${getStyles(node).fontFamily}' }}` : '')
|
|
253
|
+
: `className="text-sm font-mono text-zinc-100${langClass}"`;
|
|
254
|
+
// Content: inline value prop or body child node
|
|
255
|
+
const rawValue = p.value;
|
|
256
|
+
if (isExpr(rawValue)) {
|
|
257
|
+
ctx.lines.push(`${indent}<pre${preAttrs}>`);
|
|
258
|
+
ctx.lines.push(`${indent} <code ${codeClass}>{${rawValue.code}}</code>`);
|
|
259
|
+
ctx.lines.push(`${indent}</pre>`);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
let content = rawValue || '';
|
|
263
|
+
if (!content && node.children) {
|
|
264
|
+
const bodyNode = node.children.find((c) => c.type === 'body');
|
|
265
|
+
if (bodyNode) {
|
|
266
|
+
const bp = getProps(bodyNode);
|
|
267
|
+
// body value="..." OR body <<<...>>> (multiline block -> code prop)
|
|
268
|
+
content = bp.code || bp.value || '';
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// Escape for JSX template literal: backslashes, backticks, ${
|
|
272
|
+
const escaped = content.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
|
|
273
|
+
ctx.lines.push(`${indent}<pre${preAttrs}>`);
|
|
274
|
+
ctx.lines.push(`${indent} <code ${codeClass}>{\`${escaped}\`}</code>`);
|
|
275
|
+
ctx.lines.push(`${indent}</pre>`);
|
|
276
|
+
}
|
|
277
|
+
function renderCard(node, ctx, indent) {
|
|
278
|
+
const styles = { ...getStyles(node) };
|
|
279
|
+
const border = styles.border;
|
|
280
|
+
delete styles.border;
|
|
281
|
+
// Use a shallow-copied styles object to avoid mutating the live IR node
|
|
282
|
+
if (node.props) {
|
|
283
|
+
const origStyles = node.props.styles;
|
|
284
|
+
node.props.styles = styles;
|
|
285
|
+
const extra = border ? `border ${colorToTw('border', border, ctx.colors)}` : '';
|
|
286
|
+
ctx.lines.push(`${indent}<div${twClasses(node, ctx, extra)}>`);
|
|
287
|
+
renderChildren(node, ctx, indent);
|
|
288
|
+
ctx.lines.push(`${indent}</div>`);
|
|
289
|
+
node.props.styles = origStyles;
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
const extra = border ? `border ${colorToTw('border', border, ctx.colors)}` : '';
|
|
293
|
+
ctx.lines.push(`${indent}<div${twClasses(node, ctx, extra)}>`);
|
|
294
|
+
renderChildren(node, ctx, indent);
|
|
295
|
+
ctx.lines.push(`${indent}</div>`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function renderText(node, ctx, indent) {
|
|
299
|
+
const p = getProps(node);
|
|
300
|
+
const rawValue = p.value;
|
|
301
|
+
const bind = p.bind;
|
|
302
|
+
const el = TEXT_TAG_MAP[p.tag] || 'span';
|
|
303
|
+
const tw = twClasses(node, ctx);
|
|
304
|
+
if (isExpr(rawValue))
|
|
305
|
+
ctx.lines.push(`${indent}<${el}${tw}>{${rawValue.code}}</${el}>`);
|
|
306
|
+
else if (bind)
|
|
307
|
+
ctx.lines.push(`${indent}<${el}${tw}>{${bind}}</${el}>`);
|
|
308
|
+
else if (rawValue)
|
|
309
|
+
ctx.lines.push(`${indent}<${el}${tw}>${escapeJsxText(rawValue)}</${el}>`);
|
|
310
|
+
}
|
|
311
|
+
function renderButton(node, ctx, indent) {
|
|
312
|
+
const p = getProps(node);
|
|
313
|
+
const text = p.text || '';
|
|
314
|
+
const to = p.to;
|
|
315
|
+
const rawOnClick = p.onClick;
|
|
316
|
+
const onClick = isExpr(rawOnClick) ? rawOnClick.code : rawOnClick;
|
|
317
|
+
if (to) {
|
|
318
|
+
addDefaultImport(ctx, 'next/link', 'Link');
|
|
319
|
+
ctx.lines.push(`${indent}<Link href="/${to.toLowerCase()}"${twClasses(node, ctx)}>${escapeJsxText(text)}</Link>`);
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
ctx.isClient = true; // onClick requires 'use client'
|
|
323
|
+
ctx.lines.push(`${indent}<button${twClasses(node, ctx)} onClick={${onClick || '() => {}'}}>${escapeJsxText(text)}</button>`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function renderInput(node, ctx, indent) {
|
|
327
|
+
const p = getProps(node);
|
|
328
|
+
const isTextarea = node.type === 'textarea' || p.type === 'textarea' || p.multiline;
|
|
329
|
+
const tag = isTextarea ? 'textarea' : 'input';
|
|
330
|
+
const attrs = [];
|
|
331
|
+
const tw = twClasses(node, ctx);
|
|
332
|
+
if (p.bind) {
|
|
333
|
+
const bind = p.bind;
|
|
334
|
+
const setter = `set${bind.charAt(0).toUpperCase() + bind.slice(1)}`;
|
|
335
|
+
attrs.push(`value={${bind}}`);
|
|
336
|
+
ctx.isClient = true; // onChange requires 'use client'
|
|
337
|
+
if (isExpr(p.onChange))
|
|
338
|
+
attrs.push(`onChange={${p.onChange.code}}`);
|
|
339
|
+
else if (p.onChange)
|
|
340
|
+
attrs.push(`onChange={${p.onChange}}`);
|
|
341
|
+
else
|
|
342
|
+
attrs.push(`onChange={(e) => ${setter}(e.target.value)}`);
|
|
343
|
+
}
|
|
344
|
+
if (p.placeholder)
|
|
345
|
+
attrs.push(`placeholder="${p.placeholder}"`);
|
|
346
|
+
if (!isTextarea && p.type && p.type !== 'textarea')
|
|
347
|
+
attrs.push(`type="${p.type}"`);
|
|
348
|
+
if (p.spellcheck === 'false' || p.spellcheck === false)
|
|
349
|
+
attrs.push('spellCheck={false}');
|
|
350
|
+
const attrStr = attrs.length > 0 ? ` ${attrs.join(' ')}` : '';
|
|
351
|
+
if (isTextarea) {
|
|
352
|
+
ctx.lines.push(`${indent}<${tag}${tw}${attrStr} rows={4} />`);
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
ctx.lines.push(`${indent}<${tag}${tw}${attrStr} />`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function renderLink(node, ctx, indent) {
|
|
359
|
+
const p = getProps(node);
|
|
360
|
+
addDefaultImport(ctx, 'next/link', 'Link');
|
|
361
|
+
ctx.lines.push(`${indent}<Link href="${p.to || '/'}"${twClasses(node, ctx)}>`);
|
|
362
|
+
renderChildren(node, ctx, indent);
|
|
363
|
+
ctx.lines.push(`${indent}</Link>`);
|
|
364
|
+
}
|
|
365
|
+
function renderImage(node, ctx, indent) {
|
|
366
|
+
const p = getProps(node);
|
|
367
|
+
addDefaultImport(ctx, 'next/image', 'Image');
|
|
368
|
+
const tw = twClasses(node, ctx);
|
|
369
|
+
const rawSrc = p.src || '';
|
|
370
|
+
const src = rawSrc.startsWith('/') || rawSrc.includes('://') || rawSrc.includes('.') ? rawSrc : `/${rawSrc}.png`;
|
|
371
|
+
const alt = escapeJsxAttr(String(p.alt || p.src || ''));
|
|
372
|
+
const fill = p.fill === 'true' || p.fill === true;
|
|
373
|
+
const priority = p.priority === 'true' || p.priority === true;
|
|
374
|
+
if (fill) {
|
|
375
|
+
ctx.lines.push(`${indent}<Image src="${src}" alt="${alt}"${priority ? ' priority' : ''} fill${tw} />`);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
const width = p.width || getStyles(node).w || '100';
|
|
379
|
+
const height = p.height || getStyles(node).h || '100';
|
|
380
|
+
ctx.lines.push(`${indent}<Image src="${src}" alt="${alt}" width={${width}} height={${height}}${priority ? ' priority' : ''}${tw} />`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function renderSlider(node, ctx, indent) {
|
|
384
|
+
const p = getProps(node);
|
|
385
|
+
const bind = p.bind;
|
|
386
|
+
const setter = bind ? `set${bind.charAt(0).toUpperCase() + bind.slice(1)}` : 'setValue';
|
|
387
|
+
ctx.isClient = true; // onChange requires 'use client'
|
|
388
|
+
ctx.lines.push(`${indent}<input type="range" min={${p.min || 0}} max={${p.max || 100}} step={${p.step || 1}} value={${bind || 'value'}} onChange={(e) => ${setter}(parseFloat(e.target.value))} className="w-full h-2 bg-zinc-700 rounded-lg appearance-none cursor-pointer accent-orange-500" />`);
|
|
389
|
+
}
|
|
390
|
+
function renderToggle(node, ctx, indent) {
|
|
391
|
+
const p = getProps(node);
|
|
392
|
+
const bind = p.bind;
|
|
393
|
+
const setter = bind ? `set${bind.charAt(0).toUpperCase() + bind.slice(1)}` : 'setValue';
|
|
394
|
+
ctx.isClient = true; // onChange requires 'use client'
|
|
395
|
+
ctx.lines.push(`${indent}<label className="relative inline-flex items-center cursor-pointer">`);
|
|
396
|
+
ctx.lines.push(`${indent} <input type="checkbox" className="sr-only peer" checked={${bind || 'value'}} onChange={(e) => ${setter}(e.target.checked)} />`);
|
|
397
|
+
ctx.lines.push(`${indent} <div className="w-11 h-6 bg-zinc-700 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-orange-600" />`);
|
|
398
|
+
ctx.lines.push(`${indent}</label>`);
|
|
399
|
+
}
|
|
400
|
+
function renderOnHandler(node, ctx) {
|
|
401
|
+
const p = getProps(node);
|
|
402
|
+
const event = (p.event || p.name);
|
|
403
|
+
const handlerRef = p.handler;
|
|
404
|
+
const key = p.key;
|
|
405
|
+
const isAsync = p.async === 'true' || p.async === true;
|
|
406
|
+
const handlerChild = (node.children || []).find((c) => c.type === 'handler');
|
|
407
|
+
const code = handlerChild ? getProps(handlerChild).code || '' : '';
|
|
408
|
+
if (handlerRef && !code)
|
|
409
|
+
return;
|
|
410
|
+
ctx.isClient = true; // event handlers require 'use client'
|
|
411
|
+
const fnName = handlerRef || `handle${event.charAt(0).toUpperCase() + event.slice(1)}`;
|
|
412
|
+
const asyncKw = isAsync ? 'async ' : '';
|
|
413
|
+
const paramType = event === 'submit'
|
|
414
|
+
? 'e: React.FormEvent'
|
|
415
|
+
: event === 'click'
|
|
416
|
+
? 'e: React.MouseEvent'
|
|
417
|
+
: event === 'change'
|
|
418
|
+
? 'e: React.ChangeEvent'
|
|
419
|
+
: event === 'key' || event === 'keydown' || event === 'keyup'
|
|
420
|
+
? 'e: React.KeyboardEvent'
|
|
421
|
+
: event === 'focus' || event === 'blur'
|
|
422
|
+
? 'e: React.FocusEvent'
|
|
423
|
+
: event === 'scroll'
|
|
424
|
+
? 'e: React.UIEvent'
|
|
425
|
+
: `e: React.SyntheticEvent`;
|
|
426
|
+
const keyGuard = key ? ` if (e.key !== '${key}') return;\n` : '';
|
|
427
|
+
addNamedImport(ctx, 'react', 'useCallback');
|
|
428
|
+
let block = ` const ${fnName} = useCallback(${asyncKw}(${paramType}) => {\n`;
|
|
429
|
+
if (keyGuard)
|
|
430
|
+
block += keyGuard;
|
|
431
|
+
if (code) {
|
|
432
|
+
for (const line of code.split('\n')) {
|
|
433
|
+
block += ` ${line}\n`;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
block += ` }, []);\n`;
|
|
437
|
+
ctx.bodyLines.push(block);
|
|
438
|
+
if (event === 'key' || event === 'keydown' || event === 'keyup') {
|
|
439
|
+
addNamedImport(ctx, 'react', 'useEffect');
|
|
440
|
+
const domEvent = event === 'key' ? 'keydown' : event;
|
|
441
|
+
let effect = ` useEffect(() => {\n`;
|
|
442
|
+
effect += ` const listener = (e: KeyboardEvent) => ${fnName}(e as unknown as React.KeyboardEvent);\n`;
|
|
443
|
+
effect += ` window.addEventListener('${domEvent}', listener);\n`;
|
|
444
|
+
effect += ` return () => window.removeEventListener('${domEvent}', listener);\n`;
|
|
445
|
+
effect += ` }, [${fnName}]);\n`;
|
|
446
|
+
ctx.bodyLines.push(effect);
|
|
447
|
+
}
|
|
448
|
+
if (event === 'resize') {
|
|
449
|
+
addNamedImport(ctx, 'react', 'useEffect');
|
|
450
|
+
let effect = ` useEffect(() => {\n`;
|
|
451
|
+
effect += ` window.addEventListener('resize', ${fnName});\n`;
|
|
452
|
+
effect += ` return () => window.removeEventListener('resize', ${fnName});\n`;
|
|
453
|
+
effect += ` }, [${fnName}]);\n`;
|
|
454
|
+
ctx.bodyLines.push(effect);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function renderTableCell(node, ctx, indent, tag) {
|
|
458
|
+
const p = getProps(node);
|
|
459
|
+
const tw = twClasses(node, ctx);
|
|
460
|
+
const rawValue = p.value;
|
|
461
|
+
if (isExpr(rawValue)) {
|
|
462
|
+
ctx.lines.push(`${indent}<${tag}${tw}>{${rawValue.code}}</${tag}>`);
|
|
463
|
+
}
|
|
464
|
+
else if (rawValue) {
|
|
465
|
+
ctx.lines.push(`${indent}<${tag}${tw}>${escapeJsxText(rawValue)}</${tag}>`);
|
|
466
|
+
}
|
|
467
|
+
else if (node.children && node.children.length > 0) {
|
|
468
|
+
ctx.lines.push(`${indent}<${tag}${tw}>`);
|
|
469
|
+
renderChildren(node, ctx, indent);
|
|
470
|
+
ctx.lines.push(`${indent}</${tag}>`);
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
ctx.lines.push(`${indent}<${tag}${tw} />`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function renderGrid(node, ctx, indent) {
|
|
477
|
+
const p = getProps(node);
|
|
478
|
+
const cols = parseInt(String(p.cols || 1), 10) || 1;
|
|
479
|
+
const gap = parseInt(String(p.gap || 16), 10) || 16;
|
|
480
|
+
ctx.lines.push(`${indent}<div className="grid grid-cols-1 md:grid-cols-${cols} gap-${Math.round(gap / 4)}">`);
|
|
481
|
+
renderChildren(node, ctx, indent);
|
|
482
|
+
ctx.lines.push(`${indent}</div>`);
|
|
483
|
+
}
|
|
484
|
+
function renderSvg(node, ctx, indent) {
|
|
485
|
+
const p = getProps(node);
|
|
486
|
+
const icon = p.icon;
|
|
487
|
+
const size = parseInt(String(p.size || 24), 10) || 24;
|
|
488
|
+
if (icon) {
|
|
489
|
+
const inner = SVG_ICONS[icon] || `<circle cx="12" cy="12" r="4"/>`;
|
|
490
|
+
ctx.lines.push(`${indent}<svg xmlns="http://www.w3.org/2000/svg" width={${size}} height={${size}} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round"${twClasses(node, ctx)}>${inner}</svg>`);
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
// Custom SVG -- only emit attributes the user explicitly set (no Feather defaults)
|
|
494
|
+
const viewBox = p.viewBox || '0 0 24 24';
|
|
495
|
+
const width = parseInt(String(p.width || size), 10) || size;
|
|
496
|
+
const height = parseInt(String(p.height || size), 10) || size;
|
|
497
|
+
const content = htmlAttrsToJsx(p.content || '');
|
|
498
|
+
const optAttrs = [];
|
|
499
|
+
if (p.fill)
|
|
500
|
+
optAttrs.push(`fill="${p.fill}"`);
|
|
501
|
+
if (p.stroke)
|
|
502
|
+
optAttrs.push(`stroke="${p.stroke}"`);
|
|
503
|
+
const extra = optAttrs.length ? ` ${optAttrs.join(' ')}` : '';
|
|
504
|
+
ctx.lines.push(`${indent}<svg xmlns="http://www.w3.org/2000/svg" width={${width}} height={${height}} viewBox="${viewBox}"${extra}${twClasses(node, ctx)}>${content}</svg>`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
function renderConditional(node, ctx, indent) {
|
|
508
|
+
const cond = (getProps(node).if || 'true')
|
|
509
|
+
.replace(/&/g, ' && ')
|
|
510
|
+
.replace(/([a-zA-Z_]+)=([a-zA-Z_]+)/g, "$1 === '$2'");
|
|
511
|
+
ctx.lines.push(`${indent}{${cond} && (`);
|
|
512
|
+
ctx.lines.push(`${indent} <>`);
|
|
513
|
+
renderChildren(node, ctx, `${indent} `);
|
|
514
|
+
ctx.lines.push(`${indent} </>`);
|
|
515
|
+
ctx.lines.push(`${indent})}`);
|
|
516
|
+
}
|
|
517
|
+
function renderComponent(node, ctx, indent) {
|
|
518
|
+
const p = getProps(node);
|
|
519
|
+
const ref = (p.ref || p.name);
|
|
520
|
+
if (!ref)
|
|
521
|
+
return;
|
|
522
|
+
ctx.componentImports.add(ref);
|
|
523
|
+
const hasOnChange = 'onChange' in p;
|
|
524
|
+
const attrs = [];
|
|
525
|
+
for (const [k, v] of Object.entries(p)) {
|
|
526
|
+
if (['ref', 'name', 'styles', 'pseudoStyles', 'themeRefs'].includes(k))
|
|
527
|
+
continue;
|
|
528
|
+
if (k === 'bind') {
|
|
529
|
+
attrs.push(`value={${v}}`);
|
|
530
|
+
if (!hasOnChange)
|
|
531
|
+
attrs.push(`onChange={set${v.charAt(0).toUpperCase() + v.slice(1)}}`);
|
|
532
|
+
}
|
|
533
|
+
else if (k === 'onChange')
|
|
534
|
+
attrs.push(`onChange={${v}}`);
|
|
535
|
+
else if (k === 'props') {
|
|
536
|
+
for (const pn of v.split(','))
|
|
537
|
+
attrs.push(`${pn.trim()}={${pn.trim()}}`);
|
|
538
|
+
}
|
|
539
|
+
else if (k === 'disabled')
|
|
540
|
+
attrs.push(`disabled={${v.replace(/&/g, ' && ').replace(/([a-zA-Z_]+)=([a-zA-Z_]+)/g, "$1 === '$2'")}}`);
|
|
541
|
+
else if (k === 'default')
|
|
542
|
+
attrs.push(`defaultValue={${JSON.stringify(v)}}`);
|
|
543
|
+
else
|
|
544
|
+
attrs.push(`${k}={${JSON.stringify(v)}}`);
|
|
545
|
+
}
|
|
546
|
+
const attrStr = attrs.length ? ` ${attrs.join(' ')}` : '';
|
|
547
|
+
if (node.children && node.children.length > 0) {
|
|
548
|
+
ctx.lines.push(`${indent}<${ref}${attrStr}>`);
|
|
549
|
+
renderChildren(node, ctx, indent);
|
|
550
|
+
ctx.lines.push(`${indent}</${ref}>`);
|
|
551
|
+
}
|
|
552
|
+
else {
|
|
553
|
+
ctx.lines.push(`${indent}<${ref}${attrStr} />`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
function renderProgress(node, ctx, indent) {
|
|
557
|
+
const p = getProps(node);
|
|
558
|
+
const current = Number(p.current || 0), target = Number(p.target || 100);
|
|
559
|
+
const pct = Math.round((current / target) * 100);
|
|
560
|
+
ctx.lines.push(`${indent}<div className="mb-3">`);
|
|
561
|
+
ctx.lines.push(`${indent} <div className="flex justify-between text-sm mb-1"><span>${escapeJsxText(String(p.label || ''))}</span><span>${current}/${target} ${escapeJsxText(String(p.unit || ''))}</span></div>`);
|
|
562
|
+
ctx.lines.push(`${indent} <div className="h-2 bg-zinc-700 rounded-full overflow-hidden"><div className="h-full rounded-full bg-[${p.color || '#007AFF'}]" style={{ width: '${pct}%' }} /></div>`);
|
|
563
|
+
ctx.lines.push(`${indent}</div>`);
|
|
564
|
+
}
|
|
565
|
+
// ── Next.js 15 production pattern renderers ─────────────────────────────
|
|
566
|
+
function renderGenerateMetadata(node, ctx) {
|
|
567
|
+
// Collect handler code from children
|
|
568
|
+
let handlerCode = '';
|
|
569
|
+
if (node.children) {
|
|
570
|
+
for (const child of node.children) {
|
|
571
|
+
const cp = getProps(child);
|
|
572
|
+
if (child.type === 'handler' && cp.code) {
|
|
573
|
+
handlerCode = cp.code;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
// Also check inline code prop
|
|
578
|
+
const p = getProps(node);
|
|
579
|
+
if (p.code)
|
|
580
|
+
handlerCode = p.code;
|
|
581
|
+
ctx.generateMetadataInfo = { handlerCode };
|
|
582
|
+
}
|
|
583
|
+
function renderNotFound(node, ctx, _indent) {
|
|
584
|
+
addNamedImport(ctx, 'next/navigation', 'notFound');
|
|
585
|
+
const p = getProps(node);
|
|
586
|
+
const condition = p.if;
|
|
587
|
+
if (condition) {
|
|
588
|
+
ctx.bodyLines.push(` if (${exprCode(condition, 'true')}) { notFound(); }`);
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
ctx.bodyLines.push(` notFound();`);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
function renderRedirect(node, ctx, _indent) {
|
|
595
|
+
addNamedImport(ctx, 'next/navigation', 'redirect');
|
|
596
|
+
const p = getProps(node);
|
|
597
|
+
const to = p.to || '/';
|
|
598
|
+
ctx.bodyLines.push(` redirect('${to}');`);
|
|
599
|
+
}
|
|
600
|
+
function renderImport(node, ctx) {
|
|
601
|
+
const p = getProps(node);
|
|
602
|
+
const name = p.name;
|
|
603
|
+
const from = p.from;
|
|
604
|
+
const isDefault = p.default === 'true' || p.default === true;
|
|
605
|
+
if (name && from) {
|
|
606
|
+
if (isDefault) {
|
|
607
|
+
addDefaultImport(ctx, from, name);
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
// Support comma-separated named imports: name=Foo,Bar,Baz
|
|
611
|
+
const names = name
|
|
612
|
+
.split(',')
|
|
613
|
+
.map((n) => n.trim())
|
|
614
|
+
.filter(Boolean);
|
|
615
|
+
for (const n of names) {
|
|
616
|
+
addNamedImport(ctx, from, n);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
function renderFetchNode(node, ctx) {
|
|
622
|
+
const p = getProps(node);
|
|
623
|
+
const name = p.name || 'data';
|
|
624
|
+
const url = p.url || '/api/data';
|
|
625
|
+
const options = p.options;
|
|
626
|
+
ctx.fetchCalls.push({ name, url, options });
|
|
627
|
+
}
|
|
628
|
+
//# sourceMappingURL=nextjs-renderers.js.map
|