@tioniq/eventiq
Version:
A library providing utilities for implementing the Event pattern, facilitating event handling in JavaScript and TypeScript applications. This library is a collection of common utilities for managing events and event handlers using the Event pattern.
1,959 lines (1,927 loc) • 66.5 kB
JavaScript
// src/variable.ts
var Variable = class {
/**
* Overload of the `toString` method. Returns the string representation of the value of the variable
* @returns the string representation of the value of the variable
*/
toString() {
const _value = this.value;
if (_value === null || _value === void 0) {
return `${_value}`;
}
return _value.toString();
}
/**
* Overload of the `valueOf` method. Converts the variable to a primitive value, in this case, the value of the variable
* @returns the primitive value of the variable
*/
valueOf() {
return this.value;
}
};
// src/comparer.ts
function strictEqualityComparer(a, b) {
return a === b;
}
function simpleEqualityComparer(a, b) {
return a == b;
}
var defaultEqualityComparer = strictEqualityComparer;
function setDefaultEqualityComparer(comparer) {
defaultEqualityComparer = comparer;
}
function functionEqualityComparer(a, b) {
return a === b;
}
function generalEqualityComparer(a, b) {
if (a === b) {
return true;
}
const typeA = typeof a;
const typeB = typeof b;
if (typeA !== typeB) {
return false;
}
if (typeA === "object") {
return objectEqualityComparer(a, b);
}
if (typeA === "function") {
return functionEqualityComparer(a, b);
}
return simpleEqualityComparer(a, b);
}
function objectEqualityComparer(a, b) {
if (a === b) {
return true;
}
if (!a || !b) {
return false;
}
const arrayA = Array.isArray(a);
const arrayB = Array.isArray(b);
if (arrayA !== arrayB) {
return false;
}
if (arrayA) {
return arrayEqualityComparer(a, b);
}
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) {
return false;
}
for (const key of keysA) {
if (!keysB.includes(key)) {
return false;
}
const valueA = a[key];
const valueB = b[key];
if (!generalEqualityComparer(valueA, valueB)) {
return false;
}
}
return true;
}
function arrayEqualityComparer(a, b) {
if (a === b) {
return true;
}
if (!a || !b) {
return false;
}
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; ++i) {
if (!generalEqualityComparer(a[i], b[i])) {
return false;
}
}
return true;
}
// src/linked-chain.ts
import { DisposableAction } from "@tioniq/disposiq";
var LinkedChain = class _LinkedChain {
constructor(equalityComparer) {
/**
* @internal
*/
this._head = null;
/**
* @internal
*/
this._tail = null;
/**
* @internal
*/
this._invoking = false;
/**
* @internal
*/
this._pendingHead = null;
/**
* @internal
*/
this._pendingTail = null;
/**
* @internal
*/
this._actionHead = null;
this._equalityComparer = equalityComparer != null ? equalityComparer : defaultEqualityComparer;
}
/**
* Checks if the chain has any elements
*/
get hasAny() {
return this._head !== null || this._pendingHead !== null;
}
/**
* Checks if the chain is empty
*/
get empty() {
return this._head === null && this._pendingHead === null;
}
/**
* Gets the number of elements in the chain
* @remarks This getter should be used only for debugging purposes
*/
get count() {
let count = 0;
let node = this._head;
if (node !== null) {
do {
++count;
node = node.next;
} while (node !== null);
}
node = this._pendingHead;
if (node !== null) {
do {
count++;
node = node.next;
} while (node !== null);
}
return count;
}
/**
* Converts the chain to an array
* @returns an array containing the elements of the chain
* @remarks This method should be used only for debugging purposes
*/
toArray() {
const count = this.count;
if (count === 0) {
return [];
}
const array = new Array(count);
let node = this._head;
let index = 0;
if (node !== null) {
do {
array[index++] = node.value;
node = node.next;
} while (node !== null);
}
node = this._pendingHead;
if (node !== null) {
do {
array[index++] = node.value;
node = node.next;
} while (node !== null);
}
return array;
}
/**
* Adds an element to the chain. If the element is already in the chain, it will not be added again.
* @param value the element to add
* @returns an array containing the subscription and a boolean value indicating if the element was added
*/
addUnique(value) {
const existing = this._findNode(value);
if (existing !== null) {
return [new DisposableAction(() => this._unlinkNode(existing)), false];
}
return [this.add(value), true];
}
/**
* Adds an element to the end of the chain
* @param value the element to add
* @returns a subscription that can be used to remove the element from the chain
*/
add(value) {
let node;
if (this._invoking) {
if (this._pendingHead === null) {
node = new ChainNode(value);
this._pendingHead = node;
this._pendingTail = node;
} else {
node = new ChainNode(value, this._pendingTail, null);
this._pendingTail.next = node;
this._pendingTail = node;
}
return new DisposableAction(() => this._unlinkNode(node));
}
if (this._head === null) {
node = new ChainNode(value);
this._head = node;
this._tail = node;
} else {
node = new ChainNode(value, this._tail, null);
this._tail.next = node;
this._tail = node;
}
return new DisposableAction(() => this._unlinkNode(node));
}
/**
* Adds an element to the beginning of the chain. If the element is already in the chain, it will not be added again.
* @param value the element to add
* @returns an array containing the subscription and a boolean value indicating if the element was added
*/
addToBeginUnique(value) {
const existing = this._findNode(value);
if (existing !== null) {
return [new DisposableAction(() => this._unlinkNode(existing)), false];
}
return [this.addToBegin(value), true];
}
/**
* Adds an element to the beginning of the chain
* @param value the element to add
* @returns a subscription that can be used to remove the element from the chain
*/
addToBegin(value) {
let node;
if (this._head === null) {
node = new ChainNode(value);
this._head = node;
this._tail = node;
} else {
node = new ChainNode(value, null, this._head);
this._head.previous = node;
this._head = node;
}
return new DisposableAction(() => this._unlinkNode(node));
}
/**
* Adds a node and its children to the end of the chain
* @param node
* @remarks This method does not check if the node is already in a chain
*/
addToBeginNode(node) {
let chainNode = _LinkedChain._clearNode(node);
if (chainNode === null) {
return;
}
if (this._head === null) {
this._head = chainNode;
while (chainNode.next !== null) {
chainNode = chainNode.next;
}
this._tail = chainNode;
return;
}
let tail = chainNode;
while (tail.next !== null) {
tail = tail.next;
}
tail.next = this._head;
this._head.previous = tail;
this._head = chainNode;
}
/**
* Removes an element from the chain
* @param value the element to remove
* @returns true if the element was removed, false otherwise
*/
remove(value) {
let checkNode = this._head;
while (checkNode !== null) {
if (this._equalityComparer(checkNode.value, value)) {
this._unlinkNode(checkNode);
return true;
}
checkNode = checkNode.next;
}
checkNode = this._pendingHead;
while (checkNode !== null) {
if (this._equalityComparer(checkNode.value, value)) {
this._unlinkNode(checkNode);
return true;
}
checkNode = checkNode.next;
}
return false;
}
/**
* Removes all elements from the chain
*/
clear() {
let node = this._head;
if (node !== null) {
while (node !== null) {
node.disposed = true;
node = node.next;
}
this._head = null;
this._tail = null;
}
node = this._pendingHead;
if (node !== null) {
while (node !== null) {
node.disposed = true;
node = node.next;
}
this._pendingHead = null;
this._pendingTail = null;
}
}
/**
* Removes all elements from the chain and returns the head node
* @returns the head node of the chain or null if the chain is empty
*/
removeAll() {
const node = this._head;
this._head = null;
this._tail = null;
return node;
}
/**
* Iterates over the elements of the chain and invokes the specified action for each element
* @param valueHandler the action to invoke for each element
*/
forEach(valueHandler) {
let handler = valueHandler;
while (handler !== null) {
if (this._head !== null) {
if (this._invoking) {
if (this._actionHead == null) {
this._actionHead = new ChainNode(handler);
return;
}
let actionTail = this._actionHead;
while (actionTail.next !== null) {
actionTail = actionTail.next;
}
actionTail.next = new ChainNode(handler, actionTail, null);
return;
}
this._invoking = true;
let node = this._head;
while (node !== null) {
if (!node.disposed) {
handler(node.value);
}
node = node.next;
}
this._invoking = false;
if (this._pendingHead != null) {
if (this._head == null) {
this._head = this._pendingHead;
this._tail = this._pendingTail;
} else {
this._pendingHead.previous = this._tail;
this._tail.next = this._pendingHead;
this._tail = this._pendingTail;
}
this._pendingHead = null;
this._pendingTail = null;
}
}
if (this._actionHead == null) {
return;
}
const nextActionNode = this._actionHead;
nextActionNode.disposed = true;
this._actionHead = nextActionNode.next;
if (this._actionHead != null) {
this._actionHead.previous = null;
nextActionNode.next = null;
}
handler = nextActionNode.value;
}
}
/**
* @internal
*/
_findNode(value) {
let checkNode = this._head;
while (checkNode !== null) {
if (this._equalityComparer(checkNode.value, value)) {
return checkNode;
}
checkNode = checkNode.next;
}
if (this._invoking) {
checkNode = this._pendingHead;
while (checkNode !== null) {
if (this._equalityComparer(checkNode.value, value)) {
return checkNode;
}
checkNode = checkNode.next;
}
}
return null;
}
/**
* @internal
*/
_unlinkNode(node) {
if (node.disposed) {
return;
}
node.disposed = true;
if (node === this._head) {
if (node.next === null) {
this._head = null;
this._tail = null;
return;
}
this._head = node.next;
this._head.previous = null;
return;
}
if (node === this._tail) {
this._tail = node.previous;
this._tail.next = null;
return;
}
if (node === this._pendingHead) {
if (node.next == null) {
this._pendingHead = null;
this._pendingTail = null;
return;
}
this._pendingHead = node.next;
this._pendingHead.previous = null;
return;
}
if (node === this._pendingTail) {
this._pendingTail = node.previous;
this._pendingTail.next = null;
return;
}
if (node.previous !== null) {
node.previous.next = node.next;
}
if (node.next !== null) {
node.next.previous = node.previous;
}
}
/**
* @internal
*/
static _clearNode(chainNode) {
let node = chainNode;
let root = null;
let tail = null;
let next = node;
while (next !== null) {
node = next;
next = node.next;
if (node.disposed) {
continue;
}
if (root === null) {
root = node;
tail = node;
node.previous = null;
continue;
}
tail.next = node;
node.previous = tail;
tail = node;
}
if (tail !== null) {
tail.next = null;
}
return root;
}
};
var ChainNode = class {
constructor(value, previous, next) {
this.disposed = false;
this.value = value;
this.previous = previous != null ? previous : null;
this.next = next != null ? next : null;
}
};
// src/vars/compound.ts
import { DisposableAction as DisposableAction2 } from "@tioniq/disposiq";
var CompoundVariable = class extends Variable {
constructor(initValue, equalityComparer) {
super();
/**
* @internal
*/
this._chain = new LinkedChain(functionEqualityComparer);
this._value = initValue;
this._equalityComparer = equalityComparer != null ? equalityComparer : defaultEqualityComparer;
}
/**
* Checks if there are any subscriptions
* @returns true if there are any subscriptions, false otherwise
*/
get active() {
return this._chain.hasAny;
}
get value() {
if (this._chain.hasAny) {
return this._value;
}
return this.getExactValue();
}
/**
* Sets the value of the variable. If the value is the same as the current value, the method will do nothing
* @param value the new value of the variable
* @protected internal use only
*/
set value(value) {
if (this._equalityComparer(value, this._value)) {
return;
}
this._value = value;
this._chain.forEach((a) => a(value));
}
subscribe(callback) {
if (this._chain.empty) {
this.activate();
}
const [disposable, added] = this._chain.addUnique(callback);
if (added) {
callback(this._value);
}
return new DisposableAction2(() => {
disposable.dispose();
if (this._chain.empty) {
this.deactivate();
}
});
}
subscribeSilent(callback) {
if (this._chain.empty) {
this.activate();
}
const disposable = this._chain.addUnique(callback)[0];
return new DisposableAction2(() => {
disposable.dispose();
if (this._chain.empty) {
this.deactivate();
}
});
}
/**
* A method for getting the exact value of the variable. It is called when there are no subscriptions
* @protected internal use only
* @returns the default behavior is to return the current (last) value of the variable
* @remarks this method should be implemented in the derived class
*/
getExactValue() {
return this._value;
}
/**
* A method for setting the value of the variable without notifying subscribers
* @protected internal use only
* @param value the new value of the variable
* @deprecated user `setSilent` instead
*/
setValueSilent(value) {
this._value = value;
}
/**
* A method for setting the value of the variable and notifying subscribers without checking the equality
* @protected internal use only
* @param value the new value of the variable
* @deprecated user `setForce` instead
*/
setValueForce(value) {
this._value = value;
this._chain.forEach((a) => a(value));
}
/**
* A method for setting the value of the variable without notifying subscribers
* @protected internal use only
* @param value the new value of the variable
*/
setSilent(value) {
this._value = value;
}
/**
* A method for setting the value of the variable and notifying subscribers without checking the equality
* @protected internal use only
* @param value the new value of the variable
*/
setForce(value) {
this._value = value;
this._chain.forEach((a) => a(value));
}
/**
* A method for notifying subscribers about the value change
* @protected internal use only
*/
notify() {
const value = this._value;
this._chain.forEach((a) => a(value));
}
};
// src/vars/and.ts
import { disposeAll } from "@tioniq/disposiq";
var AndVariable = class extends CompoundVariable {
constructor(variables) {
super(false);
/**
* @internal
*/
this._subscriptions = [];
this._variables = variables;
}
activate() {
this._listen(0);
}
deactivate() {
disposeAll(this._subscriptions);
}
getExactValue() {
const variables = this._variables;
for (let i = 0; i < variables.length; ++i) {
if (!variables[i].value) {
return false;
}
}
return true;
}
/**
* @internal
*/
_listen(index) {
if (index >= this._variables.length) {
this.value = true;
return;
}
if (this._subscriptions.length > index) {
return;
}
const __listener = (value) => {
if (value) {
this._listen(index + 1);
} else {
this._unsubscribeFrom(index + 1);
this.value = false;
}
};
const variable = this._variables[index];
this._subscriptions.push(variable.subscribeSilent(__listener));
__listener(variable.value);
return;
}
/**
* @internal
*/
_unsubscribeFrom(index) {
var _a;
while (index < this._subscriptions.length) {
(_a = this._subscriptions.pop()) == null ? void 0 : _a.dispose();
}
}
};
// src/vars/combined.ts
import { DisposableStore } from "@tioniq/disposiq";
var CombinedVariable = class extends CompoundVariable {
constructor(vars) {
if (!(vars == null ? void 0 : vars.length)) {
throw new Error("No variables provided");
}
super(stubArray, arrayEqualityComparer);
/**
* @internal
*/
this._subscriptions = new DisposableStore();
this._vars = vars.slice();
}
activate() {
this._subscriptions.disposeCurrent();
const length = this._vars.length;
const result = new Array(length);
for (let i = 0; i < length; ++i) {
const vary = this._vars[i];
this._subscriptions.add(
vary.subscribeSilent((value) => {
result[i] = value;
this.setForce(result);
})
);
result[i] = vary.value;
}
this.setForce(result);
}
deactivate() {
this._subscriptions.disposeCurrent();
}
getExactValue() {
const length = this._vars.length;
const result = new Array(length);
for (let i = 0; i < length; ++i) {
result[i] = this._vars[i].value;
}
return result;
}
};
var stubArray = Object.freeze([]);
// src/vars/constant.ts
import { emptyDisposable } from "@tioniq/disposiq";
var ConstantVariable = class extends Variable {
constructor(value, equalityComparer) {
super();
this._value = value;
this._equalityComparer = equalityComparer != null ? equalityComparer : defaultEqualityComparer;
}
get value() {
return this._value;
}
get equalityComparer() {
return this._equalityComparer;
}
subscribe(callback) {
callback(this._value);
return emptyDisposable;
}
subscribeSilent(_) {
return emptyDisposable;
}
};
// src/vars/delegate.ts
import {
DisposableAction as DisposableAction3,
DisposableContainer,
emptyDisposable as emptyDisposable2
} from "@tioniq/disposiq";
var DelegateVariable = class extends CompoundVariable {
constructor(sourceOrDefaultValue) {
super(
sourceOrDefaultValue instanceof Variable ? (
// biome-ignore lint/style/noNonNullAssertion: base value will not be used
null
) : sourceOrDefaultValue != void 0 ? sourceOrDefaultValue : (
// biome-ignore lint/style/noNonNullAssertion: base value will not be used
null
)
);
/**
* @internal
*/
this._sourceSubscription = new DisposableContainer();
if (sourceOrDefaultValue instanceof Variable) {
this._source = sourceOrDefaultValue;
} else {
this._source = null;
}
}
/**
* Sets the source variable. The source variable will be used to get the value for the delegate variable
* @param source the source variable or null to remove the source
* @returns a disposable that will remove the source when disposed
*/
setSource(source) {
if (!source) {
if (this._source) {
this.value = this._source.value;
this._source = null;
}
this._sourceSubscription.disposeCurrent();
return emptyDisposable2;
}
this._source = source;
this._sourceSubscription.disposeCurrent();
if (this.active) {
this._sourceSubscription.set(
source.subscribeSilent((v) => this.setForce(v))
);
this.value = source.value;
}
return new DisposableAction3(() => {
if (this._source !== source) {
return;
}
this.setSource(null);
});
}
activate() {
if (this._source === null) {
return;
}
this._sourceSubscription.disposeCurrent();
this._sourceSubscription.set(
this._source.subscribeSilent((v) => this.setForce(v))
);
this.value = this._source.value;
}
deactivate() {
if (this._source === null) {
return;
}
this._sourceSubscription.disposeCurrent();
}
getExactValue() {
return this._source !== null ? this._source.value : super.getExactValue();
}
};
// src/vars/direct.ts
var DirectVariable = class extends Variable {
constructor(initialValue, equalityComparer) {
super();
/**
* @internal
*/
this._chain = new LinkedChain(functionEqualityComparer);
this._value = initialValue;
this._equalityComparer = equalityComparer != null ? equalityComparer : defaultEqualityComparer;
}
get value() {
return this._value;
}
/**
* Sets the value of the variable and notifies all subscribers without checking the equality
* @param value the new value for the variable
*/
set value(value) {
this._value = value;
this._chain.forEach((a) => a(value));
}
get equalityComparer() {
return this._equalityComparer;
}
subscribe(callback) {
const [disposable, added] = this._chain.addUnique(callback);
if (added) {
callback(this._value);
}
return disposable;
}
subscribeSilent(callback) {
return this._chain.addUnique(callback)[0];
}
/**
* Sets the value of the variable without notifying the subscribers
* @param value the new value for the variable
* @remarks Use this method only if you are sure what you are doing. Combine this method with the `notify` method
*/
setSilent(value) {
this._value = value;
}
/**
* Notifies all subscribers about the change of the value forcibly
* @remarks Use this method only if you are sure what you are doing. Combine this method with the `setSilent` method
*/
notify() {
const value = this._value;
this._chain.forEach((a) => a(value));
}
};
// src/vars/func.ts
import {
DisposableContainer as DisposableContainer2,
toDisposable
} from "@tioniq/disposiq";
var FuncVariable = class extends CompoundVariable {
constructor(activate, exactValue, equalityComparer) {
super(null, equalityComparer);
const disposable = new DisposableContainer2();
this._activator = (self) => {
disposable.disposeCurrent();
disposable.set(toDisposable(activate(self)));
};
this._deactivator = () => {
disposable.disposeCurrent();
};
this._exactValue = exactValue;
}
get value() {
return super.value;
}
/**
* Sets the value of the variable. If the value is the same as the current value, the method will do nothing
* @param value the new value of the variable
*/
set value(value) {
super.value = value;
}
setValueForce(value) {
super.setForce(value);
}
setValueSilent(value) {
super.setSilent(value);
}
/**
* A method for setting the value of the variable and notifying subscribers without checking the equality
* @param value the new value of the variable
*/
setForce(value) {
super.setForce(value);
}
/**
* A method for setting the value of the variable without notifying subscribers
* @param value the new value of the variable
*/
setSilent(value) {
super.setSilent(value);
}
/**
* A method for notifying subscribers about the value change
*/
notify() {
super.notify();
}
activate() {
this._activator(this);
}
deactivate() {
this._deactivator(this);
}
getExactValue() {
return this._exactValue();
}
};
// src/vars/invert.ts
import {
DisposableAction as DisposableAction4,
DisposableContainer as DisposableContainer3
} from "@tioniq/disposiq";
var InvertVariable = class extends Variable {
constructor(variable) {
super();
/**
* @internal
*/
this._chain = new LinkedChain(
functionEqualityComparer
);
/**
* @internal
*/
this._value = false;
/**
* @internal
*/
this._subscription = new DisposableContainer3();
this._variable = variable;
}
get value() {
if (this._chain.hasAny) {
return this._value;
}
return !this._variable.value;
}
subscribe(callback) {
if (this._chain.empty) {
this._activate();
}
const [disposable, added] = this._chain.addUnique(callback);
if (added) {
callback(this._value);
}
return new DisposableAction4(() => {
disposable.dispose();
if (this._chain.empty) {
this._deactivate();
}
});
}
subscribeSilent(callback) {
return this._variable.subscribeSilent((value) => callback(!value));
}
/**
* @internal
*/
_activate() {
this._subscription.disposeCurrent();
this._subscription.set(
this._variable.subscribeSilent((v) => {
const value = !v;
this._value = value;
this._chain.forEach((a) => a(value));
})
);
this._value = !this._variable.value;
}
/**
* @internal
*/
_deactivate() {
this._subscription.disposeCurrent();
}
};
// src/vars/map.ts
import { DisposableContainer as DisposableContainer4 } from "@tioniq/disposiq";
var MapVariable = class extends CompoundVariable {
constructor(variable, mapper, equalityComparer) {
super(mapper(variable.value), equalityComparer);
/**
* @internal
*/
this._subscription = new DisposableContainer4();
/**
* @internal
*/
this._listener = (value) => {
this.value = this._mapper(value);
};
this._variable = variable;
this._mapper = mapper;
}
activate() {
this._subscription.disposeCurrent();
this._subscription.set(this._variable.subscribeSilent(this._listener));
this._listener(this._variable.value);
}
deactivate() {
this._subscription.disposeCurrent();
}
getExactValue() {
return this._mapper(this._variable.value);
}
};
// src/vars/max.ts
import { DisposableStore as DisposableStore2 } from "@tioniq/disposiq";
var MaxVariable = class extends CompoundVariable {
constructor(vars) {
super(0);
/**
* @internal
*/
this._subscriptions = new DisposableStore2();
this._vars = vars.slice();
}
activate() {
const vars = this._vars;
const length = vars.length;
const subscriptions = this._subscriptions;
subscriptions.disposeCurrent();
for (let i = 0; i < length; ++i) {
subscriptions.add(
vars[i].subscribeSilent(() => {
this.postValue();
})
);
}
this.postValue();
}
deactivate() {
this._subscriptions.dispose();
}
getExactValue() {
const vars = this._vars;
const length = vars.length;
let result = Number.NEGATIVE_INFINITY;
for (let i = 0; i < length; ++i) {
result = Math.max(result, vars[i].value);
}
return result;
}
postValue() {
const vars = this._vars;
const length = vars.length;
let result = Number.NEGATIVE_INFINITY;
for (let i = 0; i < length; ++i) {
result = Math.max(result, vars[i].value);
}
this.value = result;
}
};
// src/vars/min.ts
import { DisposableStore as DisposableStore3 } from "@tioniq/disposiq";
var MinVariable = class extends CompoundVariable {
constructor(vars) {
super(0);
/**
* @internal
*/
this._subscriptions = new DisposableStore3();
this._vars = vars.slice();
}
activate() {
const vars = this._vars;
const length = vars.length;
const subscriptions = this._subscriptions;
subscriptions.disposeCurrent();
for (let i = 0; i < length; ++i) {
subscriptions.add(
vars[i].subscribeSilent(() => {
this.postValue();
})
);
}
this.postValue();
}
deactivate() {
this._subscriptions.dispose();
}
getExactValue() {
const vars = this._vars;
const length = vars.length;
let result = Number.POSITIVE_INFINITY;
for (let i = 0; i < length; ++i) {
result = Math.min(result, vars[i].value);
}
return result;
}
postValue() {
const vars = this._vars;
const length = vars.length;
let result = Number.POSITIVE_INFINITY;
for (let i = 0; i < length; ++i) {
result = Math.min(result, vars[i].value);
}
this.value = result;
}
};
// src/vars/mutable.ts
var MutableVariable = class extends Variable {
constructor(value, equalityComparer) {
super();
/**
* @internal
*/
this._chain = new LinkedChain(functionEqualityComparer);
this._value = value;
this._equalityComparer = equalityComparer != null ? equalityComparer : defaultEqualityComparer;
}
get value() {
return this._value;
}
/**
* Sets the value of the variable. The value will be changed only if the new value is different from the old value
* @param value the new value for the variable
*/
set value(value) {
if (this._equalityComparer(value, this._value)) {
return;
}
this._value = value;
this._chain.forEach((a) => a(value));
}
/**
* Returns the equality comparer used to compare the old and new values of the variable
*/
get equalityComparer() {
return this._equalityComparer;
}
subscribe(callback) {
const [disposable, added] = this._chain.addUnique(callback);
if (added) {
callback(this._value);
}
return disposable;
}
subscribeSilent(callback) {
return this._chain.addUnique(callback)[0];
}
/**
* Sets the value of the variable without notifying the subscribers
* @param value the new value for the variable
* @remarks Use this method only if you are sure what you are doing. Combine this method with the `notify` method
*/
setSilent(value) {
this._value = value;
}
/**
* Notifies all subscribers about the change of the value forcibly
* @remarks Use this method only if you are sure what you are doing. Combine this method with the `setSilent` method
*/
notify() {
const value = this._value;
this._chain.forEach((a) => a(value));
}
};
// src/vars/or.ts
import { disposeAll as disposeAll2 } from "@tioniq/disposiq";
var OrVariable = class extends CompoundVariable {
constructor(variables) {
super(false);
/**
* @internal
*/
this._subscriptions = [];
this._variables = variables;
}
activate() {
this._listen(0);
}
deactivate() {
disposeAll2(this._subscriptions);
}
getExactValue() {
const variables = this._variables;
for (let i = 0; i < variables.length; ++i) {
if (variables[i].value) {
return true;
}
}
return false;
}
/**
* @internal
*/
_listen(index) {
if (index >= this._variables.length) {
this.value = false;
return;
}
if (this._subscriptions.length > index) {
return;
}
const __listener = (value) => {
if (value) {
this._unsubscribeFrom(index + 1);
this.value = true;
} else {
this._listen(index + 1);
}
};
const variable = this._variables[index];
this._subscriptions.push(variable.subscribeSilent(__listener));
__listener(variable.value);
return;
}
/**
* @internal
*/
_unsubscribeFrom(index) {
var _a;
while (index < this._subscriptions.length) {
(_a = this._subscriptions.pop()) == null ? void 0 : _a.dispose();
}
}
};
// src/vars/seal.ts
import {
DisposableAction as DisposableAction5,
DisposableContainer as DisposableContainer5,
emptyDisposable as emptyDisposable3
} from "@tioniq/disposiq";
var SealVariable = class extends Variable {
constructor(vary, equalityComparer) {
super();
/**
* @internal
*/
this._chain = new LinkedChain(functionEqualityComparer);
/**
* @internal
*/
this._varSubscription = new DisposableContainer5();
/**
* @internal
*/
// biome-ignore lint/style/noNonNullAssertion: the field access is safe because it used only in the sealed state
this._value = null;
/**
* @internal
*/
this._sealed = false;
this._var = vary;
this._equalityComparer = typeof equalityComparer === "function" ? equalityComparer : defaultEqualityComparer;
}
get value() {
if (this._sealed) {
return this._value;
}
if (this._chain.empty) {
return this._var.value;
}
return this._value;
}
get equalityComparer() {
return this._equalityComparer;
}
subscribe(callback) {
if (this._sealed) {
callback(this._value);
return emptyDisposable3;
}
if (this._chain.empty) {
this._activate();
}
const [disposable, added] = this._chain.addUnique(callback);
if (added) {
callback(this._value);
}
return new DisposableAction5(() => {
disposable.dispose();
if (!this._sealed && this._chain.empty) {
this._deactivate();
}
});
}
subscribeSilent(callback) {
if (this._sealed) {
return emptyDisposable3;
}
if (this._chain.empty) {
this._activate();
}
const disposable = this._chain.addUnique(callback)[0];
return new DisposableAction5(() => {
disposable.dispose();
if (!this._sealed && this._chain.empty) {
this._deactivate();
}
});
}
/**
* Seals the variable. If the variable is already sealed, the method will do nothing
* @param valueToSeal the value to seal. If the value is not provided, the current value of the variable will be
* sealed
* @returns true if the variable was sealed, false if the variable was already sealed
*/
seal(valueToSeal) {
if (this._sealed) {
return false;
}
this._sealed = true;
this._varSubscription.dispose();
if (arguments.length === 0) {
const currentValue = this._chain.empty ? this._var.value : this._value;
this._varSubscription.dispose();
this._sealValue(currentValue);
return true;
}
this._varSubscription.dispose();
this._sealValue(valueToSeal);
return true;
}
/**
* @internal
*/
_activate() {
this._varSubscription.disposeCurrent();
this._varSubscription.set(
this._var.subscribeSilent((v) => {
this._value = v;
this._chain.forEach((a) => a(v));
})
);
this._value = this._var.value;
}
/**
* @internal
*/
_deactivate() {
this._varSubscription.disposeCurrent();
}
/**
* @internal
*/
_sealValue(value) {
if (this._equalityComparer(value, this._value)) {
this._chain.clear();
return;
}
this._value = value;
this._chain.forEach((a) => a(value));
this._chain.clear();
}
};
// src/vars/sum.ts
import { DisposableStore as DisposableStore4 } from "@tioniq/disposiq";
var SumVariable = class extends CompoundVariable {
constructor(vars) {
super(0);
/**
* @internal
*/
this._subscriptions = new DisposableStore4();
this._vars = vars.slice();
}
activate() {
const vars = this._vars;
const length = vars.length;
const subscriptions = this._subscriptions;
subscriptions.disposeCurrent();
for (let i = 0; i < length; ++i) {
const variable = vars[i];
subscriptions.add(
variable.subscribeSilent(() => {
this.postValue();
})
);
}
this.postValue();
}
deactivate() {
this._subscriptions.dispose();
}
getExactValue() {
const vars = this._vars;
const length = vars.length;
let result = 0;
for (let i = 0; i < length; ++i) {
result += vars[i].value;
}
return result;
}
postValue() {
const vars = this._vars;
const length = vars.length;
let result = 0;
for (let i = 0; i < length; ++i) {
result += vars[i].value;
}
this.value = result;
}
};
// src/vars/switch-map.ts
import { DisposableContainer as DisposableContainer6 } from "@tioniq/disposiq";
var SwitchMapVariable = class extends CompoundVariable {
constructor(vary, mapper, equalityComparer) {
super(null, equalityComparer);
/**
* @internal
*/
this._switchSubscription = new DisposableContainer6();
/**
* @internal
*/
this._varSubscription = new DisposableContainer6();
this._var = vary;
this._mapper = mapper;
}
activate() {
this._switchSubscription.disposeCurrent();
this._switchSubscription.set(
this._var.subscribeSilent((i) => this._handleSwitch(i))
);
this._handleSwitch(this._var.value);
}
deactivate() {
this._switchSubscription.disposeCurrent();
this._varSubscription.disposeCurrent();
}
getExactValue() {
return this._mapper(this._var.value).value;
}
/**
* @internal
*/
_handleSwitch(input) {
this._varSubscription.disposeCurrent();
const mappedVariable = this._mapper(input);
this._varSubscription.set(
mappedVariable.subscribeSilent((result) => {
this.value = result;
})
);
this.value = mappedVariable.value;
}
};
// src/vars/throttled.ts
import { DisposableContainer as DisposableContainer7 } from "@tioniq/disposiq";
var noScheduledValue = Object.freeze({});
var ThrottledVariable = class extends CompoundVariable {
constructor(vary, onUpdate, equalityComparer) {
super(null, equalityComparer);
/**
* @internal
*/
this._subscription = new DisposableContainer7();
/**
* @internal
*/
this._updateSubscription = new DisposableContainer7();
/**
* @internal
*/
this._scheduledValue = noScheduledValue;
this._var = vary;
this._onUpdate = onUpdate;
}
activate() {
this._subscription.disposeCurrent();
this._subscription.set(
this._var.subscribeSilent((v) => {
this._scheduleUpdate(v);
})
);
this.value = this._var.value;
}
deactivate() {
this._subscription.disposeCurrent();
this._updateSubscription.disposeCurrent();
}
getExactValue() {
return this._var.value;
}
/**
* @internal
*/
_scheduleUpdate(value) {
if (this._scheduledValue !== noScheduledValue) {
this._scheduledValue = value;
return;
}
this._scheduledValue = value;
this._updateSubscription.disposeCurrent();
this._updateSubscription.set(
this._onUpdate.subscribeOnce(() => {
const val = this._scheduledValue;
this._scheduledValue = noScheduledValue;
this.value = val === noScheduledValue ? this._var.value : val;
})
);
}
};
// src/functions.ts
import { DisposableAction as DisposableAction7 } from "@tioniq/disposiq";
// src/events/observer.ts
var EventObserver = class {
};
// src/events/dispatcher.ts
var EventDispatcher = class extends EventObserver {
constructor() {
super(...arguments);
/**
* @internal
*/
this._nodes = new LinkedChain(functionEqualityComparer);
}
subscribe(action) {
return this._nodes.add(action);
}
/**
* Dispatches the event to all subscribers
* @param value the value of the event
*/
dispatch(value) {
this._nodes.forEach((a) => a(value));
}
/**
* Checks if there are any subscriptions
* @returns true if there are any subscriptions, false otherwise
*/
get hasSubscriptions() {
return this._nodes.hasAny;
}
};
// src/events/safe-dispatcher.ts
var EventSafeDispatcher = class extends EventObserver {
constructor() {
super(...arguments);
/**
* @internal
*/
this._nodes = new LinkedChain(functionEqualityComparer);
}
subscribe(action) {
return this._nodes.add(action);
}
/**
* Dispatches the event to all subscribers safely
* @param value the value of the event
* @param onError error callback
*/
dispatch(value, onError) {
this._nodes.forEach((a) => {
try {
a(value);
} catch (e) {
onError == null ? void 0 : onError(e);
}
});
}
/**
* Checks if there are any subscriptions
* @returns true if there are any subscriptions, false otherwise
*/
get hasSubscriptions() {
return this._nodes.hasAny;
}
};
// src/events/stub.ts
import { emptyDisposable as emptyDisposable4 } from "@tioniq/disposiq";
var EventObserverStub = class extends EventObserver {
subscribe() {
return emptyDisposable4;
}
};
// src/events/lazy.ts
import {
DisposableAction as DisposableAction6,
DisposableContainer as DisposableContainer8,
toDisposable as toDisposable2
} from "@tioniq/disposiq";
var LazyEventDispatcher = class extends EventObserver {
constructor(activator) {
super();
/**
* @internal
*/
this._nodes = new LinkedChain(functionEqualityComparer);
/**
* @internal
*/
this._subscription = new DisposableContainer8();
this._activator = activator;
}
/**
* Checks if there are any subscriptions
* @returns true if there are any subscriptions, false otherwise
*/
get hasSubscription() {
return this._nodes.hasAny;
}
subscribe(callback) {
let subscription;
if (this._nodes.empty) {
subscription = this._nodes.add(callback);
this._activate();
} else {
subscription = this._nodes.add(callback);
}
return new DisposableAction6(() => {
subscription.dispose();
if (this._nodes.hasAny) {
return;
}
this._deactivate();
});
}
/**
* Dispatches the event to all subscribers
* @param value the value of the event
*/
dispatch(value) {
this._nodes.forEach((a) => a(value));
}
/**
* @internal
*/
_activate() {
this._subscription.disposeCurrent();
this._subscription.set(toDisposable2(this._activator(this)));
}
/**
* @internal
*/
_deactivate() {
this._subscription.disposeCurrent();
}
};
// src/events/functions.ts
import { DisposableStore as DisposableStore5 } from "@tioniq/disposiq";
function merge(...observers) {
return new LazyEventDispatcher((dispatcher) => {
const disposableStore = new DisposableStore5();
for (const t of observers) {
disposableStore.add(t.subscribe((v) => dispatcher.dispatch(v)));
}
return disposableStore;
});
}
// src/events/extensions.ts
import {
DisposableContainer as DisposableContainer9,
emptyDisposable as emptyDisposable5
} from "@tioniq/disposiq";
// src/noop.ts
var noop = Object.freeze(() => {
});
// src/events/extensions.ts
EventObserver.prototype.subscribeOnce = function(callback) {
const subscription = new DisposableContainer9();
subscription.set(
this.subscribe((value) => {
subscription.dispose();
callback(value);
})
);
return subscription;
};
EventObserver.prototype.subscribeOnceWhere = function(callback, condition) {
const subscription = new DisposableContainer9();
subscription.set(
this.subscribe((value) => {
if (!condition(value)) {
return;
}
subscription.dispose();
callback(value);
})
);
return subscription;
};
EventObserver.prototype.subscribeWhere = function(callback, condition) {
return this.subscribe((value) => {
if (condition(value)) {
callback(value);
}
});
};
EventObserver.prototype.subscribeOn = function(callback, condition) {
return condition.subscribeDisposable(
(value) => value ? this.subscribe(callback) : emptyDisposable5
);
};
EventObserver.prototype.map = function(mapper) {
return new LazyEventDispatcher(
(dispatcher) => this.subscribe((value) => dispatcher.dispatch(mapper(value)))
);
};
EventObserver.prototype.where = function(condition) {
return new LazyEventDispatcher(
(dispatcher) => this.subscribe((value) => {
if (condition(value)) {
dispatcher.dispatch(value);
}
})
);
};
EventObserver.prototype.awaited = function(onRejection) {
if (typeof onRejection === "function") {
return new LazyEventDispatcher(
(dispatcher) => this.subscribe((value) => {
if (value instanceof Promise) {
value.then(
(v) => {
dispatcher.dispatch(v);
},
(e) => {
onRejection(e, value);
}
);
} else {
dispatcher.dispatch(value);
}
})
);
}
return new LazyEventDispatcher(
(dispatcher) => this.subscribe((value) => {
if (value instanceof Promise) {
value.then((v) => {
dispatcher.dispatch(v);
}, noop);
} else {
dispatcher.dispatch(value);
}
})
);
};
EventDispatcher.prototype.dispatchSafe = function(value) {
try {
this.dispatch(value);
} catch (e) {
}
};
// src/is.ts
function isVariable(value) {
return value instanceof Variable;
}
function isVariableOf(value, typeCheckerOrExampleValue) {
if (!(value instanceof Variable)) {
return false;
}
if (typeCheckerOrExampleValue == void 0) {
return true;
}
if (typeof typeCheckerOrExampleValue === "function") {
return typeCheckerOrExampleValue(value.value);
}
return typeof value.value === typeof typeCheckerOrExampleValue;
}
function isMutableVariable(value) {
return value instanceof MutableVariable;
}
function isDelegateVariable(value) {
return value instanceof DelegateVariable;
}
// src/functions.ts
function createVar(initialValue, equalityComparer) {
return new MutableVariable(initialValue, equalityComparer);
}
function createFuncVar(activator, exactValue, equalityComparer) {
return new FuncVariable(activator, exactValue, equalityComparer);
}
function createConst(value) {
return new ConstantVariable(value);
}
function createDelegate(sourceOrDefaultValue) {
return new DelegateVariable(sourceOrDefaultValue);
}
function createDirect(initialValue) {
return new DirectVariable(initialValue);
}
function or(...variables) {
return new OrVariable(variables);
}
function and(...variables) {
return new AndVariable(variables);
}
function sum(...variables) {
return new SumVariable(variables);
}
function min(...variables) {
return new MinVariable(variables);
}
function max(...variables) {
return new MaxVariable(variables);
}
function combine(...vars) {
if (vars.length === 0) {
throw new Error("At least one variable must be provided");
}
if (vars.length === 1) {
return vars[0];
}
return new CombinedVariable(vars);
}
function createDelayDispatcher(delay) {
return new LazyEventDispatcher((dispatcher) => {
const timeout = setTimeout(() => dispatcher.dispatch(), delay);
return new DisposableAction7(() => clearTimeout(timeout));
});
}
function toVariable(value) {
return isVariableOf(value) ? value : createConst(value);
}
// src/extensions.ts
import {
DisposableAction as DisposableAction8,
DisposableContainer as DisposableContainer10,
emptyDisposable as emptyDisposable6,
toDisposable as toDisposable3
} from "@tioniq/disposiq";
Variable.prototype.subscribeDisposable = function(callback) {
const container = new DisposableContainer10();
const subscription = this.subscribe((v) => {
container.disposeCurrent();
container.set(toDisposable3(callback(v)));
});
return new DisposableAction8(() => {
subscription.dispose();
container.dispose();
});
};
Variable.prototype.subscribeOnceWhere = function(callback, condition) {
const container = new DisposableContainer10();
container.set(
this.subscribeSilent((v) => {
if (!condition(v)) {
return;
}
container.dispose();
callback(v);
})
);
const value = this.value;
if (!condition(value)) {
return container;
}
container.dispose();
callback(value);
return emptyDisposable6;
};
Variable.prototype.subscribeWhere = function(callback, condition, equalityComparer) {
if (typeof condition === "function") {
return this.subscribe((v) => {
if (condition(v)) {
callback(v);
}
});
}
const comparer = equalityComparer != null ? equalityComparer : defaultEqualityComparer;
return this.subscribe((v) => {
if (comparer(v, condition)) {
callback(v);
}
});
};
Variable.prototype.map = function(mapper, equalityComparer) {
return new MapVariable(this, mapper, equalityComparer);
};
Variable.prototype.or = function(other) {
return new OrVariable([this, other]);
};
Variable.prototype.and = function(other) {
return new AndVariable([this, other]);
};
Variable.prototype.invert = function() {
return new InvertVariable(this);
};
Variable.prototype.with = function(...others) {
return new CombinedVariable([this, ...others]);
};
Variable.prototype.switchMap = function(mapper, equalityComparer) {
return new SwitchMapVariable(this, mapper, equalityComparer);
};
Variable.prototype.throttle = function(delay, equalityComparer) {
if (typeof delay === "number") {
return new ThrottledVariable(
this,
createDelayDispatcher(delay),
equalityComparer
);
}
return new ThrottledVariable(this, delay, equalityComparer);
};
Variable.prototype.streamTo = function(receiver) {
return this.subscribe((value) => {
receive