@getforma/core 0.3.0 → 0.6.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.
Files changed (46) hide show
  1. package/README.md +201 -13
  2. package/dist/{chunk-KX5WRZH7.js → chunk-DPSAVBCP.js} +3 -4
  3. package/dist/chunk-DPSAVBCP.js.map +1 -0
  4. package/dist/{chunk-FPSLC62A.cjs → chunk-KH3F5NRU.cjs} +2 -4
  5. package/dist/chunk-KH3F5NRU.cjs.map +1 -0
  6. package/dist/{chunk-VKKPX4YU.js → chunk-TKGUHASG.js} +79 -19
  7. package/dist/chunk-TKGUHASG.js.map +1 -0
  8. package/dist/{chunk-VP5EOGM5.cjs → chunk-YSKF3VRA.cjs} +87 -26
  9. package/dist/chunk-YSKF3VRA.cjs.map +1 -0
  10. package/dist/forma-runtime-csp.js +2 -0
  11. package/dist/forma-runtime.js +2 -0
  12. package/dist/formajs-runtime-hardened.global.js +1 -1
  13. package/dist/formajs-runtime-hardened.global.js.map +1 -1
  14. package/dist/formajs-runtime.global.js +1 -1
  15. package/dist/formajs-runtime.global.js.map +1 -1
  16. package/dist/formajs.global.js +1 -1
  17. package/dist/formajs.global.js.map +1 -1
  18. package/dist/index.cjs +154 -96
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +83 -35
  21. package/dist/index.d.ts +83 -35
  22. package/dist/index.js +99 -40
  23. package/dist/index.js.map +1 -1
  24. package/dist/runtime-hardened.cjs +224 -60
  25. package/dist/runtime-hardened.cjs.map +1 -1
  26. package/dist/runtime-hardened.d.cts +86 -0
  27. package/dist/runtime-hardened.d.ts +86 -0
  28. package/dist/runtime-hardened.js +225 -61
  29. package/dist/runtime-hardened.js.map +1 -1
  30. package/dist/runtime.cjs +248 -83
  31. package/dist/runtime.cjs.map +1 -1
  32. package/dist/runtime.js +226 -61
  33. package/dist/runtime.js.map +1 -1
  34. package/dist/ssr/index.cjs +69 -73
  35. package/dist/ssr/index.cjs.map +1 -1
  36. package/dist/ssr/index.d.cts +4 -0
  37. package/dist/ssr/index.d.ts +4 -0
  38. package/dist/ssr/index.js +69 -73
  39. package/dist/ssr/index.js.map +1 -1
  40. package/dist/tc39-compat.cjs +3 -3
  41. package/dist/tc39-compat.js +1 -1
  42. package/package.json +24 -3
  43. package/dist/chunk-FPSLC62A.cjs.map +0 -1
  44. package/dist/chunk-KX5WRZH7.js.map +0 -1
  45. package/dist/chunk-VKKPX4YU.js.map +0 -1
  46. package/dist/chunk-VP5EOGM5.cjs.map +0 -1
@@ -19,7 +19,6 @@ function createSignal(initialValue) {
19
19
  const setter = (v) => applySignalSet(s, v);
20
20
  return [getter, setter];
21
21
  }
22
- var createValueSignal = createSignal;
23
22
  function internalEffect(fn) {
24
23
  const dispose = alienSignals.effect(fn);
25
24
  return dispose;
@@ -755,6 +754,7 @@ if (buildUnsafeEvalMode) {
755
754
  _unsafeEvalMode = buildUnsafeEvalMode;
756
755
  if (_unsafeEvalMode === "locked-off") _allowUnsafeEval = false;
757
756
  if (_unsafeEvalMode === "locked-on") _allowUnsafeEval = true;
757
+ if (_unsafeEvalMode === "mutable") _allowUnsafeEval = true;
758
758
  }
759
759
  var runtimeConfig = readRuntimeConfig();
760
760
  var configUnsafeMode = runtimeConfig.lockUnsafeEval ? "locked-off" : runtimeConfig.unsafeEvalMode;
@@ -832,7 +832,6 @@ var RE_STRING_DOUBLE = /^"[^"]*"$/;
832
832
  var RE_NUMBER = /^-?\d+(\.\d+)?$/;
