0x-lang 0.1.9 → 0.1.11

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/ast.d.ts CHANGED
@@ -700,6 +700,15 @@ export interface BreadcrumbNode extends BaseNode {
700
700
  type: 'Breadcrumb';
701
701
  props: Record<string, Expression>;
702
702
  }
703
+ export interface DividerNode extends BaseNode {
704
+ type: 'Divider';
705
+ props: Record<string, Expression>;
706
+ }
707
+ export interface ProgressNode extends BaseNode {
708
+ type: 'Progress';
709
+ value: Expression;
710
+ props: Record<string, Expression>;
711
+ }
703
712
  export interface StatsGridNode extends BaseNode {
704
713
  type: 'StatsGrid';
705
714
  cols: number;
@@ -893,7 +902,7 @@ export interface RtlNode extends BaseNode {
893
902
  enabled: boolean;
894
903
  props: Record<string, Expression>;
895
904
  }
896
- export type UINode = LayoutNode | TextNode | ButtonNode | InputNode | ImageNode | LinkNode | ToggleNode | SelectNode | IfBlock | ForBlock | ShowBlock | HideBlock | ComponentCall | CommentNode | TableNode | ChartNode | StatNode | NavNode | UploadNode | ModalNode | ToastNode | CrudNode | ListNode | LayoutShellNode | SlideOverNode | DrawerNode | CommandNode | ConfirmNode | PayNode | CartNode | MediaNode | NotificationNode | SearchNode | FilterNode | SocialNode | ProfileNode | HeroNode | FeaturesNode | PricingNode | FaqNode | TestimonialNode | FooterNode | AdminNode | SeoNode | A11yNode | AnimateNode | GestureNode | AiNode | AutomationNode | DevNode | EmitNode | ResponsiveNode | BreadcrumbNode | StatsGridNode | ErrorNode | LoadingNode | OfflineNode | RetryNode | LogNode;
905
+ export type UINode = LayoutNode | TextNode | ButtonNode | InputNode | ImageNode | LinkNode | ToggleNode | SelectNode | IfBlock | ForBlock | ShowBlock | HideBlock | ComponentCall | CommentNode | TableNode | ChartNode | StatNode | NavNode | UploadNode | ModalNode | ToastNode | CrudNode | ListNode | LayoutShellNode | SlideOverNode | DrawerNode | CommandNode | ConfirmNode | PayNode | CartNode | MediaNode | NotificationNode | SearchNode | FilterNode | SocialNode | ProfileNode | HeroNode | FeaturesNode | PricingNode | FaqNode | TestimonialNode | FooterNode | AdminNode | SeoNode | A11yNode | AnimateNode | GestureNode | AiNode | AutomationNode | DevNode | EmitNode | ResponsiveNode | BreadcrumbNode | StatsGridNode | DividerNode | ProgressNode | ErrorNode | LoadingNode | OfflineNode | RetryNode | LogNode;
897
906
  export type ASTNode = AppNode | PageNode | ComponentNode | StateDecl | DerivedDecl | PropDecl | TypeDecl | StoreDecl | ApiDecl | FnDecl | OnMount | OnDestroy | WatchBlock | CheckDecl | StyleDecl | JsImport | UseImport | JsBlock | ModelNode | DataDecl | FormDecl | AuthDecl | RealtimeDecl | RouteDecl | RoleDecl | AutomationNode | DevNode | DeployNode | EnvNode | DockerNode | CiNode | DomainNode | CdnNode | MonitorNode | BackupNode | EndpointNode | MiddlewareNode | QueueNode | CronNode | CacheNode | MigrateNode | SeedNode | WebhookNode | StorageNode | TestNode | E2eNode | MockNode | FixtureNode | I18nNode | LocaleNode | RtlNode | UINode;
898
907
  export interface CodeGenerator {
899
908
  target: string;
@@ -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}`),
@@ -431,6 +459,13 @@ function genUINode(node, c) {
431
459
  case 'Offline': return genOfflineUI(node, c);
432
460
  case 'Retry': return `{/* retry: max=${genExpr(node.maxRetries, c)} */}`;
433
461
  case 'Log': return `{/* log: ${genExpr(node.message, c)} */}`;
462
+ case 'Divider': return `<hr style={{ border: 'none', borderTop: '1px solid #e2e8f0', margin: '16px 0' }} />`;
463
+ case 'Progress': {
464
+ const pn = node;
465
+ const val = genExpr(pn.value, c);
466
+ const max = pn.props['max'] ? genExpr(pn.props['max'], c) : '100';
467
+ return `<div style={{ width: '100%', backgroundColor: '#e2e8f0', borderRadius: '9999px', height: '8px', overflow: 'hidden' }}>\n<div style={{ width: \`\${(${val} / ${max}) * 100}%\`, backgroundColor: '#3b82f6', height: '100%', borderRadius: '9999px', transition: 'width 0.3s' }} />\n</div>`;
468
+ }
434
469
  default: return `{/* unsupported: ${node.type} */}`;
435
470
  }
436
471
  }
