-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathReactive.ts
123 lines (105 loc) · 3.65 KB
/
Reactive.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
export class Reactive<T> {
private value: T;
constructor ( value: T | Reactive<T> ) {
if ( value instanceof Reactive ) {
this.value = value.Value;
this.BindTo( value );
}
else this.value = value;
}
get Value (): T {
return this.value;
}
set Value ( value: T ) {
if ( value == this.value ) return;
this.changeValue( value, this );
}
private changeValue ( value: T, source: Reactive<T> ) {
var prev = this.value;
this.value = value;
for ( let i = 0; i < this.bindees.length; i++ ) {
var bindee = this.bindees[i].deref();
if ( bindee == undefined ) {
this.bindees.splice( i--, 1 );
}
}
for ( const ref of [...this.bindees] ) {
var bindee = ref.deref();
if ( bindee != source )
bindee!.changeValue( value, this );
}
if ( value == this.value ) {
for ( const listener of [...this.valueChanged] ) {
listener( value, prev );
}
}
}
AddOnValueChanged ( listener: (value: T, oldValue: T) => any, fireNow?: boolean ) {
this.valueChanged.push( listener );
if ( fireNow )
listener( this.value, this.value );
}
RemoveOnValueChanged ( listener: (value: T, oldValue: T) => any ) {
var index = this.valueChanged.indexOf( listener );
if ( index != -1 ) {
this.valueChanged.splice( index, 1 );
}
}
private valueChanged: ((value: T, oldValue: T) => any)[] = [];
RemoveEvents () {
this.valueChanged = [];
}
UnbindAll () {
for ( const b of this.bindees ) {
var bindee = b.deref();
if ( bindee != undefined ) {
var index = bindee.bindees.indexOf( this.self );
bindee.bindees.splice( index, 1 );
}
}
this.bindees = [];
}
private bindees: WeakRef<Reactive<T>>[] = [];
private self: WeakRef<Reactive<T>> = new WeakRef<Reactive<T>>( this );
BindTo ( other: Reactive<T> ) {
this.bindees.push( other.self );
other.bindees.push( this.self );
this.Value = other.Value;
}
UnbindFrom ( other: Reactive<T> ) {
var index = this.bindees.indexOf( other.self );
if ( index != -1 ) this.bindees.splice( index, 1 );
index = other.bindees.indexOf( this.self );
if ( index != -1 ) other.bindees.splice( index, 1 );
}
}
export type ReactiveAggregate<Tin, Tout = Tin> = {
add: (source: Reactive<Tin>) => any,
remove: (source: Reactive<Tin>) => any,
getSources: () => Reactive<Tin>[],
reactive: Reactive<Tout>
};
export function CreateReactiveAggregate<Tin, Tout = Tin> ( fn: (sources: Reactive<Tin>[], newValue?: Tin, oldValue?: Tin) => Tout ): ReactiveAggregate<Tin, Tout> {
const sources: Reactive<Tin>[] = [];
var output = new Reactive<Tout>( fn( sources ) );
function changed ( newValue?: Tin, oldValue?: Tin ) {
output.Value = fn( sources, newValue, oldValue );
}
return {
add: (source: Reactive<Tin>) => {
sources.push( source );
source.AddOnValueChanged( changed );
changed( source.Value, undefined );
},
remove: (source: Reactive<Tin>) => {
var index = sources.indexOf( source );
if ( index != -1 ) {
source.RemoveOnValueChanged( changed );
sources.splice( index );
changed( undefined, source.Value );
}
},
getSources: () => sources,
reactive: output
};
}