@getforma/core 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-JRQNDXX7.cjs → chunk-E3FV2VSU.cjs} +43 -43
- package/dist/{chunk-JRQNDXX7.cjs.map → chunk-E3FV2VSU.cjs.map} +1 -1
- package/dist/{chunk-XLVBYXOU.js → chunk-ETPJTPYD.js} +6 -3
- package/dist/chunk-ETPJTPYD.js.map +1 -0
- package/dist/{chunk-W7OUWVRA.cjs → chunk-H7MDUHS5.cjs} +6 -2
- package/dist/chunk-H7MDUHS5.cjs.map +1 -0
- package/dist/{chunk-2Y5US35K.cjs → chunk-KUXNZ5MG.cjs} +12 -12
- package/dist/{chunk-2Y5US35K.cjs.map → chunk-KUXNZ5MG.cjs.map} +1 -1
- package/dist/{chunk-INNOI6TG.js → chunk-Y3A2EVVS.js} +3 -3
- package/dist/{chunk-INNOI6TG.js.map → chunk-Y3A2EVVS.js.map} +1 -1
- package/dist/{chunk-MIOMT2CB.js → chunk-YRNYOZF3.js} +4 -4
- package/dist/{chunk-MIOMT2CB.js.map → chunk-YRNYOZF3.js.map} +1 -1
- package/dist/formajs.global.js +176 -52
- package/dist/formajs.global.js.map +1 -1
- package/dist/http.cjs +11 -11
- package/dist/http.js +2 -2
- package/dist/index.cjs +259 -138
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -2
- package/dist/index.d.ts +21 -2
- package/dist/index.js +204 -83
- package/dist/index.js.map +1 -1
- package/dist/runtime.cjs +29 -29
- package/dist/runtime.js +3 -3
- package/dist/server.cjs +7 -7
- package/dist/server.js +2 -2
- package/dist/tc39-compat.cjs +3 -3
- package/dist/tc39-compat.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-W7OUWVRA.cjs.map +0 -1
- package/dist/chunk-XLVBYXOU.js.map +0 -1
package/dist/runtime.cjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkE3FV2VSU_cjs = require('./chunk-E3FV2VSU.cjs');
|
|
4
4
|
var chunk263HW3KN_cjs = require('./chunk-263HW3KN.cjs');
|
|
5
|
-
var
|
|
6
|
-
var
|
|
5
|
+
var chunkKUXNZ5MG_cjs = require('./chunk-KUXNZ5MG.cjs');
|
|
6
|
+
var chunkH7MDUHS5_cjs = require('./chunk-H7MDUHS5.cjs');
|
|
7
7
|
|
|
8
8
|
// src/dom/reconcile.ts
|
|
9
9
|
function getBindTargets(el) {
|
|
@@ -974,7 +974,7 @@ function parseIfHandler(expr, scope) {
|
|
|
974
974
|
}
|
|
975
975
|
if (rest.length > 0) return null;
|
|
976
976
|
return (e) => {
|
|
977
|
-
|
|
977
|
+
chunkKUXNZ5MG_cjs.batch(() => {
|
|
978
978
|
if (condExpr()) thenHandler(e);
|
|
979
979
|
else elseHandler?.(e);
|
|
980
980
|
});
|
|
@@ -1728,7 +1728,7 @@ function parseHandler(expr, scope) {
|
|
|
1728
1728
|
const handlers = stmts.map((s) => parseHandler(s, scope));
|
|
1729
1729
|
if (handlers.every((h) => h !== null)) {
|
|
1730
1730
|
return (e) => {
|
|
1731
|
-
|
|
1731
|
+
chunkKUXNZ5MG_cjs.batch(() => {
|
|
1732
1732
|
for (const h of handlers) h(e);
|
|
1733
1733
|
});
|
|
1734
1734
|
};
|
|
@@ -1741,7 +1741,7 @@ function parseHandler(expr, scope) {
|
|
|
1741
1741
|
const name = incrMatch[1];
|
|
1742
1742
|
const op = incrMatch[2];
|
|
1743
1743
|
return () => {
|
|
1744
|
-
|
|
1744
|
+
chunkKUXNZ5MG_cjs.batch(() => {
|
|
1745
1745
|
const val = scope.getters[name]?.() ?? 0;
|
|
1746
1746
|
scope.setters[name]?.(op === "++" ? val + 1 : val - 1);
|
|
1747
1747
|
});
|
|
@@ -1752,7 +1752,7 @@ function parseHandler(expr, scope) {
|
|
|
1752
1752
|
const op = preIncrMatch[1];
|
|
1753
1753
|
const name = preIncrMatch[2];
|
|
1754
1754
|
return () => {
|
|
1755
|
-
|
|
1755
|
+
chunkKUXNZ5MG_cjs.batch(() => {
|
|
1756
1756
|
const val = scope.getters[name]?.() ?? 0;
|
|
1757
1757
|
scope.setters[name]?.(op === "++" ? val + 1 : val - 1);
|
|
1758
1758
|
});
|
|
@@ -1762,7 +1762,7 @@ function parseHandler(expr, scope) {
|
|
|
1762
1762
|
if (toggleMatch && toggleMatch[1] === toggleMatch[2]) {
|
|
1763
1763
|
const name = toggleMatch[1];
|
|
1764
1764
|
return () => {
|
|
1765
|
-
|
|
1765
|
+
chunkKUXNZ5MG_cjs.batch(() => {
|
|
1766
1766
|
scope.setters[name]?.(!scope.getters[name]?.());
|
|
1767
1767
|
});
|
|
1768
1768
|
};
|
|
@@ -1774,7 +1774,7 @@ function parseHandler(expr, scope) {
|
|
|
1774
1774
|
if (valExpr) {
|
|
1775
1775
|
if (_debug) dbg(`parseHandler: assignment "${name} = ..." \u2014 setter exists:`, !!scope.setters[name], ", getter exists:", !!scope.getters[name]);
|
|
1776
1776
|
return () => {
|
|
1777
|
-
|
|
1777
|
+
chunkKUXNZ5MG_cjs.batch(() => {
|
|
1778
1778
|
const val = valExpr();
|
|
1779
1779
|
if (_debug) dbg(`SETTER: ${name} = ${val} (was: ${scope.getters[name]?.()})`);
|
|
1780
1780
|
scope.setters[name]?.(val);
|
|
@@ -1789,7 +1789,7 @@ function parseHandler(expr, scope) {
|
|
|
1789
1789
|
const valExpr = parseExpression(compoundMatch[3].trim(), scope);
|
|
1790
1790
|
if (valExpr) {
|
|
1791
1791
|
return () => {
|
|
1792
|
-
|
|
1792
|
+
chunkKUXNZ5MG_cjs.batch(() => {
|
|
1793
1793
|
const current = scope.getters[name]?.() ?? 0;
|
|
1794
1794
|
const val = valExpr();
|
|
1795
1795
|
switch (op) {
|
|
@@ -1870,7 +1870,7 @@ function buildHandler(expr, scope) {
|
|
|
1870
1870
|
}
|
|
1871
1871
|
});
|
|
1872
1872
|
const unsafeHandler = (e) => {
|
|
1873
|
-
|
|
1873
|
+
chunkKUXNZ5MG_cjs.batch(() => fn(proxy, e, e));
|
|
1874
1874
|
};
|
|
1875
1875
|
const result = {
|
|
1876
1876
|
handler: unsafeHandler,
|
|
@@ -1918,7 +1918,7 @@ function initScope(stateEl) {
|
|
|
1918
1918
|
const getters = {};
|
|
1919
1919
|
const setters = {};
|
|
1920
1920
|
for (const [key, initial] of Object.entries(state)) {
|
|
1921
|
-
const [get, set] =
|
|
1921
|
+
const [get, set] = chunkH7MDUHS5_cjs.createSignal(initial);
|
|
1922
1922
|
getters[key] = get;
|
|
1923
1923
|
setters[key] = set;
|
|
1924
1924
|
}
|
|
@@ -2030,7 +2030,7 @@ function bindElement(el, scope, disposers) {
|
|
|
2030
2030
|
const prevGetter = scope.getters[name];
|
|
2031
2031
|
delete scope.getters[name];
|
|
2032
2032
|
const evaluate = buildEvaluator(`{${expr}}`, scope);
|
|
2033
|
-
const getter =
|
|
2033
|
+
const getter = chunkH7MDUHS5_cjs.createComputed(evaluate);
|
|
2034
2034
|
scope.getters[name] = getter;
|
|
2035
2035
|
if (!prevGetter) {
|
|
2036
2036
|
delete scope.setters[name];
|
|
@@ -2041,7 +2041,7 @@ function bindElement(el, scope, disposers) {
|
|
|
2041
2041
|
const textExpr = !known || known.has("data-text") ? el.getAttribute("data-text") : null;
|
|
2042
2042
|
if (textExpr) {
|
|
2043
2043
|
const evaluate = buildEvaluator(textExpr, scope);
|
|
2044
|
-
const dispose =
|
|
2044
|
+
const dispose = chunkKUXNZ5MG_cjs.internalEffect(() => {
|
|
2045
2045
|
setElementTextFast(el, toTextValue(evaluate()));
|
|
2046
2046
|
});
|
|
2047
2047
|
disposers.push(dispose);
|
|
@@ -2056,7 +2056,7 @@ function bindElement(el, scope, disposers) {
|
|
|
2056
2056
|
dbg(`bindElement: data-show="${showExpr}" on <${tag}${cls}>`);
|
|
2057
2057
|
}
|
|
2058
2058
|
let initialized = false;
|
|
2059
|
-
const dispose =
|
|
2059
|
+
const dispose = chunkKUXNZ5MG_cjs.internalEffect(() => {
|
|
2060
2060
|
const visible = !!evaluate();
|
|
2061
2061
|
if (_debug) dbg(`data-show effect: "${showExpr}" \u2192 ${visible}`);
|
|
2062
2062
|
applyShowVisibility(el, visible, transition, !initialized);
|
|
@@ -2075,7 +2075,7 @@ function bindElement(el, scope, disposers) {
|
|
|
2075
2075
|
const parent = el.parentNode;
|
|
2076
2076
|
let inserted = true;
|
|
2077
2077
|
let initialized = false;
|
|
2078
|
-
const dispose =
|
|
2078
|
+
const dispose = chunkKUXNZ5MG_cjs.internalEffect(() => {
|
|
2079
2079
|
const show = !!evaluate();
|
|
2080
2080
|
if (show && !inserted) {
|
|
2081
2081
|
clearTransitionState(el);
|
|
@@ -2114,7 +2114,7 @@ function bindElement(el, scope, disposers) {
|
|
|
2114
2114
|
const setter = scope.setters[prop];
|
|
2115
2115
|
if (getter && setter) {
|
|
2116
2116
|
const input = el;
|
|
2117
|
-
const dispose =
|
|
2117
|
+
const dispose = chunkKUXNZ5MG_cjs.internalEffect(() => {
|
|
2118
2118
|
const val = getter();
|
|
2119
2119
|
if (input.type === "checkbox") {
|
|
2120
2120
|
input.checked = !!val;
|
|
@@ -2178,14 +2178,14 @@ function bindElement(el, scope, disposers) {
|
|
|
2178
2178
|
} else if (name.startsWith("data-class:")) {
|
|
2179
2179
|
const cls = name.slice(11);
|
|
2180
2180
|
const evaluate = buildEvaluator(attr.value, scope);
|
|
2181
|
-
const dispose =
|
|
2181
|
+
const dispose = chunkKUXNZ5MG_cjs.internalEffect(() => {
|
|
2182
2182
|
el.classList.toggle(cls, !!evaluate());
|
|
2183
2183
|
});
|
|
2184
2184
|
disposers.push(dispose);
|
|
2185
2185
|
} else if (name.startsWith("data-bind:")) {
|
|
2186
2186
|
const attrName = name.slice(10);
|
|
2187
2187
|
const evaluate = buildEvaluator(attr.value, scope);
|
|
2188
|
-
const dispose =
|
|
2188
|
+
const dispose = chunkKUXNZ5MG_cjs.internalEffect(() => {
|
|
2189
2189
|
const val = evaluate();
|
|
2190
2190
|
if (val == null || val === false) {
|
|
2191
2191
|
el.removeAttribute(attrName);
|
|
@@ -2213,7 +2213,7 @@ function bindElement(el, scope, disposers) {
|
|
|
2213
2213
|
if (saved !== null) setter(JSON.parse(saved));
|
|
2214
2214
|
} catch {
|
|
2215
2215
|
}
|
|
2216
|
-
const dispose =
|
|
2216
|
+
const dispose = chunkKUXNZ5MG_cjs.internalEffect(() => {
|
|
2217
2217
|
try {
|
|
2218
2218
|
localStorage.setItem(key, JSON.stringify(getter()));
|
|
2219
2219
|
} catch {
|
|
@@ -2300,7 +2300,7 @@ function bindElement(el, scope, disposers) {
|
|
|
2300
2300
|
});
|
|
2301
2301
|
}
|
|
2302
2302
|
} : void 0;
|
|
2303
|
-
const dispose =
|
|
2303
|
+
const dispose = chunkKUXNZ5MG_cjs.internalEffect(() => {
|
|
2304
2304
|
const rawItems = evaluate();
|
|
2305
2305
|
if (!Array.isArray(rawItems)) {
|
|
2306
2306
|
for (const n of oldNodes) {
|
|
@@ -2321,7 +2321,7 @@ function bindElement(el, scope, disposers) {
|
|
|
2321
2321
|
}
|
|
2322
2322
|
const prevNodes = new Set(oldNodes);
|
|
2323
2323
|
if (keyProp) {
|
|
2324
|
-
const result =
|
|
2324
|
+
const result = chunkE3FV2VSU_cjs.reconcileList(
|
|
2325
2325
|
el,
|
|
2326
2326
|
oldItems,
|
|
2327
2327
|
rawItems,
|
|
@@ -2351,7 +2351,7 @@ function bindElement(el, scope, disposers) {
|
|
|
2351
2351
|
} else {
|
|
2352
2352
|
const wrapped = rawItems.map((item, i) => ({ __idx: i, __item: item }));
|
|
2353
2353
|
const oldWrapped = oldItems;
|
|
2354
|
-
const result =
|
|
2354
|
+
const result = chunkE3FV2VSU_cjs.reconcileList(
|
|
2355
2355
|
el,
|
|
2356
2356
|
oldWrapped,
|
|
2357
2357
|
wrapped,
|
|
@@ -2400,16 +2400,16 @@ function bindElement(el, scope, disposers) {
|
|
|
2400
2400
|
else if (k === "error") errorTarget = v;
|
|
2401
2401
|
else if (k === "poll") interval = parseInt(v ?? "0", 10);
|
|
2402
2402
|
}
|
|
2403
|
-
const [getTarget, setTarget] =
|
|
2403
|
+
const [getTarget, setTarget] = chunkH7MDUHS5_cjs.createSignal(null);
|
|
2404
2404
|
scope.getters[target] = getTarget;
|
|
2405
2405
|
scope.setters[target] = setTarget;
|
|
2406
2406
|
if (loadingTarget) {
|
|
2407
|
-
const [gl, sl] =
|
|
2407
|
+
const [gl, sl] = chunkH7MDUHS5_cjs.createSignal(false);
|
|
2408
2408
|
scope.getters[loadingTarget] = gl;
|
|
2409
2409
|
scope.setters[loadingTarget] = sl;
|
|
2410
2410
|
}
|
|
2411
2411
|
if (errorTarget) {
|
|
2412
|
-
const [ge, se] =
|
|
2412
|
+
const [ge, se] = chunkH7MDUHS5_cjs.createSignal(null);
|
|
2413
2413
|
scope.getters[errorTarget] = ge;
|
|
2414
2414
|
scope.setters[errorTarget] = se;
|
|
2415
2415
|
}
|
|
@@ -2738,7 +2738,7 @@ function getScopes() {
|
|
|
2738
2738
|
function setScopeValue(element, key, value) {
|
|
2739
2739
|
const scope = element.__formaScope;
|
|
2740
2740
|
if (!scope?.setters[key]) return;
|
|
2741
|
-
|
|
2741
|
+
chunkKUXNZ5MG_cjs.batch(() => {
|
|
2742
2742
|
scope.setters[key](value);
|
|
2743
2743
|
});
|
|
2744
2744
|
}
|
|
@@ -2747,7 +2747,7 @@ function resetScope(element) {
|
|
|
2747
2747
|
const initialJSON = element.__formaInitialState;
|
|
2748
2748
|
if (!scope || !initialJSON) return;
|
|
2749
2749
|
const initial = parseState(initialJSON);
|
|
2750
|
-
|
|
2750
|
+
chunkKUXNZ5MG_cjs.batch(() => {
|
|
2751
2751
|
for (const [key, val] of Object.entries(initial)) {
|
|
2752
2752
|
scope.setters[key]?.(val);
|
|
2753
2753
|
}
|
|
@@ -2777,7 +2777,7 @@ function getReconciler() {
|
|
|
2777
2777
|
}
|
|
2778
2778
|
}
|
|
2779
2779
|
},
|
|
2780
|
-
batch:
|
|
2780
|
+
batch: chunkKUXNZ5MG_cjs.batch
|
|
2781
2781
|
});
|
|
2782
2782
|
}
|
|
2783
2783
|
return _reconciler;
|
package/dist/runtime.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { reconcileList } from './chunk-
|
|
1
|
+
import { reconcileList } from './chunk-YRNYOZF3.js';
|
|
2
2
|
import { isEventHandlerAttr, isUrlAttr, isDangerousUrl } from './chunk-2QQBKQIF.js';
|
|
3
|
-
import { internalEffect, batch } from './chunk-
|
|
4
|
-
import { createSignal, createComputed } from './chunk-
|
|
3
|
+
import { internalEffect, batch } from './chunk-Y3A2EVVS.js';
|
|
4
|
+
import { createSignal, createComputed } from './chunk-ETPJTPYD.js';
|
|
5
5
|
|
|
6
6
|
// src/dom/reconcile.ts
|
|
7
7
|
function getBindTargets(el) {
|
package/dist/server.cjs
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
3
|
+
var chunkKUXNZ5MG_cjs = require('./chunk-KUXNZ5MG.cjs');
|
|
4
|
+
var chunkH7MDUHS5_cjs = require('./chunk-H7MDUHS5.cjs');
|
|
5
5
|
|
|
6
6
|
// src/server/action.ts
|
|
7
7
|
function createAction(serverFn, options) {
|
|
8
|
-
const [pending, setPending] =
|
|
9
|
-
const [error, setError] =
|
|
8
|
+
const [pending, setPending] = chunkH7MDUHS5_cjs.createSignal(false);
|
|
9
|
+
const [error, setError] = chunkH7MDUHS5_cjs.createSignal(void 0);
|
|
10
10
|
const action = async (...args) => {
|
|
11
11
|
setPending(true);
|
|
12
12
|
setError(void 0);
|
|
13
13
|
if (options?.optimistic) {
|
|
14
14
|
try {
|
|
15
|
-
|
|
15
|
+
chunkKUXNZ5MG_cjs.batch(() => options.optimistic(...args));
|
|
16
16
|
} catch {
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
try {
|
|
20
20
|
const result = await serverFn(...args);
|
|
21
21
|
if (options?.onSuccess) {
|
|
22
|
-
|
|
22
|
+
chunkKUXNZ5MG_cjs.batch(() => options.onSuccess(result, ...args));
|
|
23
23
|
}
|
|
24
24
|
if (options?.invalidates) {
|
|
25
25
|
for (const resource of options.invalidates) {
|
|
@@ -31,7 +31,7 @@ function createAction(serverFn, options) {
|
|
|
31
31
|
} catch (err) {
|
|
32
32
|
if (options?.onError) {
|
|
33
33
|
try {
|
|
34
|
-
|
|
34
|
+
chunkKUXNZ5MG_cjs.batch(() => options.onError(err, ...args));
|
|
35
35
|
} catch {
|
|
36
36
|
}
|
|
37
37
|
}
|
package/dist/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { batch } from './chunk-
|
|
2
|
-
import { createSignal } from './chunk-
|
|
1
|
+
import { batch } from './chunk-Y3A2EVVS.js';
|
|
2
|
+
import { createSignal } from './chunk-ETPJTPYD.js';
|
|
3
3
|
|
|
4
4
|
// src/server/action.ts
|
|
5
5
|
function createAction(serverFn, options) {
|
package/dist/tc39-compat.cjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkH7MDUHS5_cjs = require('./chunk-H7MDUHS5.cjs');
|
|
4
4
|
|
|
5
5
|
// src/reactive/tc39-compat.ts
|
|
6
6
|
var State = class {
|
|
7
7
|
_get;
|
|
8
8
|
_set;
|
|
9
9
|
constructor(initialValue, options) {
|
|
10
|
-
const [getter, setter] =
|
|
10
|
+
const [getter, setter] = chunkH7MDUHS5_cjs.createSignal(initialValue, options);
|
|
11
11
|
this._get = getter;
|
|
12
12
|
this._set = setter;
|
|
13
13
|
}
|
|
@@ -21,7 +21,7 @@ var State = class {
|
|
|
21
21
|
var Computed = class {
|
|
22
22
|
_get;
|
|
23
23
|
constructor(fn) {
|
|
24
|
-
this._get =
|
|
24
|
+
this._get = chunkH7MDUHS5_cjs.createComputed(fn);
|
|
25
25
|
}
|
|
26
26
|
get() {
|
|
27
27
|
return this._get();
|
package/dist/tc39-compat.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getforma/core",
|
|
3
3
|
"author": "Forma <victor@getforma.dev>",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.0",
|
|
5
5
|
"description": "Real DOM reactive library — fine-grained signals, islands architecture, SSR hydration. No virtual DOM, no diffing. Core entry ~8KB gzipped; full HTML runtime ~24KB gzipped.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.cjs",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/reactive/dev.ts","../src/reactive/signal.ts","../src/reactive/computed.ts"],"names":["prevSub","setActiveSub","prev","trigger","createRawSignal","rawComputed"],"mappings":"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","file":"chunk-W7OUWVRA.cjs","sourcesContent":["/**\n * Forma Reactive - Dev Mode\n *\n * Development utilities stripped from production builds via __DEV__ flag.\n * Bundlers replace __DEV__ with false → dead-code elimination removes all dev paths.\n */\n\n// __DEV__ is replaced by the bundler (tsup define) for dead-code elimination in\n// dist. For unbundled/raw-source usage it derives from NODE_ENV, and — when the\n// environment is indeterminate — defaults to *false* (production-safe: no console\n// noise) unless a consumer opts in via a global __FORMA_DEV__.\ndeclare const process: { env?: Record<string, string | undefined> } | undefined;\ndeclare const __FORMA_DEV__: boolean | undefined;\n\n/**\n * Lazily evaluate whether we're in a dev environment. Read per-call (not frozen\n * at import) so runtime NODE_ENV changes are honored and so official prod dist\n * builds — where tsup hard-defines __DEV__ = false — stay quiet regardless.\n */\nfunction isDev(): boolean {\n if (typeof process !== 'undefined') {\n return process!.env?.NODE_ENV !== 'production';\n }\n return typeof __FORMA_DEV__ !== 'undefined' ? !!__FORMA_DEV__ : false;\n}\n\nexport const __DEV__: boolean = isDev();\n\n// ---------------------------------------------------------------------------\n// Global error handlers\n// ---------------------------------------------------------------------------\n\n/** Callback signature for a global {@link onError} handler. */\nexport type ErrorHandler = (error: unknown, info?: { source?: string }) => void;\n\nconst _errorHandlers = new Set<ErrorHandler>();\n\n/**\n * Install a global error handler for FormaJS reactive errors. Called when\n * effects, computeds, or event handlers throw. Multiple handlers may be\n * registered; returns an unsubscribe function that removes only this handler.\n *\n * ```ts\n * const off = onError((err, info) => {\n * Sentry.captureException(err, { tags: { source: info?.source } });\n * });\n * // later: off();\n * ```\n */\nexport function onError(handler: ErrorHandler): () => void {\n _errorHandlers.add(handler);\n return () => { _errorHandlers.delete(handler); };\n}\n\n/** @internal */\nexport function reportError(error: unknown, source?: string): void {\n for (const handler of _errorHandlers) {\n try { handler(error, source ? { source } : {}); } catch { /* prevent infinite loop */ }\n }\n if (isDev()) {\n console.error(`[forma] ${source ?? 'Unknown'} error:`, error);\n }\n}\n","/**\n * Forma Reactive - Signal\n *\n * Fine-grained reactive primitive backed by alien-signals.\n * API: createSignal returns [getter, setter] tuple following SolidJS conventions.\n *\n * TC39 Signals equivalent: Signal.State\n */\n\nimport { signal as createRawSignal, setActiveSub, trigger } from 'alien-signals';\nimport { __DEV__ } from './dev.js';\n\n// Debug names live in a side table rather than on the getter's `.name`, so\n// alien-signals' isSignal() (which keys off the bound function name) still\n// recognizes named signals.\nconst signalNames = new WeakMap<object, string>();\n\n/** Debug name of a signal getter, if one was provided (dev-only). */\nexport function getSignalName(fn: unknown): string | undefined {\n return typeof fn === 'function' ? signalNames.get(fn as object) : undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** A function that reads the current value of a signal. */\nexport type SignalGetter<T> = () => T;\n/** A function that updates a signal — accepts a value or an updater function. */\nexport type SignalSetter<T> = (v: T | ((prev: T) => T)) => void;\n\nexport interface SignalOptions<T> {\n /** Debug name — attached to getter in dev mode for devtools inspection. */\n name?: string;\n /**\n * Custom equality check. When provided, the setter will read the current\n * value and only update the signal if `equals(prev, next)` returns `false`.\n *\n * Default: none (alien-signals uses strict inequality `!==` internally).\n *\n * ```ts\n * const [pos, setPos] = createSignal(\n * { x: 0, y: 0 },\n * { equals: (a, b) => a.x === b.x && a.y === b.y },\n * );\n *\n * setPos({ x: 0, y: 0 }); // skipped — equals returns true\n * setPos({ x: 1, y: 0 }); // applied — equals returns false\n * ```\n */\n equals?: (prev: T, next: T) => boolean;\n}\n\n/**\n * Wrap a value so the setter treats it as a literal value, not a functional updater.\n *\n * When `T` is itself a function type, passing a function to the setter is\n * ambiguous -- it looks like a functional update (`prev => next`). Use\n * `value()` to disambiguate:\n *\n * ```ts\n * const [getFn, setFn] = createSignal<() => void>(() => console.log('a'));\n *\n * // BUG: interpreted as a functional update -- calls the arrow with prev\n * // setFn(() => console.log('b'));\n *\n * // Correct: wraps in a thunk so the setter stores it as-is\n * setFn(value(() => console.log('b')));\n * ```\n */\nexport function value<T>(v: T): () => T {\n return () => v;\n}\n\ntype RawSignal<T> = {\n (): T;\n (value: T): void;\n};\n\nfunction applySignalSet<T>(\n s: RawSignal<T>,\n v: T | ((prev: T) => T),\n equals?: (prev: T, next: T) => boolean,\n): void {\n // Fast path: plain value, no equals — no prev read needed. (A running effect\n // writing its own dependency is detected at the effect level via the Pending\n // flag, so signal.ts does not need to do anything special here.)\n if (typeof v !== 'function') {\n if (!equals) {\n s(v);\n return;\n }\n const prevSub = setActiveSub(undefined);\n const prev = s();\n setActiveSub(prevSub);\n if (equals(prev, v)) return; // suppressed by custom equality\n s(v);\n if (Object.is(prev, v)) forceNotify(s);\n return;\n }\n\n // Functional update: read prev without tracking.\n const prevSub = setActiveSub(undefined);\n const prev = s();\n setActiveSub(prevSub);\n const next = (v as (prev: T) => T)(prev);\n if (equals && equals(prev, next)) return; // suppressed by custom equality\n s(next);\n if (equals && Object.is(prev, next)) forceNotify(s);\n}\n\n/**\n * When a custom `equals` decided to APPLY (returned false) but the value is the\n * identical reference, alien-signals suppresses propagation (its guard is\n * `pendingValue !== value`). Force-notify subscribers so `equals: () => false`\n * (\"always notify\") works for mutate-in-place patterns.\n */\nfunction forceNotify<T>(s: RawSignal<T>): void {\n trigger(() => { s(); });\n}\n\n/**\n * Create a reactive signal.\n *\n * ```ts\n * const [count, setCount] = createSignal(0);\n * console.log(count()); // 0\n * setCount(1);\n * setCount(prev => prev + 1);\n * ```\n *\n * With custom equality:\n *\n * ```ts\n * const [pos, setPos] = createSignal(\n * { x: 0, y: 0 },\n * { equals: (a, b) => a.x === b.x && a.y === b.y },\n * );\n * ```\n */\nexport function createSignal<T>(initialValue: T, options?: SignalOptions<T>): [get: SignalGetter<T>, set: SignalSetter<T>] {\n const s = createRawSignal<T>(initialValue) as RawSignal<T>;\n const getter = s as unknown as SignalGetter<T>;\n if (__DEV__ && options?.name) {\n signalNames.set(getter as object, options.name);\n }\n const eq = options?.equals;\n const setter: SignalSetter<T> = (v: T | ((prev: T) => T)) => applySignalSet(s, v, eq);\n\n return [getter, setter];\n}\n","/**\n * Forma Reactive - Computed\n *\n * Lazy, cached derived value that participates in the reactive graph.\n * Backed by alien-signals for automatic dependency tracking\n * and cache invalidation.\n *\n * TC39 Signals equivalent: Signal.Computed\n */\n\nimport { computed as rawComputed } from 'alien-signals';\nimport { reportError } from './dev.js';\n\n/** Unique per-throw marker so alien-signals sees a value change and propagates. */\nconst ERR = Symbol('formaComputedError');\ninterface ErrBox { [ERR]: unknown; }\nfunction isErrBox(v: unknown): v is ErrBox {\n return typeof v === 'object' && v !== null && ERR in (v as object);\n}\n\n/**\n * Create a lazy, cached computed value.\n *\n * Note: Unlike SolidJS's createComputed (which is an eager synchronous\n * side effect), this is a lazy cached derivation — equivalent to\n * SolidJS's createMemo. Both createComputed and createMemo in FormaJS\n * are identical.\n *\n * The getter receives the previous value as an argument, enabling\n * efficient diffing patterns without a separate signal:\n *\n * ```ts\n * const [count, setCount] = createSignal(0);\n * const doubled = createComputed(() => count() * 2);\n * console.log(doubled()); // 0\n * setCount(5);\n * console.log(doubled()); // 10\n * ```\n *\n * With previous value (for diffing):\n *\n * ```ts\n * const changes = createComputed((prev) => {\n * const next = items();\n * if (prev) console.log(`changed from ${prev.length} to ${next.length} items`);\n * return next;\n * });\n * ```\n */\nexport function createComputed<T>(fn: (previousValue?: T) => T): () => T {\n // alien-signals' updateComputed leaves a throwing computed flagged clean with a\n // stale value, so a later read silently returns the stale value and never\n // rethrows. We cache the error in closure and rethrow on read until a\n // dependency changes (TC39/Solid). On throw we return a FRESH sentinel object so\n // alien-signals sees a value change and propagates the error to subscribers; the\n // user getter only ever sees the last GOOD value as `previousValue`.\n let errored = false;\n let error: unknown;\n let lastGood: T | undefined;\n\n const raw = rawComputed<T | ErrBox>(() => {\n try {\n const v = fn(lastGood);\n errored = false;\n error = undefined;\n lastGood = v;\n return v;\n } catch (e) {\n errored = true;\n error = e;\n reportError(e, 'computed');\n return { [ERR]: e };\n }\n });\n\n const reader = () => {\n const v = raw();\n if (errored || isErrBox(v)) throw error;\n return v as T;\n };\n // Preserve alien-signals' isComputed() recognition, which keys off the bound\n // function name (`bound computedOper`). Our reader wraps the raw computed, so\n // copy the raw name onto it.\n Object.defineProperty(reader, 'name', { value: raw.name, configurable: true });\n return reader;\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/reactive/dev.ts","../src/reactive/signal.ts","../src/reactive/computed.ts"],"names":["prevSub","prev","createRawSignal","rawComputed"],"mappings":"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","file":"chunk-XLVBYXOU.js","sourcesContent":["/**\n * Forma Reactive - Dev Mode\n *\n * Development utilities stripped from production builds via __DEV__ flag.\n * Bundlers replace __DEV__ with false → dead-code elimination removes all dev paths.\n */\n\n// __DEV__ is replaced by the bundler (tsup define) for dead-code elimination in\n// dist. For unbundled/raw-source usage it derives from NODE_ENV, and — when the\n// environment is indeterminate — defaults to *false* (production-safe: no console\n// noise) unless a consumer opts in via a global __FORMA_DEV__.\ndeclare const process: { env?: Record<string, string | undefined> } | undefined;\ndeclare const __FORMA_DEV__: boolean | undefined;\n\n/**\n * Lazily evaluate whether we're in a dev environment. Read per-call (not frozen\n * at import) so runtime NODE_ENV changes are honored and so official prod dist\n * builds — where tsup hard-defines __DEV__ = false — stay quiet regardless.\n */\nfunction isDev(): boolean {\n if (typeof process !== 'undefined') {\n return process!.env?.NODE_ENV !== 'production';\n }\n return typeof __FORMA_DEV__ !== 'undefined' ? !!__FORMA_DEV__ : false;\n}\n\nexport const __DEV__: boolean = isDev();\n\n// ---------------------------------------------------------------------------\n// Global error handlers\n// ---------------------------------------------------------------------------\n\n/** Callback signature for a global {@link onError} handler. */\nexport type ErrorHandler = (error: unknown, info?: { source?: string }) => void;\n\nconst _errorHandlers = new Set<ErrorHandler>();\n\n/**\n * Install a global error handler for FormaJS reactive errors. Called when\n * effects, computeds, or event handlers throw. Multiple handlers may be\n * registered; returns an unsubscribe function that removes only this handler.\n *\n * ```ts\n * const off = onError((err, info) => {\n * Sentry.captureException(err, { tags: { source: info?.source } });\n * });\n * // later: off();\n * ```\n */\nexport function onError(handler: ErrorHandler): () => void {\n _errorHandlers.add(handler);\n return () => { _errorHandlers.delete(handler); };\n}\n\n/** @internal */\nexport function reportError(error: unknown, source?: string): void {\n for (const handler of _errorHandlers) {\n try { handler(error, source ? { source } : {}); } catch { /* prevent infinite loop */ }\n }\n if (isDev()) {\n console.error(`[forma] ${source ?? 'Unknown'} error:`, error);\n }\n}\n","/**\n * Forma Reactive - Signal\n *\n * Fine-grained reactive primitive backed by alien-signals.\n * API: createSignal returns [getter, setter] tuple following SolidJS conventions.\n *\n * TC39 Signals equivalent: Signal.State\n */\n\nimport { signal as createRawSignal, setActiveSub, trigger } from 'alien-signals';\nimport { __DEV__ } from './dev.js';\n\n// Debug names live in a side table rather than on the getter's `.name`, so\n// alien-signals' isSignal() (which keys off the bound function name) still\n// recognizes named signals.\nconst signalNames = new WeakMap<object, string>();\n\n/** Debug name of a signal getter, if one was provided (dev-only). */\nexport function getSignalName(fn: unknown): string | undefined {\n return typeof fn === 'function' ? signalNames.get(fn as object) : undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** A function that reads the current value of a signal. */\nexport type SignalGetter<T> = () => T;\n/** A function that updates a signal — accepts a value or an updater function. */\nexport type SignalSetter<T> = (v: T | ((prev: T) => T)) => void;\n\nexport interface SignalOptions<T> {\n /** Debug name — attached to getter in dev mode for devtools inspection. */\n name?: string;\n /**\n * Custom equality check. When provided, the setter will read the current\n * value and only update the signal if `equals(prev, next)` returns `false`.\n *\n * Default: none (alien-signals uses strict inequality `!==` internally).\n *\n * ```ts\n * const [pos, setPos] = createSignal(\n * { x: 0, y: 0 },\n * { equals: (a, b) => a.x === b.x && a.y === b.y },\n * );\n *\n * setPos({ x: 0, y: 0 }); // skipped — equals returns true\n * setPos({ x: 1, y: 0 }); // applied — equals returns false\n * ```\n */\n equals?: (prev: T, next: T) => boolean;\n}\n\n/**\n * Wrap a value so the setter treats it as a literal value, not a functional updater.\n *\n * When `T` is itself a function type, passing a function to the setter is\n * ambiguous -- it looks like a functional update (`prev => next`). Use\n * `value()` to disambiguate:\n *\n * ```ts\n * const [getFn, setFn] = createSignal<() => void>(() => console.log('a'));\n *\n * // BUG: interpreted as a functional update -- calls the arrow with prev\n * // setFn(() => console.log('b'));\n *\n * // Correct: wraps in a thunk so the setter stores it as-is\n * setFn(value(() => console.log('b')));\n * ```\n */\nexport function value<T>(v: T): () => T {\n return () => v;\n}\n\ntype RawSignal<T> = {\n (): T;\n (value: T): void;\n};\n\nfunction applySignalSet<T>(\n s: RawSignal<T>,\n v: T | ((prev: T) => T),\n equals?: (prev: T, next: T) => boolean,\n): void {\n // Fast path: plain value, no equals — no prev read needed. (A running effect\n // writing its own dependency is detected at the effect level via the Pending\n // flag, so signal.ts does not need to do anything special here.)\n if (typeof v !== 'function') {\n if (!equals) {\n s(v);\n return;\n }\n const prevSub = setActiveSub(undefined);\n const prev = s();\n setActiveSub(prevSub);\n if (equals(prev, v)) return; // suppressed by custom equality\n s(v);\n if (Object.is(prev, v)) forceNotify(s);\n return;\n }\n\n // Functional update: read prev without tracking.\n const prevSub = setActiveSub(undefined);\n const prev = s();\n setActiveSub(prevSub);\n const next = (v as (prev: T) => T)(prev);\n if (equals && equals(prev, next)) return; // suppressed by custom equality\n s(next);\n if (equals && Object.is(prev, next)) forceNotify(s);\n}\n\n/**\n * When a custom `equals` decided to APPLY (returned false) but the value is the\n * identical reference, alien-signals suppresses propagation (its guard is\n * `pendingValue !== value`). Force-notify subscribers so `equals: () => false`\n * (\"always notify\") works for mutate-in-place patterns.\n */\nfunction forceNotify<T>(s: RawSignal<T>): void {\n trigger(() => { s(); });\n}\n\n/**\n * Create a reactive signal.\n *\n * ```ts\n * const [count, setCount] = createSignal(0);\n * console.log(count()); // 0\n * setCount(1);\n * setCount(prev => prev + 1);\n * ```\n *\n * With custom equality:\n *\n * ```ts\n * const [pos, setPos] = createSignal(\n * { x: 0, y: 0 },\n * { equals: (a, b) => a.x === b.x && a.y === b.y },\n * );\n * ```\n */\nexport function createSignal<T>(initialValue: T, options?: SignalOptions<T>): [get: SignalGetter<T>, set: SignalSetter<T>] {\n const s = createRawSignal<T>(initialValue) as RawSignal<T>;\n const getter = s as unknown as SignalGetter<T>;\n if (__DEV__ && options?.name) {\n signalNames.set(getter as object, options.name);\n }\n const eq = options?.equals;\n const setter: SignalSetter<T> = (v: T | ((prev: T) => T)) => applySignalSet(s, v, eq);\n\n return [getter, setter];\n}\n","/**\n * Forma Reactive - Computed\n *\n * Lazy, cached derived value that participates in the reactive graph.\n * Backed by alien-signals for automatic dependency tracking\n * and cache invalidation.\n *\n * TC39 Signals equivalent: Signal.Computed\n */\n\nimport { computed as rawComputed } from 'alien-signals';\nimport { reportError } from './dev.js';\n\n/** Unique per-throw marker so alien-signals sees a value change and propagates. */\nconst ERR = Symbol('formaComputedError');\ninterface ErrBox { [ERR]: unknown; }\nfunction isErrBox(v: unknown): v is ErrBox {\n return typeof v === 'object' && v !== null && ERR in (v as object);\n}\n\n/**\n * Create a lazy, cached computed value.\n *\n * Note: Unlike SolidJS's createComputed (which is an eager synchronous\n * side effect), this is a lazy cached derivation — equivalent to\n * SolidJS's createMemo. Both createComputed and createMemo in FormaJS\n * are identical.\n *\n * The getter receives the previous value as an argument, enabling\n * efficient diffing patterns without a separate signal:\n *\n * ```ts\n * const [count, setCount] = createSignal(0);\n * const doubled = createComputed(() => count() * 2);\n * console.log(doubled()); // 0\n * setCount(5);\n * console.log(doubled()); // 10\n * ```\n *\n * With previous value (for diffing):\n *\n * ```ts\n * const changes = createComputed((prev) => {\n * const next = items();\n * if (prev) console.log(`changed from ${prev.length} to ${next.length} items`);\n * return next;\n * });\n * ```\n */\nexport function createComputed<T>(fn: (previousValue?: T) => T): () => T {\n // alien-signals' updateComputed leaves a throwing computed flagged clean with a\n // stale value, so a later read silently returns the stale value and never\n // rethrows. We cache the error in closure and rethrow on read until a\n // dependency changes (TC39/Solid). On throw we return a FRESH sentinel object so\n // alien-signals sees a value change and propagates the error to subscribers; the\n // user getter only ever sees the last GOOD value as `previousValue`.\n let errored = false;\n let error: unknown;\n let lastGood: T | undefined;\n\n const raw = rawComputed<T | ErrBox>(() => {\n try {\n const v = fn(lastGood);\n errored = false;\n error = undefined;\n lastGood = v;\n return v;\n } catch (e) {\n errored = true;\n error = e;\n reportError(e, 'computed');\n return { [ERR]: e };\n }\n });\n\n const reader = () => {\n const v = raw();\n if (errored || isErrBox(v)) throw error;\n return v as T;\n };\n // Preserve alien-signals' isComputed() recognition, which keys off the bound\n // function name (`bound computedOper`). Our reader wraps the raw computed, so\n // copy the raw name onto it.\n Object.defineProperty(reader, 'name', { value: raw.name, configurable: true });\n return reader;\n}\n"]}
|