1 | import {
|
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 |
|
29 | export interface IValueWillChange<T> {
|
30 | object: IObservableValue<T>
|
31 | type: "update"
|
32 | newValue: T
|
33 | }
|
34 |
|
35 | export type IValueDidChange<T = any> = {
|
36 | type: "update"
|
37 | observableKind: "value"
|
38 | object: IObservableValue<T>
|
39 | debugObjectName: string
|
40 | newValue: unknown
|
41 | oldValue: unknown
|
42 | }
|
43 | export 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 |
|
53 | export 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 |
|
60 | const CREATE = "create"
|
61 |
|
62 | export 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 |
|
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 |
|
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 |
|
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 |
|
191 | export const isObservableValue = createInstanceofPredicate("ObservableValue", ObservableValue) as (
|
192 | x: any
|
193 | ) => x is IObservableValue<any>
|