@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. The i
1,929 lines (1,895 loc) • 73.9 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
AndVariable: () => AndVariable,
BaseLinkedChain: () => BaseLinkedChain,
CombinedVariable: () => CombinedVariable,
CompoundVariable: () => CompoundVariable,
ConstVar: () => ConstantVariable,
ConstVariable: () => ConstantVariable,
ConstantVariable: () => ConstantVariable,
DelegateVariable: () => DelegateVariable,
DirectVariable: () => DirectVariable,
EventDispatcher: () => EventDispatcher,
EventObserver: () => EventObserver,
EventObserverStub: () => EventObserverStub,
EventSafeDispatcher: () => EventSafeDispatcher,
FuncVar: () => FuncVariable,
FuncVariable: () => FuncVariable,
ImmutableVar: () => ConstantVariable,
InvertVariable: () => InvertVariable,
LazyEventDispatcher: () => LazyEventDispatcher,
LazyVariable: () => FuncVariable,
LinkedActionChain: () => LinkedActionChain,
LinkedChain: () => LinkedChain,
MapVariable: () => MapVariable,
MaxVariable: () => MaxVariable,
MinVariable: () => MinVariable,
MutableVar: () => MutableVariable,
MutableVariable: () => MutableVariable,
ObservableList: () => ObservableList,
OrVariable: () => OrVariable,
ReadonlyVar: () => ConstantVariable,
SealVariable: () => SealVariable,
SumVariable: () => SumVariable,
SwitchMapVariable: () => SwitchMapVariable,
ThrottledVariable: () => ThrottledVariable,
Var: () => Variable,
Variable: () => Variable,
Vary: () => MutableVariable,
and: () => and,
arrayEqualityComparer: () => arrayEqualityComparer,
combine: () => combine,
createConst: () => createConstVar,
createConstVar: () => createConstVar,
createDelayDispatcher: () => createDelayDispatcher,
createDelegate: () => createDelegate,
createDelegateVar: () => createDelegate,
createDirect: () => createDirect,
createDirectVar: () => createDirect,
createFuncVar: () => createFuncVar,
createLazyVar: () => createFuncVar,
createVar: () => createVar,
defaultEqualityComparer: () => defaultEqualityComparer,
emptyString: () => emptyStringVariable,
emptyStringVariable: () => emptyStringVariable,
falseVar: () => falseVariable,
falseVariable: () => falseVariable,
functionEqualityComparer: () => functionEqualityComparer,
generalEqualityComparer: () => generalEqualityComparer,
isDelegateVariable: () => isDelegateVariable,
isMutableVariable: () => isMutableVariable,
isVariable: () => isVariable,
isVariableOf: () => isVariableOf,
max: () => max,
merge: () => merge,
min: () => min,
nullVar: () => nullVariable,
nullVariable: () => nullVariable,
objectEqualityComparer: () => objectEqualityComparer,
or: () => or,
setDefaultEqualityComparer: () => setDefaultEqualityComparer,
simpleEqualityComparer: () => simpleEqualityComparer,
strictEqualityComparer: () => strictEqualityComparer,
sum: () => sum,
toVariable: () => toVariable,
trueVar: () => trueVariable,
trueVariable: () => trueVariable,
undefinedVar: () => undefinedVariable,
undefinedVariable: () => undefinedVariable,
zeroVar: () => zeroVariable,
zeroVariable: () => zeroVariable
});
module.exports = __toCommonJS(index_exports);
// src/functions.ts
var import_disposiq20 = require("@tioniq/disposiq");
// src/linked-chain.ts
var import_disposiq = require("@tioniq/disposiq");
// 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
var BaseLinkedChain = class {
constructor(equalityComparer) {
/**
* @internal
*/
this._head = null;
/**
* @internal
*/
this._tail = null;
/**
* @internal
*/
this._invoking = false;
/**
* @internal
*/
this._pendingHead = null;
/**
* @internal
*/
this._pendingTail = null;
/**
* Represents a callback action triggered when a specific condition
* or state is determined to be empty.
*
*/
this.onEmpty = 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 [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(this, value);
this._pendingHead = node;
} else {
node = new ChainNode(this, value, this._pendingTail, null);
this._pendingTail.next = node;
}
this._pendingTail = node;
return node;
}
if (this._head === null) {
node = new ChainNode(this, value);
this._head = node;
} else {
node = new ChainNode(this, value, this._tail, null);
this._tail.next = node;
}
this._tail = node;
return 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 [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(this, value);
this._head = node;
this._tail = node;
} else {
node = new ChainNode(this, value, null, this._head);
this._head.previous = node;
this._head = node;
}
return 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 = _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)) {
checkNode.dispose();
return true;
}
checkNode = checkNode.next;
}
checkNode = this._pendingHead;
while (checkNode !== null) {
if (this._equalityComparer(checkNode.value, value)) {
checkNode.dispose();
return true;
}
checkNode = checkNode.next;
}
return false;
}
/**
* Removes all elements from the chain
*/
clear() {
var _a;
let node = this._head;
if (node === null && this._pendingHead === null) {
return;
}
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;
}
(_a = this.onEmpty) == null ? void 0 : _a.call(this);
}
/**
* 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() {
var _a;
const node = this._head;
if (node === null) {
return null;
}
this._head = null;
this._tail = null;
if (this._pendingHead === null) {
(_a = this.onEmpty) == null ? void 0 : _a.call(this);
}
return node;
}
/**
* @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;
}
};
var LinkedChain = class extends BaseLinkedChain {
constructor() {
super(...arguments);
/**
* @internal
*/
this._actionHead = null;
}
/**
* 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 = [handler];
return;
}
this._actionHead.push(handler);
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;
} 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;
}
handler = this._actionHead.shift();
if (this._actionHead.length === 0) {
this._actionHead = null;
}
}
}
};
var LinkedActionChain = class extends BaseLinkedChain {
constructor() {
super(functionEqualityComparer);
/**
* @internal
*/
this._actionHead = null;
}
/**
* Iterates over the elements of the chain and invokes each element
* @param value the value to pass to each element
*/
forEach(value) {
let theValue = value;
while (true) {
if (this._head !== null) {
if (this._invoking) {
if (this._actionHead === null) {
this._actionHead = [theValue];
return;
}
this._actionHead.push(theValue);
return;
}
this._invoking = true;
let node = this._head;
while (node !== null) {
if (!node.disposed) {
node.value(theValue);
}
node = node.next;
}
this._invoking = false;
if (this._pendingHead != null) {
if (this._head == null) {
this._head = this._pendingHead;
} 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;
}
theValue = this._actionHead.shift();
if (this._actionHead.length === 0) {
this._actionHead = null;
}
}
}
};
var ChainNode = class extends import_disposiq.Disposiq {
constructor(chain, value, previous, next) {
super();
this.disposed = false;
this.chain = chain;
this.value = value;
this.previous = previous != null ? previous : null;
this.next = next != null ? next : null;
}
dispose() {
var _a, _b;
if (this.disposed) {
return;
}
this.disposed = true;
const chain = this.chain;
if (this === chain._head) {
if (this.next === null) {
chain._head = null;
chain._tail = null;
if (chain._pendingHead === null) {
(_a = chain.onEmpty) == null ? void 0 : _a.call(chain);
}
return;
}
chain._head = this.next;
chain._head.previous = null;
return;
}
if (this === chain._tail) {
chain._tail = this.previous;
chain._tail.next = null;
return;
}
if (this === chain._pendingHead) {
if (this.next === null) {
chain._pendingHead = null;
chain._pendingTail = null;
if (chain._head === null) {
(_b = chain.onEmpty) == null ? void 0 : _b.call(chain);
}
return;
}
chain._pendingHead = this.next;
chain._pendingHead.previous = null;
return;
}
if (this === chain._pendingTail) {
chain._pendingTail = this.previous;
chain._pendingTail.next = null;
return;
}
if (this.previous !== null) {
this.previous.next = this.next;
}
if (this.next !== null) {
this.next.previous = this.previous;
}
}
};
function _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;
}
// src/events/observer.ts
var EventObserver = class {
};
// src/events/dispatcher.ts
var EventDispatcher = class extends EventObserver {
constructor() {
super(...arguments);
/**
* @internal
*/
this._nodes = new LinkedActionChain();
}
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(value);
}
/**
* Checks if there are any subscriptions
* @returns true if there are any subscriptions, false otherwise
*/
get hasSubscriptions() {
return this._nodes.hasAny;
}
};
// src/events/functions.ts
var import_disposiq3 = require("@tioniq/disposiq");
// src/events/lazy.ts
var import_disposiq2 = require("@tioniq/disposiq");
var LazyEventDispatcher = class extends EventObserver {
constructor(activator) {
super();
/**
* @internal
*/
this._nodes = new LinkedActionChain();
/**
* @internal
*/
this._subscription = new import_disposiq2.DisposableContainer();
this._activator = activator;
this._nodes.onEmpty = () => this._deactivate();
}
/**
* 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 subscription;
}
/**
* Dispatches the event to all subscribers
* @param value the value of the event
*/
dispatch(value) {
this._nodes.forEach(value);
}
/**
* @internal
*/
_activate() {
this._subscription.disposeCurrent();
this._subscription.set((0, import_disposiq2.toDisposable)(this._activator(this)));
}
/**
* @internal
*/
_deactivate() {
this._subscription.disposeCurrent();
}
};
// src/events/functions.ts
function merge(...observers) {
return new LazyEventDispatcher((dispatcher) => {
const disposableStore = new import_disposiq3.DisposableStore();
for (const t of observers) {
disposableStore.add(t.subscribe((v) => dispatcher.dispatch(v)));
}
return disposableStore;
});
}
// 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
var import_disposiq4 = require("@tioniq/disposiq");
var EventObserverStub = class extends EventObserver {
subscribe() {
return import_disposiq4.emptyDisposable;
}
};
// src/events/extensions.ts
var import_disposiq5 = require("@tioniq/disposiq");
// src/noop.ts
var noop = Object.freeze(() => {
});
// src/events/extensions.ts
EventObserver.prototype.subscribeOnce = function(callback) {
const subscription = new import_disposiq5.DisposableContainer();
subscription.set(
this.subscribe((value) => {
subscription.dispose();
callback(value);
})
);
return subscription;
};
EventObserver.prototype.subscribeOnceWhere = function(callback, condition) {
const subscription = new import_disposiq5.DisposableContainer();
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) : import_disposiq5.emptyDisposable
);
};
EventObserver.prototype.subscribeDisposable = function(callback) {
const container = new import_disposiq5.DisposableContainer();
const subscription = this.subscribe((v) => {
container.disposeCurrent();
container.set(callback(v));
});
return new import_disposiq5.DisposableAction(() => {
subscription.dispose();
container.dispose();
});
};
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/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/vars/and.ts
var import_disposiq6 = require("@tioniq/disposiq");
// src/vars/compound.ts
var CompoundVariable = class extends Variable {
constructor(initValue, equalityComparer) {
super();
/**
* @internal
*/
this._chain = new LinkedActionChain();
this._value = initValue;
this._equalityComparer = equalityComparer != null ? equalityComparer : defaultEqualityComparer;
this._chain.onEmpty = () => this.deactivate();
}
/**
* Checks if there are any subscriptions
* @returns true if there are any subscriptions, false otherwise
*/
// TODO: should return true during `subscribe`
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(value);
}
subscribe(callback) {
if (this._chain.empty) {
this.activate();
}
const [disposable, added] = this._chain.addUnique(callback);
if (added) {
callback(this._value);
}
return disposable;
}
subscribeSilent(callback) {
if (this._chain.empty) {
this.activate();
}
return this._chain.addUnique(callback)[0];
}
/**
* 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(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(value);
}
/**
* A method for notifying subscribers about the value change
* @protected internal use only
*/
notify() {
const value = this._value;
this._chain.forEach(value);
}
};
// src/vars/and.ts
var AndVariable = class extends CompoundVariable {
constructor(variables) {
super(false);
/**
* @internal
*/
this._subscriptions = [];
this._variables = variables;
}
activate() {
this._listen(0);
}
deactivate() {
(0, import_disposiq6.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
var import_disposiq7 = require("@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 import_disposiq7.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
var import_disposiq8 = require("@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 import_disposiq8.emptyDisposable;
}
subscribeSilent(_) {
return import_disposiq8.emptyDisposable;
}
};
// src/vars/delegate.ts
var import_disposiq9 = require("@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 import_disposiq9.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 import_disposiq9.emptyDisposable;
}
this._source = source;
this._sourceSubscription.disposeCurrent();
if (this.active) {
this._sourceSubscription.set(
source.subscribeSilent((v) => this.setForce(v))
);
this.value = source.value;
}
return new import_disposiq9.DisposableAction(() => {
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 LinkedActionChain();
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(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() {
this._chain.forEach(this._value);
}
};
// src/vars/func.ts
var import_disposiq10 = require("@tioniq/disposiq");
var FuncVariable = class extends CompoundVariable {
constructor(activate, exactValue, equalityComparer) {
super(null, equalityComparer);
const disposable = new import_disposiq10.DisposableContainer();
this._activator = (self) => {
disposable.disposeCurrent();
disposable.set((0, import_disposiq10.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
var import_disposiq11 = require("@tioniq/disposiq");
var InvertVariable = class extends Variable {
constructor(variable) {
super();
/**
* @internal
*/
this._chain = new LinkedActionChain();
/**
* @internal
*/
this._value = false;
/**
* @internal
*/
this._subscription = new import_disposiq11.DisposableContainer();
this._variable = variable;
this._chain.onEmpty = () => this._deactivate();
}
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 disposable;
}
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(value);
})
);
this._value = !this._variable.value;
}
/**
* @internal
*/
_deactivate() {
this._subscription.disposeCurrent();
}
};
// src/vars/map.ts
var import_disposiq12 = require("@tioniq/disposiq");
var MapVariable = class extends CompoundVariable {
constructor(variable, mapper, equalityComparer) {
super(null, equalityComparer);
/**
* @internal
*/
this._subscription = new import_disposiq12.DisposableContainer();
/**
* @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
var import_disposiq13 = require("@tioniq/disposiq");
var MaxVariable = class extends CompoundVariable {
constructor(vars) {
super(0);
/**
* @internal
*/
this._subscriptions = new import_disposiq13.DisposableStore();
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
var import_disposiq14 = require("@tioniq/disposiq");
var MinVariable = class extends CompoundVariable {
constructor(vars) {
super(0);
/**
* @internal
*/
this._subscriptions = new import_disposiq14.DisposableStore();
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 LinkedActionChain();
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(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() {
this._chain.forEach(this._value);
}
};
// src/vars/or.ts
var import_disposiq15 = require("@tioniq/disposiq");
var OrVariable = class extends CompoundVariable {
constructor(variables) {
super(false);
/**
* @internal
*/
this._subscriptions = [];
this._variables = variables;
}
activate() {
this._listen(0);
}
deactivate() {
(0, import_disposiq15.disposeAll)(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
var import_disposiq16 = require("@tioniq/disposiq");
var SealVariable = class extends Variable {
constructor(vary, equalityComparer) {
super();
/**
* @internal
*/
this._chain = new LinkedActionChain();
/**
* @internal
*/
this._varSubscription = new import_disposiq16.DisposableContainer();
/**
* @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;
this._chain.onEmpty = () => {
if (!this._sealed) {
this._deactivate();
}
};
}
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 import_disposiq16.emptyDisposable;
}
if (this._chain.empty) {
this._activate();
}
const [disposable, added] = this._chain.addUnique(callback);
if (added) {
callback(this._value);
}
return disposable;
}
subscribeSilent(callback) {
if (this._sealed) {
return import_disposiq16.emptyDisposable;
}
if (this._chain.empty) {
this._activate();
}
return this._chain.addUnique(callback)[0];
}
/**
* 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(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(value);
this._chain.clear();
}
};
// src/vars/sum.ts
var import_disposiq17 = require("@tioniq/disposiq");
var SumVariable = class extends CompoundVariable {
constructor(vars) {
super(0);
/**
* @internal
*/
this._subscriptions = new import_disposiq17.DisposableStore();
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
var import_disposiq18 = require("@tioniq/disposiq");
var SwitchMapVariable = class extends CompoundVariable {
constructor(vary, mapper, equalityComparer) {
super(null, equalityComparer);
/**
* @internal
*/
this._switchSubscription = new import_disposiq18.DisposableContainer();
/**
* @internal
*/
this._varSubscription = new import_disposiq18.DisposableContainer();
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
var import_disposiq19 = require("@tioniq/disposiq");
var noScheduledValue = Object.freeze({});
var ThrottledVariable = class extends CompoundVariable {
constructor(vary, onUpdate, equalityComparer) {
super(null, equalityComparer);
/**
* @internal
*/
this._subscription = new import_disposiq19.DisposableContainer();
/**
* @internal
*/
this._updateSubscription = new import_disposiq19.DisposableContainer();
/**
* @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 (thi