@@ -498,20 +533,24 @@ function genLayout(node, c) {
498
533
  case 'grow':
499
534
  style['flexGrow'] = v;
500
535
  break;
501
- case 'scroll':
502
- style['overflow' + (v === 'y' ? 'Y' : 'X')] = 'auto';
536
+ case 'scroll': {
537
+ const sv = unquote(v);
538
+ style['overflow' + (sv === 'y' ? 'Y' : 'X')] = 'auto';
503
539
  break;
540
+ }
504
541
  case 'radius':
505
542
  style['borderRadius'] = addPx(v);
506
543
  break;
507
- case 'shadow':
508
- if (v === 'sm')
544
+ case 'shadow': {
545
+ const sv = unquote(v);
546
+ if (sv === 'sm')
509
547
  style['boxShadow'] = '0 1px 2px rgba(0,0,0,0.1)';
510
- else if (v === 'md')
548
+ else if (sv === 'md')
511
549
  style['boxShadow'] = '0 4px 6px rgba(0,0,0,0.1)';
512
- else if (v === 'lg')
550
+ else if (sv === 'lg')
513
551
  style['boxShadow'] = '0 10px 15px rgba(0,0,0,0.1)';
514
552
  break;
553
+ }
515
554
  }
516
555
  }
517
556
  // Apply style class
@@ -584,7 +623,19 @@ function genText(node, c) {
584
623
  }
585
624
  const content = genTextContent(node.content, c);
586
625
  const styleStr = Object.keys(style).length > 0 ? ` style={${genStyleObj(style, dynamicKeys)}}` : '';
587
- return `<span${styleStr}>${content}</span>`;
626
+ // Badge prop: render a badge indicator next to content
627
+ const badgeExpr = node.props['badge'];
628
+ const tooltipExpr = node.props['tooltip'];
629
+ let result = `<span${styleStr}>${content}</span>`;
630
+ if (badgeExpr) {
631
+ const badge = genExpr(badgeExpr, c);
632
+ 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>`;
633
+ }
634
+ if (tooltipExpr) {
635
+ const tooltip = genExpr(tooltipExpr, c);
636
+ result = `<span title={${tooltip}}>${badgeExpr ? result.replace(/^<span/, '<span') : `<span${styleStr}>${content}</span>`}</span>`;
637
+ }
638
+ return result;
588
639
  }
589
640
  function genButton(node, c) {
590
641
  const label = genTextContent(node.label, c);
@@ -634,6 +685,7 @@ function genInput(node, c) {
634
685
  function genImage(node, c) {
635
686
  const src = genExpr(node.src, c);
636
687
  const props = [`src={${src}}`];
688
+ const style = {};
637
689
  for (const [key, val] of Object.entries(node.props)) {
638
690
  const v = genExpr(val, c);
639
691
  switch (key) {
@@ -646,8 +698,24 @@ function genImage(node, c) {
646
698
  case 'alt':
647
699
  props.push(`alt=${quoteJsx(v)}`);
648
700
  break;
701
+ case 'round':
702
+ style['borderRadius'] = "'50%'";
703
+ break;
704
+ case 'radius':
705
+ style['borderRadius'] = `'${addPx(v)}'`;
706
+ break;
707
+ case 'size': {
708
+ const px = `'${addPx(v)}'`;
709
+ style['width'] = px;
710
+ style['height'] = px;
711
+ break;
712
+ }
649
713
  }
650
714
  }
715
+ if (Object.keys(style).length > 0) {
716
+ const entries = Object.entries(style).map(([k, v]) => `${k}: ${v}`).join(', ');
717
+ props.push(`style={{ ${entries} }}`);
718
+ }
651
719
  return `<img ${props.join(' ')} />`;
652
720
  }
653
721
  function genLink(node, c) {
@@ -825,13 +893,26 @@ function genExpr(expr, c) {
825
893
  const method = expr.callee.property;
826
894
  if (objName && c.states.has(objName)) {
827
895
  const setter = 'set' + capitalize(objName);
896
+ const memberPath = extractMemberPath(expr.callee.object);
828
897
  if (method === 'push') {
898
+ if (memberPath.length > 0) {
899
+ const arrayPath = 'prev.' + memberPath.join('.');
900
+ return `${setter}(prev => ${buildSpreadUpdate('prev', memberPath, `[...${arrayPath}, ${args}]`)})`;
901
+ }
829
902
  return `${setter}(prev => [...prev, ${args}])`;
830
903
  }
831
904
  if (method === 'filter') {
905
+ if (memberPath.length > 0) {
906
+ const arrayPath = 'prev.' + memberPath.join('.');
907
+ return `${setter}(prev => ${buildSpreadUpdate('prev', memberPath, `${arrayPath}.filter(${args})`)})`;
908
+ }
832
909
  return `${setter}(prev => prev.filter(${args}))`;
833
910
  }
834
911
  if (method === 'remove') {
912
+ if (memberPath.length > 0) {
913
+ const arrayPath = 'prev.' + memberPath.join('.');
914
+ return `${setter}(prev => ${buildSpreadUpdate('prev', memberPath, `${arrayPath}.filter(item => item.id !== ${args})`)})`;
915
+ }
835
916
  return `${setter}(prev => prev.filter(item => item.id !== ${args}))`;
836
917
  }
837
918
  }
@@ -854,6 +935,8 @@ function genExpr(expr, c) {
854
935
  return `[${els}]`;
855
936
  }
856
937
  case 'object_expr': {
938
+ if (expr.properties.length === 0)
939
+ return '{}';
857
940
  const props = expr.properties.map(p => {
858
941
  if (p.key === p.value.kind && p.value.kind === 'identifier') {
859
942
  return p.key; // shorthand