• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"use strict";
2/*
3 * Copyright (c) 2022-2025 Huawei Device Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16Object.defineProperty(exports, "__esModule", { value: true });
17exports.observableProxy = exports.observableProxyArray = exports.ObservableHandler = exports.Observed = exports.getObservableTarget = void 0;
18const OBSERVABLE_TARGET = "__proxy_observable_target__";
19function getObservableTarget(proxy) {
20    var _a;
21    return (_a = getPropertyValue(OBSERVABLE_TARGET, proxy)) !== null && _a !== void 0 ? _a : proxy;
22}
23exports.getObservableTarget = getObservableTarget;
24function getPropertyValue(name, object) {
25    return object[name];
26}
27/**
28 * Data class decorator that makes all child fields trackable.
29 */
30function Observed(constructorFunction) {
31    constructorFunction.prototype[OBSERVED] = true;
32}
33exports.Observed = Observed;
34const OBSERVED = "__ObservedByArkUI__";
35function isObserved(value) {
36    return value[OBSERVED] === true;
37}
38/** @internal */
39class ObservableHandler {
40    constructor(parent, observed = false) {
41        this.parents = new Set();
42        this.children = new Map();
43        this.observables = new Set();
44        this._modified = false;
45        this.observed = observed;
46        if (parent)
47            this.addParent(parent);
48    }
49    onAccess() {
50        var _a;
51        if (this.observables.size > 0) {
52            const it = this.observables.keys();
53            while (true) {
54                const result = it.next();
55                if (result.done)
56                    break;
57                (_a = result.value) === null || _a === void 0 ? void 0 : _a.onAccess();
58            }
59        }
60    }
61    onModify() {
62        const set = new Set();
63        this.collect(true, set);
64        set.forEach((handler) => {
65            var _a;
66            handler._modified = true;
67            if (handler.observables.size > 0) {
68                const it = handler.observables.keys();
69                while (true) {
70                    const result = it.next();
71                    if (result.done)
72                        break;
73                    (_a = result.value) === null || _a === void 0 ? void 0 : _a.onModify();
74                }
75            }
76        });
77    }
78    static dropModified(value) {
79        const handler = ObservableHandler.findIfObject(value);
80        if (handler === undefined)
81            return false;
82        const result = handler._modified;
83        handler._modified = false;
84        return result;
85    }
86    /** Adds the specified `observable` to the handler corresponding to the given `value`. */
87    static attach(value, observable) {
88        const handler = ObservableHandler.findIfObject(value);
89        if (handler)
90            handler.observables.add(observable);
91    }
92    /** Deletes the specified `observable` from the handler corresponding to the given `value`. */
93    static detach(value, observable) {
94        const handler = ObservableHandler.findIfObject(value);
95        if (handler)
96            handler.observables.delete(observable);
97    }
98    /** @returns the handler corresponding to the given `value` if it was installed */
99    static findIfObject(value) {
100        const handlers = ObservableHandler.handlers;
101        return handlers !== undefined && value instanceof Object ? handlers.get(getObservableTarget(value)) : undefined;
102    }
103    /**
104     * @param value - any non-null object including arrays
105     * @returns an observable handler or `undefined` if it is not installed
106     */
107    static find(value) {
108        const handlers = ObservableHandler.handlers;
109        return handlers ? handlers.get(getObservableTarget(value)) : undefined;
110    }
111    /**
112     * @param value - any non-null object including arrays
113     * @param observable - a handler to install on this object
114     * @throws an error if observable handler cannot be installed
115     */
116    static installOn(value, observable) {
117        let handlers = ObservableHandler.handlers;
118        if (handlers === undefined) {
119            handlers = new WeakMap();
120            ObservableHandler.handlers = handlers;
121        }
122        observable
123            ? handlers.set(getObservableTarget(value), observable)
124            : handlers.delete(getObservableTarget(value));
125    }
126    addParent(parent) {
127        var _a;
128        const count = (_a = parent.children.get(this)) !== null && _a !== void 0 ? _a : 0;
129        parent.children.set(this, count + 1);
130        this.parents.add(parent);
131    }
132    removeParent(parent) {
133        var _a;
134        const count = (_a = parent.children.get(this)) !== null && _a !== void 0 ? _a : 0;
135        if (count > 1) {
136            parent.children.set(this, count - 1);
137        }
138        else if (count == 1) {
139            parent.children.delete(this);
140            this.parents.delete(parent);
141        }
142    }
143    removeChild(value) {
144        const child = ObservableHandler.findIfObject(value);
145        if (child)
146            child.removeParent(this);
147    }
148    collect(all, guards = new Set()) {
149        if (guards.has(this))
150            return guards; // already collected
151        guards.add(this); // handler is already guarded
152        this.parents.forEach(handler => handler.collect(all, guards));
153        if (all)
154            this.children.forEach((_count, handler) => handler.collect(all, guards));
155        return guards;
156    }
157    static contains(observable, guards) {
158        if (observable.observed)
159            return true;
160        if (guards === undefined)
161            guards = new Set(); // create if needed
162        else if (guards.has(observable))
163            return false; // already checked
164        guards.add(observable); // handler is already guarded
165        for (const it of observable.parents.keys()) {
166            if (ObservableHandler.contains(it, guards))
167                return true;
168        }
169        return false;
170    }
171}
172exports.ObservableHandler = ObservableHandler;
173ObservableHandler.handlers = undefined;
174/** @internal */
175function observableProxyArray(...value) {
176    return observableProxy(value);
177}
178exports.observableProxyArray = observableProxyArray;
179/** @internal */
180function observableProxy(value, parent, observed, strict = true) {
181    if (value instanceof ObservableHandler)
182        return value; // do not proxy a marker itself
183    if (value === null || !(value instanceof Object))
184        return value; // only non-null object can be observable
185    const observable = ObservableHandler.find(value);
186    if (observable) {
187        if (parent) {
188            if (strict)
189                observable.addParent(parent);
190            if (observed === undefined)
191                observed = ObservableHandler.contains(parent);
192        }
193        if (observed) {
194            if (Array.isArray(value)) {
195                for (let index = 0; index < value.length; index++) {
196                    value[index] = observableProxy(value[index], observable, observed, false);
197                }
198            }
199            else {
200                proxyFields(value, false, observable);
201            }
202        }
203        return value;
204    }
205    if (Array.isArray(value)) {
206        const handler = new ObservableHandler(parent);
207        const array = proxyChildrenOnly(value, handler, observed);
208        copyWithinObservable(array);
209        fillObservable(array);
210        popObservable(array);
211        pushObservable(array);
212        reverseObservable(array);
213        shiftObservable(array);
214        sortObservable(array);
215        spliceObservable(array);
216        unshiftObservable(array);
217        return proxyObject(array, handler);
218    }
219    if (value instanceof Date) {
220        const valueAsAny = value;
221        const handler = new ObservableHandler(parent);
222        const setMethods = new Set([
223            "setFullYear", "setMonth", "setDate", "setHours", "setMinutes", "setSeconds",
224            "setMilliseconds", "setTime", "setUTCFullYear", "setUTCMonth", "setUTCDate",
225            "setUTCHours", "setUTCMinutes", "setUTCSeconds", "setUTCMilliseconds"
226        ]);
227        setMethods.forEach((method) => {
228            const originalMethod = method + 'Original';
229            if (valueAsAny[originalMethod] !== undefined) {
230                return;
231            }
232            valueAsAny[originalMethod] = valueAsAny[method];
233            valueAsAny[method] = function (...args) {
234                var _a;
235                (_a = ObservableHandler.find(this)) === null || _a === void 0 ? void 0 : _a.onModify();
236                return this[originalMethod](...args);
237            };
238        });
239        return proxyObject(value, handler);
240    }
241    // TODO: support set/map
242    const handler = new ObservableHandler(parent, isObserved(value));
243    if (handler.observed || observed)
244        proxyFields(value, true, handler);
245    return proxyObject(value, handler);
246}
247exports.observableProxy = observableProxy;
248function proxyObject(value, observable) {
249    ObservableHandler.installOn(value, observable);
250    return new Proxy(value, {
251        get(target, property, receiver) {
252            var _a;
253            if (property == OBSERVABLE_TARGET)
254                return target;
255            const value = Reflect.get(target, property, receiver);
256            (_a = ObservableHandler.find(target)) === null || _a === void 0 ? void 0 : _a.onAccess();
257            return typeof value == "function"
258                ? value.bind(target)
259                : value;
260        },
261        set(target, property, value, receiver) {
262            const old = Reflect.get(target, property, receiver);
263            if (value === old)
264                return true;
265            const observable = ObservableHandler.find(target);
266            if (observable) {
267                observable.onModify();
268                observable.removeChild(old);
269                const observed = ObservableHandler.contains(observable);
270                if (observed || Array.isArray(target)) {
271                    value = observableProxy(value, observable, observed);
272                }
273            }
274            return Reflect.set(target, property, value, receiver);
275        },
276        deleteProperty(target, property) {
277            var _a;
278            (_a = ObservableHandler.find(target)) === null || _a === void 0 ? void 0 : _a.onModify();
279            delete target[property];
280            return true;
281        },
282    });
283}
284function proxyFields(value, strict, parent) {
285    for (const name of Object.getOwnPropertyNames(value)) {
286        const descriptor = Object.getOwnPropertyDescriptor(value, name);
287        if (descriptor === null || descriptor === void 0 ? void 0 : descriptor.writable)
288            value[name] = observableProxy(value[name], parent, true, strict);
289    }
290}
291function proxyChildrenOnly(array, parent, observed) {
292    if (observed === undefined)
293        observed = ObservableHandler.contains(parent);
294    return array.map(it => observableProxy(it, parent, observed));
295}
296function copyWithinObservable(array) {
297    if (array.copyWithinOriginal === undefined) {
298        array.copyWithinOriginal = array.copyWithin;
299        array.copyWithin = function (target, start, end) {
300            const observable = ObservableHandler.find(this);
301            observable === null || observable === void 0 ? void 0 : observable.onModify();
302            return this.copyWithinOriginal(target, start, end);
303        };
304    }
305}
306function fillObservable(array) {
307    if (array.fillOriginal === undefined) {
308        array.fillOriginal = array.fill;
309        array.fill = function (value, start, end) {
310            const observable = ObservableHandler.find(this);
311            observable === null || observable === void 0 ? void 0 : observable.onModify();
312            if (observable)
313                value = observableProxy(value, observable);
314            return this.fillOriginal(value, start, end);
315        };
316    }
317}
318function popObservable(array) {
319    if (array.popOriginal === undefined) {
320        array.popOriginal = array.pop;
321        array.pop = function (...args) {
322            const observable = ObservableHandler.find(this);
323            observable === null || observable === void 0 ? void 0 : observable.onModify();
324            const result = this.popOriginal(...args);
325            if (observable)
326                observable.removeChild(result);
327            return result;
328        };
329    }
330}
331function pushObservable(array) {
332    if (array.pushOriginal === undefined) {
333        array.pushOriginal = array.push;
334        array.push = function (...args) {
335            const observable = ObservableHandler.find(this);
336            observable === null || observable === void 0 ? void 0 : observable.onModify();
337            if (observable)
338                args = proxyChildrenOnly(args, observable);
339            return this.pushOriginal(...args);
340        };
341    }
342}
343function reverseObservable(array) {
344    if (array.reverseOriginal === undefined) {
345        array.reverseOriginal = array.reverse;
346        array.reverse = function () {
347            const observable = ObservableHandler.find(this);
348            observable === null || observable === void 0 ? void 0 : observable.onModify();
349            return this.reverseOriginal();
350        };
351    }
352}
353function shiftObservable(array) {
354    if (array.shiftOriginal === undefined) {
355        array.shiftOriginal = array.shift;
356        array.shift = function (...args) {
357            const observable = ObservableHandler.find(this);
358            observable === null || observable === void 0 ? void 0 : observable.onModify();
359            const result = this.shiftOriginal(...args);
360            if (observable)
361                observable.removeChild(result);
362            return result;
363        };
364    }
365}
366function sortObservable(array) {
367    if (array.sortOriginal === undefined) {
368        array.sortOriginal = array.sort;
369        array.sort = function (compareFn) {
370            const observable = ObservableHandler.find(this);
371            observable === null || observable === void 0 ? void 0 : observable.onModify();
372            return this.sortOriginal(compareFn);
373        };
374    }
375}
376function spliceObservable(array) {
377    if (array.spliceOriginal === undefined) {
378        array.spliceOriginal = array.splice;
379        array.splice = function (start, deleteCount, ...items) {
380            const observable = ObservableHandler.find(this);
381            observable === null || observable === void 0 ? void 0 : observable.onModify();
382            if (observable)
383                items = proxyChildrenOnly(items, observable);
384            if (deleteCount === undefined)
385                deleteCount = array.length;
386            const result = this.spliceOriginal(start, deleteCount, ...items);
387            if (observable && Array.isArray(result)) {
388                result.forEach(it => observable.removeChild(it));
389            }
390            return result;
391        };
392    }
393}
394function unshiftObservable(array) {
395    if (array.unshiftOriginal === undefined) {
396        array.unshiftOriginal = array.unshift;
397        array.unshift = function (...items) {
398            const observable = ObservableHandler.find(this);
399            observable === null || observable === void 0 ? void 0 : observable.onModify();
400            if (observable)
401                items = proxyChildrenOnly(items, observable);
402            return this.unshiftOriginal(...items);
403        };
404    }
405}
406//# sourceMappingURL=observable.js.map