0x-lang 0.1.25 → 0.1.27

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
@@ -200,6 +200,10 @@ export interface TopLevelVarDecl extends BaseNode {
200
200
  name: string;
201
201
  value: Expression;
202
202
  }
203
+ export interface ThemeDecl extends BaseNode {
204
+ type: 'ThemeDecl';
205
+ theme: string;
206
+ }
203
207
  export interface CommentNode extends BaseNode {
204
208
  type: 'Comment';
205
209
  text: string;
@@ -915,7 +919,7 @@ export interface RtlNode extends BaseNode {
915
919
  props: Record<string, Expression>;
916
920
  }
917
921
  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 | RawBlock;
918
- export type ASTNode = AppNode | PageNode | ComponentNode | StateDecl | DerivedDecl | PropDecl | TypeDecl | StoreDecl | ApiDecl | FnDecl | OnMount | OnDestroy | WatchBlock | CheckDecl | StyleDecl | JsImport | UseImport | JsBlock | RawBlock | TopLevelVarDecl | 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;
922
+ export type ASTNode = AppNode | PageNode | ComponentNode | StateDecl | DerivedDecl | PropDecl | TypeDecl | StoreDecl | ApiDecl | FnDecl | OnMount | OnDestroy | WatchBlock | CheckDecl | StyleDecl | JsImport | UseImport | JsBlock | RawBlock | TopLevelVarDecl | 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 | ThemeDecl | UINode;
919
923
  export interface CodeGenerator {
920
924
  target: string;
921
925
  generate(ast: PageNode | ComponentNode | AppNode): GeneratedCode;
@@ -1,5 +1,6 @@
1
1
  import type { ASTNode, StateDecl, StyleDecl, Expression, Statement, GeneratedCode } from '../ast.js';
2
2
  import { SourceMapBuilder } from './source-map.js';
3
+ import { type ThemeName } from './themes.js';
3
4
  export interface GenContext {
4
5
  imports: Set<string>;
5
6
  states: Map<string, StateDecl>;
@@ -9,6 +10,8 @@ export interface GenContext {
9
10
  indent: number;
10
11
  readOnly: boolean;
11
12
  smb: SourceMapBuilder | null;
13
+ theme: ThemeName | null;
14
+ themeImports: Set<string>;
12
15
  }
13
16
  export declare function ctx(): GenContext;
14
17
  export declare function generateReact(ast: ASTNode[]): GeneratedCode;
@@ -1,6 +1,7 @@
1
1
  // 0x → React Code Generator
2
2
  import { SIZE_MAP, unquote, capitalize, parseGradient, addPx, getPassthroughProps, typeExprToJs, getFieldDefault, 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
+ import { getReactThemeRenderer } from './themes.js';
4
5
  export function ctx() {
5
6
  return {
6
7
  imports: new Set(),
@@ -11,6 +12,8 @@ export function ctx() {
11
12
  indent: 1,
12
13
  readOnly: false,
13
14
  smb: null,
15
+ theme: null,
16
+ themeImports: new Set(),
14
17
  };
15
18
  }
16
19
  function ind(c) {
@@ -18,8 +21,14 @@ function ind(c) {
18
21
  }
19
22
  export function generateReact(ast) {
20
23
  const parts = [];
24
+ // Detect theme declaration
25
+ const themeNode = ast.find(n => n.type === 'ThemeDecl');
26
+ const theme = themeNode ? themeNode.theme : null;
21
27
  for (const node of ast) {
22
- if (node.type === 'Model') {
28
+ if (node.type === 'ThemeDecl') {
29
+ // Handled above
30
+ }
31
+ else if (node.type === 'Model') {
23
32
  parts.push(genModelCode(node));
24
33
  }
25
34
  else if (node.type === 'AuthDecl') {
@@ -114,7 +123,7 @@ export function generateReact(ast) {
114
123
  parts.push(genRtlCode(node));
115
124
  }
116
125
  else if (node.type === 'Page' || node.type === 'Component' || node.type === 'App') {
117
- parts.push(generateTopLevel(node));
126
+ parts.push(generateTopLevel(node, theme));
118
127
  }
119
128
  }
120
129
  const code = parts.join('\n\n');
@@ -176,8 +185,9 @@ export function generateBackendCode(node) {
176
185
  default: return null;
177
186
  }
178
187
  }
179
- function generateTopLevel(node) {
188
+ function generateTopLevel(node, theme = null) {
180
189
  const c = ctx();
190
+ c.theme = theme;
181
191
  // First pass: collect states, derived, props, styles
182
192
  for (const child of node.body) {
183
193
  if (child.type === 'StateDecl')
@@ -265,6 +275,7 @@ function generateTopLevel(node) {
265
275
  case 'I18n':
266
276
  case 'Locale':
267
277
  case 'Rtl':
278
+ case 'ThemeDecl':
268
279
  // Handled at top level
269
280
  break;
270
281
  case 'JsImport':
@@ -333,14 +344,17 @@ function generateTopLevel(node) {
333
344
  userImports.push(`import ${ui.name} from '${ui.source}';`);
334
345
  }
335
346
  }
347
+ // Collect theme imports (deduplicated)
348
+ const themeImportLines = Array.from(c.themeImports).sort();
336
349
  const exportKw = isComponent ? '' : 'export default ';
337
350
  // Add "use client" for Next.js SSR when client-side hooks are used
338
351
  const needsClient = c.imports.has('useState') || c.imports.has('useEffect') || c.imports.has('useRef') || c.imports.has('useCallback');
339
352
  const lines = [
340
353
  ...(needsClient ? [`'use client';`, ''] : []),
341
- `// Generated by 0x`,
354
+ `// Generated by 0x${c.theme ? ` (theme: ${c.theme})` : ''}`,
342
355
  importLine,
343
356
  ...userImports,
357
+ ...themeImportLines,
344
358
  '',
345
359
  `${exportKw}function ${node.name}(${propsArg}) {`,
346
360
  ...hookLines.map(l => ` ${l}`),
@@ -451,6 +465,17 @@ function srcComment(node) {
451
465
  return node.loc?.line ? `{/* 0x:L${node.loc.line} */}` : '';
452
466
  }
453
467
  function genUINode(node, c) {
468
+ // Check for themed renderer
469
+ if (c.theme) {
470
+ const renderer = getReactThemeRenderer(c.theme, node.type);
471
+ if (renderer) {
472
+ const helpers = { genExpr: (e, ctx) => genExpr(e, ctx || c), genTextContent: (e, ctx) => genTextContent(e, ctx || c), genActionExpr: (a, ctx) => genActionExpr(a, ctx || c), genUINode: (n, ctx) => genUINode(n, ctx || c), srcComment: (n) => srcComment(n) };
473
+ const result = renderer(node, c, helpers);
474
+ for (const imp of result.imports)
475
+ c.themeImports.add(imp);
476
+ return result.jsx;
477
+ }
478
+ }
454
479
  switch (node.type) {
455
480
  case 'Layout': return genLayout(node, c);
456
481
  case 'Text': return genText(node, c);