@getforma/core 0.3.0 → 0.3.1
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 +19 -5
- package/dist/{chunk-VKKPX4YU.js → chunk-5H52PKGF.js} +62 -12
- package/dist/chunk-5H52PKGF.js.map +1 -0
- package/dist/{chunk-VP5EOGM5.cjs → chunk-TSQ7AKFT.cjs} +62 -11
- package/dist/chunk-TSQ7AKFT.cjs.map +1 -0
- package/dist/forma-runtime-csp.js +2 -0
- package/dist/forma-runtime.js +2 -0
- package/dist/formajs-runtime-hardened.global.js +1 -1
- package/dist/formajs-runtime-hardened.global.js.map +1 -1
- package/dist/formajs-runtime.global.js +1 -1
- package/dist/formajs-runtime.global.js.map +1 -1
- package/dist/formajs.global.js +1 -1
- package/dist/formajs.global.js.map +1 -1
- package/dist/index.cjs +100 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -27
- package/dist/index.d.ts +43 -27
- package/dist/index.js +57 -27
- package/dist/index.js.map +1 -1
- package/dist/runtime-hardened.cjs +166 -50
- package/dist/runtime-hardened.cjs.map +1 -1
- package/dist/runtime-hardened.d.cts +86 -0
- package/dist/runtime-hardened.d.ts +86 -0
- package/dist/runtime-hardened.js +166 -50
- package/dist/runtime-hardened.js.map +1 -1
- package/dist/runtime.cjs +188 -72
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +167 -51
- package/dist/runtime.js.map +1 -1
- package/dist/ssr/index.cjs +59 -71
- package/dist/ssr/index.cjs.map +1 -1
- package/dist/ssr/index.d.cts +4 -0
- package/dist/ssr/index.d.ts +4 -0
- package/dist/ssr/index.js +59 -71
- package/dist/ssr/index.js.map +1 -1
- package/package.json +24 -3
- package/dist/chunk-VKKPX4YU.js.map +0 -1
- package/dist/chunk-VP5EOGM5.cjs.map +0 -1
package/dist/runtime.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkTSQ7AKFT_cjs = require('./chunk-TSQ7AKFT.cjs');
|
|
4
4
|
var chunkFPSLC62A_cjs = require('./chunk-FPSLC62A.cjs');
|
|
5
5
|
|
|
6
6
|
// src/dom/reconcile.ts
|
|
@@ -481,6 +481,7 @@ if (buildUnsafeEvalMode) {
|
|
|
481
481
|
_unsafeEvalMode = buildUnsafeEvalMode;
|
|
482
482
|
if (_unsafeEvalMode === "locked-off") _allowUnsafeEval = false;
|
|
483
483
|
if (_unsafeEvalMode === "locked-on") _allowUnsafeEval = true;
|
|
484
|
+
if (_unsafeEvalMode === "mutable") _allowUnsafeEval = true;
|
|
484
485
|
}
|
|
485
486
|
var runtimeConfig = readRuntimeConfig();
|
|
486
487
|
var configUnsafeMode = runtimeConfig.lockUnsafeEval ? "locked-off" : runtimeConfig.unsafeEvalMode;
|
|
@@ -558,7 +559,6 @@ var RE_STRING_DOUBLE = /^"[^"]*"$/;
|
|
|
558
559
|
var RE_NUMBER = /^-?\d+(\.\d+)?$/;
|
|
559
560
|
var RE_IDENTIFIER = /^[a-zA-Z_$]\w*$/;
|
|
560
561
|
var RE_DOT_ACCESS = /^(\w+)\.(\w+)$/;
|
|
561
|
-
var RE_DEEP_DOT = /^(\w+)\.(\w+)\.(\w+)(?:\.(\w+))?$/;
|
|
562
562
|
var RE_BRACKET = /^(\w+)\[(\d+|'[^']*'|"[^"]*")\]$/;
|
|
563
563
|
var RE_TERNARY = /^(.+?)\s*\?\s*(.+?)\s*:\s*(.+)$/;
|
|
564
564
|
var RE_NULLISH = /^(.+?)\s*\?\?\s*(.+)$/;
|
|
@@ -569,7 +569,6 @@ var RE_MUL = /^(.+?)\s*([*/%])\s*(.+)$/;
|
|
|
569
569
|
var RE_ADD = /^(.+?)\s*([+-])\s*(.+)$/;
|
|
570
570
|
var RE_TEMPLATE_LIT = /^`([^`]*)`$/;
|
|
571
571
|
var RE_TEMPLATE_INTERP = /\$\{([^}]+)\}/g;
|
|
572
|
-
var RE_METHOD_CALL = /^(\w+)\.(\w+)\((.*)\)$/;
|
|
573
572
|
var RE_GROUP_METHOD_CALL = /^\((.+)\)\.(\w+)\((.*)\)$/;
|
|
574
573
|
var RE_STRIP_BRACES = /^\{|\}$/g;
|
|
575
574
|
var RE_DIGIT_ONLY = /^\d+$/;
|
|
@@ -584,6 +583,7 @@ var RE_COMPUTED = /^(\w+)\s*=\s*(.+)$/;
|
|
|
584
583
|
var RE_FETCH = /^(.+?)(?:→|->)\s*(\S+)(.*)$/;
|
|
585
584
|
var RE_FETCH_METHOD = /^(GET|POST|PUT|PATCH|DELETE)\s+(.+)$/i;
|
|
586
585
|
var RE_STRIP_ITEM_BRACES = /^\{item\.?|\}$/g;
|
|
586
|
+
var RE_EVENT_REF = /\bevent\b|\$event\b/;
|
|
587
587
|
var RE_REFETCH_CALL = /^\$refetch\(\s*['"]([^'"]+)['"]\s*\)$/;
|
|
588
588
|
var TRANSITION_STATE_SYM = /* @__PURE__ */ Symbol.for("forma-transition-state");
|
|
589
589
|
var EXPRESSION_CACHE_MAX = 2048;
|
|
@@ -598,7 +598,40 @@ function cacheExpression(key, factory) {
|
|
|
598
598
|
var scopeExpressionCache = /* @__PURE__ */ new WeakMap();
|
|
599
599
|
var scopeHandlerCache = /* @__PURE__ */ new WeakMap();
|
|
600
600
|
var compiledTemplateCache = /* @__PURE__ */ new Map();
|
|
601
|
-
var UNSAFE_METHOD_NAMES = /* @__PURE__ */ new Set([
|
|
601
|
+
var UNSAFE_METHOD_NAMES = /* @__PURE__ */ new Set([
|
|
602
|
+
"constructor",
|
|
603
|
+
"__proto__",
|
|
604
|
+
"prototype",
|
|
605
|
+
"__defineGetter__",
|
|
606
|
+
"__defineSetter__",
|
|
607
|
+
"__lookupGetter__",
|
|
608
|
+
"__lookupSetter__",
|
|
609
|
+
"eval"
|
|
610
|
+
]);
|
|
611
|
+
var BLOCKED_METHOD_REGEXES = (() => {
|
|
612
|
+
const result = [];
|
|
613
|
+
for (const name of UNSAFE_METHOD_NAMES) {
|
|
614
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
615
|
+
result.push({
|
|
616
|
+
name,
|
|
617
|
+
// Match as property access (.name) or bare identifier at start
|
|
618
|
+
dotRe: new RegExp(`(?:^|\\.)${escaped}(?:\\s*\\(|\\s*$|[^\\w$])`, "m"),
|
|
619
|
+
// Match bracket access with single quotes, double quotes, or backticks
|
|
620
|
+
bracketRe: new RegExp(`\\[\\s*(?:'${escaped}'|"${escaped}"|\`` + escaped + `\`)\\s*\\]`)
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
return result;
|
|
624
|
+
})();
|
|
625
|
+
function findBlockedMethod(expr) {
|
|
626
|
+
let cleaned = expr.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
627
|
+
cleaned = cleaned.replace(/\/\/[^\n]*/g, "");
|
|
628
|
+
cleaned = cleaned.replace(/\s*\.\s*/g, ".");
|
|
629
|
+
for (const { name, dotRe, bracketRe } of BLOCKED_METHOD_REGEXES) {
|
|
630
|
+
if (dotRe.test(cleaned)) return name;
|
|
631
|
+
if (bracketRe.test(cleaned)) return name;
|
|
632
|
+
}
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
602
635
|
var TEXT_BINDING_SYM = /* @__PURE__ */ Symbol.for("forma-text-binding-cache");
|
|
603
636
|
function toTextValue(value) {
|
|
604
637
|
if (value == null) return "";
|
|
@@ -873,6 +906,7 @@ function consumeStatement(raw) {
|
|
|
873
906
|
function parseIfHandler(expr, scope) {
|
|
874
907
|
const input = expr.trim();
|
|
875
908
|
if (!RE_IF_PREFIX.test(input)) return null;
|
|
909
|
+
if (RE_EVENT_REF.test(input)) return null;
|
|
876
910
|
let idx = 2;
|
|
877
911
|
while (idx < input.length && /\s/.test(input[idx])) idx++;
|
|
878
912
|
if (input[idx] !== "(") return null;
|
|
@@ -897,7 +931,7 @@ function parseIfHandler(expr, scope) {
|
|
|
897
931
|
}
|
|
898
932
|
if (rest.length > 0) return null;
|
|
899
933
|
return (e) => {
|
|
900
|
-
|
|
934
|
+
chunkTSQ7AKFT_cjs.batch(() => {
|
|
901
935
|
if (condExpr()) thenHandler(e);
|
|
902
936
|
else elseHandler?.(e);
|
|
903
937
|
});
|
|
@@ -1274,6 +1308,68 @@ function cloneAttributeTemplates(el, item) {
|
|
|
1274
1308
|
}
|
|
1275
1309
|
}
|
|
1276
1310
|
}
|
|
1311
|
+
function parseChainedAccess(expr, scope) {
|
|
1312
|
+
let pos = 0;
|
|
1313
|
+
const identMatch = expr.match(/^[a-zA-Z_$]\w*/);
|
|
1314
|
+
if (!identMatch) return null;
|
|
1315
|
+
const rootName = identMatch[0];
|
|
1316
|
+
pos = rootName.length;
|
|
1317
|
+
if (pos >= expr.length) return null;
|
|
1318
|
+
if (expr[pos] !== "." && !(expr[pos] === "?" && expr[pos + 1] === ".")) return null;
|
|
1319
|
+
const steps = [];
|
|
1320
|
+
while (pos < expr.length) {
|
|
1321
|
+
let optional = false;
|
|
1322
|
+
if (expr[pos] === "?" && expr[pos + 1] === ".") {
|
|
1323
|
+
optional = true;
|
|
1324
|
+
pos += 2;
|
|
1325
|
+
} else if (expr[pos] === ".") {
|
|
1326
|
+
pos += 1;
|
|
1327
|
+
} else {
|
|
1328
|
+
return null;
|
|
1329
|
+
}
|
|
1330
|
+
const nameMatch = expr.slice(pos).match(/^\w+/);
|
|
1331
|
+
if (!nameMatch) return null;
|
|
1332
|
+
const name = nameMatch[0];
|
|
1333
|
+
pos += name.length;
|
|
1334
|
+
if (UNSAFE_METHOD_NAMES.has(name)) return () => void 0;
|
|
1335
|
+
if (pos < expr.length && expr[pos] === "(") {
|
|
1336
|
+
const balanced = readBalancedSegment(expr, pos, "(", ")");
|
|
1337
|
+
if (!balanced) return null;
|
|
1338
|
+
const argsRaw = balanced.inner.trim();
|
|
1339
|
+
const argFns = [];
|
|
1340
|
+
for (const arg of splitCallArgs(argsRaw)) {
|
|
1341
|
+
const parsed = parseExpression(arg, scope);
|
|
1342
|
+
if (!parsed) return null;
|
|
1343
|
+
argFns.push(parsed);
|
|
1344
|
+
}
|
|
1345
|
+
steps.push({ type: "call", name, optional, argFns });
|
|
1346
|
+
pos = balanced.end + 1;
|
|
1347
|
+
} else {
|
|
1348
|
+
steps.push({ type: "prop", name, optional });
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
if (pos !== expr.length) return null;
|
|
1352
|
+
if (steps.length === 0) return null;
|
|
1353
|
+
const rootExpr = rootName === "Math" ? (() => Math) : (() => scope.getters[rootName]?.());
|
|
1354
|
+
return () => {
|
|
1355
|
+
let val = rootExpr();
|
|
1356
|
+
for (const step of steps) {
|
|
1357
|
+
if (val == null) {
|
|
1358
|
+
if (step.optional) return void 0;
|
|
1359
|
+
return void 0;
|
|
1360
|
+
}
|
|
1361
|
+
if (step.type === "prop") {
|
|
1362
|
+
val = val[step.name];
|
|
1363
|
+
} else {
|
|
1364
|
+
const method = val[step.name];
|
|
1365
|
+
if (typeof method !== "function") return void 0;
|
|
1366
|
+
const args = step.argFns.map((fn) => fn());
|
|
1367
|
+
val = method.apply(val, args);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
return val;
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1277
1373
|
function parseExpression(expr, scope) {
|
|
1278
1374
|
const cachedFactory = expressionCache.get(expr);
|
|
1279
1375
|
if (cachedFactory) return cachedFactory(scope);
|
|
@@ -1324,24 +1420,9 @@ function parseExpressionUncached(expr, scope) {
|
|
|
1324
1420
|
if (RE_IDENTIFIER.test(expr)) {
|
|
1325
1421
|
return () => scope.getters[expr]?.();
|
|
1326
1422
|
}
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
return () => {
|
|
1331
|
-
const obj = scope.getters[p1]?.();
|
|
1332
|
-
return obj?.[p2];
|
|
1333
|
-
};
|
|
1334
|
-
}
|
|
1335
|
-
const deepDotMatch = expr.match(RE_DEEP_DOT);
|
|
1336
|
-
if (deepDotMatch) {
|
|
1337
|
-
const p1 = deepDotMatch[1], p2 = deepDotMatch[2], p3 = deepDotMatch[3], p4 = deepDotMatch[4];
|
|
1338
|
-
return () => {
|
|
1339
|
-
let val = scope.getters[p1]?.();
|
|
1340
|
-
val = val?.[p2];
|
|
1341
|
-
val = val?.[p3];
|
|
1342
|
-
if (p4) val = val?.[p4];
|
|
1343
|
-
return val;
|
|
1344
|
-
};
|
|
1423
|
+
{
|
|
1424
|
+
const chainResult = parseChainedAccess(expr, scope);
|
|
1425
|
+
if (chainResult) return chainResult;
|
|
1345
1426
|
}
|
|
1346
1427
|
const groupedCallMatch = expr.match(RE_GROUP_METHOD_CALL);
|
|
1347
1428
|
if (groupedCallMatch) {
|
|
@@ -1365,28 +1446,6 @@ function parseExpressionUncached(expr, scope) {
|
|
|
1365
1446
|
return method.apply(base, args);
|
|
1366
1447
|
};
|
|
1367
1448
|
}
|
|
1368
|
-
const callMatch = expr.match(RE_METHOD_CALL);
|
|
1369
|
-
if (callMatch) {
|
|
1370
|
-
const baseName = callMatch[1];
|
|
1371
|
-
const methodName = callMatch[2];
|
|
1372
|
-
const argsRaw = callMatch[3].trim();
|
|
1373
|
-
if (UNSAFE_METHOD_NAMES.has(methodName)) return () => void 0;
|
|
1374
|
-
const baseExpr = baseName === "Math" ? (() => Math) : parseExpression(baseName, scope);
|
|
1375
|
-
if (!baseExpr) return null;
|
|
1376
|
-
const argFns = [];
|
|
1377
|
-
for (const arg of splitCallArgs(argsRaw)) {
|
|
1378
|
-
const parsed = parseExpression(arg, scope);
|
|
1379
|
-
if (!parsed) return null;
|
|
1380
|
-
argFns.push(parsed);
|
|
1381
|
-
}
|
|
1382
|
-
return () => {
|
|
1383
|
-
const base = baseExpr();
|
|
1384
|
-
const method = base?.[methodName];
|
|
1385
|
-
if (typeof method !== "function") return void 0;
|
|
1386
|
-
const args = argFns.map((fn) => fn());
|
|
1387
|
-
return method.apply(base, args);
|
|
1388
|
-
};
|
|
1389
|
-
}
|
|
1390
1449
|
if (expr.startsWith("!")) {
|
|
1391
1450
|
const inner = parseExpression(expr.slice(1).trim(), scope);
|
|
1392
1451
|
if (inner) return () => !inner();
|
|
@@ -1405,6 +1464,29 @@ function parseExpressionUncached(expr, scope) {
|
|
|
1405
1464
|
return () => objExpr()?.[key];
|
|
1406
1465
|
}
|
|
1407
1466
|
}
|
|
1467
|
+
if (expr.startsWith("[")) {
|
|
1468
|
+
const balanced = readBalancedSegment(expr, 0, "[", "]");
|
|
1469
|
+
if (balanced && balanced.end === expr.length - 1) {
|
|
1470
|
+
const inner = balanced.inner.trim();
|
|
1471
|
+
if (inner === "") {
|
|
1472
|
+
return () => [];
|
|
1473
|
+
}
|
|
1474
|
+
const elements = splitCallArgs(inner);
|
|
1475
|
+
const elementFns = [];
|
|
1476
|
+
let allParsed = true;
|
|
1477
|
+
for (const el of elements) {
|
|
1478
|
+
const parsed = parseExpression(el.trim(), scope);
|
|
1479
|
+
if (!parsed) {
|
|
1480
|
+
allParsed = false;
|
|
1481
|
+
break;
|
|
1482
|
+
}
|
|
1483
|
+
elementFns.push(parsed);
|
|
1484
|
+
}
|
|
1485
|
+
if (allParsed) {
|
|
1486
|
+
return () => elementFns.map((fn) => fn());
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1408
1490
|
const ternaryMatch = expr.match(RE_TERNARY);
|
|
1409
1491
|
if (ternaryMatch) {
|
|
1410
1492
|
const cond = parseExpression(ternaryMatch[1].trim(), scope);
|
|
@@ -1534,6 +1616,15 @@ function getScopeCache(cache, scope) {
|
|
|
1534
1616
|
}
|
|
1535
1617
|
return scoped;
|
|
1536
1618
|
}
|
|
1619
|
+
function cspExpressionHint(expr) {
|
|
1620
|
+
if (expr.includes("...")) {
|
|
1621
|
+
return `Unsupported expression in CSP-safe mode: spread syntax detected. Use .concat() instead, or enable unsafe-eval via setUnsafeEval(true).`;
|
|
1622
|
+
}
|
|
1623
|
+
if (expr.includes("=>")) {
|
|
1624
|
+
return `Unsupported expression in CSP-safe mode: arrow function detected. Extract logic to a data-computed attribute, or enable unsafe-eval via setUnsafeEval(true).`;
|
|
1625
|
+
}
|
|
1626
|
+
return `Unsupported expression in CSP-safe mode. Simplify the expression or enable unsafe-eval via setUnsafeEval(true).`;
|
|
1627
|
+
}
|
|
1537
1628
|
function buildEvaluator(expr, scope) {
|
|
1538
1629
|
const cleaned = expr.replace(RE_STRIP_BRACES, "").trim();
|
|
1539
1630
|
const cache = getScopeCache(scopeExpressionCache, scope);
|
|
@@ -1546,11 +1637,17 @@ function buildEvaluator(expr, scope) {
|
|
|
1546
1637
|
}
|
|
1547
1638
|
if (!_allowUnsafeEval) {
|
|
1548
1639
|
dbg("buildEvaluator: blocked unsafe eval fallback for expression:", cleaned);
|
|
1549
|
-
reportDiagnostic("expression-unsupported", cleaned,
|
|
1640
|
+
reportDiagnostic("expression-unsupported", cleaned, cspExpressionHint(cleaned));
|
|
1550
1641
|
const blocked = () => void 0;
|
|
1551
1642
|
cache.set(cleaned, blocked);
|
|
1552
1643
|
return blocked;
|
|
1553
1644
|
}
|
|
1645
|
+
const blockedMethod = findBlockedMethod(cleaned);
|
|
1646
|
+
if (blockedMethod) {
|
|
1647
|
+
const msg = `Blocked unsafe method "${blockedMethod}" in expression`;
|
|
1648
|
+
reportDiagnostic("expression-unsupported", cleaned, msg);
|
|
1649
|
+
throw new Error(`[FormaJS] ${msg}: ${cleaned}`);
|
|
1650
|
+
}
|
|
1554
1651
|
try {
|
|
1555
1652
|
const fn = new Function("__scope", `with(__scope) { return (${cleaned}); }`);
|
|
1556
1653
|
const proxy = new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
@@ -1566,7 +1663,7 @@ function buildEvaluator(expr, scope) {
|
|
|
1566
1663
|
cache.set(cleaned, unsafe);
|
|
1567
1664
|
return unsafe;
|
|
1568
1665
|
} catch {
|
|
1569
|
-
reportDiagnostic("expression-unsupported", cleaned, "Expression
|
|
1666
|
+
reportDiagnostic("expression-unsupported", cleaned, "Expression too complex for CSP-safe mode. Enable unsafe-eval via FormaRuntime.unsafeEval = true, or use the standard (non-hardened) build.");
|
|
1570
1667
|
const failed = () => void 0;
|
|
1571
1668
|
cache.set(cleaned, failed);
|
|
1572
1669
|
return failed;
|
|
@@ -1582,7 +1679,7 @@ function parseHandler(expr, scope) {
|
|
|
1582
1679
|
const handlers = stmts.map((s) => parseHandler(s, scope));
|
|
1583
1680
|
if (handlers.every((h) => h !== null)) {
|
|
1584
1681
|
return (e) => {
|
|
1585
|
-
|
|
1682
|
+
chunkTSQ7AKFT_cjs.batch(() => {
|
|
1586
1683
|
for (const h of handlers) h(e);
|
|
1587
1684
|
});
|
|
1588
1685
|
};
|
|
@@ -1595,7 +1692,7 @@ function parseHandler(expr, scope) {
|
|
|
1595
1692
|
const name = incrMatch[1];
|
|
1596
1693
|
const op = incrMatch[2];
|
|
1597
1694
|
return () => {
|
|
1598
|
-
|
|
1695
|
+
chunkTSQ7AKFT_cjs.batch(() => {
|
|
1599
1696
|
const val = scope.getters[name]?.() ?? 0;
|
|
1600
1697
|
scope.setters[name]?.(op === "++" ? val + 1 : val - 1);
|
|
1601
1698
|
});
|
|
@@ -1606,7 +1703,7 @@ function parseHandler(expr, scope) {
|
|
|
1606
1703
|
const op = preIncrMatch[1];
|
|
1607
1704
|
const name = preIncrMatch[2];
|
|
1608
1705
|
return () => {
|
|
1609
|
-
|
|
1706
|
+
chunkTSQ7AKFT_cjs.batch(() => {
|
|
1610
1707
|
const val = scope.getters[name]?.() ?? 0;
|
|
1611
1708
|
scope.setters[name]?.(op === "++" ? val + 1 : val - 1);
|
|
1612
1709
|
});
|
|
@@ -1616,7 +1713,7 @@ function parseHandler(expr, scope) {
|
|
|
1616
1713
|
if (toggleMatch && toggleMatch[1] === toggleMatch[2]) {
|
|
1617
1714
|
const name = toggleMatch[1];
|
|
1618
1715
|
return () => {
|
|
1619
|
-
|
|
1716
|
+
chunkTSQ7AKFT_cjs.batch(() => {
|
|
1620
1717
|
scope.setters[name]?.(!scope.getters[name]?.());
|
|
1621
1718
|
});
|
|
1622
1719
|
};
|
|
@@ -1628,7 +1725,7 @@ function parseHandler(expr, scope) {
|
|
|
1628
1725
|
if (valExpr) {
|
|
1629
1726
|
if (_debug) dbg(`parseHandler: assignment "${name} = ..." \u2014 setter exists:`, !!scope.setters[name], ", getter exists:", !!scope.getters[name]);
|
|
1630
1727
|
return () => {
|
|
1631
|
-
|
|
1728
|
+
chunkTSQ7AKFT_cjs.batch(() => {
|
|
1632
1729
|
const val = valExpr();
|
|
1633
1730
|
if (_debug) dbg(`SETTER: ${name} = ${val} (was: ${scope.getters[name]?.()})`);
|
|
1634
1731
|
scope.setters[name]?.(val);
|
|
@@ -1643,7 +1740,7 @@ function parseHandler(expr, scope) {
|
|
|
1643
1740
|
const valExpr = parseExpression(compoundMatch[3].trim(), scope);
|
|
1644
1741
|
if (valExpr) {
|
|
1645
1742
|
return () => {
|
|
1646
|
-
|
|
1743
|
+
chunkTSQ7AKFT_cjs.batch(() => {
|
|
1647
1744
|
const current = scope.getters[name]?.() ?? 0;
|
|
1648
1745
|
const val = valExpr();
|
|
1649
1746
|
switch (op) {
|
|
@@ -1672,7 +1769,13 @@ function parseHandler(expr, scope) {
|
|
|
1672
1769
|
return null;
|
|
1673
1770
|
}
|
|
1674
1771
|
function buildHandler(expr, scope) {
|
|
1675
|
-
|
|
1772
|
+
let cleaned = expr.trim();
|
|
1773
|
+
if (cleaned.startsWith("{")) {
|
|
1774
|
+
const seg = readBalancedSegment(cleaned, 0, "{", "}");
|
|
1775
|
+
if (seg && seg.end === cleaned.length - 1) {
|
|
1776
|
+
cleaned = seg.inner.trim();
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1676
1779
|
const cache = getScopeCache(scopeHandlerCache, scope);
|
|
1677
1780
|
const cached = cache.get(cleaned);
|
|
1678
1781
|
if (cached) return cached;
|
|
@@ -1684,7 +1787,7 @@ function buildHandler(expr, scope) {
|
|
|
1684
1787
|
}
|
|
1685
1788
|
if (!_allowUnsafeEval) {
|
|
1686
1789
|
dbg("buildHandler: blocked unsafe eval fallback for expression:", cleaned);
|
|
1687
|
-
reportDiagnostic("handler-unsupported", cleaned,
|
|
1790
|
+
reportDiagnostic("handler-unsupported", cleaned, cspExpressionHint(cleaned));
|
|
1688
1791
|
const result = {
|
|
1689
1792
|
handler: () => {
|
|
1690
1793
|
},
|
|
@@ -1693,6 +1796,12 @@ function buildHandler(expr, scope) {
|
|
|
1693
1796
|
cache.set(cleaned, result);
|
|
1694
1797
|
return result;
|
|
1695
1798
|
}
|
|
1799
|
+
const blockedMethod = findBlockedMethod(cleaned);
|
|
1800
|
+
if (blockedMethod) {
|
|
1801
|
+
const msg = `Blocked unsafe method "${blockedMethod}" in handler`;
|
|
1802
|
+
reportDiagnostic("handler-unsupported", cleaned, msg);
|
|
1803
|
+
throw new Error(`[FormaJS] ${msg}: ${cleaned}`);
|
|
1804
|
+
}
|
|
1696
1805
|
try {
|
|
1697
1806
|
const fn = new Function("__scope", "$event", "event", `with(__scope) { ${cleaned} }`);
|
|
1698
1807
|
const proxy = new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
@@ -1711,7 +1820,7 @@ function buildHandler(expr, scope) {
|
|
|
1711
1820
|
}
|
|
1712
1821
|
});
|
|
1713
1822
|
const unsafeHandler = (e) => {
|
|
1714
|
-
|
|
1823
|
+
chunkTSQ7AKFT_cjs.batch(() => fn(proxy, e, e));
|
|
1715
1824
|
};
|
|
1716
1825
|
const result = {
|
|
1717
1826
|
handler: unsafeHandler,
|
|
@@ -1720,7 +1829,7 @@ function buildHandler(expr, scope) {
|
|
|
1720
1829
|
cache.set(cleaned, result);
|
|
1721
1830
|
return result;
|
|
1722
1831
|
} catch {
|
|
1723
|
-
reportDiagnostic("handler-unsupported", cleaned, "
|
|
1832
|
+
reportDiagnostic("handler-unsupported", cleaned, "Expression too complex for CSP-safe mode. Enable unsafe-eval via FormaRuntime.unsafeEval = true, or use the standard (non-hardened) build.");
|
|
1724
1833
|
const result = {
|
|
1725
1834
|
handler: () => {
|
|
1726
1835
|
},
|
|
@@ -1730,16 +1839,22 @@ function buildHandler(expr, scope) {
|
|
|
1730
1839
|
return result;
|
|
1731
1840
|
}
|
|
1732
1841
|
}
|
|
1842
|
+
var FORBIDDEN_STATE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1733
1843
|
function parseState(raw) {
|
|
1844
|
+
let parsed;
|
|
1734
1845
|
try {
|
|
1735
|
-
|
|
1846
|
+
parsed = JSON.parse(raw);
|
|
1736
1847
|
} catch {
|
|
1737
1848
|
try {
|
|
1738
|
-
|
|
1849
|
+
parsed = JSON.parse(raw.replace(RE_UNQUOTED_KEYS, '"$1":'));
|
|
1739
1850
|
} catch {
|
|
1740
1851
|
return {};
|
|
1741
1852
|
}
|
|
1742
1853
|
}
|
|
1854
|
+
for (const key of FORBIDDEN_STATE_KEYS) {
|
|
1855
|
+
if (key in parsed) delete parsed[key];
|
|
1856
|
+
}
|
|
1857
|
+
return parsed;
|
|
1743
1858
|
}
|
|
1744
1859
|
function initScope(stateEl) {
|
|
1745
1860
|
const raw = stateEl.getAttribute("data-forma-state") ?? "{}";
|
|
@@ -1787,7 +1902,7 @@ function bindElement(el, scope, disposers) {
|
|
|
1787
1902
|
const textExpr = !known || known.has("data-text") ? el.getAttribute("data-text") : null;
|
|
1788
1903
|
if (textExpr) {
|
|
1789
1904
|
const evaluate = buildEvaluator(textExpr, scope);
|
|
1790
|
-
const dispose =
|
|
1905
|
+
const dispose = chunkTSQ7AKFT_cjs.internalEffect(() => {
|
|
1791
1906
|
setElementTextFast(el, toTextValue(evaluate()));
|
|
1792
1907
|
});
|
|
1793
1908
|
disposers.push(dispose);
|
|
@@ -1802,7 +1917,7 @@ function bindElement(el, scope, disposers) {
|
|
|
1802
1917
|
dbg(`bindElement: data-show="${showExpr}" on <${tag}${cls}>`);
|
|
1803
1918
|
}
|
|
1804
1919
|
let initialized = false;
|
|
1805
|
-
const dispose =
|
|
1920
|
+
const dispose = chunkTSQ7AKFT_cjs.internalEffect(() => {
|
|
1806
1921
|
const visible = !!evaluate();
|
|
1807
1922
|
if (_debug) dbg(`data-show effect: "${showExpr}" \u2192 ${visible}`);
|
|
1808
1923
|
applyShowVisibility(el, visible, transition, !initialized);
|
|
@@ -1821,7 +1936,7 @@ function bindElement(el, scope, disposers) {
|
|
|
1821
1936
|
const parent = el.parentNode;
|
|
1822
1937
|
let inserted = true;
|
|
1823
1938
|
let initialized = false;
|
|
1824
|
-
const dispose =
|
|
1939
|
+
const dispose = chunkTSQ7AKFT_cjs.internalEffect(() => {
|
|
1825
1940
|
const show = !!evaluate();
|
|
1826
1941
|
if (show && !inserted) {
|
|
1827
1942
|
clearTransitionState(el);
|
|
@@ -1860,7 +1975,7 @@ function bindElement(el, scope, disposers) {
|
|
|
1860
1975
|
const setter = scope.setters[prop];
|
|
1861
1976
|
if (getter && setter) {
|
|
1862
1977
|
const input = el;
|
|
1863
|
-
const dispose =
|
|
1978
|
+
const dispose = chunkTSQ7AKFT_cjs.internalEffect(() => {
|
|
1864
1979
|
const val = getter();
|
|
1865
1980
|
if (input.type === "checkbox") {
|
|
1866
1981
|
input.checked = !!val;
|
|
@@ -1924,14 +2039,14 @@ function bindElement(el, scope, disposers) {
|
|
|
1924
2039
|
} else if (name.startsWith("data-class:")) {
|
|
1925
2040
|
const cls = name.slice(11);
|
|
1926
2041
|
const evaluate = buildEvaluator(attr.value, scope);
|
|
1927
|
-
const dispose =
|
|
2042
|
+
const dispose = chunkTSQ7AKFT_cjs.internalEffect(() => {
|
|
1928
2043
|
el.classList.toggle(cls, !!evaluate());
|
|
1929
2044
|
});
|
|
1930
2045
|
disposers.push(dispose);
|
|
1931
2046
|
} else if (name.startsWith("data-bind:")) {
|
|
1932
2047
|
const attrName = name.slice(10);
|
|
1933
2048
|
const evaluate = buildEvaluator(attr.value, scope);
|
|
1934
|
-
const dispose =
|
|
2049
|
+
const dispose = chunkTSQ7AKFT_cjs.internalEffect(() => {
|
|
1935
2050
|
const val = evaluate();
|
|
1936
2051
|
if (val == null || val === false) {
|
|
1937
2052
|
el.removeAttribute(attrName);
|
|
@@ -1954,7 +2069,7 @@ function bindElement(el, scope, disposers) {
|
|
|
1954
2069
|
if (saved !== null) setter(JSON.parse(saved));
|
|
1955
2070
|
} catch {
|
|
1956
2071
|
}
|
|
1957
|
-
const dispose =
|
|
2072
|
+
const dispose = chunkTSQ7AKFT_cjs.internalEffect(() => {
|
|
1958
2073
|
try {
|
|
1959
2074
|
localStorage.setItem(key, JSON.stringify(getter()));
|
|
1960
2075
|
} catch {
|
|
@@ -2041,7 +2156,7 @@ function bindElement(el, scope, disposers) {
|
|
|
2041
2156
|
});
|
|
2042
2157
|
}
|
|
2043
2158
|
} : void 0;
|
|
2044
|
-
const dispose =
|
|
2159
|
+
const dispose = chunkTSQ7AKFT_cjs.internalEffect(() => {
|
|
2045
2160
|
const rawItems = evaluate();
|
|
2046
2161
|
if (!Array.isArray(rawItems)) {
|
|
2047
2162
|
for (const n of oldNodes) {
|
|
@@ -2062,7 +2177,7 @@ function bindElement(el, scope, disposers) {
|
|
|
2062
2177
|
}
|
|
2063
2178
|
const prevNodes = new Set(oldNodes);
|
|
2064
2179
|
if (keyProp) {
|
|
2065
|
-
const result =
|
|
2180
|
+
const result = chunkTSQ7AKFT_cjs.reconcileList(
|
|
2066
2181
|
el,
|
|
2067
2182
|
oldItems,
|
|
2068
2183
|
rawItems,
|
|
@@ -2092,7 +2207,7 @@ function bindElement(el, scope, disposers) {
|
|
|
2092
2207
|
} else {
|
|
2093
2208
|
const wrapped = rawItems.map((item, i) => ({ __idx: i, __item: item }));
|
|
2094
2209
|
const oldWrapped = oldItems;
|
|
2095
|
-
const result =
|
|
2210
|
+
const result = chunkTSQ7AKFT_cjs.reconcileList(
|
|
2096
2211
|
el,
|
|
2097
2212
|
oldWrapped,
|
|
2098
2213
|
wrapped,
|
|
@@ -2409,6 +2524,7 @@ function setUnsafeEvalMode(mode) {
|
|
|
2409
2524
|
_unsafeEvalMode = mode;
|
|
2410
2525
|
if (mode === "locked-off") _allowUnsafeEval = false;
|
|
2411
2526
|
if (mode === "locked-on") _allowUnsafeEval = true;
|
|
2527
|
+
if (mode === "mutable") _allowUnsafeEval = true;
|
|
2412
2528
|
scopeExpressionCache = /* @__PURE__ */ new WeakMap();
|
|
2413
2529
|
scopeHandlerCache = /* @__PURE__ */ new WeakMap();
|
|
2414
2530
|
}
|
|
@@ -2461,7 +2577,7 @@ function getScopes() {
|
|
|
2461
2577
|
function setScopeValue(element, key, value) {
|
|
2462
2578
|
const scope = element.__formaScope;
|
|
2463
2579
|
if (!scope?.setters[key]) return;
|
|
2464
|
-
|
|
2580
|
+
chunkTSQ7AKFT_cjs.batch(() => {
|
|
2465
2581
|
scope.setters[key](value);
|
|
2466
2582
|
});
|
|
2467
2583
|
}
|
|
@@ -2470,7 +2586,7 @@ function resetScope(element) {
|
|
|
2470
2586
|
const initialJSON = element.__formaInitialState;
|
|
2471
2587
|
if (!scope || !initialJSON) return;
|
|
2472
2588
|
const initial = parseState(initialJSON);
|
|
2473
|
-
|
|
2589
|
+
chunkTSQ7AKFT_cjs.batch(() => {
|
|
2474
2590
|
for (const [key, val] of Object.entries(initial)) {
|
|
2475
2591
|
scope.setters[key]?.(val);
|
|
2476
2592
|
}
|
|
@@ -2500,7 +2616,7 @@ function getReconciler() {
|
|
|
2500
2616
|
}
|
|
2501
2617
|
}
|
|
2502
2618
|
},
|
|
2503
|
-
batch:
|
|
2619
|
+
batch: chunkTSQ7AKFT_cjs.batch
|
|
2504
2620
|
});
|
|
2505
2621
|
}
|
|
2506
2622
|
return _reconciler;
|