VoidShell/DerivedConnectable.ts

77 lines
2.2 KiB
TypeScript

import { type Subscribable, type Connectable, kebabify } from "astal/binding";
export class Derived<
ResultType,
ObjectType extends Connectable,
PropKeys extends keyof ObjectType & string,
> implements Subscribable<ResultType>
{
private upstreamSubscriptions: number[];
private downstreamSubscriptions: ((value: ResultType) => void)[] = [];
private properties: PropKeys[];
// @ts-expect-error - value _is_ definitively defined in the constructor, thorugh the call to refresh()
private value: ResultType;
constructor(
private object: ObjectType,
private transform: (...args: any) => ResultType,
...properties: PropKeys[]
) {
this.upstreamSubscriptions = properties.map((prop) =>
object.connect(kebabify(prop), () => this.refresh()),
);
this.properties = properties;
this.refresh(true);
}
private refresh(initial = false) {
this.value = this.transform(
this.properties.map((prop) => this.object[prop]),
);
if (!initial) {
this.downstreamSubscriptions.forEach((callback) => callback(this.value));
}
}
subscribe(callback: (value: ResultType) => void): () => void {
this.downstreamSubscriptions.push(callback);
return () => {
const index = this.downstreamSubscriptions.findIndex(
(func) => func === callback,
);
this.downstreamSubscriptions.splice(index, 1);
};
}
get(): ResultType {
return this.value;
}
drop(): void {
this.upstreamSubscriptions.forEach((sub) => this.object.disconnect(sub));
}
}
export function nestedBind<
ObjectType extends Connectable | undefined,
KeyType extends keyof ObjectType & string,
>(
bound: Subscribable<ObjectType>,
key: KeyType,
): Subscribable<Required<ObjectType>[KeyType] | undefined> {
let subscription: number | undefined;
let lastObject: ObjectType = bound.get();
let value: ObjectType[KeyType] | undefined = bound.get()?.[key];
const subscriptions: ((value: ObjectType[KeyType]) => void)[] = [];
const unsubscribeUpstream = bound.subscribe((obj) => {
if (subscription !== undefined) {
lastObject?.disconnect(subscription);
}
subscription = obj?.connect(`notify:${key}`, () => {
subscriptions.forEach((sub) => sub(obj![key]));
});
lastObject = obj;
});
}