ripple 0.2.87 → 0.2.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/compiler/phases/1-parse/index.js +74 -7
- package/src/compiler/phases/2-analyze/index.js +3 -1
- package/src/compiler/phases/3-transform/client/index.js +52 -17
- package/src/runtime/array.js +9 -242
- package/src/runtime/index-client.js +3 -1
- package/src/runtime/internal/client/constants.js +2 -0
- package/src/runtime/internal/client/for.js +403 -403
- package/src/runtime/internal/client/if.js +29 -29
- package/src/runtime/internal/client/index.js +4 -0
- package/src/runtime/internal/client/operations.js +0 -1
- package/src/runtime/internal/client/render.js +5 -5
- package/src/runtime/internal/client/runtime.js +2 -2
- package/src/runtime/internal/client/script.js +16 -0
- package/src/runtime/internal/client/utils.js +4 -0
- package/src/runtime/internal/server/index.js +5 -2
- package/src/runtime/object.js +29 -0
- package/src/runtime/proxy.js +341 -0
- package/src/utils/events.js +3 -3
- package/tests/client/__snapshots__/basic.test.ripple.snap +13 -0
- package/tests/client/array.test.ripple +3 -3
- package/tests/client/basic.test.ripple +90 -0
- package/tests/client/head.test.ripple +196 -0
- package/tests/client/object.test.ripple +183 -0
- package/types/index.d.ts +8 -0
|
@@ -142,6 +142,81 @@ describe('basic client', () => {
|
|
|
142
142
|
expect(div.classList.contains('inactive')).toBe(true);
|
|
143
143
|
});
|
|
144
144
|
|
|
145
|
+
it('render class attribute with array, nested array, nested object', () => {
|
|
146
|
+
component Basic() {
|
|
147
|
+
<div class={[
|
|
148
|
+
'foo',
|
|
149
|
+
'bar',
|
|
150
|
+
true && 'baz',
|
|
151
|
+
false && 'aaa',
|
|
152
|
+
null && 'bbb',
|
|
153
|
+
[
|
|
154
|
+
'ccc',
|
|
155
|
+
'ddd',
|
|
156
|
+
{ eee: true, fff: false }
|
|
157
|
+
]
|
|
158
|
+
]}>
|
|
159
|
+
{'Class Array'}
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<style>
|
|
163
|
+
.foo {
|
|
164
|
+
color: red;
|
|
165
|
+
}
|
|
166
|
+
</style>
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
render(Basic);
|
|
170
|
+
|
|
171
|
+
const div = container.querySelector('div');
|
|
172
|
+
|
|
173
|
+
expect(Array.from(div.classList).some(className => className.startsWith('ripple-'))).toBe(true);
|
|
174
|
+
expect(div.classList.contains('foo')).toBe(true);
|
|
175
|
+
expect(div.classList.contains('bar')).toBe(true);
|
|
176
|
+
expect(div.classList.contains('baz')).toBe(true);
|
|
177
|
+
expect(div.classList.contains('aaa')).toBe(false);
|
|
178
|
+
expect(div.classList.contains('bbb')).toBe(false);
|
|
179
|
+
expect(div.classList.contains('ccc')).toBe(true);
|
|
180
|
+
expect(div.classList.contains('ddd')).toBe(true);
|
|
181
|
+
expect(div.classList.contains('eee')).toBe(true);
|
|
182
|
+
expect(div.classList.contains('fff')).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('render dynamic class object', () => {
|
|
186
|
+
component Basic() {
|
|
187
|
+
let active = track(false);
|
|
188
|
+
|
|
189
|
+
<button onClick={() => { @active = !@active }}>{'Toggle'}</button>
|
|
190
|
+
<div class={{ active: @active, inactive: !@active }}>{'Dynamic Class'}</div>
|
|
191
|
+
|
|
192
|
+
<style>
|
|
193
|
+
.active {
|
|
194
|
+
color: green;
|
|
195
|
+
}
|
|
196
|
+
</style>
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
render(Basic);
|
|
200
|
+
|
|
201
|
+
const button = container.querySelector('button');
|
|
202
|
+
const div = container.querySelector('div');
|
|
203
|
+
|
|
204
|
+
expect(Array.from(div.classList).some(className => className.startsWith('ripple-'))).toBe(true);
|
|
205
|
+
expect(div.classList.contains('inactive')).toBe(true);
|
|
206
|
+
expect(div.classList.contains('active')).toBe(false);
|
|
207
|
+
|
|
208
|
+
button.click();
|
|
209
|
+
flushSync();
|
|
210
|
+
expect(div.classList.contains('inactive')).toBe(false);
|
|
211
|
+
expect(div.classList.contains('active')).toBe(true);
|
|
212
|
+
|
|
213
|
+
button.click();
|
|
214
|
+
flushSync();
|
|
215
|
+
|
|
216
|
+
expect(div.classList.contains('inactive')).toBe(true);
|
|
217
|
+
expect(div.classList.contains('active')).toBe(false);
|
|
218
|
+
});
|
|
219
|
+
|
|
145
220
|
it('render dynamic id attribute', () => {
|
|
146
221
|
component Basic() {
|
|
147
222
|
let count = track(0);
|
|
@@ -1547,5 +1622,20 @@ describe('basic client', () => {
|
|
|
1547
1622
|
expect(pre1.textContent).toBe('4');
|
|
1548
1623
|
expect(pre2.textContent).toBe('2');
|
|
1549
1624
|
});
|
|
1625
|
+
|
|
1626
|
+
it('handles boolean props correctly', () => {
|
|
1627
|
+
component App() {
|
|
1628
|
+
<div data-disabled />
|
|
1629
|
+
|
|
1630
|
+
<Child isDisabled />
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
component Child({ isDisabled }) {
|
|
1634
|
+
<input disabled={isDisabled} />
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
render(App);
|
|
1638
|
+
expect(container).toMatchSnapshot();
|
|
1639
|
+
});
|
|
1550
1640
|
});
|
|
1551
1641
|
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mount, flushSync, track } from 'ripple';
|
|
3
|
+
|
|
4
|
+
describe('head elements', () => {
|
|
5
|
+
let container;
|
|
6
|
+
let originalTitle;
|
|
7
|
+
|
|
8
|
+
function render(component) {
|
|
9
|
+
mount(component, {
|
|
10
|
+
target: container
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
container = document.createElement('div');
|
|
16
|
+
document.body.appendChild(container);
|
|
17
|
+
// Store original title to restore later
|
|
18
|
+
originalTitle = document.title;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
document.body.removeChild(container);
|
|
23
|
+
container = null;
|
|
24
|
+
// Restore original title
|
|
25
|
+
document.title = originalTitle;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('renders static title element', () => {
|
|
29
|
+
component App() {
|
|
30
|
+
<head>
|
|
31
|
+
<title>{'Static Test Title'}</title>
|
|
32
|
+
</head>
|
|
33
|
+
<div>{'Content'}</div>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
render(App);
|
|
37
|
+
|
|
38
|
+
expect(document.title).toBe('Static Test Title');
|
|
39
|
+
expect(container.querySelector('div').textContent).toBe('Content');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('renders reactive title element', () => {
|
|
43
|
+
component App() {
|
|
44
|
+
let title = track('Initial Title');
|
|
45
|
+
|
|
46
|
+
<head>
|
|
47
|
+
<title>{@title}</title>
|
|
48
|
+
</head>
|
|
49
|
+
<div>
|
|
50
|
+
<button onClick={() => { @title = 'Updated Title'; }}>{'Update Title'}</button>
|
|
51
|
+
<span>{@title}</span>
|
|
52
|
+
</div>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
render(App);
|
|
56
|
+
|
|
57
|
+
expect(document.title).toBe('Initial Title');
|
|
58
|
+
expect(container.querySelector('span').textContent).toBe('Initial Title');
|
|
59
|
+
|
|
60
|
+
const button = container.querySelector('button');
|
|
61
|
+
button.click();
|
|
62
|
+
flushSync();
|
|
63
|
+
|
|
64
|
+
expect(document.title).toBe('Updated Title');
|
|
65
|
+
expect(container.querySelector('span').textContent).toBe('Updated Title');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('renders title with template literal', () => {
|
|
69
|
+
component App() {
|
|
70
|
+
let name = track('World');
|
|
71
|
+
|
|
72
|
+
<head>
|
|
73
|
+
<title>{`Hello ${@name}!`}</title>
|
|
74
|
+
</head>
|
|
75
|
+
<div>
|
|
76
|
+
<button onClick={() => { @name = 'Ripple'; }}>{'Change Name'}</button>
|
|
77
|
+
</div>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
render(App);
|
|
81
|
+
|
|
82
|
+
expect(document.title).toBe('Hello World!');
|
|
83
|
+
|
|
84
|
+
const button = container.querySelector('button');
|
|
85
|
+
button.click();
|
|
86
|
+
flushSync();
|
|
87
|
+
|
|
88
|
+
expect(document.title).toBe('Hello Ripple!');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('renders title with computed value', () => {
|
|
92
|
+
component App() {
|
|
93
|
+
let count = track(0);
|
|
94
|
+
let prefix = 'Count: ';
|
|
95
|
+
|
|
96
|
+
<head>
|
|
97
|
+
<title>{prefix + @count}</title>
|
|
98
|
+
</head>
|
|
99
|
+
<div>
|
|
100
|
+
<button onClick={() => { @count++; }}>{'Increment'}</button>
|
|
101
|
+
<span>{@count}</span>
|
|
102
|
+
</div>
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
render(App);
|
|
106
|
+
|
|
107
|
+
expect(document.title).toBe('Count: 0');
|
|
108
|
+
|
|
109
|
+
const button = container.querySelector('button');
|
|
110
|
+
button.click();
|
|
111
|
+
flushSync();
|
|
112
|
+
|
|
113
|
+
expect(document.title).toBe('Count: 1');
|
|
114
|
+
expect(container.querySelector('span').textContent).toBe('1');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('handles multiple title updates', () => {
|
|
118
|
+
component App() {
|
|
119
|
+
let step = track(1);
|
|
120
|
+
|
|
121
|
+
<head>
|
|
122
|
+
<title>{`Step ${@step} of 3`}</title>
|
|
123
|
+
</head>
|
|
124
|
+
<div>
|
|
125
|
+
<button onClick={() => { @step = (@step % 3) + 1 }}>{'Next Step'}</button>
|
|
126
|
+
</div>
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
render(App);
|
|
130
|
+
|
|
131
|
+
expect(document.title).toBe('Step 1 of 3');
|
|
132
|
+
|
|
133
|
+
const button = container.querySelector('button');
|
|
134
|
+
|
|
135
|
+
button.click();
|
|
136
|
+
flushSync();
|
|
137
|
+
expect(document.title).toBe('Step 2 of 3');
|
|
138
|
+
|
|
139
|
+
button.click();
|
|
140
|
+
flushSync();
|
|
141
|
+
expect(document.title).toBe('Step 3 of 3');
|
|
142
|
+
|
|
143
|
+
button.click();
|
|
144
|
+
flushSync();
|
|
145
|
+
expect(document.title).toBe('Step 1 of 3');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('renders empty title', () => {
|
|
149
|
+
component App() {
|
|
150
|
+
<head>
|
|
151
|
+
<title>{''}</title>
|
|
152
|
+
</head>
|
|
153
|
+
<div>{'Empty title test'}</div>
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
render(App);
|
|
157
|
+
|
|
158
|
+
expect(document.title).toBe('');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('renders title with conditional content', () => {
|
|
162
|
+
component App() {
|
|
163
|
+
let showPrefix = track(true);
|
|
164
|
+
let title = track('Main Page');
|
|
165
|
+
|
|
166
|
+
<head>
|
|
167
|
+
<title>{@showPrefix ? 'App - ' + @title : @title}</title>
|
|
168
|
+
</head>
|
|
169
|
+
<div>
|
|
170
|
+
<button onClick={() => { @showPrefix = !@showPrefix }}>{'Toggle Prefix'}</button>
|
|
171
|
+
<button onClick={() => { @title = @title === 'Main Page' ? 'Settings' : 'Main Page' }}>{'Change Page'}</button>
|
|
172
|
+
</div>
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
render(App);
|
|
176
|
+
|
|
177
|
+
expect(document.title).toBe('App - Main Page');
|
|
178
|
+
|
|
179
|
+
const buttons = container.querySelectorAll('button');
|
|
180
|
+
|
|
181
|
+
// Toggle prefix off
|
|
182
|
+
buttons[0].click();
|
|
183
|
+
flushSync();
|
|
184
|
+
expect(document.title).toBe('Main Page');
|
|
185
|
+
|
|
186
|
+
// Change page
|
|
187
|
+
buttons[1].click();
|
|
188
|
+
flushSync();
|
|
189
|
+
expect(document.title).toBe('Settings');
|
|
190
|
+
|
|
191
|
+
// Toggle prefix back on
|
|
192
|
+
buttons[0].click();
|
|
193
|
+
flushSync();
|
|
194
|
+
expect(document.title).toBe('App - Settings');
|
|
195
|
+
});
|
|
196
|
+
});
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mount, flushSync, TrackedObject, track } from 'ripple';
|
|
3
|
+
import { TRACKED_OBJECT } from '../../src/runtime/internal/client/constants.js';
|
|
4
|
+
|
|
5
|
+
describe('TrackedObject', () => {
|
|
6
|
+
let container;
|
|
7
|
+
|
|
8
|
+
function render(component) {
|
|
9
|
+
mount(component, {
|
|
10
|
+
target: container
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
container = document.createElement('div');
|
|
16
|
+
document.body.appendChild(container);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
document.body.removeChild(container);
|
|
21
|
+
container = null;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('makes new properties reactive', () => {
|
|
25
|
+
component ObjectTest() {
|
|
26
|
+
const obj = new TrackedObject({});
|
|
27
|
+
|
|
28
|
+
obj.a = 0;
|
|
29
|
+
|
|
30
|
+
<pre>{obj.a}</pre>
|
|
31
|
+
<button onClick={() => { obj.a++; }}>{'Increment A'}</button>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
render(ObjectTest);
|
|
35
|
+
|
|
36
|
+
const pre1 = container.querySelectorAll('pre')[0];
|
|
37
|
+
const button = container.querySelectorAll('button')[0 ];
|
|
38
|
+
|
|
39
|
+
expect(pre1.textContent).toBe('0');
|
|
40
|
+
|
|
41
|
+
button.click();
|
|
42
|
+
flushSync();
|
|
43
|
+
|
|
44
|
+
expect(pre1.textContent).toBe('1');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('makes existing object properties reactive', () => {
|
|
48
|
+
component ObjectTest() {
|
|
49
|
+
const obj = new TrackedObject({ a: 0 });
|
|
50
|
+
|
|
51
|
+
<pre>{obj.a}</pre>
|
|
52
|
+
<button onClick={() => { obj.a++; }}>{'Increment A'}</button>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
render(ObjectTest);
|
|
56
|
+
|
|
57
|
+
const pre1 = container.querySelectorAll('pre')[0];
|
|
58
|
+
const button = container.querySelectorAll('button')[0 ];
|
|
59
|
+
|
|
60
|
+
expect(pre1.textContent).toBe('0');
|
|
61
|
+
|
|
62
|
+
button.click();
|
|
63
|
+
flushSync();
|
|
64
|
+
|
|
65
|
+
expect(pre1.textContent).toBe('1');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('checks if property exists via the has trap', () => {
|
|
69
|
+
component ObjectTest() {
|
|
70
|
+
const obj = new TrackedObject({b: 1});
|
|
71
|
+
|
|
72
|
+
obj.a = 0;
|
|
73
|
+
|
|
74
|
+
<pre>{'a' in obj && 'b' in obj}</pre>
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
render(ObjectTest);
|
|
78
|
+
|
|
79
|
+
const pre1 = container.querySelectorAll('pre')[0];
|
|
80
|
+
|
|
81
|
+
expect(pre1.textContent).toBe('true');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('deletes properties via the delete trap', () => {
|
|
85
|
+
component ObjectTest() {
|
|
86
|
+
const obj = new TrackedObject({a: 0, b: 1});
|
|
87
|
+
|
|
88
|
+
<pre>{String(obj.a)}</pre>
|
|
89
|
+
<button onClick={() => { delete obj.a; }}>{'Delete A'}</button>
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
render(ObjectTest);
|
|
93
|
+
|
|
94
|
+
const pre1 = container.querySelectorAll('pre')[0];
|
|
95
|
+
const button = container.querySelectorAll('button')[0 ];
|
|
96
|
+
|
|
97
|
+
expect(pre1.textContent).toBe('0');
|
|
98
|
+
|
|
99
|
+
button.click();
|
|
100
|
+
flushSync();
|
|
101
|
+
|
|
102
|
+
expect(pre1.textContent).toBe('undefined');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('checks if non-existent property is reactive when added later', () => {
|
|
106
|
+
component ObjectTest() {
|
|
107
|
+
const obj = new TrackedObject({});
|
|
108
|
+
|
|
109
|
+
<pre>{String(obj.a)} </pre>
|
|
110
|
+
<button onClick={() => { obj.a = 1; }}>{'Add A'}</button>
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
render(ObjectTest);
|
|
114
|
+
|
|
115
|
+
const pre1 = container.querySelectorAll('pre')[0];
|
|
116
|
+
const button = container.querySelectorAll('button')[0 ];
|
|
117
|
+
|
|
118
|
+
expect(pre1.textContent).toBe('undefined');
|
|
119
|
+
|
|
120
|
+
button.click();
|
|
121
|
+
flushSync();
|
|
122
|
+
|
|
123
|
+
expect(pre1.textContent).toBe('1');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('checks that deeply nested objects are not proxied or reactive', () => {
|
|
127
|
+
component ObjectTest() {
|
|
128
|
+
const obj = new TrackedObject({ a: { b: 1 } });
|
|
129
|
+
|
|
130
|
+
<pre>{String(obj.a.b)}</pre>
|
|
131
|
+
<button onClick={() => { obj.a.b++; }}>{'Increment B'}</button>
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
render(ObjectTest);
|
|
135
|
+
|
|
136
|
+
const pre1 = container.querySelectorAll('pre')[0];
|
|
137
|
+
const button = container.querySelectorAll('button')[0 ];
|
|
138
|
+
|
|
139
|
+
expect(pre1.textContent).toBe('1');
|
|
140
|
+
|
|
141
|
+
button.click();
|
|
142
|
+
flushSync();
|
|
143
|
+
|
|
144
|
+
// remains unchanged
|
|
145
|
+
expect(pre1.textContent).toBe('1');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('checks if TRACKED_OBJECT symbol is present on TrackedObject instances', () => {
|
|
149
|
+
component ObjectTest() {
|
|
150
|
+
const obj = new TrackedObject({ a: 0 });
|
|
151
|
+
|
|
152
|
+
expect(obj[TRACKED_OBJECT]).toBe(true);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('uses the hash syntax for creating TrackedObject', () => {
|
|
157
|
+
component ObjectTest() {
|
|
158
|
+
const obj = #{ a: 0, b: 1, c: { d: {e: 8} } };
|
|
159
|
+
|
|
160
|
+
<pre>{obj.a}</pre>
|
|
161
|
+
<pre>{TRACKED_OBJECT in obj}</pre>
|
|
162
|
+
<pre>{JSON.stringify(obj)}</pre>
|
|
163
|
+
<button onClick={() => { obj.a++; }}>{'Increment A'}</button>
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
render(ObjectTest);
|
|
167
|
+
|
|
168
|
+
const pre1 = container.querySelectorAll('pre')[0];
|
|
169
|
+
const pre2 = container.querySelectorAll('pre')[1];
|
|
170
|
+
const pre3 = container.querySelectorAll('pre')[2];
|
|
171
|
+
const button = container.querySelectorAll('button')[0 ];
|
|
172
|
+
|
|
173
|
+
expect(pre1.textContent).toBe('0');
|
|
174
|
+
expect(pre2.textContent).toBe('true');
|
|
175
|
+
expect(pre3.textContent).toBe('{"a":0,"b":1,"c":{"d":{"e":8}}}');
|
|
176
|
+
|
|
177
|
+
button.click();
|
|
178
|
+
flushSync();
|
|
179
|
+
|
|
180
|
+
expect(pre1.textContent).toBe('1');
|
|
181
|
+
expect(pre3.textContent).toBe('{"a":1,"b":1,"c":{"d":{"e":8}}}');
|
|
182
|
+
})
|
|
183
|
+
});
|
package/types/index.d.ts
CHANGED
|
@@ -151,3 +151,11 @@ export type TrackedObjectDeep<T> =
|
|
|
151
151
|
: T extends object
|
|
152
152
|
? { [K in keyof T]: TrackedObjectDeep<T[K]> | Tracked<TrackedObjectDeep<T[K]>> }
|
|
153
153
|
: T | Tracked<T>;
|
|
154
|
+
|
|
155
|
+
export type TrackedObject<T extends object> = T & {};
|
|
156
|
+
|
|
157
|
+
export interface TrackedObjectConstructor {
|
|
158
|
+
new <T extends object>(obj: T): TrackedObject<T>;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export declare const TrackedObject: TrackedObjectConstructor;
|