release: WRNexusJS 0.2.47
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/csr",
|
||||
"version": "0.2.46",
|
||||
"version": "0.2.47",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
* data-scope="count: 0, name: 'x'" declare reactive state on a subtree
|
||||
* data-on-<event>="count++" run a statement in scope on an event
|
||||
* data-text="expr" element textContent follows an expression
|
||||
* data-wrn-loop-locals="base64-json" preserves SSR {#each} item/index values
|
||||
* data-wrnexus-csr="id" target for generated CSR fetch bindings
|
||||
* {{expr}} or {expr} interpolation inside text nodes
|
||||
*
|
||||
@@ -211,16 +212,96 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
|
||||
function signal(initial) {
|
||||
var value = initial;
|
||||
var subs = new Set();
|
||||
var subscribers = new Set();
|
||||
var notifying = false;
|
||||
var pendingNotification = false;
|
||||
var pendingPrevious;
|
||||
|
||||
function notify(previous) {
|
||||
if (notifying) {
|
||||
pendingNotification = true;
|
||||
|
||||
if (pendingPrevious === undefined) {
|
||||
pendingPrevious = previous;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
notifying = true;
|
||||
|
||||
try {
|
||||
/*
|
||||
* Iterate a snapshot. Reactive renderers may subscribe again while
|
||||
* running, but that must not mutate the collection currently being
|
||||
* iterated.
|
||||
*/
|
||||
var snapshot =
|
||||
Array.from(subscribers);
|
||||
|
||||
snapshot.forEach(function (
|
||||
subscriber,
|
||||
) {
|
||||
if (
|
||||
subscribers.has(
|
||||
subscriber,
|
||||
)
|
||||
) {
|
||||
subscriber(
|
||||
value,
|
||||
previous,
|
||||
);
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
notifying = false;
|
||||
}
|
||||
|
||||
if (pendingNotification) {
|
||||
var nextPrevious =
|
||||
pendingPrevious;
|
||||
|
||||
pendingNotification = false;
|
||||
pendingPrevious = undefined;
|
||||
|
||||
notify(nextPrevious);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
get: function () { return value; },
|
||||
set: function (v) {
|
||||
if (Object.is(v, value)) return;
|
||||
var previous = value;
|
||||
value = v;
|
||||
subs.forEach(function (f) { f(value, previous); });
|
||||
get: function () {
|
||||
return value;
|
||||
},
|
||||
|
||||
set: function (nextValue) {
|
||||
if (
|
||||
Object.is(
|
||||
nextValue,
|
||||
value,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
var previous = value;
|
||||
value = nextValue;
|
||||
|
||||
notify(previous);
|
||||
},
|
||||
|
||||
subscribe: function (
|
||||
subscriber,
|
||||
) {
|
||||
subscribers.add(
|
||||
subscriber,
|
||||
);
|
||||
|
||||
return function () {
|
||||
subscribers.delete(
|
||||
subscriber,
|
||||
);
|
||||
};
|
||||
},
|
||||
subscribe: function (f) { subs.add(f); return function () { subs.delete(f); }; }
|
||||
};
|
||||
}
|
||||
function applyReactiveAttribute(node, name, value) {
|
||||
@@ -385,21 +466,52 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
// change then re-runs only the renderers that actually read it. The Set in
|
||||
// signal.subscribe dedupes, so re-subscribing each run is cheap and bounded.
|
||||
var currentRenderer = null;
|
||||
var currentRenderer = null;
|
||||
|
||||
function reactive(fn) {
|
||||
var running = false;
|
||||
|
||||
function run() {
|
||||
var prev = currentRenderer;
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
|
||||
running = true;
|
||||
|
||||
var previousRenderer =
|
||||
currentRenderer;
|
||||
|
||||
currentRenderer = run;
|
||||
try { fn(); } finally { currentRenderer = prev; }
|
||||
|
||||
try {
|
||||
fn();
|
||||
} finally {
|
||||
currentRenderer =
|
||||
previousRenderer;
|
||||
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
||||
renderers.push(run);
|
||||
|
||||
return run;
|
||||
}
|
||||
function renderAll() { renderers.forEach(function (f) { f(); }); }
|
||||
function decodeLoopLocals(node) {
|
||||
if (!node) {
|
||||
return {};
|
||||
}
|
||||
|
||||
var element =
|
||||
node.nodeType === 1
|
||||
? node
|
||||
: node.parentElement;
|
||||
|
||||
var owner =
|
||||
node &&
|
||||
node.closest &&
|
||||
node.closest(
|
||||
element &&
|
||||
element.closest &&
|
||||
element.closest(
|
||||
"[data-wrn-loop-locals]",
|
||||
);
|
||||
|
||||
@@ -421,9 +533,7 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
window.atob(encoded);
|
||||
|
||||
var bytes =
|
||||
new Uint8Array(
|
||||
binary.length,
|
||||
);
|
||||
new Uint8Array(binary.length);
|
||||
|
||||
for (
|
||||
var index = 0;
|
||||
@@ -434,16 +544,17 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
binary.charCodeAt(index);
|
||||
}
|
||||
|
||||
var value =
|
||||
JSON.parse(
|
||||
new TextDecoder(
|
||||
"utf-8",
|
||||
).decode(bytes),
|
||||
var decoded =
|
||||
new TextDecoder("utf-8").decode(
|
||||
bytes,
|
||||
);
|
||||
|
||||
return value &&
|
||||
typeof value === "object"
|
||||
? value
|
||||
var parsed =
|
||||
JSON.parse(decoded);
|
||||
|
||||
return parsed &&
|
||||
typeof parsed === "object"
|
||||
? parsed
|
||||
: {};
|
||||
} catch (error) {
|
||||
console.error(
|
||||
@@ -515,17 +626,43 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
});
|
||||
}
|
||||
|
||||
function writeScope(name, value) {
|
||||
function writeScope(
|
||||
name,
|
||||
value,
|
||||
) {
|
||||
if (!signals[name]) {
|
||||
signals[name] = signal(value);
|
||||
signals[name] =
|
||||
signal(value);
|
||||
|
||||
notifyState(
|
||||
name,
|
||||
value,
|
||||
undefined,
|
||||
);
|
||||
|
||||
renderAll();
|
||||
notifyState(name, value, undefined);
|
||||
return;
|
||||
}
|
||||
var previous = signals[name].get();
|
||||
if (Object.is(previous, value)) return;
|
||||
|
||||
var previous =
|
||||
signals[name].get();
|
||||
|
||||
if (
|
||||
Object.is(
|
||||
previous,
|
||||
value,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
signals[name].set(value);
|
||||
notifyState(name, value, previous);
|
||||
|
||||
notifyState(
|
||||
name,
|
||||
value,
|
||||
previous,
|
||||
);
|
||||
}
|
||||
|
||||
function evalExpr(expr, locals) {
|
||||
@@ -1122,22 +1259,46 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
});
|
||||
});
|
||||
|
||||
// data-text bindings
|
||||
// data-text bindings. Server-rendered {#each} nodes recover their item/index
|
||||
// values from the nearest data-wrn-loop-locals marker.
|
||||
el.querySelectorAll("[data-text]").forEach(function (node) {
|
||||
if (!owns(node)) return;
|
||||
|
||||
var expr = node.getAttribute("data-text");
|
||||
|
||||
reactive(function () {
|
||||
try { node.textContent = String(evalExpr(expr)); } catch (e) { /* ignore */ }
|
||||
try {
|
||||
var value = evalExpr(
|
||||
expr,
|
||||
decodeLoopLocals(node),
|
||||
);
|
||||
|
||||
node.textContent =
|
||||
value == null ? "" : String(value);
|
||||
} catch (_) {
|
||||
// Keep the server-rendered value when evaluation is unavailable.
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// data-show="expr" — toggle visibility on truthiness.
|
||||
el.querySelectorAll("[data-show]").forEach(function (node) {
|
||||
if (!owns(node)) return;
|
||||
|
||||
var showExpr = node.getAttribute("data-show");
|
||||
|
||||
reactive(function () {
|
||||
var visible = true;
|
||||
try { visible = !!evalExpr(showExpr); } catch (e) { /* keep visible */ }
|
||||
|
||||
try {
|
||||
visible = !!evalExpr(
|
||||
showExpr,
|
||||
decodeLoopLocals(node),
|
||||
);
|
||||
} catch (_) {
|
||||
// Keep visible when evaluation is unavailable.
|
||||
}
|
||||
|
||||
node.style.display = visible ? "" : "none";
|
||||
});
|
||||
});
|
||||
@@ -1245,7 +1406,7 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
try {
|
||||
raw = evalExpr(
|
||||
exact[1].trim(),
|
||||
decodeLoopLocals(node),
|
||||
locals,
|
||||
);
|
||||
} catch (error) {
|
||||
return;
|
||||
@@ -1259,10 +1420,10 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
) {
|
||||
try {
|
||||
var value =
|
||||
evalExpr(
|
||||
expression.trim(),
|
||||
decodeLoopLocals(node),
|
||||
);
|
||||
evalExpr(
|
||||
expression.trim(),
|
||||
locals,
|
||||
);
|
||||
|
||||
return value == null
|
||||
? ""
|
||||
|
||||
Reference in New Issue
Block a user