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 +1 -1
- package/dist/generators/react.js +55 -14
- package/dist/generators/react.js.map +1 -1
- package/dist/generators/shared.d.ts +1 -0
- package/dist/generators/shared.js +5 -0
- package/dist/generators/shared.js.map +1 -1
- package/dist/generators/svelte.js +26 -18
- package/dist/generators/svelte.js.map +1 -1
- package/dist/generators/vue.js +32 -19
- package/dist/generators/vue.js.map +1 -1
- package/dist/parser.js +28 -2
- package/dist/parser.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/generators/react.js
CHANGED
|
@@ -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'] =
|
|
460
|
+
style['gap'] = addPx(v);
|
|
461
461
|
break;
|
|
462
462
|
case 'padding':
|
|
463
|
-
style['padding'] =
|
|
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'] =
|
|
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'] =
|
|
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
|
|
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' }}>×</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 => {
|