0x-lang 0.1.16 → 0.1.17

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,5 +1,5 @@
1
1
  // 0x → React Code Generator
2
- import { SIZE_MAP, unquote, capitalize, parseGradient, addPx } from './shared.js';
2
+ import { SIZE_MAP, unquote, capitalize, parseGradient, addPx, getPassthroughProps, KNOWN_LAYOUT_PROPS, KNOWN_TEXT_PROPS, KNOWN_BUTTON_PROPS, KNOWN_INPUT_PROPS, KNOWN_IMAGE_PROPS, KNOWN_LINK_PROPS, KNOWN_TOGGLE_PROPS, KNOWN_SELECT_PROPS } from './shared.js';
3
3
  import { SourceMapBuilder } from './source-map.js';
4
4
  export function ctx() {
5
5
  return {
@@ -369,7 +369,8 @@ function genDerived(node, c) {
369
369
  c.readOnly = prevReadOnly;
370
370
  const { deps, warning } = extractDepsWithWarning(node.expression, c);
371
371
  const warnComment = warning ? ` ${warning}` : '';
372
- return `${warnComment ? warnComment + '\n ' : ''}const ${node.name} = useMemo(() => ${expr}, [${deps.join(', ')}]);`;
372
+ const sc = node.loc?.line ? `// 0x:L${node.loc.line}\n ` : '';
373
+ return `${sc}${warnComment ? warnComment + '\n ' : ''}const ${node.name} = useMemo(() => ${expr}, [${deps.join(', ')}]);`;
373
374
  }
374
375
  function genCheck(node, c) {
375
376
  const cond = genExpr(node.condition, c);
@@ -400,32 +401,36 @@ function genFunction(node, c) {
400
401
  // requires
401
402
  const requireChecks = node.requires.map(r => `if (!(${genExpr(r, c)})) throw new Error('Precondition failed');`).join('\n ');
402
403
  const allBody = [requireChecks, body].filter(Boolean).join('\n ');
403
- return `const ${node.name} = ${asyncKw}(${params}) => {\n ${allBody}\n };`;
404
+ const sc = node.loc?.line ? `// 0x:L${node.loc.line}\n ` : '';
405
+ return `${sc}const ${node.name} = ${asyncKw}(${params}) => {\n ${allBody}\n };`;
404
406
  }
405
407
  // ── Lifecycle ───────────────────────────────────────
406
408
  function genOnMount(node, c) {
407
409
  c.imports.add('useEffect');
408
410
  const body = node.body.map(s => genStatement(s, c)).join('\n ');
409
411
  const hasAwait = bodyContainsAwait(node.body);
412
+ const sc = node.loc?.line ? `// 0x:L${node.loc.line}\n ` : '';
410
413
  if (hasAwait) {
411
- return `useEffect(() => {\n (async () => {\n ${body}\n })();\n }, []);`;
414
+ return `${sc}useEffect(() => {\n (async () => {\n ${body}\n })();\n }, []);`;
412
415
  }
413
- return `useEffect(() => {\n ${body}\n }, []);`;
416
+ return `${sc}useEffect(() => {\n ${body}\n }, []);`;
414
417
  }
415
418
  function genOnDestroy(node, c) {
416
419
  c.imports.add('useEffect');
417
420
  const body = node.body.map(s => genStatement(s, c)).join('\n ');
418
- return `useEffect(() => {\n return () => {\n ${body}\n };\n }, []);`;
421
+ const sc = node.loc?.line ? `// 0x:L${node.loc.line}\n ` : '';
422
+ return `${sc}useEffect(() => {\n return () => {\n ${body}\n };\n }, []);`;
419
423
  }
420
424
  function genWatch(node, c) {
421
425
  c.imports.add('useEffect');
422
426
  const body = node.body.map(s => genStatement(s, c)).join('\n ');
423
427
  const vars = (node.variables || [node.variable]).join(', ');
424
428
  const hasAwait = bodyContainsAwait(node.body);
429
+ const sc = node.loc?.line ? `// 0x:L${node.loc.line}\n ` : '';
425
430
  if (hasAwait) {
426
- return `useEffect(() => {\n (async () => {\n ${body}\n })();\n }, [${vars}]);`;
431
+ return `${sc}useEffect(() => {\n (async () => {\n ${body}\n })();\n }, [${vars}]);`;
427
432
  }
428
- return `useEffect(() => {\n ${body}\n }, [${vars}]);`;
433
+ return `${sc}useEffect(() => {\n ${body}\n }, [${vars}]);`;
429
434
  }
430
435
  // ── UI Nodes ────────────────────────────────────────
431
436
  function srcComment(node) {
@@ -522,10 +527,14 @@ function genLayout(node, c) {
522
527
  style['flexDirection'] = node.direction === 'row' ? 'row' : 'column';
523
528
  }
524
529
  // Process layout props
530
+ let className = null;
525
531
  for (const [key, val] of Object.entries(node.props)) {
526
532
  const v = genExpr(val, c);
527
533
  const isDynamic = val.kind === 'braced' || val.kind === 'ternary' || val.kind === 'binary' || val.kind === 'member' || val.kind === 'call';
528
534
  switch (key) {
535
+ case 'class':
536
+ className = unquote(v);
537
+ break;
529
538
  case 'gap':
530
539
  style['gap'] = addPx(v);
531
540
  break;
@@ -600,17 +609,23 @@ function genLayout(node, c) {
600
609
  }
601
610
  }
602
611
  const styleStr = genStyleObj(style, dynamicKeys);
612
+ const classAttr = className ? ` className="${className}"` : '';
613
+ const extra = getPassthroughProps(node.props, KNOWN_LAYOUT_PROPS, e => genExpr(e, c), 'react');
603
614
  const children = node.children.map(ch => genUINode(ch, c)).join('\n');
604
615
  const sc = srcComment(node);
605
- return `${sc}<div style={${styleStr}}>\n${children}\n</div>`;
616
+ return `${sc}<div${classAttr} style={${styleStr}}${extra}>\n${children}\n</div>`;
606
617
  }
607
618
  function genText(node, c) {
608
619
  const style = {};
609
620
  const dynamicKeys = new Set();
621
+ let className = null;
610
622
  for (const [key, val] of Object.entries(node.props)) {
611
623
  const v = genExpr(val, c);
612
624
  const isDynamic = val.kind === 'braced' || val.kind === 'ternary' || val.kind === 'binary' || val.kind === 'member' || val.kind === 'call';
613
625
  switch (key) {
626
+ case 'class':
627
+ className = unquote(v);
628
+ break;
614
629
  case 'size': {
615
630
  const uv = unquote(v);
616
631
  style['fontSize'] = SIZE_MAP[uv] || `${uv}px`;
@@ -658,12 +673,14 @@ function genText(node, c) {
658
673
  }
659
674
  }
660
675
  const content = genTextContent(node.content, c);
676
+ const classAttr = className ? ` className="${className}"` : '';
661
677
  const styleStr = Object.keys(style).length > 0 ? ` style={${genStyleObj(style, dynamicKeys)}}` : '';
678
+ const extra = getPassthroughProps(node.props, KNOWN_TEXT_PROPS, e => genExpr(e, c), 'react');
662
679
  // Badge prop: render a badge indicator next to content
663
680
  const badgeExpr = node.props['badge'];
664
681
  const tooltipExpr = node.props['tooltip'];
665
682
  const sc = srcComment(node);
666
- let result = `${sc}<span${styleStr}>${content}</span>`;
683
+ let result = `${sc}<span${classAttr}${styleStr}${extra}>${content}</span>`;
667
684
  if (badgeExpr) {
668
685
  const badge = genExpr(badgeExpr, c);
669
686
  result = `<span style={{ position: 'relative', display: 'inline-flex', alignItems: 'center' }}>\n<span${styleStr}>${content}</span>\n<span style={{ marginLeft: '6px', padding: '2px 6px', fontSize: '12px', fontWeight: 'bold', borderRadius: '9999px', backgroundColor: '#ef4444', color: '#fff', minWidth: '20px', textAlign: 'center' }}>{${badge}}</span>\n</span>`;
@@ -678,24 +695,37 @@ function genButton(node, c) {
678
695
  const label = genTextContent(node.label, c);
679
696
  const actionCode = genActionExpr(node.action, c);
680
697
  const styleProps = [];
698
+ let className = null;
681
699
  for (const [key, val] of Object.entries(node.props)) {
682
700
  const v = genExpr(val, c);
683
701
  switch (key) {
684
- case 'style':
685
- styleProps.push(`className="${v}"`);
702
+ case 'class':
703
+ className = unquote(v);
686
704
  break;
705
+ case 'style': {
706
+ const sv = unquote(v);
707
+ if (sv === 'primary')
708
+ styleProps.push('style={{ backgroundColor: "#3b82f6", color: "white", border: "none", padding: "8px 16px", borderRadius: "6px", cursor: "pointer" }}');
709
+ else if (sv === 'danger')
710
+ styleProps.push('style={{ backgroundColor: "#ef4444", color: "white", border: "none", padding: "8px 16px", borderRadius: "6px", cursor: "pointer" }}');
711
+ break;
712
+ }
687
713
  case 'disabled':
688
714
  styleProps.push(`disabled={${v}}`);
689
715
  break;
690
716
  case 'size': /* handled in style */ break;
691
717
  }
692
718
  }
719
+ if (className)
720
+ styleProps.push(`className="${className}"`);
721
+ const extra = getPassthroughProps(node.props, KNOWN_BUTTON_PROPS, e => genExpr(e, c), 'react');
693
722
  const propsStr = styleProps.join(' ');
694
723
  const sc = srcComment(node);
695
- return `${sc}<button onClick={() => ${actionCode}}${propsStr ? ' ' + propsStr : ''}>${label}</button>`;
724
+ return `${sc}<button onClick={() => ${actionCode}}${propsStr ? ' ' + propsStr : ''}${extra}>${label}</button>`;
696
725
  }
697
726
  function genInput(node, c) {
698
727
  const setter = 'set' + capitalize(node.binding);
728
+ let className = null;
699
729
  const props = [
700
730
  `value={${node.binding}}`,
701
731
  `onChange={e => ${setter}(e.target.value)}`,
@@ -703,6 +733,9 @@ function genInput(node, c) {
703
733
  for (const [key, val] of Object.entries(node.props)) {
704
734
  const v = genExpr(val, c);
705
735
  switch (key) {
736
+ case 'class':
737
+ className = unquote(v);
738
+ break;
706
739
  case 'placeholder':
707
740
  props.push(`placeholder=${quoteJsx(v)}`);
708
741
  break;
@@ -718,16 +751,23 @@ function genInput(node, c) {
718
751
  props.push(`onKeyPress={e => ${handler}(e.key)}`);
719
752
  }
720
753
  }
754
+ if (className)
755
+ props.push(`className="${className}"`);
756
+ const extra = getPassthroughProps(node.props, KNOWN_INPUT_PROPS, e => genExpr(e, c), 'react');
721
757
  const sc = srcComment(node);
722
- return `${sc}<input ${props.join(' ')} />`;
758
+ return `${sc}<input ${props.join(' ')}${extra} />`;
723
759
  }
724
760
  function genImage(node, c) {
725
761
  const src = genExpr(node.src, c);
726
762
  const props = [`src={${src}}`];
727
763
  const style = {};
764
+ let className = null;
728
765
  for (const [key, val] of Object.entries(node.props)) {
729
766
  const v = genExpr(val, c);
730
767
  switch (key) {
768
+ case 'class':
769
+ className = unquote(v);
770
+ break;
731
771
  case 'width':
732
772
  props.push(`width="${v}"`);
733
773
  break;
@@ -751,16 +791,26 @@ function genImage(node, c) {
751
791
  }
752
792
  }
753
793
  }
794
+ if (className)
795
+ props.push(`className="${className}"`);
754
796
  if (Object.keys(style).length > 0) {
755
797
  const entries = Object.entries(style).map(([k, v]) => `${k}: ${v}`).join(', ');
756
798
  props.push(`style={{ ${entries} }}`);
757
799
  }
758
- return `<img ${props.join(' ')} />`;
800
+ const extra = getPassthroughProps(node.props, KNOWN_IMAGE_PROPS, e => genExpr(e, c), 'react');
801
+ return `<img ${props.join(' ')}${extra} />`;
759
802
  }
760
803
  function genLink(node, c) {
761
804
  const label = genTextContent(node.label, c);
762
805
  const href = genExpr(node.href, c);
763
- return `<a href={${href}}>${label}</a>`;
806
+ let className = null;
807
+ for (const [key, val] of Object.entries(node.props || {})) {
808
+ if (key === 'class')
809
+ className = unquote(genExpr(val, c));
810
+ }
811
+ const classAttr = className ? ` className="${className}"` : '';
812
+ const extra = getPassthroughProps(node.props || {}, KNOWN_LINK_PROPS, e => genExpr(e, c), 'react');
813
+ return `<a href={${href}}${classAttr}${extra}>${label}</a>`;
764
814
  }
765
815
  function genToggle(node, c) {
766
816
  const binding = node.binding;
@@ -773,12 +823,26 @@ function genToggle(node, c) {
773
823
  else {
774
824
  setter = `set${capitalize(parts[0])}(prev => ({...prev, ${parts.slice(1).join('.')}: !prev.${parts.slice(1).join('.')}}))`;
775
825
  }
776
- return `<input type="checkbox" checked={${binding}} onChange={() => ${setter}} />`;
826
+ let className = null;
827
+ for (const [key, val] of Object.entries(node.props || {})) {
828
+ if (key === 'class')
829
+ className = unquote(genExpr(val, c));
830
+ }
831
+ const classAttr = className ? ` className="${className}"` : '';
832
+ const extra = getPassthroughProps(node.props || {}, KNOWN_TOGGLE_PROPS, e => genExpr(e, c), 'react');
833
+ return `<input type="checkbox" checked={${binding}} onChange={() => ${setter}}${classAttr}${extra} />`;
777
834
  }
778
835
  function genSelect(node, c) {
779
836
  const setter = 'set' + capitalize(node.binding);
780
837
  const options = genExpr(node.options, c);
781
- return `<select value={${node.binding}} onChange={e => ${setter}(e.target.value)}>\n {${options}.map(opt => <option key={opt} value={opt}>{opt}</option>)}\n</select>`;
838
+ let className = null;
839
+ for (const [key, val] of Object.entries(node.props || {})) {
840
+ if (key === 'class')
841
+ className = unquote(genExpr(val, c));
842
+ }
843
+ const classAttr = className ? ` className="${className}"` : '';
844
+ const extra = getPassthroughProps(node.props || {}, KNOWN_SELECT_PROPS, e => genExpr(e, c), 'react');
845
+ return `<select value={${node.binding}} onChange={e => ${setter}(e.target.value)}${classAttr}${extra}>\n {${options}.map(opt => <option key={opt} value={opt}>{opt}</option>)}\n</select>`;
782
846
  }
783
847
  function genComponentCall(node, c) {
784
848
  const parts = [];