rvx
Version:
A signal based rendering library
252 lines • 6.09 kB
JavaScript
import { Context } from "./context.js";
import { NOOP } from "./internals/noop.js";
import { ACCESS_STACK, TRACKING_STACK, useStack } from "./internals/stacks.js";
import { isolate } from "./isolate.js";
import { capture, teardown } from "./lifecycle.js";
let BATCH;
const notify = (fn) => fn();
const queueBatch = (fn) => BATCH.add(fn);
export class Signal {
#value;
#hooks = new Set();
#source;
#root;
constructor(value, source) {
this.#value = value;
this.#source = source;
this.#root = source ? source.#root : this;
}
get value() {
this.access();
return this.#value;
}
set value(value) {
if (!Object.is(this.#value, value)) {
this.#value = value;
this.notify();
}
}
get source() {
return this.#source;
}
get root() {
return this.#root;
}
update(fn) {
if (fn(this.#value) !== false) {
this.notify();
}
}
get active() {
return this.#hooks.size > 0;
}
access() {
if (TRACKING_STACK[TRACKING_STACK.length - 1]) {
const length = ACCESS_STACK.length;
if (length > 0) {
ACCESS_STACK[length - 1]?.(this.#hooks);
}
}
}
notify() {
const hooks = this.#hooks;
if (hooks.size === 0) {
return;
}
if (BATCH === undefined) {
const record = Array.from(hooks);
hooks.clear();
record.forEach(notify);
}
else {
hooks.forEach(queueBatch);
}
}
pipe(fn, ...args) {
return fn(this, ...args);
}
}
export function $(value, source) {
return new Signal(value, source);
}
const _unfold = (hook) => {
let depth = 0;
return () => {
if (depth < 2) {
depth++;
}
if (depth === 1) {
try {
while (depth > 0) {
hook();
depth--;
}
}
finally {
depth = 0;
}
}
};
};
const _observer = (hook) => {
const signals = [];
return {
c: () => {
for (let i = 0; i < signals.length; i++) {
signals[i].delete(hook);
}
signals.length = 0;
},
a: (hooks) => {
signals.push(hooks);
hooks.add(hook);
},
};
};
const _access = (frame, fn) => {
try {
ACCESS_STACK.push(frame);
TRACKING_STACK.push(true);
return fn();
}
finally {
ACCESS_STACK.pop();
TRACKING_STACK.pop();
}
};
export function watch(expr, effect) {
const isSignal = expr instanceof Signal;
if (isSignal || typeof expr === "function") {
let value;
let disposed = false;
let dispose = NOOP;
const runExpr = isSignal ? () => expr.value : expr;
const entry = _unfold(Context.wrap(() => {
if (disposed) {
return;
}
clear();
isolate(dispose);
dispose = capture(() => {
value = _access(access, runExpr);
if (effect) {
_access(undefined, () => effect(value));
}
});
}));
const { c: clear, a: access } = _observer(entry);
teardown(() => {
disposed = true;
clear();
dispose();
});
entry();
}
else {
effect(expr);
}
}
export function watchUpdates(expr, effect) {
let first;
let update = false;
watch(expr, value => {
if (update) {
effect(value);
}
else {
first = value;
update = true;
}
});
return first;
}
function dispatch(batch) {
while (batch.size > 0) {
try {
batch.forEach(notify => {
batch.delete(notify);
notify();
});
}
finally {
dispatch(batch);
}
}
}
export function batch(fn) {
if (BATCH === undefined) {
const batch = new Set();
let value;
try {
BATCH = batch;
value = fn();
dispatch(batch);
}
finally {
BATCH = undefined;
}
return value;
}
return fn();
}
export function memo(expr) {
const signal = $(undefined);
watch(() => signal.value = get(expr));
return () => signal.value;
}
export function untrack(fn) {
return useStack(TRACKING_STACK, false, fn);
}
export function track(fn) {
return useStack(TRACKING_STACK, true, fn);
}
export function isTracking() {
return TRACKING_STACK[TRACKING_STACK.length - 1] && ACCESS_STACK[ACCESS_STACK.length - 1] !== undefined;
}
export function trigger(callback) {
const hookFn = Context.wrap(() => {
clear();
isolate(callback);
});
const { c: clear, a: access } = _observer(hookFn);
teardown(clear);
return (expr) => {
clear();
try {
const outerLength = ACCESS_STACK.length;
if (outerLength > 0) {
const outer = ACCESS_STACK[outerLength - 1];
ACCESS_STACK.push(hooks => {
access(hooks);
outer?.(hooks);
});
}
else {
ACCESS_STACK.push(access);
}
return get(expr);
}
finally {
ACCESS_STACK.pop();
}
};
}
export function get(expr) {
if (expr instanceof Signal) {
return expr.value;
}
if (typeof expr === "function") {
return expr();
}
return expr;
}
export function map(input, mapFn) {
if (input instanceof Signal) {
return () => mapFn(input.value);
}
if (typeof input === "function") {
return () => mapFn(input());
}
return mapFn(input);
}
//# sourceMappingURL=signals.js.map