ripple 0.2.27 → 0.2.29
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 +1 -1
- package/src/compiler/phases/1-parse/index.js +46 -1
- package/src/compiler/phases/2-analyze/index.js +25 -8
- package/src/compiler/phases/3-transform/index.js +71 -3
- package/src/compiler/utils.js +5 -4
- package/src/runtime/array.js +47 -4
- package/src/runtime/index.js +2 -0
- package/src/runtime/internal/client/blocks.js +51 -4
- package/src/runtime/internal/client/runtime.js +57 -15
- package/src/runtime/internal/client/types.d.ts +6 -5
- package/src/runtime/map.js +147 -0
- package/src/runtime/set.js +30 -53
- package/tests/boundaries.ripple +109 -0
- package/tests/compiler.test.ripple +21 -0
- package/tests/map.test.ripple +134 -0
- package/tests/set.test.ripple +1 -1
- package/types/index.d.ts +5 -0
- /package/tests/{use.test.ripple → decorators.ripple} +0 -0
package/package.json
CHANGED
|
@@ -90,6 +90,41 @@ function RipplePlugin(config) {
|
|
|
90
90
|
|
|
91
91
|
jsx_parseAttribute() {
|
|
92
92
|
let node = this.startNode();
|
|
93
|
+
const lookahead = this.lookahead();
|
|
94
|
+
|
|
95
|
+
if (lookahead.type?.label === ':') {
|
|
96
|
+
let id = this.startNode();
|
|
97
|
+
id.name = this.value;
|
|
98
|
+
node.name = id;
|
|
99
|
+
this.next();
|
|
100
|
+
this.finishNode(id, 'Identifier');
|
|
101
|
+
|
|
102
|
+
if (this.lookahead().value !== '=') {
|
|
103
|
+
this.unexpected();
|
|
104
|
+
}
|
|
105
|
+
this.next();
|
|
106
|
+
if (this.lookahead().type !== tt.braceL) {
|
|
107
|
+
this.unexpected();
|
|
108
|
+
}
|
|
109
|
+
this.next();
|
|
110
|
+
const value = this.jsx_parseAttributeValue();
|
|
111
|
+
const expression = value.expression;
|
|
112
|
+
node.get = null;
|
|
113
|
+
node.set = null;
|
|
114
|
+
|
|
115
|
+
if (expression.type == 'SequenceExpression') {
|
|
116
|
+
node.get = expression.expressions[0];
|
|
117
|
+
node.set = expression.expressions[1];
|
|
118
|
+
if (expression.expressions.length > 2) {
|
|
119
|
+
this.unexpected();
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
node.get = expression;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return this.finishNode(node, 'AccessorAttribute');
|
|
126
|
+
}
|
|
127
|
+
|
|
93
128
|
if (this.eat(tt.braceL)) {
|
|
94
129
|
if (this.type.label === '@') {
|
|
95
130
|
this.next();
|
|
@@ -389,12 +424,22 @@ function RipplePlugin(config) {
|
|
|
389
424
|
// This node is used for Prettier, we don't actually need
|
|
390
425
|
// the node for Ripple's transform process
|
|
391
426
|
element.children = [component.css];
|
|
427
|
+
// Ensure we escape JSX <tag></tag> context
|
|
428
|
+
const tokContexts = this.acornTypeScript.tokContexts;
|
|
429
|
+
const curContext = this.curContext();
|
|
430
|
+
|
|
431
|
+
if (curContext === tokContexts.tc_expr) {
|
|
432
|
+
this.context.pop();
|
|
433
|
+
}
|
|
434
|
+
|
|
392
435
|
return element;
|
|
393
436
|
} else {
|
|
437
|
+
this.enterScope(0);
|
|
394
438
|
this.parseTemplateBody(element.children);
|
|
439
|
+
this.exitScope();
|
|
395
440
|
}
|
|
441
|
+
// Ensure we escape JSX <tag></tag> context
|
|
396
442
|
const tokContexts = this.acornTypeScript.tokContexts;
|
|
397
|
-
|
|
398
443
|
const curContext = this.curContext();
|
|
399
444
|
|
|
400
445
|
if (curContext === tokContexts.tc_expr) {
|
|
@@ -151,6 +151,13 @@ const visitors = {
|
|
|
151
151
|
const { state, visit, path } = context;
|
|
152
152
|
|
|
153
153
|
for (const declarator of node.declarations) {
|
|
154
|
+
if (is_inside_component(context) && node.kind === 'var') {
|
|
155
|
+
error(
|
|
156
|
+
'var declarations are not allowed in components, use let or const instead',
|
|
157
|
+
state.analysis.module.filename,
|
|
158
|
+
declarator,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
154
161
|
const metadata = { tracking: false, await: false };
|
|
155
162
|
const parent = path.at(-1);
|
|
156
163
|
const init_is_untracked =
|
|
@@ -321,12 +328,7 @@ const visitors = {
|
|
|
321
328
|
binding.transform = {
|
|
322
329
|
read: (_) => b.call('$.get_computed', path.node),
|
|
323
330
|
assign: (node, value) => {
|
|
324
|
-
return b.call(
|
|
325
|
-
'$.set',
|
|
326
|
-
path.node,
|
|
327
|
-
value,
|
|
328
|
-
b.id('__block'),
|
|
329
|
-
);
|
|
331
|
+
return b.call('$.set', path.node, value, b.id('__block'));
|
|
330
332
|
},
|
|
331
333
|
update: (_) =>
|
|
332
334
|
b.call(
|
|
@@ -451,6 +453,12 @@ const visitors = {
|
|
|
451
453
|
}
|
|
452
454
|
}
|
|
453
455
|
}
|
|
456
|
+
} else if (attr.type === 'AccessorAttribute') {
|
|
457
|
+
error(
|
|
458
|
+
'Accessor props are not supported on DOM elements',
|
|
459
|
+
state.analysis.module.filename,
|
|
460
|
+
attr,
|
|
461
|
+
);
|
|
454
462
|
}
|
|
455
463
|
}
|
|
456
464
|
} else {
|
|
@@ -459,6 +467,15 @@ const visitors = {
|
|
|
459
467
|
if (attr.name.type === 'Identifier') {
|
|
460
468
|
attribute_names.add(attr.name);
|
|
461
469
|
}
|
|
470
|
+
} else if (attr.type === 'AccessorAttribute') {
|
|
471
|
+
if (!attr.name.name.startsWith('$')) {
|
|
472
|
+
error(
|
|
473
|
+
'Accessor props must always be $ prefixed as they are reactive',
|
|
474
|
+
state.analysis.module.filename,
|
|
475
|
+
attr,
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
attribute_names.add(attr.name);
|
|
462
479
|
}
|
|
463
480
|
}
|
|
464
481
|
|
|
@@ -472,7 +489,7 @@ const visitors = {
|
|
|
472
489
|
if (implicit_children) {
|
|
473
490
|
error(
|
|
474
491
|
'Cannot have both implicit and explicit children',
|
|
475
|
-
|
|
492
|
+
state.analysis.module.filename,
|
|
476
493
|
node,
|
|
477
494
|
);
|
|
478
495
|
}
|
|
@@ -482,7 +499,7 @@ const visitors = {
|
|
|
482
499
|
if (explicit_children) {
|
|
483
500
|
error(
|
|
484
501
|
'Cannot have both implicit and explicit children',
|
|
485
|
-
|
|
502
|
+
state.analysis.module.filename,
|
|
486
503
|
node,
|
|
487
504
|
);
|
|
488
505
|
}
|
|
@@ -133,6 +133,7 @@ const visitors = {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
if (
|
|
136
|
+
!is_inside_component(context, true) ||
|
|
136
137
|
context.state.to_ts ||
|
|
137
138
|
(parent?.type === 'MemberExpression' && parent.property === node) ||
|
|
138
139
|
is_inside_call_expression(context) ||
|
|
@@ -164,6 +165,10 @@ const visitors = {
|
|
|
164
165
|
context.state.metadata.tracking = true;
|
|
165
166
|
}
|
|
166
167
|
|
|
168
|
+
if (!is_inside_component(context, true) || is_inside_call_expression(context)) {
|
|
169
|
+
return context.next();
|
|
170
|
+
}
|
|
171
|
+
|
|
167
172
|
return b.call(
|
|
168
173
|
'$.with_scope',
|
|
169
174
|
b.id('__block'),
|
|
@@ -572,7 +577,18 @@ const visitors = {
|
|
|
572
577
|
);
|
|
573
578
|
}
|
|
574
579
|
|
|
575
|
-
|
|
580
|
+
const init = [];
|
|
581
|
+
const update = [];
|
|
582
|
+
|
|
583
|
+
transform_children(node.children, { visit, state: { ...state, init, update }, root: false });
|
|
584
|
+
|
|
585
|
+
if (init.length > 0) {
|
|
586
|
+
state.init.push(b.block(init));
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (update.length > 0) {
|
|
590
|
+
state.init.push(b.stmt(b.call('$.render', b.thunk(b.block(update)))));
|
|
591
|
+
}
|
|
576
592
|
|
|
577
593
|
state.template.push(`</${node.id.name}>`);
|
|
578
594
|
} else {
|
|
@@ -617,6 +633,52 @@ const visitors = {
|
|
|
617
633
|
);
|
|
618
634
|
} else if (attr.type === 'UseAttribute') {
|
|
619
635
|
props.push(b.prop('init', b.call('$.use_prop'), visit(attr.argument, state), true));
|
|
636
|
+
} else if (attr.type === 'AccessorAttribute') {
|
|
637
|
+
// # means it's an accessor to the runtime
|
|
638
|
+
tracked.push(b.literal('#' + attr.name.name));
|
|
639
|
+
let get_expr;
|
|
640
|
+
|
|
641
|
+
if (
|
|
642
|
+
attr.get.type === 'FunctionExpression' ||
|
|
643
|
+
attr.get.type === 'ArrowFunctionExpression'
|
|
644
|
+
) {
|
|
645
|
+
get_expr = context.state.scope.generate(attr.name.name + '_get');
|
|
646
|
+
|
|
647
|
+
state.init.push(b.const(get_expr, visit(attr.get, state)));
|
|
648
|
+
} else {
|
|
649
|
+
get_expr = visit(attr.get, state);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
props.push(
|
|
653
|
+
b.prop('get', attr.name, b.function(null, [], b.block([b.return(b.call(get_expr))]))),
|
|
654
|
+
);
|
|
655
|
+
|
|
656
|
+
if (attr.set) {
|
|
657
|
+
let set_expr;
|
|
658
|
+
|
|
659
|
+
if (
|
|
660
|
+
attr.set.type === 'FunctionExpression' ||
|
|
661
|
+
attr.set.type === 'ArrowFunctionExpression'
|
|
662
|
+
) {
|
|
663
|
+
set_expr = context.state.scope.generate(attr.name.name + '_set');
|
|
664
|
+
|
|
665
|
+
state.init.push(b.const(set_expr, visit(attr.set, state)));
|
|
666
|
+
} else {
|
|
667
|
+
set_expr = visit(attr.set, state);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
props.push(
|
|
671
|
+
b.prop(
|
|
672
|
+
'set',
|
|
673
|
+
attr.name,
|
|
674
|
+
b.function(
|
|
675
|
+
null,
|
|
676
|
+
[b.id('__value')],
|
|
677
|
+
b.block([b.return(b.call(set_expr, b.id('__value')))]),
|
|
678
|
+
),
|
|
679
|
+
),
|
|
680
|
+
);
|
|
681
|
+
}
|
|
620
682
|
} else {
|
|
621
683
|
throw new Error('TODO');
|
|
622
684
|
}
|
|
@@ -802,7 +864,8 @@ const visitors = {
|
|
|
802
864
|
if (
|
|
803
865
|
left.type === 'MemberExpression' &&
|
|
804
866
|
left.property.type === 'Identifier' &&
|
|
805
|
-
is_tracked_name(left.property.name)
|
|
867
|
+
is_tracked_name(left.property.name) &&
|
|
868
|
+
left.property.name !== '$length'
|
|
806
869
|
) {
|
|
807
870
|
return b.call(
|
|
808
871
|
'$.set_property',
|
|
@@ -1211,7 +1274,7 @@ function transform_ts_child(node, context) {
|
|
|
1211
1274
|
.filter((attr) => {
|
|
1212
1275
|
if (attr.type === 'UseAttribute') {
|
|
1213
1276
|
use_attributes.push(attr);
|
|
1214
|
-
return false;
|
|
1277
|
+
return false;
|
|
1215
1278
|
}
|
|
1216
1279
|
return true;
|
|
1217
1280
|
})
|
|
@@ -1368,7 +1431,12 @@ function transform_ts_child(node, context) {
|
|
|
1368
1431
|
),
|
|
1369
1432
|
),
|
|
1370
1433
|
);
|
|
1434
|
+
} else if (node.type === 'Component') {
|
|
1435
|
+
const component = visit(node, context.state);
|
|
1436
|
+
|
|
1437
|
+
state.init.push(component);
|
|
1371
1438
|
} else {
|
|
1439
|
+
debugger;
|
|
1372
1440
|
throw new Error('TODO');
|
|
1373
1441
|
}
|
|
1374
1442
|
}
|
package/src/compiler/utils.js
CHANGED
|
@@ -327,15 +327,16 @@ export function build_hoisted_params(node, context) {
|
|
|
327
327
|
return params;
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
-
export function is_inside_component(context) {
|
|
330
|
+
export function is_inside_component(context, includes_functions = false) {
|
|
331
331
|
for (let i = context.path.length - 1; i >= 0; i -= 1) {
|
|
332
332
|
const context_node = context.path[i];
|
|
333
333
|
const type = context_node.type;
|
|
334
334
|
|
|
335
335
|
if (
|
|
336
|
-
|
|
337
|
-
type === '
|
|
338
|
-
|
|
336
|
+
!includes_functions &&
|
|
337
|
+
(type === 'FunctionExpression' ||
|
|
338
|
+
type === 'ArrowFunctionExpression' ||
|
|
339
|
+
type === 'FunctionDeclaration')
|
|
339
340
|
) {
|
|
340
341
|
return false;
|
|
341
342
|
}
|
package/src/runtime/array.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** @import { Block } from '#client' */
|
|
2
|
+
|
|
1
3
|
import { TRACKED_OBJECT } from './internal/client/constants.js';
|
|
2
4
|
import { get, increment, scope, set, tracked } from './internal/client/runtime.js';
|
|
3
5
|
|
|
@@ -35,6 +37,16 @@ const introspect_methods = [
|
|
|
35
37
|
|
|
36
38
|
let init = false;
|
|
37
39
|
|
|
40
|
+
/**
|
|
41
|
+
* @param {Block | null} block
|
|
42
|
+
* @throws {Error}
|
|
43
|
+
*/
|
|
44
|
+
function check_block(block) {
|
|
45
|
+
if (block === null) {
|
|
46
|
+
throw new Error('Cannot set $length outside of a reactive context');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
38
50
|
export class RippleArray extends Array {
|
|
39
51
|
#tracked_elements = [];
|
|
40
52
|
#tracked_index;
|
|
@@ -51,6 +63,7 @@ export class RippleArray extends Array {
|
|
|
51
63
|
super(...elements);
|
|
52
64
|
|
|
53
65
|
var block = scope();
|
|
66
|
+
check_block(block);
|
|
54
67
|
var tracked_elements = this.#tracked_elements;
|
|
55
68
|
|
|
56
69
|
for (var i = 0; i < this.length; i++) {
|
|
@@ -79,6 +92,7 @@ export class RippleArray extends Array {
|
|
|
79
92
|
|
|
80
93
|
fill() {
|
|
81
94
|
var block = scope();
|
|
95
|
+
check_block(block);
|
|
82
96
|
var tracked_elements = this.#tracked_elements;
|
|
83
97
|
|
|
84
98
|
super.fill();
|
|
@@ -89,6 +103,7 @@ export class RippleArray extends Array {
|
|
|
89
103
|
|
|
90
104
|
reverse() {
|
|
91
105
|
var block = scope();
|
|
106
|
+
check_block(block);
|
|
92
107
|
var tracked_elements = this.#tracked_elements;
|
|
93
108
|
|
|
94
109
|
super.reverse();
|
|
@@ -99,6 +114,7 @@ export class RippleArray extends Array {
|
|
|
99
114
|
|
|
100
115
|
sort(fn) {
|
|
101
116
|
var block = scope();
|
|
117
|
+
check_block(block);
|
|
102
118
|
var tracked_elements = this.#tracked_elements;
|
|
103
119
|
|
|
104
120
|
super.sort(fn);
|
|
@@ -107,8 +123,13 @@ export class RippleArray extends Array {
|
|
|
107
123
|
}
|
|
108
124
|
}
|
|
109
125
|
|
|
126
|
+
/**
|
|
127
|
+
* @param {...any} elements
|
|
128
|
+
* @returns {number}
|
|
129
|
+
*/
|
|
110
130
|
unshift(...elements) {
|
|
111
131
|
var block = scope();
|
|
132
|
+
check_block(block);
|
|
112
133
|
var tracked_elements = this.#tracked_elements;
|
|
113
134
|
|
|
114
135
|
super.unshift(...elements);
|
|
@@ -117,11 +138,14 @@ export class RippleArray extends Array {
|
|
|
117
138
|
}
|
|
118
139
|
tracked_elements.unshift(...elements.map(() => tracked(0, block)));
|
|
119
140
|
|
|
120
|
-
|
|
141
|
+
var length = this.length;
|
|
142
|
+
set(this.#tracked_index, length, block);
|
|
143
|
+
return length;
|
|
121
144
|
}
|
|
122
145
|
|
|
123
146
|
shift() {
|
|
124
147
|
var block = scope();
|
|
148
|
+
check_block(block);
|
|
125
149
|
var tracked_elements = this.#tracked_elements;
|
|
126
150
|
|
|
127
151
|
super.shift();
|
|
@@ -133,8 +157,13 @@ export class RippleArray extends Array {
|
|
|
133
157
|
set(this.#tracked_index, this.length, block);
|
|
134
158
|
}
|
|
135
159
|
|
|
160
|
+
/**
|
|
161
|
+
* @param {...any} elements
|
|
162
|
+
* @returns {number}
|
|
163
|
+
*/
|
|
136
164
|
push(...elements) {
|
|
137
165
|
var block = scope();
|
|
166
|
+
check_block(block);
|
|
138
167
|
var start_index = this.length;
|
|
139
168
|
var tracked_elements = this.#tracked_elements;
|
|
140
169
|
|
|
@@ -143,11 +172,14 @@ export class RippleArray extends Array {
|
|
|
143
172
|
for (var i = 0; i < elements.length; i++) {
|
|
144
173
|
tracked_elements[start_index + i] = tracked(0, block);
|
|
145
174
|
}
|
|
175
|
+
var length = this.length;
|
|
146
176
|
set(this.#tracked_index, this.length, block);
|
|
177
|
+
return length;
|
|
147
178
|
}
|
|
148
179
|
|
|
149
180
|
pop() {
|
|
150
181
|
var block = scope();
|
|
182
|
+
check_block(block);
|
|
151
183
|
var tracked_elements = this.#tracked_elements;
|
|
152
184
|
|
|
153
185
|
super.pop();
|
|
@@ -159,8 +191,15 @@ export class RippleArray extends Array {
|
|
|
159
191
|
set(this.#tracked_index, this.length, block);
|
|
160
192
|
}
|
|
161
193
|
|
|
194
|
+
/**
|
|
195
|
+
* @param {number} start
|
|
196
|
+
* @param {number} [delete_count]
|
|
197
|
+
* @param {...any} elements
|
|
198
|
+
* @returns {any[]}
|
|
199
|
+
*/
|
|
162
200
|
splice(start, delete_count, ...elements) {
|
|
163
201
|
var block = scope();
|
|
202
|
+
check_block(block);
|
|
164
203
|
var tracked_elements = this.#tracked_elements;
|
|
165
204
|
|
|
166
205
|
super.splice(start, delete_count, ...elements);
|
|
@@ -182,6 +221,7 @@ export class RippleArray extends Array {
|
|
|
182
221
|
|
|
183
222
|
set $length(length) {
|
|
184
223
|
var block = scope();
|
|
224
|
+
check_block(block);
|
|
185
225
|
var tracked_elements = this.#tracked_elements;
|
|
186
226
|
|
|
187
227
|
if (length !== this.$length) {
|
|
@@ -189,13 +229,12 @@ export class RippleArray extends Array {
|
|
|
189
229
|
increment(tracked_elements[i], block);
|
|
190
230
|
}
|
|
191
231
|
this.length = length;
|
|
232
|
+
set(this.#tracked_index, length, block);
|
|
192
233
|
tracked_elements.length = length;
|
|
193
|
-
|
|
194
|
-
return true;
|
|
195
234
|
}
|
|
196
|
-
return false;
|
|
197
235
|
}
|
|
198
236
|
|
|
237
|
+
/** @param {number} _ */
|
|
199
238
|
set length(_) {
|
|
200
239
|
throw new Error('Cannot set length on RippleArray, use $length instead');
|
|
201
240
|
}
|
|
@@ -206,6 +245,10 @@ export class RippleArray extends Array {
|
|
|
206
245
|
}
|
|
207
246
|
}
|
|
208
247
|
|
|
248
|
+
/**
|
|
249
|
+
* @param {RippleArray} array
|
|
250
|
+
* @returns {any[]}
|
|
251
|
+
*/
|
|
209
252
|
export function get_all_elements(array) {
|
|
210
253
|
var tracked_elements = array[TRACKED_OBJECT];
|
|
211
254
|
var arr = [];
|
package/src/runtime/index.js
CHANGED
|
@@ -42,6 +42,8 @@ export { RippleArray } from './array.js';
|
|
|
42
42
|
|
|
43
43
|
export { RippleSet } from './set.js';
|
|
44
44
|
|
|
45
|
+
export { RippleMap } from './map.js';
|
|
46
|
+
|
|
45
47
|
export { keyed } from './internal/client/for.js';
|
|
46
48
|
|
|
47
49
|
export { user_effect as effect } from './internal/client/blocks.js';
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
active_block,
|
|
19
19
|
active_component,
|
|
20
20
|
active_reaction,
|
|
21
|
+
is_block_dirty,
|
|
21
22
|
run_block,
|
|
22
23
|
run_teardown,
|
|
23
24
|
schedule_update,
|
|
@@ -93,21 +94,28 @@ export function async(fn) {
|
|
|
93
94
|
});
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
/**
|
|
98
|
+
* @param {Element} element
|
|
99
|
+
* @param {() => (element: Element) => (void | (() => void))} get_fn
|
|
100
|
+
* @returns {Block}
|
|
101
|
+
*/
|
|
96
102
|
export function use(element, get_fn) {
|
|
97
|
-
|
|
103
|
+
/** @type {(element: Element) => (void | (() => void) | undefined)} */
|
|
104
|
+
var use_fn;
|
|
105
|
+
/** @type {Block | null} */
|
|
98
106
|
var e;
|
|
99
107
|
|
|
100
108
|
return block(RENDER_BLOCK, () => {
|
|
101
|
-
if (
|
|
109
|
+
if (use_fn !== (use_fn = get_fn())) {
|
|
102
110
|
if (e) {
|
|
103
111
|
destroy_block(e);
|
|
104
112
|
e = null;
|
|
105
113
|
}
|
|
106
114
|
|
|
107
|
-
if (
|
|
115
|
+
if (use_fn) {
|
|
108
116
|
e = branch(() => {
|
|
109
117
|
effect(() => {
|
|
110
|
-
return
|
|
118
|
+
return use_fn(element);
|
|
111
119
|
});
|
|
112
120
|
});
|
|
113
121
|
}
|
|
@@ -115,14 +123,27 @@ export function use(element, get_fn) {
|
|
|
115
123
|
});
|
|
116
124
|
}
|
|
117
125
|
|
|
126
|
+
/**
|
|
127
|
+
* @param {() => void} fn
|
|
128
|
+
* @returns {Block}
|
|
129
|
+
*/
|
|
118
130
|
export function root(fn) {
|
|
119
131
|
return block(ROOT_BLOCK, fn);
|
|
120
132
|
}
|
|
121
133
|
|
|
134
|
+
/**
|
|
135
|
+
* @param {() => void} fn
|
|
136
|
+
* @param {any} state
|
|
137
|
+
* @returns {Block}
|
|
138
|
+
*/
|
|
122
139
|
export function create_try_block(fn, state) {
|
|
123
140
|
return block(TRY_BLOCK, fn, state);
|
|
124
141
|
}
|
|
125
142
|
|
|
143
|
+
/**
|
|
144
|
+
* @param {Block} block
|
|
145
|
+
* @param {Block} parent_block
|
|
146
|
+
*/
|
|
126
147
|
function push_block(block, parent_block) {
|
|
127
148
|
var parent_last = parent_block.last;
|
|
128
149
|
if (parent_last === null) {
|
|
@@ -174,6 +195,10 @@ export function block(flags, fn, state = null) {
|
|
|
174
195
|
return block;
|
|
175
196
|
}
|
|
176
197
|
|
|
198
|
+
/**
|
|
199
|
+
* @param {Block} parent
|
|
200
|
+
* @param {boolean} [remove_dom]
|
|
201
|
+
*/
|
|
177
202
|
export function destroy_block_children(parent, remove_dom = false) {
|
|
178
203
|
var block = parent.first;
|
|
179
204
|
parent.first = parent.last = null;
|
|
@@ -187,6 +212,10 @@ export function destroy_block_children(parent, remove_dom = false) {
|
|
|
187
212
|
}
|
|
188
213
|
}
|
|
189
214
|
|
|
215
|
+
/**
|
|
216
|
+
* @param {Block} parent
|
|
217
|
+
* @param {boolean} [remove_dom]
|
|
218
|
+
*/
|
|
190
219
|
export function destroy_non_branch_children(parent, remove_dom = false) {
|
|
191
220
|
var block = parent.first;
|
|
192
221
|
|
|
@@ -207,6 +236,9 @@ export function destroy_non_branch_children(parent, remove_dom = false) {
|
|
|
207
236
|
}
|
|
208
237
|
}
|
|
209
238
|
|
|
239
|
+
/**
|
|
240
|
+
* @param {Block} block
|
|
241
|
+
*/
|
|
210
242
|
export function unlink_block(block) {
|
|
211
243
|
var parent = block.p;
|
|
212
244
|
var prev = block.prev;
|
|
@@ -221,6 +253,9 @@ export function unlink_block(block) {
|
|
|
221
253
|
}
|
|
222
254
|
}
|
|
223
255
|
|
|
256
|
+
/**
|
|
257
|
+
* @param {Block} block
|
|
258
|
+
*/
|
|
224
259
|
export function pause_block(block) {
|
|
225
260
|
if ((block.f & PAUSED) !== 0) {
|
|
226
261
|
return;
|
|
@@ -238,6 +273,9 @@ export function pause_block(block) {
|
|
|
238
273
|
run_teardown(block);
|
|
239
274
|
}
|
|
240
275
|
|
|
276
|
+
/**
|
|
277
|
+
* @param {Block} block
|
|
278
|
+
*/
|
|
241
279
|
export function resume_block(block) {
|
|
242
280
|
if ((block.f & PAUSED) === 0) {
|
|
243
281
|
return;
|
|
@@ -257,7 +295,12 @@ export function resume_block(block) {
|
|
|
257
295
|
}
|
|
258
296
|
}
|
|
259
297
|
|
|
298
|
+
/**
|
|
299
|
+
* @param {Block} target_block
|
|
300
|
+
* @returns {boolean}
|
|
301
|
+
*/
|
|
260
302
|
export function is_destroyed(target_block) {
|
|
303
|
+
/** @type {Block | null} */
|
|
261
304
|
var block = target_block;
|
|
262
305
|
|
|
263
306
|
while (block !== null) {
|
|
@@ -274,6 +317,10 @@ export function is_destroyed(target_block) {
|
|
|
274
317
|
return true;
|
|
275
318
|
}
|
|
276
319
|
|
|
320
|
+
/**
|
|
321
|
+
* @param {Block} block
|
|
322
|
+
* @param {boolean} [remove_dom]
|
|
323
|
+
*/
|
|
277
324
|
export function destroy_block(block, remove_dom = true) {
|
|
278
325
|
block.f ^= DESTROYED;
|
|
279
326
|
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
USE_PROP,
|
|
29
29
|
} from './constants';
|
|
30
30
|
import { capture, suspend } from './try.js';
|
|
31
|
-
import { define_property, is_array } from './utils';
|
|
31
|
+
import { define_property, get_descriptor, is_array } from './utils';
|
|
32
32
|
import {
|
|
33
33
|
object_keys as original_object_keys,
|
|
34
34
|
object_values as original_object_values,
|
|
@@ -192,6 +192,7 @@ function run_computed(computed) {
|
|
|
192
192
|
* @param {Block} block
|
|
193
193
|
*/
|
|
194
194
|
export function handle_error(error, block) {
|
|
195
|
+
/** @type {Block | null} */
|
|
195
196
|
var current = block;
|
|
196
197
|
|
|
197
198
|
while (current !== null) {
|
|
@@ -230,6 +231,7 @@ export function run_block(block) {
|
|
|
230
231
|
|
|
231
232
|
if (typeof res === 'function') {
|
|
232
233
|
block.t = res;
|
|
234
|
+
/** @type {Block | null} */
|
|
233
235
|
let current = block;
|
|
234
236
|
|
|
235
237
|
while (current !== null && (current.f & CONTAINS_TEARDOWN) === 0) {
|
|
@@ -332,7 +334,7 @@ function is_tracking_dirty(tracking) {
|
|
|
332
334
|
/**
|
|
333
335
|
* @param {Block} block
|
|
334
336
|
*/
|
|
335
|
-
function is_block_dirty(block) {
|
|
337
|
+
export function is_block_dirty(block) {
|
|
336
338
|
var flags = block.f;
|
|
337
339
|
|
|
338
340
|
if ((flags & (ROOT_BLOCK | BRANCH_BLOCK)) !== 0) {
|
|
@@ -407,6 +409,7 @@ export function deferred(fn) {
|
|
|
407
409
|
var parent = active_block;
|
|
408
410
|
var block = active_scope;
|
|
409
411
|
var res = [UNINITIALIZED];
|
|
412
|
+
// TODO implement DEFERRED flag on tracked
|
|
410
413
|
var t = tracked(UNINITIALIZED, block, DEFERRED);
|
|
411
414
|
var tracked_properties = [t];
|
|
412
415
|
var prev_value = UNINITIALIZED;
|
|
@@ -535,7 +538,7 @@ function flush_updates(root_block) {
|
|
|
535
538
|
}
|
|
536
539
|
}
|
|
537
540
|
|
|
538
|
-
/** @type {Block} */
|
|
541
|
+
/** @type {Block | null} */
|
|
539
542
|
var parent = current.p;
|
|
540
543
|
current = current.next;
|
|
541
544
|
|
|
@@ -626,7 +629,7 @@ export function schedule_update(block) {
|
|
|
626
629
|
if ((flags & ROOT_BLOCK) !== 0) {
|
|
627
630
|
break;
|
|
628
631
|
}
|
|
629
|
-
current = current.p;
|
|
632
|
+
current = /** @type {Block} */ (current.p);
|
|
630
633
|
}
|
|
631
634
|
|
|
632
635
|
queued_root_blocks.push(current);
|
|
@@ -773,6 +776,11 @@ export function flush_sync(fn) {
|
|
|
773
776
|
}
|
|
774
777
|
}
|
|
775
778
|
|
|
779
|
+
/**
|
|
780
|
+
* @template T
|
|
781
|
+
* @param {() => T} fn
|
|
782
|
+
* @returns {T & { [SPREAD_OBJECT]: () => T }}
|
|
783
|
+
*/
|
|
776
784
|
export function tracked_spread_object(fn) {
|
|
777
785
|
var obj = fn();
|
|
778
786
|
|
|
@@ -784,7 +792,14 @@ export function tracked_spread_object(fn) {
|
|
|
784
792
|
return obj;
|
|
785
793
|
}
|
|
786
794
|
|
|
795
|
+
/**
|
|
796
|
+
* @param {any} obj
|
|
797
|
+
* @param {string[]} properties
|
|
798
|
+
* @param {Block} block
|
|
799
|
+
* @returns {object}
|
|
800
|
+
*/
|
|
787
801
|
export function tracked_object(obj, properties, block) {
|
|
802
|
+
/** @type {Record<string, Tracked | Computed>} */
|
|
788
803
|
var tracked_properties = obj[TRACKED_OBJECT];
|
|
789
804
|
|
|
790
805
|
if (tracked_properties === undefined) {
|
|
@@ -797,27 +812,42 @@ export function tracked_object(obj, properties, block) {
|
|
|
797
812
|
|
|
798
813
|
for (var i = 0; i < properties.length; i++) {
|
|
799
814
|
var property = properties[i];
|
|
800
|
-
|
|
815
|
+
/** @type {Tracked | Computed} */
|
|
801
816
|
var tracked_property;
|
|
802
817
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
818
|
+
// accessor passed in, to avoid an expensive get_descriptor call
|
|
819
|
+
// in the fast path
|
|
820
|
+
if (property[0] === '#') {
|
|
821
|
+
property = property.slice(1);
|
|
822
|
+
var descriptor = /** @type {PropertyDescriptor} */ (get_descriptor(obj, property));
|
|
823
|
+
var desc_get = descriptor.get;
|
|
824
|
+
tracked_property = computed(desc_get, block);
|
|
825
|
+
/** @type {any} */
|
|
826
|
+
var initial = run_computed(/** @type {Computed} */ (tracked_property));
|
|
806
827
|
obj[property] = initial;
|
|
807
|
-
// TODO If nothing is tracked in the computed function, we can make it a standard tracked
|
|
808
|
-
// however this is more allocations, so we probably want to minimize this
|
|
809
|
-
// if (tracked_property.d === null) {
|
|
810
|
-
// tracked_property = tracked(initial, block);
|
|
811
|
-
// }
|
|
812
828
|
} else {
|
|
813
|
-
|
|
829
|
+
var initial = obj[property];
|
|
830
|
+
|
|
831
|
+
if (typeof initial === 'function' && initial[COMPUTED_PROPERTY] === true) {
|
|
832
|
+
tracked_property = computed(initial, block);
|
|
833
|
+
initial = run_computed(/** @type {Computed} */ (tracked_property));
|
|
834
|
+
obj[property] = initial;
|
|
835
|
+
} else {
|
|
836
|
+
tracked_property = tracked(initial, block);
|
|
837
|
+
}
|
|
814
838
|
}
|
|
839
|
+
|
|
815
840
|
tracked_properties[property] = tracked_property;
|
|
816
841
|
}
|
|
817
842
|
|
|
818
843
|
return obj;
|
|
819
844
|
}
|
|
820
845
|
|
|
846
|
+
/**
|
|
847
|
+
* @template T
|
|
848
|
+
* @param {() => T} fn
|
|
849
|
+
* @returns {() => T}
|
|
850
|
+
*/
|
|
821
851
|
export function computed_property(fn) {
|
|
822
852
|
define_property(fn, COMPUTED_PROPERTY, {
|
|
823
853
|
value: true,
|
|
@@ -1002,6 +1032,12 @@ export function spread_object(obj) {
|
|
|
1002
1032
|
return values;
|
|
1003
1033
|
}
|
|
1004
1034
|
|
|
1035
|
+
/**
|
|
1036
|
+
* @template T
|
|
1037
|
+
* @param {Block} block
|
|
1038
|
+
* @param {() => T} fn
|
|
1039
|
+
* @returns {T}
|
|
1040
|
+
*/
|
|
1005
1041
|
export function with_scope(block, fn) {
|
|
1006
1042
|
var previous_scope = active_scope;
|
|
1007
1043
|
try {
|
|
@@ -1054,6 +1090,12 @@ export function use_prop() {
|
|
|
1054
1090
|
return Symbol(USE_PROP);
|
|
1055
1091
|
}
|
|
1056
1092
|
|
|
1093
|
+
/**
|
|
1094
|
+
* @template T
|
|
1095
|
+
* @param {T | undefined} value
|
|
1096
|
+
* @param {T} fallback
|
|
1097
|
+
* @returns {T}
|
|
1098
|
+
*/
|
|
1057
1099
|
export function fallback(value, fallback) {
|
|
1058
1100
|
return value === undefined ? fallback : value;
|
|
1059
|
-
}
|
|
1101
|
+
}
|
|
@@ -5,7 +5,7 @@ export type Component = {
|
|
|
5
5
|
e: null | Array<{
|
|
6
6
|
b: Block;
|
|
7
7
|
fn: Function;
|
|
8
|
-
r: null | Block;
|
|
8
|
+
r: null | Block | Computed;
|
|
9
9
|
}>;
|
|
10
10
|
p: null | Component;
|
|
11
11
|
m: boolean;
|
|
@@ -40,10 +40,11 @@ export type Block = {
|
|
|
40
40
|
first: null | Block;
|
|
41
41
|
f: number;
|
|
42
42
|
fn: any;
|
|
43
|
-
last: null;
|
|
44
|
-
next: null;
|
|
43
|
+
last: null | Block;
|
|
44
|
+
next: null | Block;
|
|
45
45
|
p: null | Block;
|
|
46
|
-
prev: null;
|
|
46
|
+
prev: null | Block;
|
|
47
47
|
s: any;
|
|
48
|
-
|
|
48
|
+
// teardown function
|
|
49
|
+
t: (() => {}) | null;
|
|
49
50
|
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { get, increment, scope, set, tracked } from './internal/client/runtime.js';
|
|
2
|
+
|
|
3
|
+
const introspect_methods = ['entries', 'forEach', 'values', Symbol.iterator];
|
|
4
|
+
|
|
5
|
+
let init = false;
|
|
6
|
+
|
|
7
|
+
export class RippleMap extends Map {
|
|
8
|
+
#tracked_size;
|
|
9
|
+
#tracked_items = new Map();
|
|
10
|
+
|
|
11
|
+
constructor(iterable) {
|
|
12
|
+
super();
|
|
13
|
+
|
|
14
|
+
var block = scope();
|
|
15
|
+
|
|
16
|
+
if (iterable) {
|
|
17
|
+
for (var [key, value] of iterable) {
|
|
18
|
+
super.set(key, value);
|
|
19
|
+
this.#tracked_items.set(key, tracked(0, block));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
this.#tracked_size = tracked(this.size, block);
|
|
24
|
+
|
|
25
|
+
if (!init) {
|
|
26
|
+
init = true;
|
|
27
|
+
this.#init();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#init() {
|
|
32
|
+
var proto = RippleMap.prototype;
|
|
33
|
+
var map_proto = Map.prototype;
|
|
34
|
+
|
|
35
|
+
for (const method of introspect_methods) {
|
|
36
|
+
proto[method] = function (...v) {
|
|
37
|
+
this.$size;
|
|
38
|
+
this.#read_all();
|
|
39
|
+
|
|
40
|
+
return map_proto[method].apply(this, v);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get(key) {
|
|
46
|
+
var tracked_items = this.#tracked_items;
|
|
47
|
+
var t = tracked_items.get(key);
|
|
48
|
+
|
|
49
|
+
if (t === undefined) {
|
|
50
|
+
// same logic as has
|
|
51
|
+
this.$size;
|
|
52
|
+
} else {
|
|
53
|
+
get(t);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return super.get(key);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
has(key) {
|
|
60
|
+
var has = super.has(key);
|
|
61
|
+
var tracked_items = this.#tracked_items;
|
|
62
|
+
var t = tracked_items.get(key);
|
|
63
|
+
|
|
64
|
+
if (t === undefined) {
|
|
65
|
+
// if no tracked it also means super didn't have it
|
|
66
|
+
// It's not possible to have a disconnect, we tract each key
|
|
67
|
+
// If the key doesn't exist, track the size in case it's added later
|
|
68
|
+
// but don't create tracked entries willy-nilly to track all possible keys
|
|
69
|
+
this.$size;
|
|
70
|
+
} else {
|
|
71
|
+
get(t);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return has;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
set(key, value) {
|
|
78
|
+
var block = scope();
|
|
79
|
+
var tracked_items = this.#tracked_items;
|
|
80
|
+
var t = tracked_items.get(key);
|
|
81
|
+
var prev_res = super.get(key);
|
|
82
|
+
|
|
83
|
+
super.set(key, value);
|
|
84
|
+
|
|
85
|
+
if (!t) {
|
|
86
|
+
tracked_items.set(key, tracked(0, block));
|
|
87
|
+
set(this.#tracked_size, this.size, block);
|
|
88
|
+
} else if (prev_res !== value) {
|
|
89
|
+
increment(t, block);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
delete(key) {
|
|
96
|
+
var block = scope();
|
|
97
|
+
var tracked_items = this.#tracked_items;
|
|
98
|
+
var t = tracked_items.get(key);
|
|
99
|
+
var result = super.delete(key);
|
|
100
|
+
|
|
101
|
+
if (t) {
|
|
102
|
+
increment(t, block);
|
|
103
|
+
tracked_items.delete(key);
|
|
104
|
+
set(this.#tracked_size, this.size, block);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
clear() {
|
|
111
|
+
var block = scope();
|
|
112
|
+
|
|
113
|
+
if (super.size === 0) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (var [_, t] of this.#tracked_items) {
|
|
118
|
+
increment(t, block);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
super.clear();
|
|
122
|
+
this.#tracked_items.clear();
|
|
123
|
+
set(this.#tracked_size, 0, block);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
keys() {
|
|
127
|
+
this.$size;
|
|
128
|
+
return super.keys();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#read_all() {
|
|
132
|
+
for (const [, t] of this.#tracked_items) {
|
|
133
|
+
get(t);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
get $size() {
|
|
138
|
+
return get(this.#tracked_size);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
toJSON() {
|
|
142
|
+
this.$size;
|
|
143
|
+
this.#read_all();
|
|
144
|
+
|
|
145
|
+
return [...this];
|
|
146
|
+
}
|
|
147
|
+
}
|
package/src/runtime/set.js
CHANGED
|
@@ -1,25 +1,10 @@
|
|
|
1
1
|
import { get, increment, scope, set, tracked } from './internal/client/runtime.js';
|
|
2
2
|
|
|
3
|
-
const introspect_methods = [
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Symbol.iterator
|
|
9
|
-
];
|
|
10
|
-
|
|
11
|
-
const compare_other_methods = [
|
|
12
|
-
'isDisjointFrom',
|
|
13
|
-
'isSubsetOf',
|
|
14
|
-
'isSupersetOf',
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
const new_other_methods = [
|
|
18
|
-
'difference',
|
|
19
|
-
'intersection',
|
|
20
|
-
'symmetricDifference',
|
|
21
|
-
'union',
|
|
22
|
-
];
|
|
3
|
+
const introspect_methods = ['entries', 'forEach', 'keys', 'values', Symbol.iterator];
|
|
4
|
+
|
|
5
|
+
const compare_other_methods = ['isDisjointFrom', 'isSubsetOf', 'isSupersetOf'];
|
|
6
|
+
|
|
7
|
+
const new_other_methods = ['difference', 'intersection', 'symmetricDifference', 'union'];
|
|
23
8
|
|
|
24
9
|
let init = false;
|
|
25
10
|
|
|
@@ -91,9 +76,7 @@ export class RippleSet extends Set {
|
|
|
91
76
|
other.$size;
|
|
92
77
|
}
|
|
93
78
|
|
|
94
|
-
return new RippleSet(
|
|
95
|
-
set_proto[method].apply(this, [other, ...v])
|
|
96
|
-
);
|
|
79
|
+
return new RippleSet(set_proto[method].apply(this, [other, ...v]));
|
|
97
80
|
};
|
|
98
81
|
}
|
|
99
82
|
}
|
|
@@ -113,21 +96,17 @@ export class RippleSet extends Set {
|
|
|
113
96
|
delete(value) {
|
|
114
97
|
var block = scope();
|
|
115
98
|
|
|
116
|
-
if (super.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (t) {
|
|
121
|
-
increment(t, block);
|
|
122
|
-
}
|
|
99
|
+
if (!super.delete(value)) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
123
102
|
|
|
124
|
-
|
|
125
|
-
set(this.#tracked_size, this.size, block);
|
|
103
|
+
var t = this.#tracked_items.get(value);
|
|
126
104
|
|
|
127
|
-
|
|
128
|
-
|
|
105
|
+
increment(t, block);
|
|
106
|
+
this.#tracked_items.delete(value);
|
|
107
|
+
set(this.#tracked_size, this.size, block);
|
|
129
108
|
|
|
130
|
-
return
|
|
109
|
+
return true;
|
|
131
110
|
}
|
|
132
111
|
|
|
133
112
|
has(value) {
|
|
@@ -137,34 +116,32 @@ export class RippleSet extends Set {
|
|
|
137
116
|
var t = tracked_items.get(value);
|
|
138
117
|
|
|
139
118
|
if (t === undefined) {
|
|
140
|
-
if
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
t = tracked(0, block);
|
|
149
|
-
tracked_items.set(value, t);
|
|
119
|
+
// if no tracked it also means super didn't have it
|
|
120
|
+
// It's not possible to have a disconnect, we track each value
|
|
121
|
+
// If the value doesn't exist, track the size in case it's added later
|
|
122
|
+
// but don't create tracked entries willy-nilly to track all possible values
|
|
123
|
+
this.$size;
|
|
124
|
+
} else {
|
|
125
|
+
get(t);
|
|
150
126
|
}
|
|
151
127
|
|
|
152
|
-
get(t);
|
|
153
128
|
return has;
|
|
154
129
|
}
|
|
155
130
|
|
|
156
131
|
clear() {
|
|
157
132
|
var block = scope();
|
|
158
133
|
|
|
159
|
-
if (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
134
|
+
if (super.size === 0) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
163
137
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
set(this.#tracked_size, 0, block);
|
|
138
|
+
for (var [_, t] of this.#tracked_items) {
|
|
139
|
+
increment(t, block);
|
|
167
140
|
}
|
|
141
|
+
|
|
142
|
+
super.clear();
|
|
143
|
+
this.#tracked_items.clear();
|
|
144
|
+
set(this.#tracked_size, 0, block);
|
|
168
145
|
}
|
|
169
146
|
|
|
170
147
|
get $size() {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { mount, flushSync, effect } from 'ripple';
|
|
4
|
+
|
|
5
|
+
describe('passing reactivity between boundaries tests', () => {
|
|
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('can pass reactivity between functions with simple arrays and destructuring', () => {
|
|
25
|
+
let log: string[] = [];
|
|
26
|
+
|
|
27
|
+
function createDouble([ $count ]) {
|
|
28
|
+
const $double = $count * 2;
|
|
29
|
+
|
|
30
|
+
effect(() => {
|
|
31
|
+
log.push('Count:' + $count);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return [ $double ];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
component App() {
|
|
38
|
+
let $count = 0;
|
|
39
|
+
|
|
40
|
+
const [ $double ] = createDouble([ $count ]);
|
|
41
|
+
|
|
42
|
+
<div>{'Double: ' + $double}</div>
|
|
43
|
+
<button onClick={() => { $count++; }}>{'Increment'}</button>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
render(App);
|
|
47
|
+
flushSync();
|
|
48
|
+
|
|
49
|
+
expect(container.querySelector('div').textContent).toBe('Double: 0');
|
|
50
|
+
expect(log).toEqual(['Count:0']);
|
|
51
|
+
|
|
52
|
+
const button = container.querySelector('button');
|
|
53
|
+
|
|
54
|
+
button.click();
|
|
55
|
+
flushSync();
|
|
56
|
+
|
|
57
|
+
expect(container.querySelector('div').textContent).toBe('Double: 2');
|
|
58
|
+
expect(log).toEqual(['Count:0', 'Count:1']);
|
|
59
|
+
|
|
60
|
+
button.click();
|
|
61
|
+
flushSync();
|
|
62
|
+
|
|
63
|
+
expect(container.querySelector('div').textContent).toBe('Double: 4');
|
|
64
|
+
expect(log).toEqual(['Count:0', 'Count:1', 'Count:2']);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('can pass reactivity between functions with simple objects and destructuring', () => {
|
|
68
|
+
let log: string[] = [];
|
|
69
|
+
|
|
70
|
+
function createDouble({ $count }) {
|
|
71
|
+
const $double = $count * 2;
|
|
72
|
+
|
|
73
|
+
effect(() => {
|
|
74
|
+
log.push('Count:' + $count);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return { $double };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
component App() {
|
|
81
|
+
let $count = 0;
|
|
82
|
+
|
|
83
|
+
const { $double } = createDouble({ $count });
|
|
84
|
+
|
|
85
|
+
<div>{'Double: ' + $double}</div>
|
|
86
|
+
<button onClick={() => { $count++; }}>{'Increment'}</button>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
render(App);
|
|
90
|
+
flushSync();
|
|
91
|
+
|
|
92
|
+
expect(container.querySelector('div').textContent).toBe('Double: 0');
|
|
93
|
+
expect(log).toEqual(['Count:0']);
|
|
94
|
+
|
|
95
|
+
const button = container.querySelector('button');
|
|
96
|
+
|
|
97
|
+
button.click();
|
|
98
|
+
flushSync();
|
|
99
|
+
|
|
100
|
+
expect(container.querySelector('div').textContent).toBe('Double: 2');
|
|
101
|
+
expect(log).toEqual(['Count:0', 'Count:1']);
|
|
102
|
+
|
|
103
|
+
button.click();
|
|
104
|
+
flushSync();
|
|
105
|
+
|
|
106
|
+
expect(container.querySelector('div').textContent).toBe('Double: 4');
|
|
107
|
+
expect(log).toEqual(['Count:0', 'Count:1', 'Count:2']);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -58,4 +58,25 @@ describe('compiler success tests', () => {
|
|
|
58
58
|
render(App);
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
+
it('render lexical blocks without crashing', () => {
|
|
62
|
+
component App() {
|
|
63
|
+
<div>
|
|
64
|
+
const a = 1;
|
|
65
|
+
<div>
|
|
66
|
+
const b = 1;
|
|
67
|
+
</div>
|
|
68
|
+
<div>
|
|
69
|
+
const b = 1;
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
<div>
|
|
73
|
+
const a = 2;
|
|
74
|
+
<div>
|
|
75
|
+
const b = 1;
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
render(App);
|
|
81
|
+
})
|
|
61
82
|
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mount, flushSync, RippleMap } from 'ripple';
|
|
3
|
+
|
|
4
|
+
describe('RippleMap', () => {
|
|
5
|
+
let container;
|
|
6
|
+
|
|
7
|
+
function render(component) {
|
|
8
|
+
mount(component, {
|
|
9
|
+
target: container
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
container = document.createElement('div');
|
|
15
|
+
document.body.appendChild(container);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
document.body.removeChild(container);
|
|
20
|
+
container = null;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('handles set with update and delete operations with a reactive $size var', () => {
|
|
24
|
+
component MapTest() {
|
|
25
|
+
let map = new RippleMap([['a', 1], ['b', 2], ['c', 3]]);
|
|
26
|
+
let $value = map.get('a');
|
|
27
|
+
let $size = map.$size;
|
|
28
|
+
|
|
29
|
+
<button onClick={() => map.set('d', 4)}>{'set'}</button>
|
|
30
|
+
<button onClick={() => map.delete('b')}>{'delete'}</button>
|
|
31
|
+
<button onClick={() => map.set('a', 5)}>{'update'}</button>
|
|
32
|
+
|
|
33
|
+
<pre>{map.get('d')}</pre>
|
|
34
|
+
<pre>{$size}</pre>
|
|
35
|
+
<pre>{$value}</pre>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
render(MapTest);
|
|
39
|
+
|
|
40
|
+
const setButton = container.querySelectorAll('button')[0];
|
|
41
|
+
const deleteButton = container.querySelectorAll('button')[1];
|
|
42
|
+
const updateButton = container.querySelectorAll('button')[2];
|
|
43
|
+
|
|
44
|
+
setButton.click();
|
|
45
|
+
flushSync();
|
|
46
|
+
|
|
47
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('4');
|
|
48
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('4');
|
|
49
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('1');
|
|
50
|
+
|
|
51
|
+
deleteButton.click();
|
|
52
|
+
flushSync();
|
|
53
|
+
|
|
54
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('3');
|
|
55
|
+
|
|
56
|
+
updateButton.click();
|
|
57
|
+
flushSync();
|
|
58
|
+
|
|
59
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('5');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('handles clear operation', () => {
|
|
63
|
+
component MapTest() {
|
|
64
|
+
let map = new RippleMap([['a', 1], ['b', 2], ['c', 3]]);
|
|
65
|
+
|
|
66
|
+
<button onClick={() => map.clear()}>{'clear'}</button>
|
|
67
|
+
<pre>{map.$size}</pre>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
render(MapTest);
|
|
71
|
+
|
|
72
|
+
const clearButton = container.querySelector('button');
|
|
73
|
+
|
|
74
|
+
clearButton.click();
|
|
75
|
+
flushSync();
|
|
76
|
+
|
|
77
|
+
expect(container.querySelector('pre').textContent).toBe('0');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('handles has operation and tracks reactive $has', () => {
|
|
81
|
+
component MapTest() {
|
|
82
|
+
let map = new RippleMap([['a', 1], ['b', 2], ['c', 3]]);
|
|
83
|
+
let $has = map.has('b');
|
|
84
|
+
|
|
85
|
+
<button onClick={() => map.delete('b')}>{'delete'}</button>
|
|
86
|
+
<pre>{$has}</pre>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
render(MapTest);
|
|
90
|
+
|
|
91
|
+
const deleteButton = container.querySelector('button');
|
|
92
|
+
expect(container.querySelector('pre').textContent).toBe('true');
|
|
93
|
+
|
|
94
|
+
deleteButton.click();
|
|
95
|
+
flushSync();
|
|
96
|
+
|
|
97
|
+
expect(container.querySelector('pre').textContent).toBe('false');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('handles reactivity of keys, values, and entries', () => {
|
|
101
|
+
component MapTest() {
|
|
102
|
+
let map = new RippleMap([['x', 10], ['y', 20]]);
|
|
103
|
+
let $keys = Array.from(map.keys());
|
|
104
|
+
let $values = Array.from(map.values());
|
|
105
|
+
let $entries = Array.from(map.entries());
|
|
106
|
+
|
|
107
|
+
<button onClick={() => map.delete('x')}>{'delete'}</button>
|
|
108
|
+
|
|
109
|
+
<pre>{JSON.stringify($keys)}</pre>
|
|
110
|
+
<pre>{JSON.stringify($values)}</pre>
|
|
111
|
+
<pre>{JSON.stringify($entries)}</pre>
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
render(MapTest);
|
|
115
|
+
|
|
116
|
+
const deleteButton = container.querySelector('button');
|
|
117
|
+
|
|
118
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('["x","y"]');
|
|
119
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('[10,20]');
|
|
120
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('[["x",10],["y",20]]');
|
|
121
|
+
|
|
122
|
+
deleteButton.click();
|
|
123
|
+
flushSync();
|
|
124
|
+
|
|
125
|
+
expect(container.querySelectorAll('pre')[0].textContent).toBe('["y"]');
|
|
126
|
+
expect(container.querySelectorAll('pre')[1].textContent).toBe('[20]');
|
|
127
|
+
expect(container.querySelectorAll('pre')[2].textContent).toBe('[["y",20]]');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('toJSON returns correct object', () => {
|
|
131
|
+
let map = new RippleMap([['foo', 1], ['bar', 2]]);
|
|
132
|
+
expect(JSON.stringify(map)).toEqual('[["foo",1],["bar",2]]');
|
|
133
|
+
});
|
|
134
|
+
});
|
package/tests/set.test.ripple
CHANGED
package/types/index.d.ts
CHANGED
|
File without changes
|