833
833
  var RE_IDENTIFIER = /^[a-zA-Z_$]\w*$/;
834
834
  var RE_DOT_ACCESS = /^(\w+)\.(\w+)$/;
835
- var RE_DEEP_DOT = /^(\w+)\.(\w+)\.(\w+)(?:\.(\w+))?$/;
836
835
  var RE_BRACKET = /^(\w+)\[(\d+|'[^']*'|"[^"]*")\]$/;
837
836
  var RE_TERNARY = /^(.+?)\s*\?\s*(.+?)\s*:\s*(.+)$/;
838
837
  var RE_NULLISH = /^(.+?)\s*\?\?\s*(.+)$/;
@@ -843,7 +842,6 @@ var RE_MUL = /^(.+?)\s*([*/%])\s*(.+)$/;
843
842
  var RE_ADD = /^(.+?)\s*([+-])\s*(.+)$/;
844
843
  var RE_TEMPLATE_LIT = /^`([^`]*)`$/;
845
844
  var RE_TEMPLATE_INTERP = /\$\{([^}]+)\}/g;
846
- var RE_METHOD_CALL = /^(\w+)\.(\w+)\((.*)\)$/;
847
845
  var RE_GROUP_METHOD_CALL = /^\((.+)\)\.(\w+)\((.*)\)$/;
848
846
  var RE_STRIP_BRACES = /^\{|\}$/g;
849
847
  var RE_DIGIT_ONLY = /^\d+$/;
@@ -853,11 +851,11 @@ var RE_TOGGLE = /^(\w+)\s*=\s*!(\w+)$/;
853
851
  var RE_ASSIGN = /^(\w+)\s*=\s*(.+)$/;
854
852
  var RE_COMPOUND = /^(\w+)\s*(\+=|-=|\*=|\/=)\s*(.+)$/;
855
853
  var RE_IF_PREFIX = /^if\b/;
856
- var RE_UNQUOTED_KEYS = /(\w+)\s*:/g;
857
854
  var RE_COMPUTED = /^(\w+)\s*=\s*(.+)$/;
858
855
  var RE_FETCH = /^(.+?)(?:→|->)\s*(\S+)(.*)$/;
859
856
  var RE_FETCH_METHOD = /^(GET|POST|PUT|PATCH|DELETE)\s+(.+)$/i;
860
857
  var RE_STRIP_ITEM_BRACES = /^\{item\.?|\}$/g;
858
+ var RE_EVENT_REF = /\bevent\s*[.([]|\$event\b/;
861
859
  var RE_REFETCH_CALL = /^\$refetch\(\s*['"]([^'"]+)['"]\s*\)$/;
862
860
  var TRANSITION_STATE_SYM = /* @__PURE__ */ Symbol.for("forma-transition-state");
863
861
  var EXPRESSION_CACHE_MAX = 2048;
@@ -872,7 +870,77 @@ function cacheExpression(key, factory) {
872
870
  var scopeExpressionCache = /* @__PURE__ */ new WeakMap();
873
871
  var scopeHandlerCache = /* @__PURE__ */ new WeakMap();
874
872
  var compiledTemplateCache = /* @__PURE__ */ new Map();
875
- var UNSAFE_METHOD_NAMES = /* @__PURE__ */ new Set(["constructor", "__proto__", "prototype"]);
873
+ var COMPILED_TEMPLATE_CACHE_MAX = 2048;
874
+ function cacheCompiledTemplate(key, template) {
875
+ if (compiledTemplateCache.size >= COMPILED_TEMPLATE_CACHE_MAX) {
876
+ const first = compiledTemplateCache.keys().next().value;
877
+ if (first !== void 0) compiledTemplateCache.delete(first);
878
+ }
879
+ compiledTemplateCache.set(key, template);
880
+ }
881
+ var UNSAFE_METHOD_NAMES = /* @__PURE__ */ new Set([
882
+ "constructor",
883
+ "__proto__",
884
+ "prototype",
885
+ "__defineGetter__",
886
+ "__defineSetter__",
887
+ "__lookupGetter__",
888
+ "__lookupSetter__",
889
+ "eval",
890
+ "Function"
891
+ ]);
892
+ var BLOCKED_METHOD_REGEXES = (() => {
893
+ const result = [];
894
+ for (const name of UNSAFE_METHOD_NAMES) {
895
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
896
+ result.push({
897
+ name,
898
+ // Match as property access (.name) or bare identifier at start
899
+ dotRe: new RegExp(`(?:^|\\.)${escaped}(?:\\s*\\(|\\s*$|[^\\w$])`, "m"),
900
+ // Match bracket access with single quotes, double quotes, or backticks
901
+ bracketRe: new RegExp(`\\[\\s*(?:'${escaped}'|"${escaped}"|\`` + escaped + `\`)\\s*\\]`)
902
+ });
903
+ }
904
+ return result;
905
+ })();
906
+ function findBlockedMethod(expr) {
907
+ let cleaned = expr.replace(/\/\*[\s\S]*?\*\//g, "");
908
+ cleaned = cleaned.replace(/\/\/[^\n]*/g, "");
909
+ cleaned = cleaned.replace(/\s*\.\s*/g, ".");
910
+ for (const { name, dotRe, bracketRe } of BLOCKED_METHOD_REGEXES) {
911
+ if (dotRe.test(cleaned)) return name;
912
+ if (bracketRe.test(cleaned)) return name;
913
+ }
914
+ if (cleaned.includes("[")) {
915
+ const bracketContents = extractBracketContents(cleaned);
916
+ for (const content of bracketContents) {
917
+ if (!content.includes("+")) continue;
918
+ const fragments = content.match(/['"`]([^'"`]*?)['"`]/g);
919
+ if (!fragments) continue;
920
+ const joined = fragments.map((f) => f.slice(1, -1)).join("");
921
+ if (UNSAFE_METHOD_NAMES.has(joined)) return joined;
922
+ }
923
+ }
924
+ return null;
925
+ }
926
+ function extractBracketContents(expr) {
927
+ const results = [];
928
+ let depth = 0;
929
+ let start = -1;
930
+ for (let i = 0; i < expr.length; i++) {
931
+ if (expr[i] === "[") {
932
+ if (depth === 0) start = i + 1;
933
+ depth++;
934
+ } else if (expr[i] === "]") {
935
+ depth--;
936
+ if (depth === 0 && start >= 0) {
937
+ results.push(expr.slice(start, i));
938
+ start = -1;
939
+ }
940
+ }
941
+ }
942
+ return results;
943
+ }
876
944
  var TEXT_BINDING_SYM = /* @__PURE__ */ Symbol.for("forma-text-binding-cache");
877
945
  function toTextValue(value2) {
878
946
  if (value2 == null) return "";
@@ -1147,6 +1215,7 @@ function consumeStatement(raw) {
1147
1215
  function parseIfHandler(expr, scope) {
1148
1216
  const input = expr.trim();
1149
1217
  if (!RE_IF_PREFIX.test(input)) return null;
1218
+ if (RE_EVENT_REF.test(input)) return null;
1150
1219
  let idx = 2;
1151
1220
  while (idx < input.length && /\s/.test(input[idx])) idx++;
1152
1221
  if (input[idx] !== "(") return null;
@@ -1207,7 +1276,7 @@ function compileTemplate(text) {
1207
1276
  dynamics,
1208
1277
  hasItemRef: dynamics.length > 0
1209
1278
  };
1210
- compiledTemplateCache.set(text, result);
1279
+ cacheCompiledTemplate(text, result);
1211
1280
  return result;
1212
1281
  }
1213
1282
  var templateTexts = /* @__PURE__ */ new WeakMap();
@@ -1548,6 +1617,68 @@ function cloneAttributeTemplates(el, item) {
1548
1617
  }
1549
1618
  }
1550
1619
  }
1620
+ function parseChainedAccess(expr, scope) {
1621
+ let pos = 0;
1622
+ const identMatch = expr.match(/^[a-zA-Z_$]\w*/);
1623
+ if (!identMatch) return null;
1624
+ const rootName = identMatch[0];
1625
+ pos = rootName.length;
1626
+ if (pos >= expr.length) return null;
1627
+ if (expr[pos] !== "." && !(expr[pos] === "?" && expr[pos + 1] === ".")) return null;
1628
+ const steps = [];
1629
+ while (pos < expr.length) {
1630
+ let optional = false;
1631
+ if (expr[pos] === "?" && expr[pos + 1] === ".") {
1632
+ optional = true;
1633
+ pos += 2;
1634
+ } else if (expr[pos] === ".") {
1635
+ pos += 1;
1636
+ } else {
1637
+ return null;
1638
+ }
1639
+ const nameMatch = expr.slice(pos).match(/^\w+/);
1640
+ if (!nameMatch) return null;
1641
+ const name = nameMatch[0];
1642
+ pos += name.length;
1643
+ if (UNSAFE_METHOD_NAMES.has(name)) return () => void 0;
1644
+ if (pos < expr.length && expr[pos] === "(") {
1645
+ const balanced = readBalancedSegment(expr, pos, "(", ")");
1646
+ if (!balanced) return null;
1647
+ const argsRaw = balanced.inner.trim();
1648
+ const argFns = [];
1649
+ for (const arg of splitCallArgs(argsRaw)) {
1650
+ const parsed = parseExpression(arg, scope);
1651
+ if (!parsed) return null;
1652
+ argFns.push(parsed);
1653
+ }
1654
+ steps.push({ type: "call", name, optional, argFns });
1655
+ pos = balanced.end + 1;
1656
+ } else {
1657
+ steps.push({ type: "prop", name, optional });
1658
+ }
1659
+ }
1660
+ if (pos !== expr.length) return null;
1661
+ if (steps.length === 0) return null;
1662
+ const rootExpr = rootName === "Math" ? (() => Math) : (() => scope.getters[rootName]?.());
1663
+ return () => {
1664
+ let val = rootExpr();
1665
+ for (const step of steps) {
1666
+ if (val == null) {
1667
+ if (step.optional) return void 0;
1668
+ return void 0;
1669
+ }
1670
+ if (step.type === "prop") {
1671
+ val = val[step.name];
1672
+ } else {
1673
+ const method = val[step.name];
1674
+ if (typeof method !== "function") return void 0;
1675
+ const args = step.argFns.map((fn) => fn());
1676
+ val = method.apply(val, args);
1677
+ }
1678
+ }
1679
+ return val;
1680
+ };
1681
+ }
1551
1682
  function parseExpression(expr, scope) {
1552
1683
  const cachedFactory = expressionCache.get(expr);
1553
1684
  if (cachedFactory) return cachedFactory(scope);
@@ -1598,24 +1729,9 @@ function parseExpressionUncached(expr, scope) {
1598
1729
  if (RE_IDENTIFIER.test(expr)) {
1599
1730
  return () => scope.getters[expr]?.();
1600
1731
  }
1601
- const dotMatch = expr.match(RE_DOT_ACCESS);
1602
- if (dotMatch) {
1603
- const p1 = dotMatch[1], p2 = dotMatch[2];
1604
- return () => {
1605
- const obj = scope.getters[p1]?.();
1606
- return obj?.[p2];
1607
- };
1608
- }
1609
- const deepDotMatch = expr.match(RE_DEEP_DOT);
1610
- if (deepDotMatch) {
1611
- const p1 = deepDotMatch[1], p2 = deepDotMatch[2], p3 = deepDotMatch[3], p4 = deepDotMatch[4];
1612
- return () => {
1613
- let val = scope.getters[p1]?.();
1614
- val = val?.[p2];
1615
- val = val?.[p3];
1616
- if (p4) val = val?.[p4];
1617
- return val;
1618
- };
1732
+ {
1733
+ const chainResult = parseChainedAccess(expr, scope);
1734
+ if (chainResult) return chainResult;
1619
1735
  }
1620
1736
  const groupedCallMatch = expr.match(RE_GROUP_METHOD_CALL);
1621
1737
  if (groupedCallMatch) {
@@ -1639,28 +1755,6 @@ function parseExpressionUncached(expr, scope) {
1639
1755
  return method.apply(base, args);
1640
1756
  };
1641
1757
  }
1642
- const callMatch = expr.match(RE_METHOD_CALL);
1643
- if (callMatch) {
1644
- const baseName = callMatch[1];
1645
- const methodName = callMatch[2];
1646
- const argsRaw = callMatch[3].trim();
1647
- if (UNSAFE_METHOD_NAMES.has(methodName)) return () => void 0;
1648
- const baseExpr = baseName === "Math" ? (() => Math) : parseExpression(baseName, scope);
1649
- if (!baseExpr) return null;
1650
- const argFns = [];
1651
- for (const arg of splitCallArgs(argsRaw)) {
1652
- const parsed = parseExpression(arg, scope);
1653
- if (!parsed) return null;
1654
- argFns.push(parsed);
1655
- }
1656
- return () => {
1657
- const base = baseExpr();
1658
- const method = base?.[methodName];
1659
- if (typeof method !== "function") return void 0;
1660
- const args = argFns.map((fn) => fn());
1661
- return method.apply(base, args);
1662
- };
1663
- }
1664
1758
  if (expr.startsWith("!")) {
1665
1759
  const inner = parseExpression(expr.slice(1).trim(), scope);
1666
1760
  if (inner) return () => !inner();
@@ -1679,6 +1773,29 @@ function parseExpressionUncached(expr, scope) {
1679
1773
  return () => objExpr()?.[key];
1680
1774
  }
1681
1775
  }
1776
+ if (expr.startsWith("[")) {
1777
+ const balanced = readBalancedSegment(expr, 0, "[", "]");
1778
+ if (balanced && balanced.end === expr.length - 1) {
1779
+ const inner = balanced.inner.trim();
1780
+ if (inner === "") {
1781
+ return () => [];
1782
+ }
1783
+ const elements = splitCallArgs(inner);
1784
+ const elementFns = [];
1785
+ let allParsed = true;
1786
+ for (const el of elements) {
1787
+ const parsed = parseExpression(el.trim(), scope);
1788
+ if (!parsed) {
1789
+ allParsed = false;
1790
+ break;
1791
+ }
1792
+ elementFns.push(parsed);
1793
+ }
1794
+ if (allParsed) {
1795
+ return () => elementFns.map((fn) => fn());
1796
+ }
1797
+ }
1798
+ }
1682
1799
  const ternaryMatch = expr.match(RE_TERNARY);
1683
1800
  if (ternaryMatch) {
1684
1801
  const cond = parseExpression(ternaryMatch[1].trim(), scope);
@@ -1808,6 +1925,15 @@ function getScopeCache(cache, scope) {
1808
1925
  }
1809
1926
  return scoped;
1810
1927
  }
1928
+ function cspExpressionHint(expr) {
1929
+ if (expr.includes("...")) {
1930
+ return `Unsupported expression in CSP-safe mode: spread syntax detected. Use .concat() instead, or enable unsafe-eval via setUnsafeEval(true).`;
1931
+ }
1932
+ if (expr.includes("=>")) {
1933
+ return `Unsupported expression in CSP-safe mode: arrow function detected. Extract logic to a data-computed attribute, or enable unsafe-eval via setUnsafeEval(true).`;
1934
+ }
1935
+ return `Unsupported expression in CSP-safe mode. Simplify the expression or enable unsafe-eval via setUnsafeEval(true).`;
1936
+ }
1811
1937
  function buildEvaluator(expr, scope) {
1812
1938
  const cleaned = expr.replace(RE_STRIP_BRACES, "").trim();
1813
1939
  const cache = getScopeCache(scopeExpressionCache, scope);
@@ -1820,11 +1946,17 @@ function buildEvaluator(expr, scope) {
1820
1946
  }
1821
1947
  if (!_allowUnsafeEval) {
1822
1948
  dbg("buildEvaluator: blocked unsafe eval fallback for expression:", cleaned);
1823
- reportDiagnostic("expression-unsupported", cleaned, "Unsupported expression in CSP-safe mode");
1949
+ reportDiagnostic("expression-unsupported", cleaned, cspExpressionHint(cleaned));
1824
1950
  const blocked = () => void 0;
1825
1951
  cache.set(cleaned, blocked);
1826
1952
  return blocked;
1827
1953
  }
1954
+ const blockedMethod = findBlockedMethod(cleaned);
1955
+ if (blockedMethod) {
1956
+ const msg = `Blocked unsafe method "${blockedMethod}" in expression`;
1957
+ reportDiagnostic("expression-unsupported", cleaned, msg);
1958
+ throw new Error(`[FormaJS] ${msg}: ${cleaned}`);
1959
+ }
1828
1960
  try {
1829
1961
  const fn = new Function("__scope", `with(__scope) { return (${cleaned}); }`);
1830
1962
  const proxy = new Proxy(/* @__PURE__ */ Object.create(null), {
@@ -1832,6 +1964,7 @@ function buildEvaluator(expr, scope) {
1832
1964
  return key in scope.getters;
1833
1965
  },
1834
1966
  get(_, key) {
1967
+ if (UNSAFE_METHOD_NAMES.has(key)) return void 0;
1835
1968
  const g = scope.getters[key];
1836
1969
  return g ? g() : void 0;
1837
1970
  }
@@ -1840,7 +1973,7 @@ function buildEvaluator(expr, scope) {
1840
1973
  cache.set(cleaned, unsafe);
1841
1974
  return unsafe;
1842
1975
  } catch {
1843
- reportDiagnostic("expression-unsupported", cleaned, "Expression failed to compile");
1976
+ 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.");
1844
1977
  const failed = () => void 0;
1845
1978
  cache.set(cleaned, failed);
1846
1979
  return failed;
@@ -1946,7 +2079,13 @@ function parseHandler(expr, scope) {
1946
2079
  return null;
1947
2080
  }
1948
2081
  function buildHandler(expr, scope) {
1949
- const cleaned = expr.replace(RE_STRIP_BRACES, "").trim();
2082
+ let cleaned = expr.trim();
2083
+ if (cleaned.startsWith("{")) {
2084
+ const seg = readBalancedSegment(cleaned, 0, "{", "}");
2085
+ if (seg && seg.end === cleaned.length - 1) {
2086
+ cleaned = seg.inner.trim();
2087
+ }
2088
+ }
1950
2089
  const cache = getScopeCache(scopeHandlerCache, scope);
1951
2090
  const cached = cache.get(cleaned);
1952
2091
  if (cached) return cached;
@@ -1958,7 +2097,7 @@ function buildHandler(expr, scope) {
1958
2097
  }
1959
2098
  if (!_allowUnsafeEval) {
1960
2099
  dbg("buildHandler: blocked unsafe eval fallback for expression:", cleaned);
1961
- reportDiagnostic("handler-unsupported", cleaned, "Unsupported handler in CSP-safe mode");
2100
+ reportDiagnostic("handler-unsupported", cleaned, cspExpressionHint(cleaned));
1962
2101
  const result = {
1963
2102
  handler: () => {
1964
2103
  },
@@ -1967,6 +2106,12 @@ function buildHandler(expr, scope) {
1967
2106
  cache.set(cleaned, result);
1968
2107
  return result;
1969
2108
  }
2109
+ const blockedMethod = findBlockedMethod(cleaned);
2110
+ if (blockedMethod) {
2111
+ const msg = `Blocked unsafe method "${blockedMethod}" in handler`;
2112
+ reportDiagnostic("handler-unsupported", cleaned, msg);
2113
+ throw new Error(`[FormaJS] ${msg}: ${cleaned}`);
2114
+ }
1970
2115
  try {
1971
2116
  const fn = new Function("__scope", "$event", "event", `with(__scope) { ${cleaned} }`);
1972
2117
  const proxy = new Proxy(/* @__PURE__ */ Object.create(null), {
@@ -1975,6 +2120,7 @@ function buildHandler(expr, scope) {
1975
2120
  return key in scope.getters || key in scope.setters;
1976
2121
  },
1977
2122
  get(_, key) {
2123
+ if (UNSAFE_METHOD_NAMES.has(key)) return void 0;
1978
2124
  const g = scope.getters[key];
1979
2125
  return g ? g() : void 0;
1980
2126
  },
@@ -1994,7 +2140,7 @@ function buildHandler(expr, scope) {
1994
2140
  cache.set(cleaned, result);
1995
2141
  return result;
1996
2142
  } catch {
1997
- reportDiagnostic("handler-unsupported", cleaned, "Handler failed to compile");
2143
+ 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.");
1998
2144
  const result = {
1999
2145
  handler: () => {
2000
2146
  },
@@ -2004,16 +2150,21 @@ function buildHandler(expr, scope) {
2004
2150
  return result;
2005
2151
  }
2006
2152
  }
2153
+ var FORBIDDEN_STATE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
2007
2154
  function parseState(raw) {
2155
+ let parsed;
2008
2156
  try {
2009
- return JSON.parse(raw);
2157
+ parsed = JSON.parse(raw);
2010
2158
  } catch {
2011
- try {
2012
- return JSON.parse(raw.replace(RE_UNQUOTED_KEYS, '"$1":'));
2013
- } catch {
2014
- return {};
2159
+ if (_debug) {
2160
+ dbg("parseState: Invalid JSON in data-forma-state \u2014 use valid JSON with quoted keys. Got:", raw.slice(0, 200));
2015
2161
  }
2162
+ return {};
2163
+ }
2164
+ for (const key of FORBIDDEN_STATE_KEYS) {
2165
+ if (key in parsed) delete parsed[key];
2016
2166
  }
2167
+ return parsed;
2017
2168
  }
2018
2169
  function initScope(stateEl) {
2019
2170
  const raw = stateEl.getAttribute("data-forma-state") ?? "{}";
@@ -2028,7 +2179,7 @@ function initScope(stateEl) {
2028
2179
  const getters = {};
2029
2180
  const setters = {};
2030
2181
  for (const [key, initial] of Object.entries(state)) {
2031
- const [get, set] = createValueSignal(initial);
2182
+ const [get, set] = createSignal(initial);
2032
2183
  getters[key] = get;
2033
2184
  setters[key] = set;
2034
2185
  }
@@ -2036,6 +2187,18 @@ function initScope(stateEl) {
2036
2187
  return { getters, setters };
2037
2188
  }
2038
2189
  function bindElement(el, scope, disposers) {
2190
+ const elMagics = {
2191
+ $el: el,
2192
+ $dispatch: (name, detail) => {
2193
+ el.dispatchEvent(new CustomEvent(name, {
2194
+ bubbles: true,
2195
+ composed: true,
2196
+ // crosses Shadow DOM boundaries (important for <forma-stage>)
2197
+ detail
2198
+ }));
2199
+ }
2200
+ };
2201
+ scope = createChildScope(scope, elMagics);
2039
2202
  const known = getDirectives(el);
2040
2203
  const computedAttr = !known || known.has("data-computed") ? el.getAttribute("data-computed") : null;
2041
2204
  if (computedAttr) {
@@ -2415,16 +2578,16 @@ function bindElement(el, scope, disposers) {
2415
2578
  else if (k === "error") errorTarget = v;
2416
2579
  else if (k === "poll") interval = parseInt(v ?? "0", 10);
2417
2580
  }
2418
- const [getTarget, setTarget] = createValueSignal(null);
2581
+ const [getTarget, setTarget] = createSignal(null);
2419
2582
  scope.getters[target] = getTarget;
2420
2583
  scope.setters[target] = setTarget;
2421
2584
  if (loadingTarget) {
2422
- const [gl, sl] = createValueSignal(false);
2585
+ const [gl, sl] = createSignal(false);
2423
2586
  scope.getters[loadingTarget] = gl;
2424
2587
  scope.setters[loadingTarget] = sl;
2425
2588
  }
2426
2589
  if (errorTarget) {
2427
- const [ge, se] = createValueSignal(null);
2590
+ const [ge, se] = createSignal(null);
2428
2591
  scope.getters[errorTarget] = ge;
2429
2592
  scope.setters[errorTarget] = se;
2430
2593
  }
@@ -2683,6 +2846,7 @@ function setUnsafeEvalMode(mode) {
2683
2846
  _unsafeEvalMode = mode;
2684
2847
  if (mode === "locked-off") _allowUnsafeEval = false;
2685
2848
  if (mode === "locked-on") _allowUnsafeEval = true;
2849
+ if (mode === "mutable") _allowUnsafeEval = true;
2686
2850
  scopeExpressionCache = /* @__PURE__ */ new WeakMap();
2687
2851
  scopeHandlerCache = /* @__PURE__ */ new WeakMap();
2688
2852
  }