rip-lang 3.13.133 → 3.13.135
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/CHANGELOG.md +0 -1
- package/README.md +4 -7
- package/bin/rip +16 -4
- package/docs/RIP-LANG.md +0 -42
- package/docs/RIP-TYPES.md +47 -52
- package/docs/demo.html +2 -2
- package/docs/dist/rip.js +2294 -1544
- package/docs/dist/rip.min.js +202 -192
- package/docs/dist/rip.min.js.br +0 -0
- package/package.json +1 -1
- package/rip-loader.js +2 -2
- package/src/AGENTS.md +76 -11
- package/src/browser.js +5 -5
- package/src/compiler.js +961 -639
- package/src/components.js +274 -109
- package/src/error.js +250 -0
- package/src/grammar/grammar.rip +2 -12
- package/src/lexer.js +15 -11
- package/src/parser.js +220 -223
- package/src/repl.js +3 -2
- package/src/sourcemap-utils.js +39 -6
- package/src/typecheck.js +312 -80
- package/src/types.js +229 -54
- package/src/ui.rip +4 -0
package/src/components.js
CHANGED
|
@@ -3,9 +3,9 @@ import { HTML_TAGS, SVG_TAGS, TEMPLATE_TAGS } from './generated/dom-tags.js';
|
|
|
3
3
|
|
|
4
4
|
// Component System — Fine-grained reactive components for Rip
|
|
5
5
|
//
|
|
6
|
-
// Architecture: installComponentSupport(
|
|
6
|
+
// Architecture: installComponentSupport(CodeEmitter, Lexer) adds methods to
|
|
7
7
|
// both prototypes — render rewriting on the Lexer, component code generation
|
|
8
|
-
// on the
|
|
8
|
+
// on the CodeEmitter. A separate getComponentRuntime() emits runtime helpers
|
|
9
9
|
// only when components are used.
|
|
10
10
|
//
|
|
11
11
|
// Naming: All render-tree generators use generate* (consistent with compiler).
|
|
@@ -83,7 +83,7 @@ function getMemberType(target) {
|
|
|
83
83
|
// Prototype Installation
|
|
84
84
|
// ============================================================================
|
|
85
85
|
|
|
86
|
-
export function installComponentSupport(
|
|
86
|
+
export function installComponentSupport(CodeEmitter, Lexer) {
|
|
87
87
|
|
|
88
88
|
let meta = (node, key) => node instanceof String ? node[key] : undefined;
|
|
89
89
|
|
|
@@ -516,10 +516,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
516
516
|
};
|
|
517
517
|
|
|
518
518
|
// ==========================================================================
|
|
519
|
-
//
|
|
519
|
+
// CodeEmitter: Component compilation
|
|
520
520
|
// ==========================================================================
|
|
521
521
|
|
|
522
|
-
const proto =
|
|
522
|
+
const proto = CodeEmitter.prototype;
|
|
523
523
|
|
|
524
524
|
// ==========================================================================
|
|
525
525
|
// Utilities
|
|
@@ -667,7 +667,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
667
667
|
* Generate component: produces an anonymous ES6 class expression.
|
|
668
668
|
* Pattern: ["component", null, ["block", ...statements]]
|
|
669
669
|
*/
|
|
670
|
-
proto.
|
|
670
|
+
proto.emitComponent = function(head, rest, context, sexpr) {
|
|
671
671
|
const [, body] = rest;
|
|
672
672
|
|
|
673
673
|
// Extract component body statements
|
|
@@ -724,7 +724,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
724
724
|
} else if (op === 'computed') {
|
|
725
725
|
const varName = getMemberName(stmt[1]);
|
|
726
726
|
if (varName) {
|
|
727
|
-
derivedVars.push({ name: varName, expr: stmt[2] });
|
|
727
|
+
derivedVars.push({ name: varName, expr: stmt[2], type: getMemberType(stmt[1]) });
|
|
728
728
|
memberNames.add(varName);
|
|
729
729
|
reactiveMembers.add(varName);
|
|
730
730
|
}
|
|
@@ -803,8 +803,9 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
803
803
|
.replace(/(\w+(?:<[^>]+>)?)\!/g, 'NonNullable<$1>') : null;
|
|
804
804
|
|
|
805
805
|
const sl = [];
|
|
806
|
-
|
|
807
|
-
sl.push(
|
|
806
|
+
const componentTypeParams = this._componentTypeParams || '';
|
|
807
|
+
sl.push(`class ${componentTypeParams}{`);
|
|
808
|
+
sl.push(' declare _root: Element | null; declare app: any;');
|
|
808
809
|
sl.push(' emit(_name: string, _detail?: any): void {}');
|
|
809
810
|
|
|
810
811
|
// Constructor — typed props for public state/readonly (matches DTS)
|
|
@@ -849,31 +850,33 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
849
850
|
const ts = expandType(type) || inferLiteralType(value);
|
|
850
851
|
sl.push(ts ? ` declare ${name}: ${ts};` : ` declare ${name}: any;`);
|
|
851
852
|
}
|
|
852
|
-
for (const { name, expr } of derivedVars) {
|
|
853
|
+
for (const { name, expr, type } of derivedVars) {
|
|
854
|
+
const ts = expandType(type);
|
|
855
|
+
const typeAnnot = ts ? `: Computed<${ts}>` : '';
|
|
853
856
|
if (this.is(expr, 'block')) {
|
|
854
857
|
const transformed = this.transformComponentMembers(expr);
|
|
855
|
-
const body = this.
|
|
856
|
-
sl.push(` ${name} = __computed(() => ${body});`);
|
|
858
|
+
const body = this.emitFunctionBody(transformed);
|
|
859
|
+
sl.push(` ${name}${typeAnnot} = __computed(() => ${body});`);
|
|
857
860
|
} else {
|
|
858
|
-
const val = this.
|
|
859
|
-
sl.push(` ${name} = __computed(() => ${val});`);
|
|
861
|
+
const val = this.emitInComponent(expr, 'value');
|
|
862
|
+
sl.push(` ${name}${typeAnnot} = __computed(() => ${val});`);
|
|
860
863
|
}
|
|
861
864
|
}
|
|
862
865
|
|
|
863
866
|
// _init body — readonly, state, computed assignments (skip accepted/offered)
|
|
864
867
|
sl.push(' _init(props) {');
|
|
865
868
|
for (const { name, value, isPublic } of readonlyVars) {
|
|
866
|
-
const val = this.
|
|
869
|
+
const val = this.emitInComponent(value, 'value');
|
|
867
870
|
sl.push(isPublic ? ` this.${name} = props.${name} ?? ${val};` : ` this.${name} = ${val};`);
|
|
868
871
|
}
|
|
869
872
|
for (const { name, value, isPublic, required, type } of stateVars) {
|
|
870
873
|
if (isPublic && required) {
|
|
871
874
|
sl.push(` this.${name} = __state(props.__bind_${name}__ ?? props.${name});`);
|
|
872
875
|
} else if (isPublic) {
|
|
873
|
-
const val = this.
|
|
876
|
+
const val = this.emitInComponent(value, 'value');
|
|
874
877
|
sl.push(` this.${name} = __state(props.__bind_${name}__ ?? props.${name} ?? ${val});`);
|
|
875
878
|
} else {
|
|
876
|
-
const val = this.
|
|
879
|
+
const val = this.emitInComponent(value, 'value');
|
|
877
880
|
sl.push(` this.${name} = __state(${val});`);
|
|
878
881
|
}
|
|
879
882
|
}
|
|
@@ -883,10 +886,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
883
886
|
const isAsync = this.containsAwait(effectBody) ? 'async ' : '';
|
|
884
887
|
if (this.is(effectBody, 'block')) {
|
|
885
888
|
const transformed = this.transformComponentMembers(effectBody);
|
|
886
|
-
const body = this.
|
|
889
|
+
const body = this.emitFunctionBody(transformed, [], true);
|
|
887
890
|
sl.push(` __effect(${isAsync}() => ${body});`);
|
|
888
891
|
} else {
|
|
889
|
-
const effectCode = this.
|
|
892
|
+
const effectCode = this.emitInComponent(effectBody, 'value');
|
|
890
893
|
sl.push(` __effect(${isAsync}() => { ${effectCode}; });`);
|
|
891
894
|
}
|
|
892
895
|
}
|
|
@@ -953,7 +956,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
953
956
|
}
|
|
954
957
|
const transformed = this.reactiveMembers ? this.transformComponentMembers(methodBody) : methodBody;
|
|
955
958
|
const isAsync = this.containsAwait(methodBody);
|
|
956
|
-
const bodyCode = this.
|
|
959
|
+
const bodyCode = this.emitFunctionBody(transformed, params || []);
|
|
957
960
|
sl.push(` ${isAsync ? 'async ' : ''}${name}(${paramStr}) ${bodyCode}`);
|
|
958
961
|
}
|
|
959
962
|
}
|
|
@@ -965,7 +968,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
965
968
|
const paramStr = Array.isArray(params) ? params.map(p => this.formatParam(p)).join(', ') : '';
|
|
966
969
|
const transformed = this.reactiveMembers ? this.transformComponentMembers(hookBody) : hookBody;
|
|
967
970
|
const isAsync = this.containsAwait(hookBody);
|
|
968
|
-
const bodyCode = this.
|
|
971
|
+
const bodyCode = this.emitFunctionBody(transformed, params || []);
|
|
969
972
|
sl.push(` ${isAsync ? 'async ' : ''}${name}(${paramStr}) ${bodyCode}`);
|
|
970
973
|
}
|
|
971
974
|
}
|
|
@@ -975,6 +978,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
975
978
|
if (renderBlock) {
|
|
976
979
|
const constructions = [];
|
|
977
980
|
let constructionIdx = 0;
|
|
981
|
+
const sourceLines = this.options.source?.split('\n');
|
|
978
982
|
const extractProps = (args) => {
|
|
979
983
|
const props = [];
|
|
980
984
|
for (const arg of args) {
|
|
@@ -995,10 +999,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
995
999
|
const srcLine = pair.loc?.r ?? obj.loc?.r;
|
|
996
1000
|
if (key.startsWith('__bind_') && key.endsWith('__')) {
|
|
997
1001
|
// Two-way binding: emit the Signal object (this.xxx), not this.xxx.value
|
|
998
|
-
const member = typeof value === 'string' && this.reactiveMembers?.has(value) ? `this.${value}` : this.
|
|
1002
|
+
const member = typeof value === 'string' && this.reactiveMembers?.has(value) ? `this.${value}` : this.emitInComponent(value, 'value');
|
|
999
1003
|
props.push({ code: `${key}: ${member}`, srcLine });
|
|
1000
1004
|
} else {
|
|
1001
|
-
const val = this.
|
|
1005
|
+
const val = this.emitInComponent(value, 'value');
|
|
1002
1006
|
props.push({ code: `${key}: ${val}`, srcLine });
|
|
1003
1007
|
}
|
|
1004
1008
|
}
|
|
@@ -1028,16 +1032,23 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1028
1032
|
let memberName = typeof key[2] === 'string' ? key[2] : key[2]?.valueOf?.();
|
|
1029
1033
|
if (!memberName) continue;
|
|
1030
1034
|
const eventKey = '@' + memberName.split('.')[0];
|
|
1031
|
-
const val = this.
|
|
1035
|
+
const val = this.emitInComponent(value, 'value');
|
|
1032
1036
|
props.push({ code: `'${eventKey}': ${val}`, srcLine });
|
|
1033
1037
|
} else if (typeof key === 'string') {
|
|
1034
|
-
if (key === 'key')
|
|
1038
|
+
if (key === 'key') {
|
|
1039
|
+
// key: is not an HTML attribute, but emit its value
|
|
1040
|
+
// expression for type-checking and semantic tokens
|
|
1041
|
+
const val = this.emitInComponent(value, 'value');
|
|
1042
|
+
const marker = srcLine != null ? ` // @rip-src:${srcLine}` : '';
|
|
1043
|
+
constructions.push(` (${val});${marker}`);
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
1035
1046
|
if (key.startsWith('__bind_') && key.endsWith('__')) {
|
|
1036
1047
|
const propName = key.slice(7, -2);
|
|
1037
|
-
const val = this.
|
|
1048
|
+
const val = this.emitInComponent(value, 'value');
|
|
1038
1049
|
props.push({ code: `${propName}: ${val}`, srcLine });
|
|
1039
1050
|
} else {
|
|
1040
|
-
const val = this.
|
|
1051
|
+
const val = this.emitInComponent(value, 'value');
|
|
1041
1052
|
props.push({ code: `${key}: ${val}`, srcLine });
|
|
1042
1053
|
}
|
|
1043
1054
|
}
|
|
@@ -1049,6 +1060,146 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1049
1060
|
const walkRender = (node) => {
|
|
1050
1061
|
if (!Array.isArray(node)) return;
|
|
1051
1062
|
const head = node[0]?.valueOf?.() ?? node[0];
|
|
1063
|
+
|
|
1064
|
+
// Object nodes are property bags (key-value pairs) — their values
|
|
1065
|
+
// are code expressions (event handlers, bindings, literals), not
|
|
1066
|
+
// render template. extractIntrinsicProps handles them separately.
|
|
1067
|
+
// Walking into them would treat function bodies as template content
|
|
1068
|
+
// (e.g. `@blur: (e) -> p(e)` would emit `e;` and `__ripEl('p')`).
|
|
1069
|
+
if (head === 'object') return;
|
|
1070
|
+
|
|
1071
|
+
// Type-check conditional and loop expressions in render blocks.
|
|
1072
|
+
// Without this, `if labelz` (a typo for `label`) silently evaluates
|
|
1073
|
+
// as undefined and skips the block — the condition goes unchecked.
|
|
1074
|
+
// Similarly, `switch statusz` and `for item in itemsz` go unchecked.
|
|
1075
|
+
if (head === 'if' || head === 'unless') {
|
|
1076
|
+
const condition = node[1];
|
|
1077
|
+
if (condition != null) {
|
|
1078
|
+
const condCode = this.emitInComponent(condition, 'value');
|
|
1079
|
+
const srcLine = node.loc?.r;
|
|
1080
|
+
const srcMarker = srcLine != null ? ` // @rip-src:${srcLine}` : '';
|
|
1081
|
+
constructions.push(` ${condCode};${srcMarker}`);
|
|
1082
|
+
}
|
|
1083
|
+
} else if (head === '?:') {
|
|
1084
|
+
// Emit the full ternary so all branches are type-checked
|
|
1085
|
+
const ternCode = this.emitInComponent(node, 'value');
|
|
1086
|
+
const srcLine = node.loc?.r;
|
|
1087
|
+
const srcMarker = srcLine != null ? ` // @rip-src:${srcLine}` : '';
|
|
1088
|
+
constructions.push(` ${ternCode};${srcMarker}`);
|
|
1089
|
+
} else if (head === 'switch') {
|
|
1090
|
+
const discriminant = node[1];
|
|
1091
|
+
if (discriminant != null) {
|
|
1092
|
+
const discCode = this.emitInComponent(discriminant, 'value');
|
|
1093
|
+
const srcLine = node.loc?.r;
|
|
1094
|
+
const srcMarker = srcLine != null ? ` // @rip-src:${srcLine}` : '';
|
|
1095
|
+
constructions.push(` ${discCode};${srcMarker}`);
|
|
1096
|
+
}
|
|
1097
|
+
} else if (head === 'for-in' || head === 'for-of' || head === 'for-as') {
|
|
1098
|
+
// Emit a real for-loop so the loop variable is in scope for the body.
|
|
1099
|
+
// node: [head, vars, iterable, step, guard, body]
|
|
1100
|
+
const vars = node[1];
|
|
1101
|
+
const iterable = node[2];
|
|
1102
|
+
if (iterable != null) {
|
|
1103
|
+
const iterCode = this.emitInComponent(iterable, 'value');
|
|
1104
|
+
const srcLine = node.loc?.r;
|
|
1105
|
+
const srcMarker = srcLine != null ? ` // @rip-src:${srcLine}` : '';
|
|
1106
|
+
// Extract loop variable pattern
|
|
1107
|
+
let varPattern;
|
|
1108
|
+
if (Array.isArray(vars)) {
|
|
1109
|
+
if (vars.length === 1) {
|
|
1110
|
+
const v = vars[0];
|
|
1111
|
+
varPattern = Array.isArray(v) ? this.emitDestructuringPattern(v) : String(v);
|
|
1112
|
+
} else if (head === 'for-of') {
|
|
1113
|
+
// for key, val of obj — destructure as [key, val] from Object.entries
|
|
1114
|
+
varPattern = `[${vars.map(v => String(v)).join(', ')}]`;
|
|
1115
|
+
} else {
|
|
1116
|
+
// for item, index in arr — first is the item
|
|
1117
|
+
varPattern = String(vars[0]);
|
|
1118
|
+
}
|
|
1119
|
+
} else {
|
|
1120
|
+
varPattern = String(vars);
|
|
1121
|
+
}
|
|
1122
|
+
if (head === 'for-of') {
|
|
1123
|
+
constructions.push(` for (const ${varPattern} of Object.entries(${iterCode})) {${srcMarker}`);
|
|
1124
|
+
} else {
|
|
1125
|
+
constructions.push(` for (const ${varPattern} of ${iterCode}) {${srcMarker}`);
|
|
1126
|
+
}
|
|
1127
|
+
// Walk body children (indices 3+ may contain guard, body, etc.)
|
|
1128
|
+
for (let bi = 3; bi < node.length; bi++) {
|
|
1129
|
+
if (node[bi] != null) walkRender(node[bi]);
|
|
1130
|
+
}
|
|
1131
|
+
constructions.push(` }`);
|
|
1132
|
+
return; // Don't walk children again below
|
|
1133
|
+
}
|
|
1134
|
+
} else if (head === '__text__') {
|
|
1135
|
+
// = expr — text expression: emit the expression for type-checking
|
|
1136
|
+
const textExpr = node[1];
|
|
1137
|
+
if (textExpr != null) {
|
|
1138
|
+
const exprCode = this.emitInComponent(textExpr, 'value');
|
|
1139
|
+
const srcLine = node.loc?.r;
|
|
1140
|
+
const srcMarker = srcLine != null ? ` // @rip-src:${srcLine}` : '';
|
|
1141
|
+
constructions.push(` ${exprCode};${srcMarker}`);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// Emit a bare lowercase identifier as either a property access
|
|
1146
|
+
// (component member used as text), __ripEl (tag name check when at
|
|
1147
|
+
// block level), or a plain variable reference (text child of a tag).
|
|
1148
|
+
const emitBareIdent = (child, parentNode, isTextChild) => {
|
|
1149
|
+
if (typeof child !== 'string' || !/^[a-z][\w-]*$/.test(child)) return;
|
|
1150
|
+
if (CodeEmitter.GENERATORS[child]) return;
|
|
1151
|
+
if (child === 'null' || child === 'undefined' || child === 'true' || child === 'false') return;
|
|
1152
|
+
let srcLine = parentNode.loc?.r;
|
|
1153
|
+
if (srcLine != null && sourceLines) {
|
|
1154
|
+
const re = new RegExp(`\\b${child}\\b`);
|
|
1155
|
+
for (let ln = srcLine; ln < sourceLines.length; ln++) {
|
|
1156
|
+
if (re.test(sourceLines[ln])) { srcLine = ln; break; }
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
const srcMarker = srcLine != null ? ` // @rip-src:${srcLine}` : '';
|
|
1160
|
+
if (this.componentMembers && this.componentMembers.has(child)) {
|
|
1161
|
+
constructions.push(` this.${child};${srcMarker}`);
|
|
1162
|
+
} else if (isTextChild) {
|
|
1163
|
+
// Text child of a tag — emit as variable reference so TS
|
|
1164
|
+
// reports "Cannot find name 'x'" instead of "not a known element"
|
|
1165
|
+
constructions.push(` ${child};${srcMarker}`);
|
|
1166
|
+
} else {
|
|
1167
|
+
constructions.push(` __ripEl('${child}');${srcMarker}`);
|
|
1168
|
+
}
|
|
1169
|
+
};
|
|
1170
|
+
|
|
1171
|
+
// Bare lowercase identifiers inside a block or as children of tag nodes
|
|
1172
|
+
// — emit __ripEl so TS catches tag typos (e.g., slotz for slot), or
|
|
1173
|
+
// emit this.prop for component member text references.
|
|
1174
|
+
const isTagHead = typeof head === 'string' && /^[a-z][\w-]*$/.test(head) &&
|
|
1175
|
+
!CodeEmitter.GENERATORS[head] && TEMPLATE_TAGS.has(head.split(/[.#]/)[0]);
|
|
1176
|
+
if (head === 'block') {
|
|
1177
|
+
for (let i = 1; i < node.length; i++) emitBareIdent(node[i], node, false);
|
|
1178
|
+
} else if (isTagHead) {
|
|
1179
|
+
for (let i = 1; i < node.length; i++) emitBareIdent(node[i], node, true);
|
|
1180
|
+
// Emit expression children of intrinsic tags for type-checking.
|
|
1181
|
+
// Without this, text content like "#{item.name}" in `li "#{item.name}"`
|
|
1182
|
+
// is invisible to TypeScript and loop variables appear unused (TS 6133).
|
|
1183
|
+
for (let i = 1; i < node.length; i++) {
|
|
1184
|
+
const child = node[i];
|
|
1185
|
+
if (!Array.isArray(child)) continue;
|
|
1186
|
+
const ch = child[0]?.valueOf?.() ?? child[0];
|
|
1187
|
+
if (ch === 'object' || ch === 'block' || ch === '__text__') continue;
|
|
1188
|
+
if (typeof ch === 'string') {
|
|
1189
|
+
if (/^[A-Z]/.test(ch)) continue;
|
|
1190
|
+
if (TEMPLATE_TAGS.has(ch.split(/[.#]/)[0])) continue;
|
|
1191
|
+
if (/^[a-z][\w-]*$/.test(ch) && !CodeEmitter.GENERATORS[ch]) continue;
|
|
1192
|
+
if (/^(if|unless|switch|for-in|for-of|for-as|while|until|loop|loop-n|try|throw|break|continue|break-if|continue-if|control|when|return|def|->|=>|class|enum|state|computed|readonly|effect|=|program)$/.test(ch)) continue;
|
|
1193
|
+
}
|
|
1194
|
+
try {
|
|
1195
|
+
const exprCode = this.emitInComponent(child, 'value');
|
|
1196
|
+
const srcLine = child.loc?.r ?? node.loc?.r;
|
|
1197
|
+
const srcMarker = srcLine != null ? ` // @rip-src:${srcLine}` : '';
|
|
1198
|
+
constructions.push(` ${exprCode};${srcMarker}`);
|
|
1199
|
+
} catch {}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
for (let i = 1; i < node.length; i++) walkRender(node[i]);
|
|
1052
1203
|
if (typeof head === 'string' && /^[A-Z]/.test(head)) {
|
|
1053
1204
|
const props = extractProps(node.slice(1));
|
|
1054
1205
|
const varName = `_${constructionIdx++}`;
|
|
@@ -1073,7 +1224,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1073
1224
|
constructions.push(` };`);
|
|
1074
1225
|
}
|
|
1075
1226
|
}
|
|
1076
|
-
} else if (typeof head === 'string' && !
|
|
1227
|
+
} else if (typeof head === 'string' && !CodeEmitter.GENERATORS[head] && (TEMPLATE_TAGS.has(head.split(/[.#]/)[0]) ||
|
|
1077
1228
|
(/^[a-z][\w-]*$/.test(head) && node.length > 1))) {
|
|
1078
1229
|
const tagName = head.split(/[.#]/)[0];
|
|
1079
1230
|
const iProps = extractIntrinsicProps(node.slice(1));
|
|
@@ -1100,7 +1251,6 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1100
1251
|
}
|
|
1101
1252
|
}
|
|
1102
1253
|
}
|
|
1103
|
-
for (let i = 1; i < node.length; i++) walkRender(node[i]);
|
|
1104
1254
|
};
|
|
1105
1255
|
walkRender(renderBlock);
|
|
1106
1256
|
if (constructions.length > 0) {
|
|
@@ -1132,7 +1282,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1132
1282
|
|
|
1133
1283
|
// Constants (readonly)
|
|
1134
1284
|
for (const { name, value, isPublic } of readonlyVars) {
|
|
1135
|
-
const val = this.
|
|
1285
|
+
const val = this.emitInComponent(value, 'value');
|
|
1136
1286
|
lines.push(isPublic
|
|
1137
1287
|
? ` this.${name} = props.${name} ?? ${val};`
|
|
1138
1288
|
: ` this.${name} = ${val};`);
|
|
@@ -1148,10 +1298,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1148
1298
|
if (isPublic && required) {
|
|
1149
1299
|
lines.push(` this.${name} = __state(props.__bind_${name}__ ?? props.${name});`);
|
|
1150
1300
|
} else if (isPublic) {
|
|
1151
|
-
const val = this.
|
|
1301
|
+
const val = this.emitInComponent(value, 'value');
|
|
1152
1302
|
lines.push(` this.${name} = __state(props.__bind_${name}__ ?? props.${name} ?? ${val});`);
|
|
1153
1303
|
} else {
|
|
1154
|
-
const val = this.
|
|
1304
|
+
const val = this.emitInComponent(value, 'value');
|
|
1155
1305
|
lines.push(` this.${name} = __state(${val});`);
|
|
1156
1306
|
}
|
|
1157
1307
|
}
|
|
@@ -1172,10 +1322,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1172
1322
|
for (const { name, expr } of derivedVars) {
|
|
1173
1323
|
if (this.is(expr, 'block')) {
|
|
1174
1324
|
const transformed = this.transformComponentMembers(expr);
|
|
1175
|
-
const body = this.
|
|
1325
|
+
const body = this.emitFunctionBody(transformed);
|
|
1176
1326
|
lines.push(` this.${name} = __computed(() => ${body});`);
|
|
1177
1327
|
} else {
|
|
1178
|
-
const val = this.
|
|
1328
|
+
const val = this.emitInComponent(expr, 'value');
|
|
1179
1329
|
lines.push(` this.${name} = __computed(() => ${val});`);
|
|
1180
1330
|
}
|
|
1181
1331
|
}
|
|
@@ -1191,10 +1341,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1191
1341
|
const isAsync = this.containsAwait(effectBody) ? 'async ' : '';
|
|
1192
1342
|
if (this.is(effectBody, 'block')) {
|
|
1193
1343
|
const transformed = this.transformComponentMembers(effectBody);
|
|
1194
|
-
const body = this.
|
|
1344
|
+
const body = this.emitFunctionBody(transformed, [], true);
|
|
1195
1345
|
lines.push(` __effect(${isAsync}() => ${body});`);
|
|
1196
1346
|
} else {
|
|
1197
|
-
const effectCode = this.
|
|
1347
|
+
const effectCode = this.emitInComponent(effectBody, 'value');
|
|
1198
1348
|
lines.push(` __effect(${isAsync}() => { ${effectCode}; });`);
|
|
1199
1349
|
}
|
|
1200
1350
|
}
|
|
@@ -1267,7 +1417,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1267
1417
|
const paramStr = Array.isArray(params) ? params.map(p => this.formatParam(p)).join(', ') : '';
|
|
1268
1418
|
const transformed = this.reactiveMembers ? this.transformComponentMembers(methodBody) : methodBody;
|
|
1269
1419
|
const isAsync = this.containsAwait(methodBody);
|
|
1270
|
-
const bodyCode = this.
|
|
1420
|
+
const bodyCode = this.emitFunctionBody(transformed, params || []);
|
|
1271
1421
|
lines.push(` ${isAsync ? 'async ' : ''}${name}(${paramStr}) ${bodyCode}`);
|
|
1272
1422
|
}
|
|
1273
1423
|
}
|
|
@@ -1279,7 +1429,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1279
1429
|
const paramStr = Array.isArray(params) ? params.map(p => this.formatParam(p)).join(', ') : '';
|
|
1280
1430
|
const transformed = this.reactiveMembers ? this.transformComponentMembers(hookBody) : hookBody;
|
|
1281
1431
|
const isAsync = this.containsAwait(hookBody);
|
|
1282
|
-
const bodyCode = this.
|
|
1432
|
+
const bodyCode = this.emitFunctionBody(transformed, params || []);
|
|
1283
1433
|
lines.push(` ${isAsync ? 'async ' : ''}${name}(${paramStr}) ${bodyCode}`);
|
|
1284
1434
|
}
|
|
1285
1435
|
}
|
|
@@ -1328,7 +1478,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1328
1478
|
/**
|
|
1329
1479
|
* Generate code inside component context (transforms member access to this.X.value)
|
|
1330
1480
|
*/
|
|
1331
|
-
proto.
|
|
1481
|
+
proto.emitInComponent = function(sexpr, context) {
|
|
1332
1482
|
if (typeof sexpr === 'string' && this.reactiveMembers && this.reactiveMembers.has(sexpr)) {
|
|
1333
1483
|
return `${this._self}.${sexpr}.value`;
|
|
1334
1484
|
}
|
|
@@ -1337,24 +1487,24 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1337
1487
|
}
|
|
1338
1488
|
if (Array.isArray(sexpr) && this.reactiveMembers) {
|
|
1339
1489
|
const transformed = this.transformComponentMembers(sexpr);
|
|
1340
|
-
return this.
|
|
1490
|
+
return this.emit(transformed, context);
|
|
1341
1491
|
}
|
|
1342
|
-
return this.
|
|
1492
|
+
return this.emit(sexpr, context);
|
|
1343
1493
|
};
|
|
1344
1494
|
|
|
1345
1495
|
/**
|
|
1346
1496
|
* Handle standalone render (outside component): error
|
|
1347
1497
|
*/
|
|
1348
|
-
proto.
|
|
1349
|
-
|
|
1498
|
+
proto.emitRender = function(head, rest, context, sexpr) {
|
|
1499
|
+
this.error('render blocks can only be used inside a component', sexpr);
|
|
1350
1500
|
};
|
|
1351
1501
|
|
|
1352
|
-
proto.
|
|
1353
|
-
|
|
1502
|
+
proto.emitOffer = function(head, rest, context, sexpr) {
|
|
1503
|
+
this.error('offer can only be used inside a component', sexpr);
|
|
1354
1504
|
};
|
|
1355
1505
|
|
|
1356
|
-
proto.
|
|
1357
|
-
|
|
1506
|
+
proto.emitAccept = function(head, rest, context, sexpr) {
|
|
1507
|
+
this.error('accept can only be used inside a component', sexpr);
|
|
1358
1508
|
};
|
|
1359
1509
|
|
|
1360
1510
|
// ==========================================================================
|
|
@@ -1388,14 +1538,14 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1388
1538
|
rootVar = 'null';
|
|
1389
1539
|
} else if (statements.length === 1) {
|
|
1390
1540
|
this._pendingAutoWire = !!this._autoEventHandlers;
|
|
1391
|
-
rootVar = this.
|
|
1541
|
+
rootVar = this.emitNode(statements[0]);
|
|
1392
1542
|
this._pendingAutoWire = false;
|
|
1393
1543
|
} else {
|
|
1394
1544
|
rootVar = this.newElementVar('frag');
|
|
1395
1545
|
this._createLines.push(`${rootVar} = document.createDocumentFragment();`);
|
|
1396
1546
|
const children = [];
|
|
1397
1547
|
for (const stmt of statements) {
|
|
1398
|
-
const childVar = this.
|
|
1548
|
+
const childVar = this.emitNode(stmt);
|
|
1399
1549
|
this._createLines.push(`${rootVar}.appendChild(${childVar});`);
|
|
1400
1550
|
children.push(childVar);
|
|
1401
1551
|
}
|
|
@@ -1444,10 +1594,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1444
1594
|
};
|
|
1445
1595
|
|
|
1446
1596
|
// --------------------------------------------------------------------------
|
|
1447
|
-
//
|
|
1597
|
+
// emitNode — main dispatch for all render tree nodes
|
|
1448
1598
|
// --------------------------------------------------------------------------
|
|
1449
1599
|
|
|
1450
|
-
proto.
|
|
1600
|
+
proto.emitNode = function(sexpr) {
|
|
1451
1601
|
// String literal → text node (handle both primitive and String objects)
|
|
1452
1602
|
if (typeof sexpr === 'string' || sexpr instanceof String) {
|
|
1453
1603
|
const str = sexpr.valueOf();
|
|
@@ -1495,7 +1645,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1495
1645
|
|
|
1496
1646
|
// Component instantiation (PascalCase)
|
|
1497
1647
|
if (headStr && this.isComponent(headStr)) {
|
|
1498
|
-
return this.
|
|
1648
|
+
return this.emitChildComponent(headStr, rest);
|
|
1499
1649
|
}
|
|
1500
1650
|
|
|
1501
1651
|
// Slot projection — replace <slot> with @children in component render
|
|
@@ -1525,8 +1675,8 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1525
1675
|
chain = ['if', cond, body, chain];
|
|
1526
1676
|
}
|
|
1527
1677
|
if (chain) {
|
|
1528
|
-
if (Array.isArray(chain) && chain[0] === 'if') return this.
|
|
1529
|
-
return this.
|
|
1678
|
+
if (Array.isArray(chain) && chain[0] === 'if') return this.emitConditional(chain);
|
|
1679
|
+
return this.emitTemplateBlock(chain);
|
|
1530
1680
|
}
|
|
1531
1681
|
const cv = this.newElementVar('c');
|
|
1532
1682
|
this._createLines.push(`${cv} = document.createComment('switch');`);
|
|
@@ -1536,7 +1686,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1536
1686
|
// HTML tag (possibly with #id, e.g. div#content)
|
|
1537
1687
|
if (headStr && this.isHtmlTag(headStr) && !meta(head, 'text')) {
|
|
1538
1688
|
let [tagName, id] = headStr.split('#');
|
|
1539
|
-
return this.
|
|
1689
|
+
return this.emitTag(tagName || 'div', [], rest, id);
|
|
1540
1690
|
}
|
|
1541
1691
|
|
|
1542
1692
|
// Property chain (div.class or item.name)
|
|
@@ -1560,12 +1710,12 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1560
1710
|
// HTML tag with classes (div.class) — skip if base is marked .text by = prefix
|
|
1561
1711
|
const { tag, classes, id, base } = this.collectTemplateClasses(sexpr);
|
|
1562
1712
|
if (!meta(base, 'text') && tag && this.isHtmlTag(tag)) {
|
|
1563
|
-
return this.
|
|
1713
|
+
return this.emitTag(tag, classes, [], id);
|
|
1564
1714
|
}
|
|
1565
1715
|
|
|
1566
1716
|
// General property access (e.g., item.name in a loop)
|
|
1567
1717
|
const textVar = this.newTextVar();
|
|
1568
|
-
const exprCode = this.
|
|
1718
|
+
const exprCode = this.emitInComponent(sexpr, 'value');
|
|
1569
1719
|
this._createLines.push(`${textVar} = document.createTextNode(String(${exprCode}));`);
|
|
1570
1720
|
return textVar;
|
|
1571
1721
|
}
|
|
@@ -1582,11 +1732,11 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1582
1732
|
const { tag, classes, id } = this.collectTemplateClasses(tagExpr);
|
|
1583
1733
|
if (tag) {
|
|
1584
1734
|
const staticArgs = classes.map(c => `"${c}"`);
|
|
1585
|
-
return this.
|
|
1735
|
+
return this.emitDynamicTag(tag, classExprs, rest, staticArgs, id);
|
|
1586
1736
|
}
|
|
1587
1737
|
}
|
|
1588
1738
|
const tag = typeof tagExpr === 'string' ? tagExpr : tagExpr.valueOf();
|
|
1589
|
-
return this.
|
|
1739
|
+
return this.emitDynamicTag(tag, classExprs, rest);
|
|
1590
1740
|
}
|
|
1591
1741
|
|
|
1592
1742
|
const { tag, classes, id } = this.collectTemplateClasses(head);
|
|
@@ -1595,32 +1745,32 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1595
1745
|
if (classes.length > 0 && classes[classes.length - 1] === '__clsx') {
|
|
1596
1746
|
const staticClasses = classes.slice(0, -1);
|
|
1597
1747
|
const staticArgs = staticClasses.map(c => `"${c}"`);
|
|
1598
|
-
return this.
|
|
1748
|
+
return this.emitDynamicTag(tag, rest, [], staticArgs, id);
|
|
1599
1749
|
}
|
|
1600
|
-
return this.
|
|
1750
|
+
return this.emitTag(tag, classes, rest, id);
|
|
1601
1751
|
}
|
|
1602
1752
|
}
|
|
1603
1753
|
|
|
1604
1754
|
// Arrow function (children block)
|
|
1605
1755
|
if (headStr === '->' || headStr === '=>') {
|
|
1606
|
-
return this.
|
|
1756
|
+
return this.emitTemplateBlock(rest[1]);
|
|
1607
1757
|
}
|
|
1608
1758
|
|
|
1609
1759
|
// Conditional: if/else
|
|
1610
1760
|
if (headStr === 'if') {
|
|
1611
|
-
return this.
|
|
1761
|
+
return this.emitConditional(sexpr);
|
|
1612
1762
|
}
|
|
1613
1763
|
|
|
1614
1764
|
// For loop
|
|
1615
1765
|
if (headStr === 'for' || headStr === 'for-in' || headStr === 'for-of' || headStr === 'for-as') {
|
|
1616
|
-
return this.
|
|
1766
|
+
return this.emitTemplateLoop(sexpr);
|
|
1617
1767
|
}
|
|
1618
1768
|
|
|
1619
1769
|
// Synthetic text node inserted by rewriteRender for `= expr`
|
|
1620
1770
|
if (headStr === '__text__') {
|
|
1621
1771
|
const expr = rest[0] ?? 'undefined';
|
|
1622
1772
|
const textVar = this.newTextVar();
|
|
1623
|
-
const exprCode = this.
|
|
1773
|
+
const exprCode = this.emitInComponent(expr, 'value');
|
|
1624
1774
|
if (this.hasReactiveDeps(expr)) {
|
|
1625
1775
|
this._createLines.push(`${textVar} = document.createTextNode('');`);
|
|
1626
1776
|
this._pushEffect(`${textVar}.data = String(${exprCode});`);
|
|
@@ -1632,7 +1782,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1632
1782
|
|
|
1633
1783
|
// General expression (computed value, function call, binary op, etc.)
|
|
1634
1784
|
const textVar = this.newTextVar();
|
|
1635
|
-
const exprCode = this.
|
|
1785
|
+
const exprCode = this.emitInComponent(sexpr, 'value');
|
|
1636
1786
|
if (this.hasReactiveDeps(sexpr)) {
|
|
1637
1787
|
this._createLines.push(`${textVar} = document.createTextNode('');`);
|
|
1638
1788
|
this._pushEffect(`${textVar}.data = ${exprCode};`);
|
|
@@ -1643,7 +1793,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1643
1793
|
};
|
|
1644
1794
|
|
|
1645
1795
|
// --------------------------------------------------------------------------
|
|
1646
|
-
// appendChildren — shared child-processing loop for
|
|
1796
|
+
// appendChildren — shared child-processing loop for emitTag/emitDynamicTag
|
|
1647
1797
|
// --------------------------------------------------------------------------
|
|
1648
1798
|
|
|
1649
1799
|
proto.appendChildren = function(elVar, args) {
|
|
@@ -1653,26 +1803,26 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1653
1803
|
if (this.is(block, 'block')) {
|
|
1654
1804
|
for (const child of block.slice(1)) {
|
|
1655
1805
|
if (this.is(child, 'object')) {
|
|
1656
|
-
this.
|
|
1806
|
+
this.emitAttributes(elVar, child);
|
|
1657
1807
|
} else {
|
|
1658
|
-
const childVar = this.
|
|
1808
|
+
const childVar = this.emitNode(child);
|
|
1659
1809
|
this._createLines.push(`${elVar}.appendChild(${childVar});`);
|
|
1660
1810
|
}
|
|
1661
1811
|
}
|
|
1662
1812
|
} else if (block) {
|
|
1663
|
-
const childVar = this.
|
|
1813
|
+
const childVar = this.emitNode(block);
|
|
1664
1814
|
this._createLines.push(`${elVar}.appendChild(${childVar});`);
|
|
1665
1815
|
}
|
|
1666
1816
|
}
|
|
1667
1817
|
else if (this.is(arg, 'object')) {
|
|
1668
|
-
this.
|
|
1818
|
+
this.emitAttributes(elVar, arg);
|
|
1669
1819
|
}
|
|
1670
1820
|
else if (typeof arg === 'string' || arg instanceof String) {
|
|
1671
1821
|
const val = arg.valueOf();
|
|
1672
1822
|
// Template tag appearing as a string arg (e.g., slot after multi-line attrs)
|
|
1673
1823
|
const baseName = val.split(/[#.]/)[0];
|
|
1674
1824
|
if (this.isHtmlTag(baseName || 'div') || this.isComponent(baseName)) {
|
|
1675
|
-
const childVar = this.
|
|
1825
|
+
const childVar = this.emitNode(arg);
|
|
1676
1826
|
this._createLines.push(`${elVar}.appendChild(${childVar});`);
|
|
1677
1827
|
} else {
|
|
1678
1828
|
const textVar = this.newTextVar();
|
|
@@ -1684,13 +1834,13 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1684
1834
|
} else if (this.componentMembers && this.componentMembers.has(val)) {
|
|
1685
1835
|
this._createLines.push(`${textVar} = document.createTextNode(String(${this._self}.${val}));`);
|
|
1686
1836
|
} else {
|
|
1687
|
-
this._createLines.push(`${textVar} = document.createTextNode(${this.
|
|
1837
|
+
this._createLines.push(`${textVar} = document.createTextNode(${this.emitInComponent(arg, 'value')});`);
|
|
1688
1838
|
}
|
|
1689
1839
|
this._createLines.push(`${elVar}.appendChild(${textVar});`);
|
|
1690
1840
|
}
|
|
1691
1841
|
}
|
|
1692
1842
|
else if (arg) {
|
|
1693
|
-
const childVar = this.
|
|
1843
|
+
const childVar = this.emitNode(arg);
|
|
1694
1844
|
this._createLines.push(`${elVar}.appendChild(${childVar});`);
|
|
1695
1845
|
}
|
|
1696
1846
|
}
|
|
@@ -1728,10 +1878,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1728
1878
|
};
|
|
1729
1879
|
|
|
1730
1880
|
// --------------------------------------------------------------------------
|
|
1731
|
-
//
|
|
1881
|
+
// emitTag — HTML element with static classes and children
|
|
1732
1882
|
// --------------------------------------------------------------------------
|
|
1733
1883
|
|
|
1734
|
-
proto.
|
|
1884
|
+
proto.emitTag = function(tag, classes, args, id) {
|
|
1735
1885
|
const elVar = this.newElementVar();
|
|
1736
1886
|
const isSvg = SVG_TAGS.has(tag) || this._svgDepth > 0;
|
|
1737
1887
|
if (isSvg) {
|
|
@@ -1789,10 +1939,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1789
1939
|
};
|
|
1790
1940
|
|
|
1791
1941
|
// --------------------------------------------------------------------------
|
|
1792
|
-
//
|
|
1942
|
+
// emitDynamicTag — tag with .() CLSX dynamic classes
|
|
1793
1943
|
// --------------------------------------------------------------------------
|
|
1794
1944
|
|
|
1795
|
-
proto.
|
|
1945
|
+
proto.emitDynamicTag = function(tag, classExprs, children, staticClassArgs, id) {
|
|
1796
1946
|
const elVar = this.newElementVar();
|
|
1797
1947
|
if (SVG_TAGS.has(tag) || this._svgDepth > 0) {
|
|
1798
1948
|
this._createLines.push(`${elVar} = document.createElementNS('${SVG_NS}', '${tag}');`);
|
|
@@ -1805,7 +1955,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1805
1955
|
const autoWireClaimed = this._claimAutoWire(elVar);
|
|
1806
1956
|
|
|
1807
1957
|
// Defer className emission so class: attributes can merge with .() classes
|
|
1808
|
-
const classArgs = [...(staticClassArgs || []), ...classExprs.map(e => this.
|
|
1958
|
+
const classArgs = [...(staticClassArgs || []), ...classExprs.map(e => this.emitInComponent(e, 'value'))];
|
|
1809
1959
|
const prevClassArgs = this._pendingClassArgs;
|
|
1810
1960
|
const prevClassEl = this._pendingClassEl;
|
|
1811
1961
|
this._pendingClassArgs = classArgs;
|
|
@@ -1833,10 +1983,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1833
1983
|
};
|
|
1834
1984
|
|
|
1835
1985
|
// --------------------------------------------------------------------------
|
|
1836
|
-
//
|
|
1986
|
+
// emitAttributes — attributes, events, and bindings on an element
|
|
1837
1987
|
// --------------------------------------------------------------------------
|
|
1838
1988
|
|
|
1839
|
-
proto.
|
|
1989
|
+
proto.emitAttributes = function(elVar, objExpr) {
|
|
1840
1990
|
const inputType = extractInputType(objExpr.slice(1));
|
|
1841
1991
|
|
|
1842
1992
|
for (let i = 1; i < objExpr.length; i++) {
|
|
@@ -1851,7 +2001,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1851
2001
|
if (typeof value === 'string' && this.componentMembers?.has(value)) {
|
|
1852
2002
|
this._createLines.push(`${elVar}.addEventListener('${eventName}', (e) => __batch(() => ${this._self}.${value}(e)));`);
|
|
1853
2003
|
} else {
|
|
1854
|
-
const handlerCode = this.
|
|
2004
|
+
const handlerCode = this.emitInComponent(value, 'value');
|
|
1855
2005
|
this._createLines.push(`${elVar}.addEventListener('${eventName}', (e) => __batch(() => (${handlerCode})(e)));`);
|
|
1856
2006
|
}
|
|
1857
2007
|
continue;
|
|
@@ -1866,7 +2016,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1866
2016
|
|
|
1867
2017
|
// Class merging: class: values merge with .() dynamic classes
|
|
1868
2018
|
if (key === 'class' || key === 'className') {
|
|
1869
|
-
const valueCode = this.
|
|
2019
|
+
const valueCode = this.emitInComponent(value, 'value');
|
|
1870
2020
|
if (this._pendingClassArgs && this._pendingClassEl === elVar) {
|
|
1871
2021
|
this._pendingClassArgs.push(valueCode);
|
|
1872
2022
|
} else if (this.hasReactiveDeps(value)) {
|
|
@@ -1902,7 +2052,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1902
2052
|
// Two-way binding: __bind_value__ pattern
|
|
1903
2053
|
if (key.startsWith(BIND_PREFIX) && key.endsWith(BIND_SUFFIX)) {
|
|
1904
2054
|
const prop = key.slice(BIND_PREFIX.length, -BIND_SUFFIX.length);
|
|
1905
|
-
const valueCode = this.
|
|
2055
|
+
const valueCode = this.emitInComponent(value, 'value');
|
|
1906
2056
|
|
|
1907
2057
|
let event, valueAccessor;
|
|
1908
2058
|
if (prop === 'checked') {
|
|
@@ -1924,7 +2074,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1924
2074
|
continue;
|
|
1925
2075
|
}
|
|
1926
2076
|
|
|
1927
|
-
const valueCode = this.
|
|
2077
|
+
const valueCode = this.emitInComponent(value, 'value');
|
|
1928
2078
|
|
|
1929
2079
|
// value/checked with reactive deps: one-way push (use <=> for two-way)
|
|
1930
2080
|
if ((key === 'value' || key === 'checked') && this.hasReactiveDeps(value)) {
|
|
@@ -1962,12 +2112,12 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1962
2112
|
};
|
|
1963
2113
|
|
|
1964
2114
|
// --------------------------------------------------------------------------
|
|
1965
|
-
//
|
|
2115
|
+
// emitTemplateBlock — a block of template children
|
|
1966
2116
|
// --------------------------------------------------------------------------
|
|
1967
2117
|
|
|
1968
|
-
proto.
|
|
2118
|
+
proto.emitTemplateBlock = function(body) {
|
|
1969
2119
|
if (!Array.isArray(body) || body[0] !== 'block') {
|
|
1970
|
-
return this.
|
|
2120
|
+
return this.emitNode(body);
|
|
1971
2121
|
}
|
|
1972
2122
|
|
|
1973
2123
|
const statements = body.slice(1);
|
|
@@ -1977,14 +2127,14 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1977
2127
|
return commentVar;
|
|
1978
2128
|
}
|
|
1979
2129
|
if (statements.length === 1) {
|
|
1980
|
-
return this.
|
|
2130
|
+
return this.emitNode(statements[0]);
|
|
1981
2131
|
}
|
|
1982
2132
|
|
|
1983
2133
|
const fragVar = this.newElementVar('frag');
|
|
1984
2134
|
this._createLines.push(`${fragVar} = document.createDocumentFragment();`);
|
|
1985
2135
|
const children = [];
|
|
1986
2136
|
for (const stmt of statements) {
|
|
1987
|
-
const childVar = this.
|
|
2137
|
+
const childVar = this.emitNode(stmt);
|
|
1988
2138
|
this._createLines.push(`${fragVar}.appendChild(${childVar});`);
|
|
1989
2139
|
children.push(childVar);
|
|
1990
2140
|
}
|
|
@@ -1993,28 +2143,40 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1993
2143
|
};
|
|
1994
2144
|
|
|
1995
2145
|
// --------------------------------------------------------------------------
|
|
1996
|
-
//
|
|
2146
|
+
// emitConditional — reactive if/else using block factories
|
|
1997
2147
|
// --------------------------------------------------------------------------
|
|
1998
2148
|
|
|
1999
|
-
proto.
|
|
2149
|
+
proto.emitConditional = function(sexpr) {
|
|
2000
2150
|
this._pendingAutoWire = false;
|
|
2151
|
+
|
|
2152
|
+
// Fold flat else-if chains into nested structure.
|
|
2153
|
+
// Parser emits: ['if', c1, t1, ['if', c2, t2], ..., finalElse]
|
|
2154
|
+
// We need: ['if', c1, t1, ['if', c2, t2, [..., finalElse]]]
|
|
2155
|
+
if (sexpr.length > 4) {
|
|
2156
|
+
let chain = sexpr[sexpr.length - 1];
|
|
2157
|
+
for (let i = sexpr.length - 2; i >= 3; i--) {
|
|
2158
|
+
chain = [...sexpr[i], chain];
|
|
2159
|
+
}
|
|
2160
|
+
sexpr = [sexpr[0], sexpr[1], sexpr[2], chain];
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2001
2163
|
const [, condition, thenBlock, elseBlock] = sexpr;
|
|
2002
2164
|
|
|
2003
2165
|
const anchorVar = this.newElementVar('anchor');
|
|
2004
2166
|
this._createLines.push(`${anchorVar} = document.createComment('if');`);
|
|
2005
2167
|
|
|
2006
|
-
const condCode = this.
|
|
2168
|
+
const condCode = this.emitInComponent(condition, 'value');
|
|
2007
2169
|
|
|
2008
2170
|
const outerParams = this._loopVarStack.map(v => `${v.itemVar}, ${v.indexVar}`).join(', ');
|
|
2009
2171
|
const outerExtra = outerParams ? `, ${outerParams}` : '';
|
|
2010
2172
|
|
|
2011
2173
|
const thenBlockName = this.newBlockVar();
|
|
2012
|
-
this.
|
|
2174
|
+
this.emitConditionBranch(thenBlockName, thenBlock);
|
|
2013
2175
|
|
|
2014
2176
|
let elseBlockName = null;
|
|
2015
2177
|
if (elseBlock) {
|
|
2016
2178
|
elseBlockName = this.newBlockVar();
|
|
2017
|
-
this.
|
|
2179
|
+
this.emitConditionBranch(elseBlockName, elseBlock);
|
|
2018
2180
|
}
|
|
2019
2181
|
|
|
2020
2182
|
const setupLines = [];
|
|
@@ -2055,6 +2217,9 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
2055
2217
|
setupLines.push(` }`);
|
|
2056
2218
|
}
|
|
2057
2219
|
setupLines.push(` ${effClose}`);
|
|
2220
|
+
if (this._factoryMode) {
|
|
2221
|
+
setupLines.push(` disposers.push(() => { if (currentBlock) { currentBlock.d(true); currentBlock = null; } });`);
|
|
2222
|
+
}
|
|
2058
2223
|
setupLines.push(`}`);
|
|
2059
2224
|
|
|
2060
2225
|
this._setupLines.push(setupLines.join('\n '));
|
|
@@ -2063,10 +2228,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
2063
2228
|
};
|
|
2064
2229
|
|
|
2065
2230
|
// --------------------------------------------------------------------------
|
|
2066
|
-
//
|
|
2231
|
+
// emitConditionBranch — block factory for a conditional branch
|
|
2067
2232
|
// --------------------------------------------------------------------------
|
|
2068
2233
|
|
|
2069
|
-
proto.
|
|
2234
|
+
proto.emitConditionBranch = function(blockName, block) {
|
|
2070
2235
|
const saved = [this._createLines, this._setupLines, this._factoryMode, this._factoryVars];
|
|
2071
2236
|
|
|
2072
2237
|
this._createLines = [];
|
|
@@ -2074,7 +2239,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
2074
2239
|
this._factoryMode = true;
|
|
2075
2240
|
this._factoryVars = new Set();
|
|
2076
2241
|
|
|
2077
|
-
const rootVar = this.
|
|
2242
|
+
const rootVar = this.emitTemplateBlock(block);
|
|
2078
2243
|
const createLines = this._createLines;
|
|
2079
2244
|
const setupLines = this._setupLines;
|
|
2080
2245
|
const factoryVars = this._factoryVars;
|
|
@@ -2160,10 +2325,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
2160
2325
|
};
|
|
2161
2326
|
|
|
2162
2327
|
// --------------------------------------------------------------------------
|
|
2163
|
-
//
|
|
2328
|
+
// emitTemplateLoop — reactive for-loop with keyed reconciliation
|
|
2164
2329
|
// --------------------------------------------------------------------------
|
|
2165
2330
|
|
|
2166
|
-
proto.
|
|
2331
|
+
proto.emitTemplateLoop = function(sexpr) {
|
|
2167
2332
|
this._pendingAutoWire = false;
|
|
2168
2333
|
const [head, vars, collection, guard, step, body] = sexpr;
|
|
2169
2334
|
|
|
@@ -2184,7 +2349,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
2184
2349
|
indexVar = indexVar || `_i${this._loopVarStack.length}`;
|
|
2185
2350
|
}
|
|
2186
2351
|
|
|
2187
|
-
const collectionCode = this.
|
|
2352
|
+
const collectionCode = this.emitInComponent(collection, 'value');
|
|
2188
2353
|
|
|
2189
2354
|
// Extract key expression from body if present
|
|
2190
2355
|
let keyExpr = itemVar;
|
|
@@ -2196,7 +2361,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
2196
2361
|
for (let i = 1; i < arg.length; i++) {
|
|
2197
2362
|
const [k, v] = arg[i];
|
|
2198
2363
|
if (k === 'key') {
|
|
2199
|
-
keyExpr = this.
|
|
2364
|
+
keyExpr = this.emit(v, 'value');
|
|
2200
2365
|
break;
|
|
2201
2366
|
}
|
|
2202
2367
|
}
|
|
@@ -2217,7 +2382,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
2217
2382
|
const outerExtra = outerParams ? `, ${outerParams}` : '';
|
|
2218
2383
|
|
|
2219
2384
|
this._loopVarStack.push({ itemVar, indexVar });
|
|
2220
|
-
const itemNode = this.
|
|
2385
|
+
const itemNode = this.emitTemplateBlock(body);
|
|
2221
2386
|
this._loopVarStack.pop();
|
|
2222
2387
|
const itemCreateLines = this._createLines;
|
|
2223
2388
|
const itemSetupLines = this._setupLines;
|
|
@@ -2254,10 +2419,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
2254
2419
|
};
|
|
2255
2420
|
|
|
2256
2421
|
// --------------------------------------------------------------------------
|
|
2257
|
-
//
|
|
2422
|
+
// emitChildComponent — instantiate a child component
|
|
2258
2423
|
// --------------------------------------------------------------------------
|
|
2259
2424
|
|
|
2260
|
-
proto.
|
|
2425
|
+
proto.emitChildComponent = function(componentName, args) {
|
|
2261
2426
|
this._pendingAutoWire = false;
|
|
2262
2427
|
const instVar = this.newElementVar('inst');
|
|
2263
2428
|
const elVar = this.newElementVar('el');
|
|
@@ -2271,7 +2436,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
2271
2436
|
this._createLines.push(`} finally { __popComponent(__prev); } }`);
|
|
2272
2437
|
|
|
2273
2438
|
for (const { event, value } of eventBindings) {
|
|
2274
|
-
const handlerCode = this.
|
|
2439
|
+
const handlerCode = this.emitInComponent(value, 'value');
|
|
2275
2440
|
this._createLines.push(`${elVar}.addEventListener('${event}', (e) => __batch(() => (${handlerCode})(e)));`);
|
|
2276
2441
|
}
|
|
2277
2442
|
|
|
@@ -2314,7 +2479,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
2314
2479
|
const member = typeof value === 'string' ? value : value[2];
|
|
2315
2480
|
props.push(`${key}: ${this._self}.${member}`);
|
|
2316
2481
|
} else {
|
|
2317
|
-
const valueCode = this.
|
|
2482
|
+
const valueCode = this.emitInComponent(value, 'value');
|
|
2318
2483
|
props.push(`${key}: ${valueCode}`);
|
|
2319
2484
|
if (this.hasReactiveDeps(value)) {
|
|
2320
2485
|
reactiveProps.push({ key, valueCode });
|
|
@@ -2357,7 +2522,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
2357
2522
|
this._createLines = [];
|
|
2358
2523
|
this._setupLines = [];
|
|
2359
2524
|
|
|
2360
|
-
childrenVar = this.
|
|
2525
|
+
childrenVar = this.emitTemplateBlock(block);
|
|
2361
2526
|
|
|
2362
2527
|
const childCreateLines = this._createLines;
|
|
2363
2528
|
const childSetupLinesCopy = this._setupLines;
|
|
@@ -2374,7 +2539,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
2374
2539
|
}
|
|
2375
2540
|
} else if (arg && !childrenVar) {
|
|
2376
2541
|
const textVar = this.newTextVar();
|
|
2377
|
-
const exprCode = this.
|
|
2542
|
+
const exprCode = this.emitInComponent(arg, 'value');
|
|
2378
2543
|
if (this.hasReactiveDeps(arg)) {
|
|
2379
2544
|
this._createLines.push(`${textVar} = document.createTextNode('');`);
|
|
2380
2545
|
const body = `${textVar}.data = ${exprCode};`;
|