UNPKG

@ibyar/expressions

Version:

Aurora expression, an template expression and evaluation, An 100% spec compliant ES2022 JavaScript toolchain,

191 lines 5.41 kB
import { skipChangeDetection } from './cd.js'; import { ReactiveScope } from './scope.js'; function compute(updateFn) { try { return updateFn(); } catch (error) { return error; } } export class SignalScope extends ReactiveScope { static create() { return new SignalScope(); } state = []; watch = true; constructor() { super([]); } getNextKey() { return this.getContext().length; } createSignal(initValue) { return new Signal(this, this.getContext().length, initValue); } createLazy(updateFn) { return new Lazy(this, this.getContext().length, updateFn); } createComputed(updateFn) { const index = this.getContext().length; this.watchState(); const value = compute(updateFn); const computed = new Computed(this, index, value); const observeComputed = () => { this.watchState(); const value = compute(updateFn); const state = this.readState(); this.restoreState(); Object.keys(subscriptions) .filter(index => !state.includes(+index)) .forEach(index => subscriptions[index].pause()); state.forEach(index => { subscriptions[index]?.resume(); subscriptions[index] ??= this.subscribe(index, observeComputed); }); this.set(index, value); }; const subscriptions = this.observeState(observeComputed); this.restoreState(); return computed; } createEffect(effectFn) { let cleanupFn; let isCleanupRegistered = false; const cleanupRegister = onClean => { cleanupFn = onClean; isCleanupRegistered = true; }; const callback = () => { isCleanupRegistered = false; const error = compute(() => effectFn(cleanupRegister)); if (error instanceof Error) { console.error(error); } if (!isCleanupRegistered) { cleanupFn = undefined; } }; this.watchState(); callback(); const observeComputed = () => { cleanupFn?.(); this.watchState(); callback(); const state = this.readState(); this.restoreState(); Object.keys(subscriptions) .filter(index => !state.includes(+index)) .forEach(index => subscriptions[index].pause()); state.forEach(index => { subscriptions[index]?.resume(); subscriptions[index] ??= this.subscribe(index, observeComputed); }); }; const subscriptions = this.observeState(observeComputed); this.restoreState(); return { destroy: () => { Object.values(subscriptions).forEach(sub => sub.unsubscribe()); cleanupFn?.(); }, }; } watchState() { this.state.push([]); } untrack() { this.watch = false; } observeIndex(index) { if (this.watch) { this.state.at(-1)?.push(index); } } track() { this.watch = true; } readState() { return this.state.at(-1) ?? []; } observeState(updateFn) { return Object.fromEntries(this.readState().map(index => [index, this.subscribe(index, updateFn)])); } restoreState() { this.state.pop(); } } export class ReactiveNode { scope; index; constructor(scope, index) { this.scope = scope; this.index = index; skipChangeDetection(this); } get() { this.scope.observeIndex(this.index); return this.scope.get(this.index); } getScope() { return this.scope; } getIndex() { return this.index; } subscribe(callback) { return this.scope.subscribe(this.index, callback); } } export class Computed extends ReactiveNode { constructor(scope, index, initValue) { super(scope, index); scope.set(index, initValue); } } export class Lazy extends ReactiveNode { updateFn; constructor(scope, index, updateFn) { super(scope, index); this.updateFn = updateFn; scope.set(index, compute(updateFn)); } get() { const value = compute(this.updateFn); this.scope.set(this.index, value); return super.get(); } } export class Signal extends ReactiveNode { constructor(scope, index, initValue) { super(scope, index); scope.set(index, initValue); } set(value) { this.scope.set(this.index, value); } update(updateFn) { this.set(updateFn(this.get())); } asReadonly() { return new ReadOnlySignal(this.scope, this.index); } } export class ReadOnlySignal extends ReactiveNode { } export function isSignal(signal) { return signal instanceof Signal; } export function isComputed(signal) { return signal instanceof Computed; } export function isLazy(signal) { return signal instanceof Lazy; } export function isReadOnlySignal(signal) { return signal instanceof ReadOnlySignal; } export function isReactive(signal) { return signal instanceof ReactiveNode; } //# sourceMappingURL=signal.js.map