@jxsuite/compiler 1.0.0 → 1.1.0

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.
@@ -17,8 +17,10 @@ import {
17
17
  DEFAULT_REACTIVITY_SRC,
18
18
  buildAttrs,
19
19
  compileExpression,
20
+ compileStatements,
20
21
  compileStyles,
21
22
  createCompileContext,
23
+ emitFormulaFn,
22
24
  escapeHtml,
23
25
  isMutating,
24
26
  isRefObject,
@@ -27,10 +29,12 @@ import {
27
29
  resolveStaticValue,
28
30
  } from "../shared.ts";
29
31
  import {
32
+ hasStructuredBody,
30
33
  isExpandedSignal,
31
34
  isExpressionDef,
32
35
  isFunctionDef,
33
36
  isMappedArray,
37
+ isNamedFormulaDef,
34
38
  isPrototypeDef,
35
39
  paramNames,
36
40
  } from "@jxsuite/schema/guards";
@@ -124,7 +128,11 @@ export function compileClient(
124
128
  eventParam: "e",
125
129
  statePrefix: "state",
126
130
  });
127
- if (isMutating(node.operator)) {
131
+ if (isNamedFormulaDef(def)) {
132
+ // Named formula → a scope callable mapping positional args onto declared parameters.
133
+ // Call sites stay positional, matching the interpreter.
134
+ initBlocks.push(`state[${JSON.stringify(key)}] = ${emitFormulaFn(def, compiled)};`);
135
+ } else if (isMutating(node.operator)) {
128
136
  onEntries.push([key, { args: ["state", "e"], body: compiled }]);
129
137
  } else {
130
138
  computedEntries.push([key, `() => ${compiled}`]);
@@ -135,7 +143,18 @@ export function compileClient(
135
143
  // $prototype: "Function"
136
144
  if (isFunctionDef(def)) {
137
145
  const args = def.parameters ? paramNames(def.parameters) : def.arguments;
138
- if (def.$src) {
146
+ if (hasStructuredBody(def)) {
147
+ // Structured body (spec §20) → an event handler compiled from statements.
148
+ // With parameters, a positional callable like a named formula.
149
+ const compiled = compileStatements(def.body, { eventParam: "e", statePrefix: "state" });
150
+ if (def.parameters && def.parameters.length > 0) {
151
+ initBlocks.push(
152
+ `state[${JSON.stringify(key)}] = ${emitFormulaFn(def, `(() => {\n${compiled}\n})()`)};`,
153
+ );
154
+ } else {
155
+ onEntries.push([key, { args: ["state", "e"], body: compiled }]);
156
+ }
157
+ } else if (def.$src) {
139
158
  if (!srcImportMap.has(def.$src)) {
140
159
  srcImportMap.set(def.$src, new Set());
141
160
  }
@@ -143,12 +162,12 @@ export function compileClient(
143
162
 
144
163
  // $src functions always produce computed entries (they return values)
145
164
  computedEntries.push([key, `() => { return ${key}(state); }`]);
146
- } else if (def.body && def.body.includes("return")) {
165
+ } else if (typeof def.body === "string" && def.body.includes("return")) {
147
166
  // Body contains return → computed
148
167
  computedEntries.push([key, `() => { ${def.body} }`]);
149
168
  } else {
150
169
  // No return → event handler
151
- onEntries.push([key, { args: args ?? ["state"], body: def.body }]);
170
+ onEntries.push([key, { args: args ?? ["state"], body: def.body as string | undefined }]);
152
171
  }
153
172
  continue;
154
173
  }
@@ -335,13 +354,22 @@ function buildClientNode(
335
354
  const key = refToBindingKey(val.$ref);
336
355
  bindAttrs.push(`@${eventName}="${key}"`);
337
356
  needsBind = true;
357
+ } else if (hasStructuredBody(val)) {
358
+ const key = `_h${counter.h}`;
359
+ counter.h += 1;
360
+ bindAttrs.push(`@${eventName}="${key}"`);
361
+ handlers.set(key, {
362
+ args: ["state", "e"],
363
+ body: compileStatements(val.body, { eventParam: "e", statePrefix: "state" }),
364
+ });
365
+ needsBind = true;
338
366
  } else if (isFunctionDef(val)) {
339
367
  const key = `_h${counter.h}`;
340
368
  counter.h += 1;
341
369
  bindAttrs.push(`@${eventName}="${key}"`);
342
370
  handlers.set(key, {
343
371
  args: val.parameters ? paramNames(val.parameters) : (val.arguments ?? ["state", "event"]),
344
- body: val.body,
372
+ body: val.body as string | undefined,
345
373
  });
346
374
  needsBind = true;
347
375
  } else if (isExpressionDef(val)) {
@@ -594,6 +622,9 @@ function emitLitMapTemplate(def: JxMutableNode | undefined) {
594
622
  if (isRefObject(val)) {
595
623
  const key = refToBindingKey((val as JxMutableNode).$ref as string);
596
624
  attrs += ` @${eventName}=\${(e) => { state.$map = { item, index }; on.${key}(e); }}`;
625
+ } else if (hasStructuredBody(val)) {
626
+ const compiled = compileStatements(val.body, { eventParam: "e", statePrefix: "state" });
627
+ attrs += ` @${eventName}=\${(e) => { state.$map = { item, index }; ${compiled} }}`;
597
628
  } else if (val && typeof val === "object" && (val as JxMutableNode).$prototype === "Function") {
598
629
  const body = mapRefsToLit((val as JxMutableNode).body as string);
599
630
  attrs += ` @${eventName}=\${(e) => { ${body} }}`;
@@ -9,15 +9,19 @@ import { RESERVED_KEYS, camelToKebab } from "@jxsuite/runtime";
9
9
  import {
10
10
  collectStyles,
11
11
  compileExpression,
12
+ compileStatements,
13
+ emitFormulaFn,
12
14
  escapeHtml,
13
15
  isMutating,
14
16
  isSchemaOnly,
15
17
  tagNameToClassName,
16
18
  } from "../shared.ts";
17
19
  import {
20
+ hasStructuredBody,
18
21
  isExpressionDef,
19
22
  isFunctionDef,
20
23
  isMappedArray,
24
+ isNamedFormulaDef,
21
25
  isRef,
22
26
  paramNames,
23
27
  } from "@jxsuite/schema/guards";
@@ -284,11 +288,14 @@ export function emitElementModule(doc: JxDocument, className: string, elementImp
284
288
  const computedEntries: [string, JxExpressionDef | JxFunctionDef][] = [];
285
289
  const functionEntries: [string, JxExpressionDef | JxFunctionDef][] = [];
286
290
 
291
+ const formulaEntries: [string, JxExpressionDef][] = [];
287
292
  for (const [key, def] of Object.entries(defs)) {
288
293
  const d = def as JxMutableNode;
289
294
  if (isExpressionDef(d)) {
290
295
  const node = d.$expression as ExpressionNode;
291
- if (isMutating(node.operator)) {
296
+ if (isNamedFormulaDef(d)) {
297
+ formulaEntries.push([key, d]);
298
+ } else if (isMutating(node.operator)) {
292
299
  functionEntries.push([key, d]);
293
300
  } else {
294
301
  computedEntries.push([key, d]);
@@ -324,6 +331,16 @@ export function emitElementModule(doc: JxDocument, className: string, elementImp
324
331
  statePrefix: "s",
325
332
  });
326
333
  lines.push(` this.state.${key} = (s, e) => { ${compiled}; };`);
334
+ } else if (hasStructuredBody(def)) {
335
+ // Structured body (spec §20) — statements compiled against this.state; dispatch target is
336
+ // The component instance itself (WHATWG dispatchEvent on the custom element).
337
+ const compiled = compileStatements(def.body, {
338
+ dispatchTarget: "this",
339
+ eventParam: "e",
340
+ indent: " ",
341
+ statePrefix: "this.state",
342
+ });
343
+ lines.push(` this.state.${key} = (s, e) => {`, compiled, " };");
327
344
  } else {
328
345
  const args = def.parameters ? paramNames(def.parameters) : (def.arguments ?? ["state"]);
329
346
  const paramList = args.join(", ");
@@ -333,13 +350,23 @@ export function emitElementModule(doc: JxDocument, className: string, elementImp
333
350
  } else {
334
351
  lines.push(
335
352
  ` this.state.${key} = (${paramList}) => {`,
336
- ` ${def.body ?? ""}`,
353
+ ` ${typeof def.body === "string" ? def.body : ""}`,
337
354
  " };",
338
355
  );
339
356
  }
340
357
  }
341
358
  }
342
359
 
360
+ // Emit named formulas — scope callables mapping positional args onto declared parameters
361
+ for (const [key, def] of formulaEntries) {
362
+ lines.push("");
363
+ const compiled = compileExpression(def.$expression as ExpressionNode, {
364
+ eventParam: "e",
365
+ statePrefix: "this.state",
366
+ });
367
+ lines.push(` this.state.${key} = ${emitFormulaFn(def, compiled)};`);
368
+ }
369
+
343
370
  // Emit computed signals — $src or inline body
344
371
  for (const [key, def] of computedEntries) {
345
372
  lines.push("");
@@ -353,7 +380,10 @@ export function emitElementModule(doc: JxDocument, className: string, elementImp
353
380
  lines.push(` this.state.${key} = computed(() => ${key}(this.state));`);
354
381
  } else {
355
382
  lines.push(` this.state.${key} = computed(() => {`);
356
- const body = (def.body ?? "").replaceAll("state.", "this.state.");
383
+ const body = (typeof def.body === "string" ? def.body : "").replaceAll(
384
+ "state.",
385
+ "this.state.",
386
+ );
357
387
  lines.push(` ${body}`, " });");
358
388
  }
359
389
  }
@@ -765,7 +795,14 @@ function toLitTextContent(value: unknown) {
765
795
  * @returns {string}
766
796
  */
767
797
  function inlineHandlerBody(def: JxFunctionDef) {
768
- const body = def.body ?? "";
798
+ if (hasStructuredBody(def)) {
799
+ return compileStatements(def.body, {
800
+ dispatchTarget: "e.currentTarget",
801
+ eventParam: "e",
802
+ statePrefix: "s",
803
+ });
804
+ }
805
+ const body = typeof def.body === "string" ? def.body : "";
769
806
  return body.replaceAll(/(?<!this\.)state\./g, "s.").replaceAll(/(?<!this\.)state(?!\.)/g, "s");
770
807
  }
771
808