export interface $StateOption { format: (value: T) => string; } export class $State { protected _value!: T | $State; readonly attributes = new Map>(); readonly linkStates = new Set<$State>; options: Partial<$StateOption> = {} constructor(value: T, options?: $StateOption) { this.set(value); if (options) this.options = options; } set(value: T | $State) { this._value = value; if (value instanceof $State) value.linkStates.add(this as any); this.update(); this.linkStates.forEach($state => $state.update()); } static toJSON(object: Object): Object { const data = {}; for (let [key, value] of Object.entries(object)) { if (value instanceof $State) value = value.toJSON(); else if (value instanceof Object) $State.toJSON(value); Object.assign(data, {[key]: value}) } return data; } protected update() { // update element content for eatch attributes for (const [node, attrList] of this.attributes.entries()) { for (const attr of attrList) { //@ts-expect-error if (node[attr] instanceof Function) { //@ts-expect-error if (this.options.format) node[attr](this.options.format(this.value)) //@ts-expect-error else node[attr](this.value) } else if (attr in node) { //@ts-expect-error node[attr] = this.value } } } } use(object: O, attrName: K) { const attrList = this.attributes.get(object) if (attrList) attrList.add(attrName); else this.attributes.set(object, new Set().add(attrName)) } convert(fn: (value: T) => string) { return new $State(this as any, {format: fn}); } get value(): T { return this._value instanceof $State ? this._value.value as T : this._value; } toString(): string { if (this.options.format) return this.options.format(this.value); if (this.value instanceof Object) return JSON.stringify(this.toJSON()); return `${this.value}` } toJSON(): Object { if (this.value instanceof $State) return this.value.toJSON(); if (this.value instanceof Object) return $State.toJSON(this.value); else return this.toString(); } }; export type $StateArgument = $State | undefined | (T extends (infer R)[] ? R : T);