rip-lang 3.10.6 → 3.10.8
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/README.md +1 -1
- package/docs/RIP-LANG.md +109 -0
- package/docs/dist/rip-ui.min.js +179 -169
- package/docs/dist/rip-ui.min.js.br +0 -0
- package/docs/dist/rip.browser.min.js +186 -176
- package/docs/dist/rip.browser.min.js.br +0 -0
- package/docs/results/images/cover_bg.jpg +0 -0
- package/docs/results/images/crossover.svg +20 -0
- package/docs/results/images/heart.png +0 -0
- package/docs/results/images/human_body.png +0 -0
- package/docs/results/images/pancreas.png +0 -0
- package/docs/results/images/yoga_lady.jpg +0 -0
- package/docs/results/index.html +1130 -0
- package/package.json +1 -1
- package/src/compiler.js +10 -0
- package/src/components.js +182 -55
- package/src/grammar/grammar.rip +9 -9
- package/src/grammar/lunar.rip +2 -2
- package/src/lexer.js +1 -1
- package/src/parser.js +6 -7
- package/src/types.js +2 -2
package/package.json
CHANGED
package/src/compiler.js
CHANGED
|
@@ -2980,6 +2980,16 @@ function __state(initialValue) {
|
|
|
2980
2980
|
},
|
|
2981
2981
|
|
|
2982
2982
|
read() { return value; },
|
|
2983
|
+
touch() {
|
|
2984
|
+
if (dead || notifying) return;
|
|
2985
|
+
notifying = true;
|
|
2986
|
+
for (const sub of subscribers) {
|
|
2987
|
+
if (sub.markDirty) sub.markDirty();
|
|
2988
|
+
else __pendingEffects.add(sub);
|
|
2989
|
+
}
|
|
2990
|
+
if (!__batching) __flushEffects();
|
|
2991
|
+
notifying = false;
|
|
2992
|
+
},
|
|
2983
2993
|
lock() { locked = true; return state; },
|
|
2984
2994
|
free() { subscribers.clear(); return state; },
|
|
2985
2995
|
kill() { dead = true; subscribers.clear(); return value; },
|
package/src/components.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
//
|
|
8
8
|
// Naming: All render-tree generators use generate* (consistent with compiler).
|
|
9
9
|
|
|
10
|
-
import { TEMPLATE_TAGS } from './tags.js';
|
|
10
|
+
import { TEMPLATE_TAGS, SVG_TAGS } from './tags.js';
|
|
11
11
|
|
|
12
12
|
// ============================================================================
|
|
13
13
|
// Constants
|
|
@@ -24,6 +24,8 @@ const BOOLEAN_ATTRS = new Set([
|
|
|
24
24
|
'allowfullscreen', 'inert',
|
|
25
25
|
]);
|
|
26
26
|
|
|
27
|
+
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
28
|
+
|
|
27
29
|
// ============================================================================
|
|
28
30
|
// Standalone Utilities
|
|
29
31
|
// ============================================================================
|
|
@@ -490,7 +492,8 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
490
492
|
}
|
|
491
493
|
current = current[1];
|
|
492
494
|
}
|
|
493
|
-
let raw = typeof current === 'string' ? current : (current instanceof String ? current.valueOf() :
|
|
495
|
+
let raw = typeof current === 'string' ? current : (current instanceof String ? current.valueOf() : null);
|
|
496
|
+
if (raw === null) return { tag: null, classes, id: undefined };
|
|
494
497
|
// Split tag#id — e.g. "div#content" → tag: "div", id: "content"
|
|
495
498
|
let [tag, id] = raw.split('#');
|
|
496
499
|
if (!tag) tag = 'div'; // bare #id → div
|
|
@@ -792,6 +795,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
792
795
|
this._createLines = [];
|
|
793
796
|
this._setupLines = [];
|
|
794
797
|
this._blockFactories = [];
|
|
798
|
+
this._loopVarStack = [];
|
|
795
799
|
|
|
796
800
|
const statements = this.is(body, 'block') ? body.slice(1) : [body];
|
|
797
801
|
|
|
@@ -855,7 +859,12 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
855
859
|
// Static tag without content (possibly with #id)
|
|
856
860
|
const [tagStr, idStr] = str.split('#');
|
|
857
861
|
const elVar = this.newElementVar();
|
|
858
|
-
|
|
862
|
+
const actualTag = tagStr || 'div';
|
|
863
|
+
if (SVG_TAGS.has(actualTag) || this._svgDepth > 0) {
|
|
864
|
+
this._createLines.push(`${elVar} = document.createElementNS('${SVG_NS}', '${actualTag}');`);
|
|
865
|
+
} else {
|
|
866
|
+
this._createLines.push(`${elVar} = document.createElement('${actualTag}');`);
|
|
867
|
+
}
|
|
859
868
|
if (idStr) this._createLines.push(`${elVar}.id = '${idStr}';`);
|
|
860
869
|
return elVar;
|
|
861
870
|
}
|
|
@@ -1011,16 +1020,49 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1011
1020
|
|
|
1012
1021
|
proto.generateTag = function(tag, classes, args, id) {
|
|
1013
1022
|
const elVar = this.newElementVar();
|
|
1014
|
-
|
|
1023
|
+
const isSvg = SVG_TAGS.has(tag) || this._svgDepth > 0;
|
|
1024
|
+
if (isSvg) {
|
|
1025
|
+
this._createLines.push(`${elVar} = document.createElementNS('${SVG_NS}', '${tag}');`);
|
|
1026
|
+
} else {
|
|
1027
|
+
this._createLines.push(`${elVar} = document.createElement('${tag}');`);
|
|
1028
|
+
}
|
|
1015
1029
|
|
|
1016
1030
|
if (id) {
|
|
1017
1031
|
this._createLines.push(`${elVar}.id = '${id}';`);
|
|
1018
1032
|
}
|
|
1033
|
+
|
|
1034
|
+
// Defer class emission when selector classes exist so class: attributes merge
|
|
1035
|
+
const prevClassArgs = this._pendingClassArgs;
|
|
1036
|
+
const prevClassEl = this._pendingClassEl;
|
|
1019
1037
|
if (classes.length > 0) {
|
|
1020
|
-
this.
|
|
1038
|
+
this._pendingClassArgs = [`'${classes.join(' ')}'`];
|
|
1039
|
+
this._pendingClassEl = elVar;
|
|
1021
1040
|
}
|
|
1022
1041
|
|
|
1042
|
+
if (tag === 'svg') this._svgDepth = (this._svgDepth || 0) + 1;
|
|
1023
1043
|
this.appendChildren(elVar, args);
|
|
1044
|
+
if (tag === 'svg') this._svgDepth--;
|
|
1045
|
+
|
|
1046
|
+
// Emit final class: if only selector classes (no dynamic additions), set statically
|
|
1047
|
+
if (classes.length > 0) {
|
|
1048
|
+
if (this._pendingClassArgs.length === 1) {
|
|
1049
|
+
if (isSvg) {
|
|
1050
|
+
this._createLines.push(`${elVar}.setAttribute('class', '${classes.join(' ')}');`);
|
|
1051
|
+
} else {
|
|
1052
|
+
this._createLines.push(`${elVar}.className = '${classes.join(' ')}';`);
|
|
1053
|
+
}
|
|
1054
|
+
} else {
|
|
1055
|
+
const combined = this._pendingClassArgs.join(', ');
|
|
1056
|
+
if (isSvg) {
|
|
1057
|
+
this._setupLines.push(`__effect(() => { ${elVar}.setAttribute('class', __clsx(${combined})); });`);
|
|
1058
|
+
} else {
|
|
1059
|
+
this._setupLines.push(`__effect(() => { ${elVar}.className = __clsx(${combined}); });`);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
this._pendingClassArgs = prevClassArgs;
|
|
1063
|
+
this._pendingClassEl = prevClassEl;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1024
1066
|
return elVar;
|
|
1025
1067
|
};
|
|
1026
1068
|
|
|
@@ -1030,7 +1072,11 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1030
1072
|
|
|
1031
1073
|
proto.generateDynamicTag = function(tag, classExprs, children) {
|
|
1032
1074
|
const elVar = this.newElementVar();
|
|
1033
|
-
|
|
1075
|
+
if (SVG_TAGS.has(tag) || this._svgDepth > 0) {
|
|
1076
|
+
this._createLines.push(`${elVar} = document.createElementNS('${SVG_NS}', '${tag}');`);
|
|
1077
|
+
} else {
|
|
1078
|
+
this._createLines.push(`${elVar} = document.createElement('${tag}');`);
|
|
1079
|
+
}
|
|
1034
1080
|
|
|
1035
1081
|
// Defer className emission so class: attributes can merge with .() classes
|
|
1036
1082
|
const classArgs = classExprs.map(e => this.generateInComponent(e, 'value'));
|
|
@@ -1039,11 +1085,18 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1039
1085
|
this._pendingClassArgs = classArgs;
|
|
1040
1086
|
this._pendingClassEl = elVar;
|
|
1041
1087
|
|
|
1088
|
+
if (tag === 'svg') this._svgDepth = (this._svgDepth || 0) + 1;
|
|
1042
1089
|
this.appendChildren(elVar, children);
|
|
1090
|
+
if (tag === 'svg') this._svgDepth--;
|
|
1043
1091
|
|
|
1044
1092
|
if (this._pendingClassArgs.length > 0) {
|
|
1045
1093
|
const combined = this._pendingClassArgs.join(', ');
|
|
1046
|
-
|
|
1094
|
+
const isSvg = SVG_TAGS.has(tag) || this._svgDepth > 0;
|
|
1095
|
+
if (isSvg) {
|
|
1096
|
+
this._setupLines.push(`__effect(() => { ${elVar}.setAttribute('class', __clsx(${combined})); });`);
|
|
1097
|
+
} else {
|
|
1098
|
+
this._setupLines.push(`__effect(() => { ${elVar}.className = __clsx(${combined}); });`);
|
|
1099
|
+
}
|
|
1047
1100
|
}
|
|
1048
1101
|
this._pendingClassArgs = prevClassArgs;
|
|
1049
1102
|
this._pendingClassEl = prevClassEl;
|
|
@@ -1087,9 +1140,17 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1087
1140
|
if (this._pendingClassArgs && this._pendingClassEl === elVar) {
|
|
1088
1141
|
this._pendingClassArgs.push(valueCode);
|
|
1089
1142
|
} else if (this.hasReactiveDeps(value)) {
|
|
1090
|
-
this.
|
|
1143
|
+
if (this._svgDepth > 0) {
|
|
1144
|
+
this._setupLines.push(`__effect(() => { ${elVar}.setAttribute('class', __clsx(${valueCode})); });`);
|
|
1145
|
+
} else {
|
|
1146
|
+
this._setupLines.push(`__effect(() => { ${elVar}.className = __clsx(${valueCode}); });`);
|
|
1147
|
+
}
|
|
1091
1148
|
} else {
|
|
1092
|
-
this.
|
|
1149
|
+
if (this._svgDepth > 0) {
|
|
1150
|
+
this._createLines.push(`${elVar}.setAttribute('class', ${valueCode});`);
|
|
1151
|
+
} else {
|
|
1152
|
+
this._createLines.push(`${elVar}.className = ${valueCode};`);
|
|
1153
|
+
}
|
|
1093
1154
|
}
|
|
1094
1155
|
continue;
|
|
1095
1156
|
}
|
|
@@ -1117,7 +1178,12 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1117
1178
|
}
|
|
1118
1179
|
|
|
1119
1180
|
this._setupLines.push(`__effect(() => { ${elVar}.${prop} = ${valueCode}; });`);
|
|
1120
|
-
|
|
1181
|
+
let assignCode = `${valueCode} = ${valueAccessor}`;
|
|
1182
|
+
const rootMember = !this.isSimpleAssignable(value) && this.findRootReactiveMember(value);
|
|
1183
|
+
if (rootMember) {
|
|
1184
|
+
assignCode += `; this.${rootMember}.touch?.()`;
|
|
1185
|
+
}
|
|
1186
|
+
this._createLines.push(`${elVar}.addEventListener('${event}', (e) => { ${assignCode}; });`);
|
|
1121
1187
|
continue;
|
|
1122
1188
|
}
|
|
1123
1189
|
|
|
@@ -1126,20 +1192,30 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1126
1192
|
// Smart two-way binding for value/checked when bound to reactive state
|
|
1127
1193
|
if ((key === 'value' || key === 'checked') && this.hasReactiveDeps(value)) {
|
|
1128
1194
|
this._setupLines.push(`__effect(() => { ${elVar}.${key} = ${valueCode}; });`);
|
|
1129
|
-
//
|
|
1130
|
-
//
|
|
1131
|
-
|
|
1132
|
-
if (this.isSimpleAssignable(value)) {
|
|
1195
|
+
// Generate reverse binding for simple assignable targets or nested
|
|
1196
|
+
// reactive paths (with touch() for Svelte-style invalidation)
|
|
1197
|
+
const rootMemberImplicit = !this.isSimpleAssignable(value) && this.findRootReactiveMember(value);
|
|
1198
|
+
if (this.isSimpleAssignable(value) || rootMemberImplicit) {
|
|
1133
1199
|
const event = key === 'checked' ? 'change' : 'input';
|
|
1134
1200
|
const accessor = key === 'checked' ? 'e.target.checked'
|
|
1135
1201
|
: (inputType === 'number' || inputType === 'range') ? 'e.target.valueAsNumber'
|
|
1136
1202
|
: 'e.target.value';
|
|
1137
|
-
|
|
1203
|
+
let assignCode = `${valueCode} = ${accessor}`;
|
|
1204
|
+
if (rootMemberImplicit) {
|
|
1205
|
+
assignCode += `; this.${rootMemberImplicit}.touch?.()`;
|
|
1206
|
+
}
|
|
1207
|
+
this._createLines.push(`${elVar}.addEventListener('${event}', (e) => { ${assignCode}; });`);
|
|
1138
1208
|
}
|
|
1139
1209
|
continue;
|
|
1140
1210
|
}
|
|
1141
1211
|
|
|
1142
|
-
if (
|
|
1212
|
+
if (key === 'innerHTML' || key === 'textContent' || key === 'innerText') {
|
|
1213
|
+
if (this.hasReactiveDeps(value)) {
|
|
1214
|
+
this._setupLines.push(`__effect(() => { ${elVar}.${key} = ${valueCode}; });`);
|
|
1215
|
+
} else {
|
|
1216
|
+
this._createLines.push(`${elVar}.${key} = ${valueCode};`);
|
|
1217
|
+
}
|
|
1218
|
+
} else if (BOOLEAN_ATTRS.has(key)) {
|
|
1143
1219
|
if (this.hasReactiveDeps(value)) {
|
|
1144
1220
|
this._setupLines.push(`__effect(() => { ${elVar}.toggleAttribute('${key}', !!${valueCode}); });`);
|
|
1145
1221
|
} else {
|
|
@@ -1194,6 +1270,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1194
1270
|
|
|
1195
1271
|
const condCode = this.generateInComponent(condition, 'value');
|
|
1196
1272
|
|
|
1273
|
+
// Collect loop variables from enclosing for-loops
|
|
1274
|
+
const loopParams = this._loopVarStack.map(v => `${v.itemVar}, ${v.indexVar}`).join(', ');
|
|
1275
|
+
const extraArgs = loopParams ? `, ${loopParams}` : '';
|
|
1276
|
+
|
|
1197
1277
|
const thenBlockName = this.newBlockVar();
|
|
1198
1278
|
this.generateConditionBranch(thenBlockName, thenBlock);
|
|
1199
1279
|
|
|
@@ -1221,17 +1301,17 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1221
1301
|
setupLines.push(` showing = want;`);
|
|
1222
1302
|
setupLines.push(``);
|
|
1223
1303
|
setupLines.push(` if (want === 'then') {`);
|
|
1224
|
-
setupLines.push(` currentBlock = ${thenBlockName}(this);`);
|
|
1304
|
+
setupLines.push(` currentBlock = ${thenBlockName}(this${extraArgs});`);
|
|
1225
1305
|
setupLines.push(` currentBlock.c();`);
|
|
1226
1306
|
setupLines.push(` currentBlock.m(anchor.parentNode, anchor.nextSibling);`);
|
|
1227
|
-
setupLines.push(` currentBlock.p(this);`);
|
|
1307
|
+
setupLines.push(` currentBlock.p(this${extraArgs});`);
|
|
1228
1308
|
setupLines.push(` }`);
|
|
1229
1309
|
if (elseBlock) {
|
|
1230
1310
|
setupLines.push(` if (want === 'else') {`);
|
|
1231
|
-
setupLines.push(` currentBlock = ${elseBlockName}(this);`);
|
|
1311
|
+
setupLines.push(` currentBlock = ${elseBlockName}(this${extraArgs});`);
|
|
1232
1312
|
setupLines.push(` currentBlock.c();`);
|
|
1233
1313
|
setupLines.push(` currentBlock.m(anchor.parentNode, anchor.nextSibling);`);
|
|
1234
|
-
setupLines.push(` currentBlock.p(this);`);
|
|
1314
|
+
setupLines.push(` currentBlock.p(this${extraArgs});`);
|
|
1235
1315
|
setupLines.push(` }`);
|
|
1236
1316
|
}
|
|
1237
1317
|
setupLines.push(` });`);
|
|
@@ -1262,8 +1342,12 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1262
1342
|
|
|
1263
1343
|
const localizeVar = (line) => this.localizeVar(line);
|
|
1264
1344
|
|
|
1345
|
+
// Include enclosing loop variables in the factory signature
|
|
1346
|
+
const loopParams = this._loopVarStack.map(v => `${v.itemVar}, ${v.indexVar}`).join(', ');
|
|
1347
|
+
const extraParams = loopParams ? `, ${loopParams}` : '';
|
|
1348
|
+
|
|
1265
1349
|
const factoryLines = [];
|
|
1266
|
-
factoryLines.push(`function ${blockName}(ctx) {`);
|
|
1350
|
+
factoryLines.push(`function ${blockName}(ctx${extraParams}) {`);
|
|
1267
1351
|
|
|
1268
1352
|
// Declare local variables
|
|
1269
1353
|
const localVars = new Set();
|
|
@@ -1295,7 +1379,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1295
1379
|
factoryLines.push(` },`);
|
|
1296
1380
|
|
|
1297
1381
|
// p() - update/patch
|
|
1298
|
-
factoryLines.push(` p(ctx) {`);
|
|
1382
|
+
factoryLines.push(` p(ctx${extraParams}) {`);
|
|
1299
1383
|
if (hasEffects) {
|
|
1300
1384
|
factoryLines.push(` disposers.forEach(d => d());`);
|
|
1301
1385
|
factoryLines.push(` disposers = [];`);
|
|
@@ -1379,7 +1463,9 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1379
1463
|
this._createLines = [];
|
|
1380
1464
|
this._setupLines = [];
|
|
1381
1465
|
|
|
1466
|
+
this._loopVarStack.push({ itemVar, indexVar });
|
|
1382
1467
|
const itemNode = this.generateTemplateBlock(body);
|
|
1468
|
+
this._loopVarStack.pop();
|
|
1383
1469
|
const itemCreateLines = this._createLines;
|
|
1384
1470
|
const itemSetupLines = this._setupLines;
|
|
1385
1471
|
|
|
@@ -1469,32 +1555,32 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1469
1555
|
const setupLines = [];
|
|
1470
1556
|
setupLines.push(`// Loop: ${blockName}`);
|
|
1471
1557
|
setupLines.push(`{`);
|
|
1472
|
-
setupLines.push(` const
|
|
1473
|
-
setupLines.push(` const
|
|
1558
|
+
setupLines.push(` const __anchor = ${anchorVar};`);
|
|
1559
|
+
setupLines.push(` const __map = new Map();`);
|
|
1474
1560
|
setupLines.push(` __effect(() => {`);
|
|
1475
|
-
setupLines.push(` const
|
|
1476
|
-
setupLines.push(` const
|
|
1477
|
-
setupLines.push(` const
|
|
1561
|
+
setupLines.push(` const __items = ${collectionCode};`);
|
|
1562
|
+
setupLines.push(` const __parent = __anchor.parentNode;`);
|
|
1563
|
+
setupLines.push(` const __newMap = new Map();`);
|
|
1478
1564
|
setupLines.push(``);
|
|
1479
|
-
setupLines.push(` for (let ${indexVar} = 0; ${indexVar} <
|
|
1480
|
-
setupLines.push(` const ${itemVar} =
|
|
1481
|
-
setupLines.push(` const
|
|
1482
|
-
setupLines.push(` let
|
|
1483
|
-
setupLines.push(` if (!
|
|
1484
|
-
setupLines.push(`
|
|
1485
|
-
setupLines.push(`
|
|
1565
|
+
setupLines.push(` for (let ${indexVar} = 0; ${indexVar} < __items.length; ${indexVar}++) {`);
|
|
1566
|
+
setupLines.push(` const ${itemVar} = __items[${indexVar}];`);
|
|
1567
|
+
setupLines.push(` const __key = ${keyExpr};`);
|
|
1568
|
+
setupLines.push(` let __block = __map.get(__key);`);
|
|
1569
|
+
setupLines.push(` if (!__block) {`);
|
|
1570
|
+
setupLines.push(` __block = ${blockName}(this, ${itemVar}, ${indexVar});`);
|
|
1571
|
+
setupLines.push(` __block.c();`);
|
|
1486
1572
|
setupLines.push(` }`);
|
|
1487
|
-
setupLines.push(`
|
|
1488
|
-
setupLines.push(`
|
|
1489
|
-
setupLines.push(`
|
|
1573
|
+
setupLines.push(` __block.m(__parent, __anchor);`);
|
|
1574
|
+
setupLines.push(` __block.p(this, ${itemVar}, ${indexVar});`);
|
|
1575
|
+
setupLines.push(` __newMap.set(__key, __block);`);
|
|
1490
1576
|
setupLines.push(` }`);
|
|
1491
1577
|
setupLines.push(``);
|
|
1492
|
-
setupLines.push(` for (const [
|
|
1493
|
-
setupLines.push(` if (!
|
|
1578
|
+
setupLines.push(` for (const [__k, __b] of __map) {`);
|
|
1579
|
+
setupLines.push(` if (!__newMap.has(__k)) __b.d(true);`);
|
|
1494
1580
|
setupLines.push(` }`);
|
|
1495
1581
|
setupLines.push(``);
|
|
1496
|
-
setupLines.push(`
|
|
1497
|
-
setupLines.push(` for (const [
|
|
1582
|
+
setupLines.push(` __map.clear();`);
|
|
1583
|
+
setupLines.push(` for (const [__k, __v] of __newMap) __map.set(__k, __v);`);
|
|
1498
1584
|
setupLines.push(` });`);
|
|
1499
1585
|
setupLines.push(`}`);
|
|
1500
1586
|
|
|
@@ -1510,7 +1596,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1510
1596
|
proto.generateChildComponent = function(componentName, args) {
|
|
1511
1597
|
const instVar = this.newElementVar('inst');
|
|
1512
1598
|
const elVar = this.newElementVar('el');
|
|
1513
|
-
const { propsCode, childrenSetupLines } = this.buildComponentProps(args);
|
|
1599
|
+
const { propsCode, reactiveProps, childrenSetupLines } = this.buildComponentProps(args);
|
|
1514
1600
|
|
|
1515
1601
|
this._createLines.push(`${instVar} = new ${componentName}(${propsCode});`);
|
|
1516
1602
|
this._createLines.push(`${elVar} = ${instVar}._create();`);
|
|
@@ -1518,6 +1604,10 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1518
1604
|
|
|
1519
1605
|
this._setupLines.push(`if (${instVar}._setup) ${instVar}._setup();`);
|
|
1520
1606
|
|
|
1607
|
+
for (const { key, valueCode } of reactiveProps) {
|
|
1608
|
+
this._setupLines.push(`__effect(() => { if (${instVar}.${key}) ${instVar}.${key}.value = ${valueCode}; });`);
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1521
1611
|
for (const line of childrenSetupLines) {
|
|
1522
1612
|
this._setupLines.push(line);
|
|
1523
1613
|
}
|
|
@@ -1531,6 +1621,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1531
1621
|
|
|
1532
1622
|
proto.buildComponentProps = function(args) {
|
|
1533
1623
|
const props = [];
|
|
1624
|
+
const reactiveProps = [];
|
|
1534
1625
|
let childrenVar = null;
|
|
1535
1626
|
const childrenSetupLines = [];
|
|
1536
1627
|
|
|
@@ -1539,13 +1630,22 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1539
1630
|
for (let i = 1; i < arg.length; i++) {
|
|
1540
1631
|
const [key, value] = arg[i];
|
|
1541
1632
|
if (typeof key === 'string') {
|
|
1542
|
-
//
|
|
1543
|
-
//
|
|
1544
|
-
const
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1633
|
+
// Simple reactive identifier — pass signal directly for shared reactivity.
|
|
1634
|
+
// Complex expressions — use normal .value unwrapping to compute the value.
|
|
1635
|
+
const isSimpleReactive = this.reactiveMembers && (
|
|
1636
|
+
(typeof value === 'string' && this.reactiveMembers.has(value)) ||
|
|
1637
|
+
(Array.isArray(value) && value[0] === '.' && value[1] === 'this' && typeof value[2] === 'string' && this.reactiveMembers.has(value[2]))
|
|
1638
|
+
);
|
|
1639
|
+
if (isSimpleReactive) {
|
|
1640
|
+
const member = typeof value === 'string' ? value : value[2];
|
|
1641
|
+
props.push(`${key}: this.${member}`);
|
|
1642
|
+
} else {
|
|
1643
|
+
const valueCode = this.generateInComponent(value, 'value');
|
|
1644
|
+
props.push(`${key}: ${valueCode}`);
|
|
1645
|
+
if (this.hasReactiveDeps(value)) {
|
|
1646
|
+
reactiveProps.push({ key, valueCode });
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1549
1649
|
}
|
|
1550
1650
|
}
|
|
1551
1651
|
} else if (Array.isArray(arg) && (arg[0] === '->' || arg[0] === '=>')) {
|
|
@@ -1559,11 +1659,20 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1559
1659
|
for (let i = 1; i < child.length; i++) {
|
|
1560
1660
|
const [key, value] = child[i];
|
|
1561
1661
|
if (typeof key === 'string') {
|
|
1562
|
-
const
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1662
|
+
const isSimpleReactive = this.reactiveMembers && (
|
|
1663
|
+
(typeof value === 'string' && this.reactiveMembers.has(value)) ||
|
|
1664
|
+
(Array.isArray(value) && value[0] === '.' && value[1] === 'this' && typeof value[2] === 'string' && this.reactiveMembers.has(value[2]))
|
|
1665
|
+
);
|
|
1666
|
+
if (isSimpleReactive) {
|
|
1667
|
+
const member = typeof value === 'string' ? value : value[2];
|
|
1668
|
+
props.push(`${key}: this.${member}`);
|
|
1669
|
+
} else {
|
|
1670
|
+
const valueCode = this.generateInComponent(value, 'value');
|
|
1671
|
+
props.push(`${key}: ${valueCode}`);
|
|
1672
|
+
if (this.hasReactiveDeps(value)) {
|
|
1673
|
+
reactiveProps.push({ key, valueCode });
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1567
1676
|
}
|
|
1568
1677
|
}
|
|
1569
1678
|
} else {
|
|
@@ -1598,7 +1707,7 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1598
1707
|
}
|
|
1599
1708
|
|
|
1600
1709
|
const propsCode = props.length > 0 ? `{ ${props.join(', ')} }` : '{}';
|
|
1601
|
-
return { propsCode, childrenSetupLines };
|
|
1710
|
+
return { propsCode, reactiveProps, childrenSetupLines };
|
|
1602
1711
|
};
|
|
1603
1712
|
|
|
1604
1713
|
// --------------------------------------------------------------------------
|
|
@@ -1646,6 +1755,24 @@ export function installComponentSupport(CodeGenerator, Lexer) {
|
|
|
1646
1755
|
return false;
|
|
1647
1756
|
};
|
|
1648
1757
|
|
|
1758
|
+
// findRootReactiveMember — walk a nested access chain to find the root reactive member
|
|
1759
|
+
// e.g. (. ([] history 0) triglycerides) → 'history'
|
|
1760
|
+
// --------------------------------------------------------------------------
|
|
1761
|
+
|
|
1762
|
+
proto.findRootReactiveMember = function(sexpr) {
|
|
1763
|
+
if (typeof sexpr === 'string') {
|
|
1764
|
+
return this.reactiveMembers?.has(sexpr) ? sexpr : null;
|
|
1765
|
+
}
|
|
1766
|
+
if (!Array.isArray(sexpr)) return null;
|
|
1767
|
+
if (sexpr[0] === '.' && sexpr[1] === 'this' && typeof sexpr[2] === 'string') {
|
|
1768
|
+
return this.reactiveMembers?.has(sexpr[2]) ? sexpr[2] : null;
|
|
1769
|
+
}
|
|
1770
|
+
if (sexpr[0] === '.' || sexpr[0] === '[]') {
|
|
1771
|
+
return this.findRootReactiveMember(sexpr[1]);
|
|
1772
|
+
}
|
|
1773
|
+
return null;
|
|
1774
|
+
};
|
|
1775
|
+
|
|
1649
1776
|
// _rootsAtThis — check if a property-access chain is rooted at 'this'
|
|
1650
1777
|
// --------------------------------------------------------------------------
|
|
1651
1778
|
|
package/src/grammar/grammar.rip
CHANGED
|
@@ -73,7 +73,7 @@ grammar =
|
|
|
73
73
|
o 'ReactiveAssign'
|
|
74
74
|
o 'ComputedAssign'
|
|
75
75
|
o 'ReadonlyAssign'
|
|
76
|
-
o '
|
|
76
|
+
o 'Effect'
|
|
77
77
|
o 'If'
|
|
78
78
|
o 'Try'
|
|
79
79
|
o 'While'
|
|
@@ -208,13 +208,13 @@ grammar =
|
|
|
208
208
|
]
|
|
209
209
|
|
|
210
210
|
# Reactive effect (~>) — side effects that run when dependencies change
|
|
211
|
-
|
|
212
|
-
o 'Assignable
|
|
213
|
-
o 'Assignable
|
|
214
|
-
o 'Assignable
|
|
215
|
-
o '
|
|
216
|
-
o '
|
|
217
|
-
o '
|
|
211
|
+
Effect: [
|
|
212
|
+
o 'Assignable EFFECT Expression' , '["effect", 1, 3]'
|
|
213
|
+
o 'Assignable EFFECT TERMINATOR Expression' , '["effect", 1, 4]'
|
|
214
|
+
o 'Assignable EFFECT Block' , '["effect", 1, 3]'
|
|
215
|
+
o 'EFFECT Expression' , '["effect", null, 2]'
|
|
216
|
+
o 'EFFECT TERMINATOR Expression' , '["effect", null, 3]'
|
|
217
|
+
o 'EFFECT Block' , '["effect", null, 3]'
|
|
218
218
|
]
|
|
219
219
|
|
|
220
220
|
# ============================================================================
|
|
@@ -802,7 +802,7 @@ grammar =
|
|
|
802
802
|
o 'EXPORT ReactiveAssign' , '["export", 2]'
|
|
803
803
|
o 'EXPORT ComputedAssign' , '["export", 2]'
|
|
804
804
|
o 'EXPORT ReadonlyAssign' , '["export", 2]'
|
|
805
|
-
o 'EXPORT
|
|
805
|
+
o 'EXPORT Effect' , '["export", 2]'
|
|
806
806
|
o 'EXPORT DEFAULT Expression' , '["export-default", 3]'
|
|
807
807
|
o 'EXPORT DEFAULT INDENT Object OUTDENT' , '["export-default", 4]'
|
|
808
808
|
o 'EXPORT EXPORT_ALL FROM String' , '["export-all", 4]'
|
package/src/grammar/lunar.rip
CHANGED
|
@@ -437,7 +437,7 @@ export install = (Generator) ->
|
|
|
437
437
|
# Statement tokens handled by parseUnary (for 'break if done', 'return x unless err')
|
|
438
438
|
@_exprHandledTokens.add 'STATEMENT'
|
|
439
439
|
@_exprHandledTokens.add 'RETURN'
|
|
440
|
-
@_exprHandledTokens.add '
|
|
440
|
+
@_exprHandledTokens.add 'EFFECT'
|
|
441
441
|
|
|
442
442
|
Generator::_findKeywordTokens = (type, dispatchName, handledTokens, visited) ->
|
|
443
443
|
return if visited.has type.name
|
|
@@ -2271,7 +2271,7 @@ export install = (Generator) ->
|
|
|
2271
2271
|
lines.push " if (token === 'STATEMENT') { const v = tokenText; advance(); return v; }"
|
|
2272
2272
|
lines.push " if (token === 'RETURN') return parseReturn();"
|
|
2273
2273
|
# Fire-and-forget effect (~> expr) — prefix form without left-hand side
|
|
2274
|
-
lines.push " if (token === '
|
|
2274
|
+
lines.push " if (token === 'EFFECT') return parseEffect();"
|
|
2275
2275
|
lines.push " throw new Error('Parse error: unexpected token ' + token + ' at line ' + ((tokenLoc && tokenLoc.r || 0) + 1));"
|
|
2276
2276
|
lines.push "}"
|
|
2277
2277
|
lines.join '\n'
|
package/src/lexer.js
CHANGED
|
@@ -1156,7 +1156,7 @@ export class Lexer {
|
|
|
1156
1156
|
else if (val === '~=') tag = 'COMPUTED_ASSIGN';
|
|
1157
1157
|
else if (val === ':=') tag = 'REACTIVE_ASSIGN';
|
|
1158
1158
|
else if (val === '<=>') tag = 'BIND';
|
|
1159
|
-
else if (val === '~>') tag = '
|
|
1159
|
+
else if (val === '~>') tag = 'EFFECT';
|
|
1160
1160
|
else if (val === '=!') tag = 'READONLY_ASSIGN';
|
|
1161
1161
|
// Merge assignment: *config = {a: 1} → Object.assign(config, {a: 1})
|
|
1162
1162
|
// Also supports *@ = props → Object.assign(this, props)
|