forked from rwjblue/sparkles-component
-
Notifications
You must be signed in to change notification settings - Fork 0
/
tracked.ts
131 lines (115 loc) · 4.15 KB
/
tracked.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
124
125
126
127
128
129
130
131
import { assert } from '@ember/debug';
import { DEBUG } from '@glimmer/env';
import { notifyPropertyChange } from '@ember/object';
import { addObserver } from '@ember/object/observers';
import { decoratorWithParams } from '@ember-decorators/utils/decorator';
function setupObservers<O extends object>(instance: O, dependentKeys: (keyof O)[], notifyMethod: (() => void)) {
for (let i = 0; i < dependentKeys.length; i++) {
let dependentKey = dependentKeys[i];
addObserver(instance, dependentKey, instance, notifyMethod);
}
}
function descriptorForTrackedComputedProperty(key: string | symbol, desc: PropertyDescriptor, dependencies?: string[]) {
// TODO: really should use WeakSet here, but that isn't available on IE11
const OBSERVERS_SETUP = new WeakMap();
assert(
`You cannot use property paths with the tracked decorator, but for ${String(key)} you specified \`${(dependencies || []).join('`, `')}\`.`,
(function() {
if (dependencies === undefined) return true; // @tracked()
for (let i = 0; i < dependencies.length; i++) {
if (dependencies[i] !== undefined && dependencies[i].indexOf('.') > -1) {
return false;
}
}
return true;
})()
);
const getterProvided = desc.get;
const setterProvided = desc.set;
if (!getterProvided) {
throw new Error(`@tracked - property descriptor for ${String(key)} must include a get() function`);
}
// will be bound to the instance when invoked
function notify(this: object) {
if (typeof key === 'string') {
notifyPropertyChange(this, key);
} else if (DEBUG) {
throw new Error(`@tracked - unsupported property type ${String(key)}`);
}
}
desc.get = function() {
if (!OBSERVERS_SETUP.has(this) && Array.isArray(dependencies)) {
setupObservers<any>(this, dependencies, notify);
}
OBSERVERS_SETUP.set(this, true);
return getterProvided.call(this);
};
if (setterProvided) {
desc.set = function(value) {
if (typeof key === 'string') {
notifyPropertyChange(this, key);
setterProvided.call(this, value);
} else if (DEBUG) {
throw new Error(`@tracked - unsupported property type ${String(key)}`);
}
};
}
return desc;
}
function installTrackedProperty(key: string | symbol, descriptor?: PropertyDescriptor, initializer?: () => any): PropertyDescriptor {
let values = new WeakMap();
let get;
if (typeof initializer === 'function') {
get = function(this: object) {
if (values.has(this)) {
return values.get(this);
} else {
let value = initializer.call(this);
values.set(this, value);
return value;
}
};
} else {
get = function(this: object) {
return values.get(this);
}
}
return {
configurable: descriptor ? descriptor.configurable : true,
enumerable: descriptor ? descriptor.enumerable : true,
get,
set(value) {
if (typeof key === 'string') {
values.set(this, value);
notifyPropertyChange(this, key);
} else if (DEBUG) {
throw new Error(`@tracked - unsupported property type ${String(key)}`);
}
}
};
}
function _tracked(
key: string | symbol,
descriptor?: PropertyDescriptor,
initializer?: () => any,
dependencies?: string[]
): PropertyDescriptor {
if (!descriptor || typeof descriptor.get !== 'function' && typeof descriptor.set !== 'function') {
return installTrackedProperty(key, descriptor, initializer);
} else {
return descriptorForTrackedComputedProperty(key, descriptor, dependencies);
}
}
// TODO: replace return w/ PropertyDescriptor once TS gets their decorator act together
type TSDecorator = (target: object, propertyKey: string | symbol, descriptor?: PropertyDescriptor) => void;
type TrackedDecorator = TSDecorator & ((...args: string[]) => TSDecorator);
export const tracked: TrackedDecorator = decoratorWithParams((desc, params = []) => {
assert(`@tracked - Can only be used on class fields.`, desc.kind === 'field' || desc.kind === 'method');
const descriptor = _tracked(desc.key, desc.descriptor, desc.initializer, params);
return {
...desc,
descriptor,
kind: 'method',
initializer: undefined
};
});