77 lines
2.2 KiB
TypeScript
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;
|
|
});
|
|
}
|