0x-lang 0.1.10 → 0.1.12

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.
@@ -249,6 +249,16 @@ function generateTopLevel(node) {
249
249
  case 'Rtl':
250
250
  // Handled at top level
251
251
  break;
252
+ case 'JsImport':
253
+ // Handled separately in import section
254
+ break;
255
+ case 'UseImport':
256
+ // use X from "source" → import + hook call
257
+ hookLines.push(`const ${child.name} = ${child.name}();`);
258
+ break;
259
+ case 'JsBlock':
260
+ hookLines.push(child.code);
261
+ break;
252
262
  case 'StyleDecl':
253
263
  // Collected already, used by reference
254
264
  break;
@@ -273,12 +283,30 @@ function generateTopLevel(node) {
273
283
  reactImports.push(...hookNames);
274
284
  }
275
285
  const importLine = `import ${reactImports[0]}, { ${hookNames.join(', ')} } from 'react';`;
286
+ // Collect user imports (js import / import / use)
287
+ const userImports = [];
288
+ for (const child of node.body) {
289
+ if (child.type === 'JsImport') {
290
+ const ji = child;
291
+ if (ji.isDefault) {
292
+ userImports.push(`import ${ji.specifiers[0]} from '${ji.source}';`);
293
+ }
294
+ else {
295
+ userImports.push(`import { ${ji.specifiers.join(', ')} } from '${ji.source}';`);
296
+ }
297
+ }
298
+ else if (child.type === 'UseImport') {
299
+ const ui = child;
300
+ userImports.push(`import ${ui.name} from '${ui.source}';`);
301
+ }
302
+ }
276
303
  // Build component
277
304
  const isComponent = node.type === 'Component';
278
305
  const exportKw = isComponent ? '' : 'export default ';
279
306
  const lines = [
280
307
  `// Generated by 0x`,
281
308
  hookNames.length > 0 ? importLine : `import React from 'react';`,
309
+ ...userImports,
282
310
  '',
283
311
  `${exportKw}function ${node.name}(${propsArg}) {`,
284
312
  ...hookLines.map(l => ` ${l}`),
@@ -505,20 +533,24 @@ function genLayout(node, c) {
505
533
  case 'grow':
506
534
  style['flexGrow'] = v;
507
535
  break;
508
- case 'scroll':
509
- style['overflow' + (v === 'y' ? 'Y' : 'X')] = 'auto';
536
+ case 'scroll': {
537
+ const sv = unquote(v);
538
+ style['overflow' + (sv === 'y' ? 'Y' : 'X')] = 'auto';
510
539
  break;
540
+ }
511
541
  case 'radius':
512
542
  style['borderRadius'] = addPx(v);
513
543
  break;
514
- case 'shadow':
515
- if (v === 'sm')
544
+ case 'shadow': {
545
+ const sv = unquote(v);
546
+ if (sv === 'sm')
516
547
  style['boxShadow'] = '0 1px 2px rgba(0,0,0,0.1)';
517
- else if (v === 'md')
548
+ else if (sv === 'md')
518
549
  style['boxShadow'] = '0 4px 6px rgba(0,0,0,0.1)';
519
- else if (v === 'lg')
550
+ else if (sv === 'lg')
520
551
  style['boxShadow'] = '0 10px 15px rgba(0,0,0,0.1)';
521
552
  break;
553
+ }
522
554
  }
523
555
  }
524
556
  // Apply style class
@@ -761,12 +793,14 @@ function genFor(node, c) {
761
793
  function genShow(node, c) {
762
794
  const cond = genExpr(node.condition, c);
763
795
  const body = node.body.map(ch => genUINode(ch, c)).join('\n');
764
- return `<div style={{ display: ${cond} ? 'block' : 'none' }}>\n${body}\n</div>`;
796
+ const wrapped = node.body.length === 1 ? body : `<>\n${body}\n</>`;
797
+ return `{${cond} && (\n${wrapped}\n)}`;
765
798
  }
766
799
  function genHide(node, c) {
767
800
  const cond = genExpr(node.condition, c);
768
801
  const body = node.body.map(ch => genUINode(ch, c)).join('\n');
769
- return `<div style={{ display: ${cond} ? 'none' : 'block' }}>\n${body}\n</div>`;
802
+ const wrapped = node.body.length === 1 ? body : `<>\n${body}\n</>`;
803
+ return `{!${cond} && (\n${wrapped}\n)}`;
770
804
  }
771
805
  // ── Statements ──────────────────────────────────────
772
806
  function genStatement(stmt, c) {
@@ -903,6 +937,8 @@ function genExpr(expr, c) {
903
937
  return `[${els}]`;
904
938
  }
905
939
  case 'object_expr': {
940
+ if (expr.properties.length === 0)
941
+ return '{}';
906
942
  const props = expr.properties.map(p => {
907
943
  if (p.key === p.value.kind && p.value.kind === 'identifier') {
908
944
  return p.key; // shorthand
@@ -1231,18 +1267,19 @@ function genTableUI(node, c) {
1231
1267
  }
1232
1268
  lines.push(`<table style={{ width: '100%', borderCollapse: 'collapse' }}>`);
1233
1269
  // Header
1270
+ const thStyle = `padding: '12px 8px', borderBottom: '2px solid #e2e8f0', textAlign: 'left', fontWeight: 600`;
1234
1271
  lines.push(`<thead>`);
1235
1272
  lines.push(`<tr>`);
1236
1273
  for (const col of node.columns) {
1237
1274
  if (col.kind === 'select') {
1238
- lines.push(`<th style={{ padding: '12px 8px', borderBottom: '2px solid #e2e8f0', textAlign: 'left' }}><input type="checkbox" /></th>`);
1275
+ lines.push(`<th style={{ ${thStyle} }}><input type="checkbox" /></th>`);
1239
1276
  }
1240
1277
  else if (col.kind === 'field') {
1241
- const sortAttr = col.sortable ? ` style={{ cursor: 'pointer', padding: '12px 8px', borderBottom: '2px solid #e2e8f0', textAlign: 'left', fontWeight: 600 }}` : ` style={{ padding: '12px 8px', borderBottom: '2px solid #e2e8f0', textAlign: 'left', fontWeight: 600 }}`;
1278
+ const sortAttr = col.sortable ? ` style={{ cursor: 'pointer', ${thStyle} }}` : ` style={{ ${thStyle} }}`;
1242
1279
  lines.push(`<th${sortAttr}>${col.label || col.field}</th>`);
1243
1280
  }
1244
1281
  else if (col.kind === 'actions') {
1245
- lines.push(`<th style={{ padding: '12px 8px', borderBottom: '2px solid #e2e8f0', textAlign: 'right', fontWeight: 600 }}>Actions</th>`);
1282
+ lines.push(`<th style={{ ${thStyle}, textAlign: 'right' }}>Actions</th>`);
1246
1283
  }
1247
1284
  }
1248
1285
  lines.push(`</tr>`);
@@ -1406,16 +1443,22 @@ function genChartUI(node, c) {
1406
1443
  lines.push(` {/* Chart: ${node.chartType} - ${node.name} */}`);
1407
1444
  lines.push(` {/* Data: ${data}, X: ${x}, Y: ${y} */}`);
1408
1445
  if (node.chartType === 'bar') {
1409
- lines.push(` <div style={{ display: 'flex', alignItems: 'flex-end', gap: '4px', height: '100%', padding: '20px 0' }}>`);
1410
- lines.push(` <span style={{ fontSize: '14px', fontWeight: 'bold', position: 'absolute', top: 0 }}>{${title}}</span>`);
1411
- lines.push(` {${data}.map((item, i) => (`);
1412
- lines.push(` <div key={item?.id ?? i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', height: '100%', justifyContent: 'flex-end' }}>`);
1413
- lines.push(` <span style={{ fontSize: '12px', marginBottom: '4px' }}>{item[${y}]}</span>`);
1414
- lines.push(` <div style={{ width: '100%', backgroundColor: ${color ? `item[${color}] || '#3182ce'` : "'#3182ce'"}, height: \`\${(item[${y}] / Math.max(...${data}.map(d => d[${y}]))) * 100}%\`, borderRadius: '4px 4px 0 0', minHeight: '4px' }} />`);
1415
- lines.push(` <span style={{ fontSize: '11px', marginTop: '4px', color: '#666' }}>{item[${x}]}</span>`);
1446
+ c.imports.add('useMemo');
1447
+ lines.push(` {(() => {`);
1448
+ lines.push(` const maxVal = Math.max(...${data}.map(d => d[${y}]));`);
1449
+ lines.push(` return (`);
1450
+ lines.push(` <div style={{ display: 'flex', alignItems: 'flex-end', gap: '4px', height: '100%', padding: '20px 0' }}>`);
1451
+ lines.push(` <span style={{ fontSize: '14px', fontWeight: 'bold', position: 'absolute', top: 0 }}>{${title}}</span>`);
1452
+ lines.push(` {${data}.map((item, i) => (`);
1453
+ lines.push(` <div key={item?.id ?? i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', height: '100%', justifyContent: 'flex-end' }}>`);
1454
+ lines.push(` <span style={{ fontSize: '12px', marginBottom: '4px' }}>{item[${y}]}</span>`);
1455
+ lines.push(` <div style={{ width: '100%', backgroundColor: ${color ? `item[${color}] || '#3182ce'` : "'#3182ce'"}, height: \`\${(item[${y}] / maxVal) * 100}%\`, borderRadius: '4px 4px 0 0', minHeight: '4px' }} />`);
1456
+ lines.push(` <span style={{ fontSize: '11px', marginTop: '4px', color: '#666' }}>{item[${x}]}</span>`);
1457
+ lines.push(` </div>`);
1458
+ lines.push(` ))}`);
1416
1459
  lines.push(` </div>`);
1417
- lines.push(` ))}`);
1418
- lines.push(` </div>`);
1460
+ lines.push(` );`);
1461
+ lines.push(` })()}`);
1419
1462
  }
1420
1463
  else if (node.chartType === 'pie') {
1421
1464
  lines.push(` <div style={{ position: 'relative', width: '200px', height: '200px', margin: '0 auto' }}>`);
@@ -1423,7 +1466,7 @@ function genChartUI(node, c) {
1423
1466
  lines.push(` {/* Pie chart - use recharts/chart.js for production */}`);
1424
1467
  lines.push(` {${data}.map((item, i) => (`);
1425
1468
  lines.push(` <div key={item?.id ?? i} style={{ display: 'flex', alignItems: 'center', gap: '8px', marginTop: '8px' }}>`);
1426
- lines.push(` <div style={{ width: '12px', height: '12px', borderRadius: '50%', backgroundColor: ['#3182ce','#38a169','#d69e2e','#e53e3e','#805ad5'][i % 5] }} />`);
1469
+ lines.push(` <div style={{ width: '12px', height: '12px', borderRadius: '50%', backgroundColor: ${color ? `item[${color}]` : `['#3182ce','#38a169','#d69e2e','#e53e3e','#805ad5'][i % 5]`} }} />`);
1427
1470
  lines.push(` <span>{item[${x}]}: {item[${y}]}</span>`);
1428
1471
  lines.push(` </div>`);
1429
1472
  lines.push(` ))}`);
@@ -1732,7 +1775,7 @@ function genMediaUI(node, c) {
1732
1775
  const src = genExpr(node.src, c);
1733
1776
  if (node.mediaType === 'gallery') {
1734
1777
  const cols = node.props['cols'] ? genExpr(node.props['cols'], c) : '3';
1735
- return `<div style={{ display: 'grid', gridTemplateColumns: \`repeat(\${${cols}}, 1fr)\`, gap: '8px' }}>\n {${src}.map((img, i) => <img key={i} src={img} style={{ width: '100%', borderRadius: '8px', objectFit: 'cover' }} />)}\n</div>`;
1778
+ return `<div style={{ display: 'grid', gridTemplateColumns: \`repeat(\${${cols}}, 1fr)\`, gap: '8px' }}>\n {${src}.map((img, i) => <img key={img ?? i} src={img} style={{ width: '100%', borderRadius: '8px', objectFit: 'cover' }} />)}\n</div>`;
1736
1779
  }
1737
1780
  if (node.mediaType === 'video') {
1738
1781
  return `<video src={${src}} controls style={{ width: '100%', borderRadius: '12px' }} />`;