-
Notifications
You must be signed in to change notification settings - Fork 0
/
observer.ts
108 lines (100 loc) · 3.27 KB
/
observer.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
import { ReactiveElement } from "./deps.ts";
import { ObservedValue, sObservable, sPath } from "./observable.ts";
import type { ChangeEvent } from "./observable.ts";
import { Constructor } from "./constructor.ts";
const sHandlers = Symbol.for("c-handlers");
const sObservables = Symbol.for("c-observables");
export function ObserverElement<U extends Constructor<ReactiveElement>>(
Base: U,
) {
return class ObserverElement extends Base {
[sObservables] = new Map<PropertyKey, ObservedValue>();
[sHandlers] = new Map<PropertyKey, (event: ChangeEvent) => void>();
declare static observables: Array<PropertyKey>;
// deno-lint-ignore no-explicit-any
constructor(...args: any[]) {
super(...args);
const { observables } = this
.constructor as typeof ObserverElement;
if (observables) {
for (const property of observables) {
this.setObservable(property);
}
}
}
onObservableChange(
property: PropertyKey,
event: ChangeEvent,
): void {
const pathPrefix: string =
(this as unknown as { [key: PropertyKey]: ObservedValue })[property][
sPath
];
if (event.path.startsWith(pathPrefix)) {
const elementPath = `${property as string}${
event.path.slice(pathPrefix.length)
}`;
this.requestUpdate(elementPath, event);
}
}
setObservable(
property: PropertyKey,
) {
const value =
(this as unknown as { [key: PropertyKey]: ObservedValue })[property];
Object.defineProperty(this, property, {
get(this: ObserverElement): ObservedValue | undefined {
return this[sObservables].get(property);
},
set(this: ObserverElement, value: ObservedValue) {
const oldValue = this[sObservables].get(property);
if (oldValue === value) return;
if (!this[sHandlers].has(property)) {
this[sHandlers].set(
property,
this.onObservableChange.bind(this, property),
);
}
const handler = this[sHandlers].get(property)!;
if (oldValue && oldValue[sObservable]) {
oldValue[sObservable].removeEventListener("change", handler);
}
if (value && value[sObservable]) {
value[sObservable].addEventListener("change", handler);
}
this[sObservables].set(property, value);
this.requestUpdate(property, oldValue);
},
configurable: true,
enumerable: true,
});
if (value) {
(this as unknown as { [key: PropertyKey]: ObservedValue })[property] =
value;
}
}
disconnectedCallback(): void {
super.disconnectedCallback();
for (const [property, observable] of this[sObservables].entries()) {
observable[sObservable].removeEventListener(
"change",
this[sHandlers].get(property)!,
);
}
}
} as U;
}
export function observer() {
return ObserverElement;
}
export function observe() {
return function (
// deno-lint-ignore no-explicit-any
target: any,
property: PropertyKey,
) {
const observables = target.constructor.observables || [];
observables.push(property);
target.constructor.observables = observables;
};
}