UNPKG

5.15 kBPlain TextView Raw
1import {
2 Atom,
3 IEnhancer,
4 IInterceptable,
5 IEqualsComparer,
6 IInterceptor,
7 IListenable,
8 Lambda,
9 checkIfStateModificationsAreAllowed,
10 comparer,
11 createInstanceofPredicate,
12 getNextId,
13 hasInterceptors,
14 hasListeners,
15 interceptChange,
16 isSpyEnabled,
17 notifyListeners,
18 registerInterceptor,
19 registerListener,
20 spyReport,
21 spyReportEnd,
22 spyReportStart,
23 toPrimitive,
24 globalState,
25 IUNCHANGED,
26 UPDATE
27} from "../internal"
28
29export interface IValueWillChange<T> {
30 object: IObservableValue<T>
31 type: "update"
32 newValue: T
33}
34
35export type IValueDidChange<T = any> = {
36 type: "update"
37 observableKind: "value"
38 object: IObservableValue<T>
39 debugObjectName: string
40 newValue: unknown
41 oldValue: unknown
42}
43export type IBoxDidChange<T = any> =
44 | {
45 type: "create"
46 observableKind: "value"
47 object: IObservableValue<T>
48 debugObjectName: string
49 newValue: unknown
50 }
51 | IValueDidChange<T>
52
53export interface IObservableValue<T> {
54 get(): T
55 set(value: T): void
56 intercept_(handler: IInterceptor<IValueWillChange<T>>): Lambda
57 observe_(listener: (change: IValueDidChange<T>) => void, fireImmediately?: boolean): Lambda
58}
59
60const CREATE = "create"
61
62export class ObservableValue<T>
63 extends Atom
64 implements IObservableValue<T>, IInterceptable<IValueWillChange<T>>, IListenable {
65 hasUnreportedChange_ = false
66 interceptors_
67 changeListeners_
68 value_
69 dehancer: any
70
71 constructor(
72 value: T,
73 public enhancer: IEnhancer<T>,
74 public name_ = __DEV__ ? "ObservableValue@" + getNextId() : "ObservableValue",
75 notifySpy = true,
76 private equals: IEqualsComparer<any> = comparer.default
77 ) {
78 super(name_)
79 this.value_ = enhancer(value, undefined, name_)
80 if (__DEV__ && notifySpy && isSpyEnabled()) {
81 // only notify spy if this is a stand-alone observable
82 spyReport({
83 type: CREATE,
84 object: this,
85 observableKind: "value",
86 debugObjectName: this.name_,
87 newValue: "" + this.value_
88 })
89 }
90 }
91
92 private dehanceValue(value: T): T {
93 if (this.dehancer !== undefined) return this.dehancer(value)
94 return value
95 }
96
97 public set(newValue: T) {
98 const oldValue = this.value_
99 newValue = this.prepareNewValue_(newValue) as any
100 if (newValue !== globalState.UNCHANGED) {
101 const notifySpy = isSpyEnabled()
102 if (__DEV__ && notifySpy) {
103 spyReportStart({
104 type: UPDATE,
105 object: this,
106 observableKind: "value",
107 debugObjectName: this.name_,
108 newValue,
109 oldValue
110 })
111 }
112 this.setNewValue_(newValue)
113 if (__DEV__ && notifySpy) spyReportEnd()
114 }
115 }
116
117 private prepareNewValue_(newValue): T | IUNCHANGED {
118 checkIfStateModificationsAreAllowed(this)
119 if (hasInterceptors(this)) {
120 const change = interceptChange<IValueWillChange<T>>(this, {
121 object: this,
122 type: UPDATE,
123 newValue
124 })
125 if (!change) return globalState.UNCHANGED
126 newValue = change.newValue
127 }
128 // apply modifier
129 newValue = this.enhancer(newValue, this.value_, this.name_)
130 return this.equals(this.value_, newValue) ? globalState.UNCHANGED : newValue
131 }
132
133 setNewValue_(newValue: T) {
134 const oldValue = this.value_
135 this.value_ = newValue
136 this.reportChanged()
137 if (hasListeners(this)) {
138 notifyListeners(this, {
139 type: UPDATE,
140 object: this,
141 newValue,
142 oldValue
143 })
144 }
145 }
146
147 public get(): T {
148 this.reportObserved()
149 return this.dehanceValue(this.value_)
150 }
151
152 intercept_(handler: IInterceptor<IValueWillChange<T>>): Lambda {
153 return registerInterceptor(this, handler)
154 }
155
156 observe_(listener: (change: IValueDidChange<T>) => void, fireImmediately?: boolean): Lambda {
157 if (fireImmediately)
158 listener({
159 observableKind: "value",
160 debugObjectName: this.name_,
161 object: this,
162 type: UPDATE,
163 newValue: this.value_,
164 oldValue: undefined
165 })
166 return registerListener(this, listener)
167 }
168
169 raw() {
170 // used by MST ot get undehanced value
171 return this.value_
172 }
173
174 toJSON() {
175 return this.get()
176 }
177
178 toString() {
179 return `${this.name_}[${this.value_}]`
180 }
181
182 valueOf(): T {
183 return toPrimitive(this.get())
184 }
185
186 [Symbol.toPrimitive]() {
187 return this.valueOf()
188 }
189}
190
191export const isObservableValue = createInstanceofPredicate("ObservableValue", ObservableValue) as (
192 x: any
193) => x is IObservableValue<any>