Last active
March 31, 2024 23:48
-
-
Save scil/ec4438db24fbbbc642701aa95c76d631 to your computer and use it in GitHub Desktop.
the compile-free/bundless version of maverick-js/signals 5.11.4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://github.com/maverick-js/signals | |
var Signals = (function (exports) { | |
'use strict'; | |
const SCOPE = Symbol(0); | |
let scheduledEffects = false, runningEffects = false, currentScope = null, currentObserver = null, currentObservers = null, currentObserversIndex = 0, effects = [], defaultContext = {}; | |
const NOOP = () => { | |
}, STATE_CLEAN = 0, STATE_CHECK = 1, STATE_DIRTY = 2, STATE_DISPOSED = 3; | |
function flushEffects() { | |
scheduledEffects = true; | |
queueMicrotask(runEffects); | |
} | |
function runEffects() { | |
if (!effects.length) { | |
scheduledEffects = false; | |
return; | |
} | |
runningEffects = true; | |
for (let i = 0; i < effects.length; i++) { | |
if (effects[i].$st !== STATE_CLEAN) | |
runTop(effects[i]); | |
} | |
effects = []; | |
scheduledEffects = false; | |
runningEffects = false; | |
} | |
function runTop(node) { | |
let ancestors = [node]; | |
while (node = node[SCOPE]) { | |
if (node.$e && node.$st !== STATE_CLEAN) | |
ancestors.push(node); | |
} | |
for (let i = ancestors.length - 1; i >= 0; i--) { | |
updateCheck(ancestors[i]); | |
} | |
} | |
function root(init) { | |
const scope = createScope(); | |
return compute(scope, !init.length ? init : init.bind(null, dispose.bind(scope)), null); | |
} | |
function peek(fn) { | |
return compute(currentScope, fn, null); | |
} | |
function untrack(fn) { | |
return compute(null, fn, null); | |
} | |
function tick() { | |
if (!runningEffects) | |
runEffects(); | |
} | |
function getScope() { | |
return currentScope; | |
} | |
function scoped(run, scope) { | |
try { | |
return compute(scope, run, null); | |
} catch (error) { | |
handleError(scope, error); | |
return; | |
} | |
} | |
function getContext(key, scope = currentScope) { | |
return scope?.$cx[key]; | |
} | |
function setContext(key, value, scope = currentScope) { | |
if (scope) | |
scope.$cx = { ...scope.$cx, [key]: value }; | |
} | |
function onError(handler) { | |
if (!currentScope) | |
return; | |
currentScope.$eh = currentScope.$eh ? [handler, ...currentScope.$eh] : [handler]; | |
} | |
function onDispose(disposable) { | |
if (!disposable || !currentScope) | |
return disposable || NOOP; | |
const node = currentScope; | |
if (!node.$d) { | |
node.$d = disposable; | |
} else if (Array.isArray(node.$d)) { | |
node.$d.push(disposable); | |
} else { | |
node.$d = [node.$d, disposable]; | |
} | |
return function removeDispose() { | |
if (node.$st === STATE_DISPOSED) | |
return; | |
disposable.call(null); | |
if (isFunction(node.$d)) { | |
node.$d = null; | |
} else if (Array.isArray(node.$d)) { | |
node.$d.splice(node.$d.indexOf(disposable), 1); | |
} | |
}; | |
} | |
function dispose(self = true) { | |
if (this.$st === STATE_DISPOSED) | |
return; | |
let head = self ? this.$ps || this[SCOPE] : this, current = this.$ns, next = null; | |
while (current && current[SCOPE] === this) { | |
dispose.call(current, true); | |
disposeNode(current); | |
next = current.$ns; | |
current.$ns = null; | |
current = next; | |
} | |
if (self) | |
disposeNode(this); | |
if (current) | |
current.$ps = !self ? this : this.$ps; | |
if (head) | |
head.$ns = current; | |
} | |
function disposeNode(node) { | |
node.$st = STATE_DISPOSED; | |
if (node.$d) | |
emptyDisposal(node); | |
if (node.$s) | |
removeSourceObservers(node, 0); | |
if (node.$ps) | |
node.$ps.$ns = null; | |
node[SCOPE] = null; | |
node.$s = null; | |
node.$o = null; | |
node.$ps = null; | |
node.$cx = defaultContext; | |
node.$eh = null; | |
} | |
function emptyDisposal(scope) { | |
try { | |
if (Array.isArray(scope.$d)) { | |
for (let i = scope.$d.length - 1; i >= 0; i--) { | |
const callable = scope.$d[i]; | |
callable.call(callable); | |
} | |
} else { | |
scope.$d.call(scope.$d); | |
} | |
scope.$d = null; | |
} catch (error) { | |
handleError(scope, error); | |
} | |
} | |
function compute(scope, compute2, observer) { | |
const prevScope = currentScope, prevObserver = currentObserver; | |
currentScope = scope; | |
currentObserver = observer; | |
try { | |
return compute2.call(scope); | |
} finally { | |
currentScope = prevScope; | |
currentObserver = prevObserver; | |
} | |
} | |
function handleError(scope, error) { | |
if (!scope || !scope.$eh) | |
throw error; | |
let i = 0, len = scope.$eh.length, coercedError = coerceError(error); | |
for (i = 0; i < len; i++) { | |
try { | |
scope.$eh[i](coercedError); | |
break; | |
} catch (error2) { | |
coercedError = coerceError(error2); | |
} | |
} | |
if (i === len) | |
throw coercedError; | |
} | |
function coerceError(error) { | |
return error instanceof Error ? error : Error(JSON.stringify(error)); | |
} | |
function read() { | |
if (this.$st === STATE_DISPOSED) | |
return this.$v; | |
if (currentObserver && !this.$e) { | |
if (!currentObservers && currentObserver.$s && currentObserver.$s[currentObserversIndex] == this) { | |
currentObserversIndex++; | |
} else if (!currentObservers) | |
currentObservers = [this]; | |
else | |
currentObservers.push(this); | |
} | |
if (this.$c) | |
updateCheck(this); | |
return this.$v; | |
} | |
function write(newValue) { | |
const value = isFunction(newValue) ? newValue(this.$v) : newValue; | |
if (this.$ch(this.$v, value)) { | |
this.$v = value; | |
if (this.$o) { | |
for (let i = 0; i < this.$o.length; i++) { | |
notify(this.$o[i], STATE_DIRTY); | |
} | |
} | |
} | |
return this.$v; | |
} | |
const ScopeNode = function Scope() { | |
this[SCOPE] = null; | |
this.$ns = null; | |
this.$ps = null; | |
if (currentScope) | |
currentScope.append(this); | |
}; | |
const ScopeProto = ScopeNode.prototype; | |
ScopeProto.$cx = defaultContext; | |
ScopeProto.$eh = null; | |
ScopeProto.$c = null; | |
ScopeProto.$d = null; | |
ScopeProto.append = function(child) { | |
child[SCOPE] = this; | |
child.$ps = this; | |
if (this.$ns) { | |
if (child.$ns) { | |
let tail = child.$ns; | |
while (tail.$ns) | |
tail = tail.$ns; | |
tail.$ns = this.$ns; | |
this.$ns.$ps = tail; | |
} else { | |
child.$ns = this.$ns; | |
this.$ns.$ps = child; | |
} | |
} | |
this.$ns = child; | |
child.$cx = child.$cx === defaultContext ? this.$cx : { ...this.$cx, ...child.$cx }; | |
if (this.$eh) { | |
child.$eh = !child.$eh ? this.$eh : [...child.$eh, ...this.$eh]; | |
} | |
}; | |
ScopeProto.dispose = function() { | |
dispose.call(this); | |
}; | |
function createScope() { | |
return new ScopeNode(); | |
} | |
const ComputeNode = function Computation(initialValue, compute2, options) { | |
ScopeNode.call(this); | |
this.$st = compute2 ? STATE_DIRTY : STATE_CLEAN; | |
this.$i = false; | |
this.$e = false; | |
this.$s = null; | |
this.$o = null; | |
this.$v = initialValue; | |
if (compute2) | |
this.$c = compute2; | |
if (options && options.dirty) | |
this.$ch = options.dirty; | |
}; | |
const ComputeProto = ComputeNode.prototype; | |
Object.setPrototypeOf(ComputeProto, ScopeProto); | |
ComputeProto.$ch = isNotEqual; | |
ComputeProto.call = read; | |
function createComputation(initialValue, compute2, options) { | |
return new ComputeNode(initialValue, compute2, options); | |
} | |
function isNotEqual(a, b) { | |
return a !== b; | |
} | |
function isFunction(value) { | |
return typeof value === "function"; | |
} | |
function updateCheck(node) { | |
if (node.$st === STATE_CHECK) { | |
for (let i = 0; i < node.$s.length; i++) { | |
updateCheck(node.$s[i]); | |
if (node.$st === STATE_DIRTY) { | |
break; | |
} | |
} | |
} | |
if (node.$st === STATE_DIRTY) | |
update(node); | |
else | |
node.$st = STATE_CLEAN; | |
} | |
function cleanup(node) { | |
if (node.$ns && node.$ns[SCOPE] === node) | |
dispose.call(node, false); | |
if (node.$d) | |
emptyDisposal(node); | |
node.$eh = node[SCOPE] ? node[SCOPE].$eh : null; | |
} | |
function update(node) { | |
let prevObservers = currentObservers, prevObserversIndex = currentObserversIndex; | |
currentObservers = null; | |
currentObserversIndex = 0; | |
try { | |
cleanup(node); | |
const result = compute(node, node.$c, node); | |
if (currentObservers) { | |
if (node.$s) | |
removeSourceObservers(node, currentObserversIndex); | |
if (node.$s && currentObserversIndex > 0) { | |
node.$s.length = currentObserversIndex + currentObservers.length; | |
for (let i = 0; i < currentObservers.length; i++) { | |
node.$s[currentObserversIndex + i] = currentObservers[i]; | |
} | |
} else { | |
node.$s = currentObservers; | |
} | |
let source; | |
for (let i = currentObserversIndex; i < node.$s.length; i++) { | |
source = node.$s[i]; | |
if (!source.$o) | |
source.$o = [node]; | |
else | |
source.$o.push(node); | |
} | |
} else if (node.$s && currentObserversIndex < node.$s.length) { | |
removeSourceObservers(node, currentObserversIndex); | |
node.$s.length = currentObserversIndex; | |
} | |
if (!node.$e && node.$i) { | |
write.call(node, result); | |
} else { | |
node.$v = result; | |
node.$i = true; | |
} | |
} catch (error) { | |
handleError(node, error); | |
if (node.$st === STATE_DIRTY) { | |
cleanup(node); | |
if (node.$s) | |
removeSourceObservers(node, 0); | |
} | |
return; | |
} | |
currentObservers = prevObservers; | |
currentObserversIndex = prevObserversIndex; | |
node.$st = STATE_CLEAN; | |
} | |
function notify(node, state) { | |
if (node.$st >= state) | |
return; | |
if (node.$e && node.$st === STATE_CLEAN) { | |
effects.push(node); | |
if (!scheduledEffects) | |
flushEffects(); | |
} | |
node.$st = state; | |
if (node.$o) { | |
for (let i = 0; i < node.$o.length; i++) { | |
notify(node.$o[i], STATE_CHECK); | |
} | |
} | |
} | |
function removeSourceObservers(node, index) { | |
let source, swap; | |
for (let i = index; i < node.$s.length; i++) { | |
source = node.$s[i]; | |
if (source.$o) { | |
swap = source.$o.indexOf(node); | |
source.$o[swap] = source.$o[source.$o.length - 1]; | |
source.$o.pop(); | |
} | |
} | |
} | |
function signal(initialValue, options) { | |
const node = createComputation(initialValue, null, options), signal2 = read.bind(node); | |
signal2[SCOPE] = true; | |
signal2.set = write.bind(node); | |
return signal2; | |
} | |
function isReadSignal(fn) { | |
return isFunction(fn) && SCOPE in fn; | |
} | |
function computed(compute, options) { | |
const node = createComputation( | |
options?.initial, | |
compute, | |
options | |
), signal2 = read.bind(node); | |
signal2[SCOPE] = true; | |
return signal2; | |
} | |
function effect(effect2, options) { | |
const signal2 = createComputation( | |
null, | |
function runEffect() { | |
let effectResult = effect2(); | |
isFunction(effectResult) && onDispose(effectResult); | |
return null; | |
}, | |
void 0 | |
); | |
signal2.$e = true; | |
update(signal2); | |
return dispose.bind(signal2, true); | |
} | |
function readonly(signal2) { | |
const readonly2 = () => signal2(); | |
readonly2[SCOPE] = true; | |
return readonly2; | |
} | |
function isWriteSignal(fn) { | |
return isReadSignal(fn) && "set" in fn; | |
} | |
exports.SCOPE = SCOPE; | |
exports.computed = computed; | |
exports.createComputation = createComputation; | |
exports.createScope = createScope; | |
exports.effect = effect; | |
exports.getContext = getContext; | |
exports.getScope = getScope; | |
exports.isFunction = isFunction; | |
exports.isNotEqual = isNotEqual; | |
exports.isReadSignal = isReadSignal; | |
exports.isWriteSignal = isWriteSignal; | |
exports.onDispose = onDispose; | |
exports.onError = onError; | |
exports.peek = peek; | |
exports.readonly = readonly; | |
exports.root = root; | |
exports.scoped = scoped; | |
exports.setContext = setContext; | |
exports.signal = signal; | |
exports.tick = tick; | |
exports.untrack = untrack; | |
return exports; | |
})({}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment