0x-lang 0.1.6 → 0.1.7

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/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="website/assets/logo.svg" alt="0x Logo" width="100" height="100" />
2
+ <img src="website/assets/0xlogo.svg" alt="0x Logo" width="100" height="100" />
3
3
  </p>
4
4
 
5
5
  <h1 align="center">0x</h1>
@@ -1,5 +1,5 @@
1
1
  // 0x → React Code Generator
2
- import { SIZE_MAP, unquote, capitalize, parseGradient } from './shared.js';
2
+ import { SIZE_MAP, unquote, capitalize, parseGradient, addPx } from './shared.js';
3
3
  function ctx() {
4
4
  return {
5
5
  imports: new Set(),
@@ -457,16 +457,16 @@ function genLayout(node, c) {
457
457
  const isDynamic = val.kind === 'braced' || val.kind === 'ternary' || val.kind === 'binary' || val.kind === 'member' || val.kind === 'call';
458
458
  switch (key) {
459
459
  case 'gap':
460
- style['gap'] = `${v}px`;
460
+ style['gap'] = addPx(v);
461
461
  break;
462
462
  case 'padding':
463
- style['padding'] = `${v}px`;
463
+ style['padding'] = addPx(v);
464
464
  break;
465
465
  case 'margin':
466
466
  style['margin'] = v;
467
467
  break;
468
468
  case 'maxWidth':
469
- style['maxWidth'] = `${v}px`;
469
+ style['maxWidth'] = addPx(v);
470
470
  break;
471
471
  case 'height':
472
472
  style['height'] = v;
@@ -502,7 +502,7 @@ function genLayout(node, c) {
502
502
  style['overflow' + (v === 'y' ? 'Y' : 'X')] = 'auto';
503
503
  break;
504
504
  case 'radius':
505
- style['borderRadius'] = `${v}px`;
505
+ style['borderRadius'] = addPx(v);
506
506
  break;
507
507
  case 'shadow':
508
508
  if (v === 'sm')
@@ -740,6 +740,18 @@ function genStatement(stmt, c) {
740
740
  const value = genExpr(stmt.value, c);
741
741
  c.readOnly = prevReadOnly;
742
742
  const setter = 'set' + capitalize(stateName);
743
+ const memberPath = extractMemberPath(stmt.target);
744
+ if (memberPath.length > 0) {
745
+ // Nested state assignment: user.name = "Bob" → setUser(prev => ({...prev, name: 'Bob'}))
746
+ if (stmt.op === '=') {
747
+ return `${setter}(prev => ${buildSpreadUpdate('prev', memberPath, value)});`;
748
+ }
749
+ else {
750
+ const opChar = stmt.op.charAt(0);
751
+ const prevAccess = 'prev.' + memberPath.join('.');
752
+ return `${setter}(prev => ${buildSpreadUpdate('prev', memberPath, `${prevAccess} ${opChar} ${value}`)});`;
753
+ }
754
+ }
743
755
  if (stmt.op === '=') {
744
756
  return `${setter}(${value});`;
745
757
  }
@@ -787,7 +799,7 @@ function genStatement(stmt, c) {
787
799
  function genExpr(expr, c) {
788
800
  switch (expr.kind) {
789
801
  case 'number': return String(expr.value);
790
- case 'string': return `'${expr.value}'`;
802
+ case 'string': return `'${expr.value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
791
803
  case 'boolean': return String(expr.value);
792
804
  case 'null': return 'null';
793
805
  case 'identifier': return expr.name;
@@ -825,7 +837,7 @@ function genExpr(expr, c) {
825
837
  const body = expr.body.map(s => genStatement(s, c)).join('\n');
826
838
  return `(${params}) => { ${body} }`;
827
839
  }
828
- return `${params} => ${genExpr(expr.body, c)}`;
840
+ return `(${params}) => ${genExpr(expr.body, c)}`;
829
841
  }
830
842
  case 'array': {
831
843
  const els = expr.elements.map(e => genExpr(e, c)).join(', ');
@@ -858,6 +870,14 @@ function genExpr(expr, c) {
858
870
  const value = genExpr(expr.value, c);
859
871
  c.readOnly = prevReadOnly;
860
872
  const setter = 'set' + capitalize(stateName);
873
+ const memberPath = extractMemberPath(expr.target);
874
+ if (memberPath.length > 0) {
875
+ if (expr.op === '=')
876
+ return `${setter}(prev => ${buildSpreadUpdate('prev', memberPath, value)})`;
877
+ const opChar = expr.op.charAt(0);
878
+ const prevAccess = 'prev.' + memberPath.join('.');
879
+ return `${setter}(prev => ${buildSpreadUpdate('prev', memberPath, `${prevAccess} ${opChar} ${value}`)})`;
880
+ }
861
881
  if (expr.op === '=')
862
882
  return `${setter}(${value})`;
863
883
  if (expr.op === '+=')
@@ -1159,7 +1179,7 @@ function genTableUI(node, c) {
1159
1179
  // Body
1160
1180
  lines.push(`<tbody>`);
1161
1181
  lines.push(`{${data}.map((row, idx) => (`);
1162
- lines.push(`<tr key={idx} style={{ borderBottom: '1px solid #e2e8f0' }}>`);
1182
+ lines.push(`<tr key={row.id ?? idx} style={{ borderBottom: '1px solid #e2e8f0' }}>`);
1163
1183
  for (const col of node.columns) {
1164
1184
  if (col.kind === 'select') {
1165
1185
  lines.push(`<td style={{ padding: '12px 8px' }}><input type="checkbox" /></td>`);
@@ -1318,7 +1338,7 @@ function genChartUI(node, c) {
1318
1338
  lines.push(` <div style={{ display: 'flex', alignItems: 'flex-end', gap: '4px', height: '100%', padding: '20px 0' }}>`);
1319
1339
  lines.push(` <span style={{ fontSize: '14px', fontWeight: 'bold', position: 'absolute', top: 0 }}>{${title}}</span>`);
1320
1340
  lines.push(` {${data}.map((item, i) => (`);
1321
- lines.push(` <div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', height: '100%', justifyContent: 'flex-end' }}>`);
1341
+ lines.push(` <div key={item?.id ?? i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', height: '100%', justifyContent: 'flex-end' }}>`);
1322
1342
  lines.push(` <span style={{ fontSize: '12px', marginBottom: '4px' }}>{item[${y}]}</span>`);
1323
1343
  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' }} />`);
1324
1344
  lines.push(` <span style={{ fontSize: '11px', marginTop: '4px', color: '#666' }}>{item[${x}]}</span>`);
@@ -1331,7 +1351,7 @@ function genChartUI(node, c) {
1331
1351
  lines.push(` <span style={{ fontSize: '14px', fontWeight: 'bold' }}>{${title}}</span>`);
1332
1352
  lines.push(` {/* Pie chart - use recharts/chart.js for production */}`);
1333
1353
  lines.push(` {${data}.map((item, i) => (`);
1334
- lines.push(` <div key={i} style={{ display: 'flex', alignItems: 'center', gap: '8px', marginTop: '8px' }}>`);
1354
+ lines.push(` <div key={item?.id ?? i} style={{ display: 'flex', alignItems: 'center', gap: '8px', marginTop: '8px' }}>`);
1335
1355
  lines.push(` <div style={{ width: '12px', height: '12px', borderRadius: '50%', backgroundColor: ['#3182ce','#38a169','#d69e2e','#e53e3e','#805ad5'][i % 5] }} />`);
1336
1356
  lines.push(` <span>{item[${x}]}: {item[${y}]}</span>`);
1337
1357
  lines.push(` </div>`);
@@ -1344,7 +1364,7 @@ function genChartUI(node, c) {
1344
1364
  lines.push(` <span style={{ fontSize: '14px', fontWeight: 'bold' }}>{${title}}</span>`);
1345
1365
  lines.push(` <div style={{ marginTop: '12px', color: '#666' }}>`);
1346
1366
  lines.push(` {${data}.map((item, i) => (`);
1347
- lines.push(` <div key={i} style={{ display: 'flex', justifyContent: 'space-between', padding: '4px 0', borderBottom: '1px solid #eee' }}>`);
1367
+ lines.push(` <div key={item?.id ?? i} style={{ display: 'flex', justifyContent: 'space-between', padding: '4px 0', borderBottom: '1px solid #eee' }}>`);
1348
1368
  lines.push(` <span>{item[${x}]}</span>`);
1349
1369
  lines.push(` <span style={{ fontWeight: 600 }}>{item[${y}]}</span>`);
1350
1370
  lines.push(` </div>`);
@@ -1521,7 +1541,7 @@ function genModalUI(node, c) {
1521
1541
  lines.push(` <div style={{ backgroundColor: '#fff', borderRadius: '12px', padding: '24px', minWidth: '400px', maxWidth: '90vw', boxShadow: '0 20px 60px rgba(0,0,0,0.15)' }}`);
1522
1542
  lines.push(` onClick={e => e.stopPropagation()}>`);
1523
1543
  lines.push(` <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>`);
1524
- lines.push(` <h2 style={{ margin: 0, fontSize: '20px' }}>${node.title}</h2>`);
1544
+ lines.push(` <h2 style={{ margin: 0, fontSize: '20px' }}>${capitalize(node.title)}</h2>`);
1525
1545
  lines.push(` <button onClick={() => set${capitalize(showVar)}(false)} style={{ border: 'none', background: 'none', fontSize: '20px', cursor: 'pointer', padding: '4px' }}>&times;</button>`);
1526
1546
  lines.push(` </div>`);
1527
1547
  // Modal body
@@ -1615,9 +1635,9 @@ function genListUI(node, c) {
1615
1635
  const data = genExpr(node.dataSource, c);
1616
1636
  const body = node.body.map(ch => genUINode(ch, c)).join('\n');
1617
1637
  if (node.listType === 'grid') {
1618
- return `<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', gap: '16px' }}>\n {${data}.map((item, i) => (\n <div key={i}>\n ${body}\n </div>\n ))}\n</div>`;
1638
+ return `<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', gap: '16px' }}>\n {${data}.map((item, i) => (\n <div key={item?.id ?? i}>\n ${body}\n </div>\n ))}\n</div>`;
1619
1639
  }
1620
- return `<div className="list-${node.listType}">\n {${data}.map((item, i) => (\n <div key={i}>${body}</div>\n ))}\n</div>`;
1640
+ return `<div className="list-${node.listType}">\n {${data}.map((item, i) => (\n <div key={item?.id ?? i}>${body}</div>\n ))}\n</div>`;
1621
1641
  }
1622
1642
  function genDrawerUI(node, c) {
1623
1643
  c.imports.add('useState');
@@ -2290,6 +2310,27 @@ function extractStateName(expr) {
2290
2310
  return extractStateName(expr.object);
2291
2311
  return null;
2292
2312
  }
2313
+ /** Extract property path from a member expression, excluding the root state name.
2314
+ * e.g., user.name → ['name'], user.profile.name → ['profile', 'name'] */
2315
+ function extractMemberPath(expr) {
2316
+ const path = [];
2317
+ let current = expr;
2318
+ while (current.kind === 'member') {
2319
+ path.unshift(current.property);
2320
+ current = current.object;
2321
+ }
2322
+ return path;
2323
+ }
2324
+ /** Build nested spread update: buildSpreadUpdate('prev', ['profile', 'name'], '"Bob"')
2325
+ * → ({...prev, profile: {...prev.profile, name: "Bob"}}) */
2326
+ function buildSpreadUpdate(prevVar, path, value) {
2327
+ if (path.length === 1) {
2328
+ return `({...${prevVar}, ${path[0]}: ${value}})`;
2329
+ }
2330
+ const [head, ...rest] = path;
2331
+ const inner = buildSpreadUpdate(`${prevVar}.${head}`, rest, value);
2332
+ return `({...${prevVar}, ${head}: ${inner}})`;
2333
+ }
2293
2334
  function extractDeps(expr, c) {
2294
2335
  const deps = new Set();
2295
2336
  walkExpr(expr, e => {