ripple 0.2.202 → 0.2.204
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 -2
- package/src/compiler/index.d.ts +4 -0
- package/src/compiler/phases/3-transform/client/index.js +33 -7
- package/src/compiler/phases/3-transform/segments.js +15 -2
- package/src/compiler/source-map-utils.js +3 -3
- package/src/compiler/utils.js +76 -0
- package/src/runtime/internal/client/bindings.js +22 -17
- package/tests/client/input-value.test.ripple +302 -2
- package/types/index.d.ts +5 -1
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Ripple is an elegant TypeScript UI framework",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Dominic Gannaway",
|
|
6
|
-
"version": "0.2.
|
|
6
|
+
"version": "0.2.204",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/runtime/index-client.js",
|
|
9
9
|
"main": "src/runtime/index-client.js",
|
|
@@ -97,6 +97,6 @@
|
|
|
97
97
|
"vscode-languageserver-types": "^3.17.5"
|
|
98
98
|
},
|
|
99
99
|
"peerDependencies": {
|
|
100
|
-
"ripple": "0.2.
|
|
100
|
+
"ripple": "0.2.204"
|
|
101
101
|
}
|
|
102
102
|
}
|
package/src/compiler/index.d.ts
CHANGED
|
@@ -71,6 +71,10 @@ export interface CodeMapping extends VolarMapping<MappingData> {
|
|
|
71
71
|
data: MappingData;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
export interface CodeMappingWithAll extends CodeMapping {
|
|
75
|
+
generatedLengths: number[];
|
|
76
|
+
}
|
|
77
|
+
|
|
74
78
|
export interface VolarMappingsResult {
|
|
75
79
|
code: string;
|
|
76
80
|
mappings: CodeMapping[];
|
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
determine_namespace_for_children,
|
|
55
55
|
index_to_key,
|
|
56
56
|
is_element_dynamic,
|
|
57
|
+
is_inside_left_side_assignment,
|
|
57
58
|
} from '../../../utils.js';
|
|
58
59
|
import {
|
|
59
60
|
CSS_HASH_IDENTIFIER,
|
|
@@ -413,9 +414,25 @@ const visitors = {
|
|
|
413
414
|
is_capitalized: true,
|
|
414
415
|
},
|
|
415
416
|
};
|
|
416
|
-
|
|
417
|
+
const member = b.member(
|
|
418
|
+
capitalized_node,
|
|
419
|
+
b.literal('#v'),
|
|
420
|
+
true,
|
|
421
|
+
!is_inside_left_side_assignment(node),
|
|
422
|
+
/** @type {AST.NodeWithLocation} */ (node),
|
|
423
|
+
);
|
|
424
|
+
member.tracked = true;
|
|
425
|
+
return member;
|
|
417
426
|
}
|
|
418
|
-
|
|
427
|
+
const member = b.member(
|
|
428
|
+
node,
|
|
429
|
+
b.literal('#v'),
|
|
430
|
+
true,
|
|
431
|
+
!is_inside_left_side_assignment(node),
|
|
432
|
+
/** @type {AST.NodeWithLocation} */ (node),
|
|
433
|
+
);
|
|
434
|
+
member.tracked = true;
|
|
435
|
+
return member;
|
|
419
436
|
}
|
|
420
437
|
} else {
|
|
421
438
|
const binding = context.state.scope.get(node.name);
|
|
@@ -754,16 +771,15 @@ const visitors = {
|
|
|
754
771
|
);
|
|
755
772
|
|
|
756
773
|
// Wrap with ['#v'] access
|
|
757
|
-
|
|
774
|
+
const member_expanded = b.member(
|
|
758
775
|
member,
|
|
759
776
|
b.literal('#v'),
|
|
760
777
|
true,
|
|
761
|
-
|
|
762
|
-
// ts declaration had it as optional
|
|
763
|
-
// It's safe to set it as ts won't report it as such unless the user's ts had it
|
|
764
|
-
true,
|
|
778
|
+
!is_inside_left_side_assignment(node),
|
|
765
779
|
/** @type {AST.NodeWithLocation} */ (node),
|
|
766
780
|
);
|
|
781
|
+
member_expanded.tracked = true;
|
|
782
|
+
return member_expanded;
|
|
767
783
|
} else {
|
|
768
784
|
if (!context.state.to_ts) {
|
|
769
785
|
return b.call(
|
|
@@ -3668,6 +3684,16 @@ function create_tsx_with_typescript_support(comments) {
|
|
|
3668
3684
|
context.visit(node.value.body);
|
|
3669
3685
|
}
|
|
3670
3686
|
},
|
|
3687
|
+
TSAsExpression(node, context) {
|
|
3688
|
+
if (!node.loc) {
|
|
3689
|
+
base_tsx.TSAsExpression?.(node, context);
|
|
3690
|
+
return;
|
|
3691
|
+
}
|
|
3692
|
+
const loc = /** @type {AST.SourceLocation} */ (node.loc);
|
|
3693
|
+
context.location(loc.start.line, loc.start.column);
|
|
3694
|
+
base_tsx.TSAsExpression?.(node, context);
|
|
3695
|
+
context.location(loc.end.line, loc.end.column);
|
|
3696
|
+
},
|
|
3671
3697
|
TSObjectKeyword(node, context) {
|
|
3672
3698
|
if (node.loc) {
|
|
3673
3699
|
context.location(node.loc.start.line, node.loc.start.column);
|
|
@@ -1031,9 +1031,22 @@ export function convert_source_map_to_mappings(
|
|
|
1031
1031
|
return;
|
|
1032
1032
|
} else if (node.type === 'MemberExpression') {
|
|
1033
1033
|
if (node.loc) {
|
|
1034
|
-
|
|
1035
|
-
|
|
1034
|
+
const mapping = get_mapping_from_node(
|
|
1035
|
+
node,
|
|
1036
|
+
src_to_gen_map,
|
|
1037
|
+
gen_line_offsets,
|
|
1038
|
+
mapping_data_verify_only,
|
|
1036
1039
|
);
|
|
1040
|
+
|
|
1041
|
+
if (node.tracked) {
|
|
1042
|
+
mapping.generatedLengths[0] = mapping.generatedLengths[0] + "['#v']".length;
|
|
1043
|
+
if (node.optional) {
|
|
1044
|
+
mapping.generatedLengths[0] = mapping.generatedLengths[0] + '.?'.length;
|
|
1045
|
+
}
|
|
1046
|
+
mapping.data.customData.generatedLengths = mapping.generatedLengths;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
mappings.push(mapping);
|
|
1037
1050
|
}
|
|
1038
1051
|
|
|
1039
1052
|
if (node.object) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
@import { PostProcessingChanges, LineOffsets } from './phases/3-transform/client/index.js';
|
|
3
3
|
@import * as AST from 'estree';
|
|
4
|
-
@import {
|
|
4
|
+
@import { CodeMappingWithAll } from 'ripple/compiler';
|
|
5
5
|
@import { CodeMapping as VolarCodeMapping } from '@volar/language-core';
|
|
6
6
|
@import { RawSourceMap } from 'source-map';
|
|
7
7
|
*/
|
|
@@ -284,7 +284,7 @@ export function build_line_offsets(text) {
|
|
|
284
284
|
* @param {Partial<VolarCodeMapping['data']>} [filtered_data]
|
|
285
285
|
* @param {number} [src_max_len]
|
|
286
286
|
* @param {number} [gen_max_len]
|
|
287
|
-
* @returns {
|
|
287
|
+
* @returns {CodeMappingWithAll | Error}
|
|
288
288
|
*/
|
|
289
289
|
function maybe_get_mapping_from_node(
|
|
290
290
|
node,
|
|
@@ -333,7 +333,7 @@ function maybe_get_mapping_from_node(
|
|
|
333
333
|
* @param {Partial<VolarCodeMapping['data']>} [filtered_data]
|
|
334
334
|
* @param {number} [src_max_len]
|
|
335
335
|
* @param {number} [gen_max_len]
|
|
336
|
-
* @returns {
|
|
336
|
+
* @returns {CodeMappingWithAll}
|
|
337
337
|
*/
|
|
338
338
|
export function get_mapping_from_node(
|
|
339
339
|
node,
|
package/src/compiler/utils.js
CHANGED
|
@@ -833,3 +833,79 @@ export function is_inside_try_block(try_parent_stmt, context) {
|
|
|
833
833
|
|
|
834
834
|
return block_node !== null && try_parent_stmt.block === block_node;
|
|
835
835
|
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Checks if a node is used as the left side of an assignment or update expression.
|
|
839
|
+
* @param {AST.Node} node
|
|
840
|
+
* @returns {boolean}
|
|
841
|
+
*/
|
|
842
|
+
export function is_inside_left_side_assignment(node) {
|
|
843
|
+
const path = node.metadata?.path;
|
|
844
|
+
if (!path || path.length === 0) {
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/** @type {AST.Node} */
|
|
849
|
+
let current = node;
|
|
850
|
+
|
|
851
|
+
for (let i = path.length - 1; i >= 0; i--) {
|
|
852
|
+
const parent = path[i];
|
|
853
|
+
|
|
854
|
+
switch (parent.type) {
|
|
855
|
+
case 'AssignmentExpression':
|
|
856
|
+
case 'AssignmentPattern':
|
|
857
|
+
if (parent.right === current) {
|
|
858
|
+
return false;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
if (parent.left === current) {
|
|
862
|
+
return true;
|
|
863
|
+
}
|
|
864
|
+
current = parent;
|
|
865
|
+
continue;
|
|
866
|
+
case 'UpdateExpression':
|
|
867
|
+
return true;
|
|
868
|
+
case 'MemberExpression':
|
|
869
|
+
// In obj[computeKey()] = 10, computeKey() is evaluated to determine
|
|
870
|
+
// which property to assign to, but is not itself an assignment target
|
|
871
|
+
if (parent.computed && parent.property === current) {
|
|
872
|
+
return false;
|
|
873
|
+
}
|
|
874
|
+
current = parent;
|
|
875
|
+
continue;
|
|
876
|
+
case 'Property':
|
|
877
|
+
// exit here to stop promoting current to parent
|
|
878
|
+
// and thus reaching VariableDeclarator, causing an erroneous truthy result
|
|
879
|
+
// e.g. const { [computeKey()]: value } = obj; where node = computeKey:
|
|
880
|
+
if (parent.key === current) {
|
|
881
|
+
return false;
|
|
882
|
+
}
|
|
883
|
+
current = parent;
|
|
884
|
+
continue;
|
|
885
|
+
case 'VariableDeclarator':
|
|
886
|
+
return parent.id === current;
|
|
887
|
+
case 'ForInStatement':
|
|
888
|
+
case 'ForOfStatement':
|
|
889
|
+
return parent.left === current;
|
|
890
|
+
|
|
891
|
+
case 'Program':
|
|
892
|
+
case 'FunctionDeclaration':
|
|
893
|
+
case 'FunctionExpression':
|
|
894
|
+
case 'ArrowFunctionExpression':
|
|
895
|
+
case 'ClassDeclaration':
|
|
896
|
+
case 'ClassExpression':
|
|
897
|
+
case 'MethodDefinition':
|
|
898
|
+
case 'PropertyDefinition':
|
|
899
|
+
case 'StaticBlock':
|
|
900
|
+
case 'Component':
|
|
901
|
+
case 'Element':
|
|
902
|
+
return false;
|
|
903
|
+
|
|
904
|
+
default:
|
|
905
|
+
current = parent;
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
return false;
|
|
911
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { effect, render } from './blocks.js';
|
|
11
11
|
import { on } from './events.js';
|
|
12
|
-
import { get, set
|
|
12
|
+
import { get, set } from './runtime.js';
|
|
13
13
|
import { is_array, is_tracked_object } from './utils.js';
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -246,26 +246,15 @@ export function bindValue(maybe_tracked, set_func = undefined) {
|
|
|
246
246
|
});
|
|
247
247
|
} else {
|
|
248
248
|
var input = /** @type {HTMLInputElement} */ (node);
|
|
249
|
+
var selection_restore_needed = false;
|
|
249
250
|
|
|
250
|
-
clear_event = on(input, 'input',
|
|
251
|
+
clear_event = on(input, 'input', () => {
|
|
252
|
+
selection_restore_needed = true;
|
|
251
253
|
/** @type {any} */
|
|
252
254
|
var value = input.value;
|
|
253
255
|
value = is_numberlike_input(input) ? to_number(value) : value;
|
|
256
|
+
// the setter will schedule a microtask and the render block below will run
|
|
254
257
|
setter(value);
|
|
255
|
-
|
|
256
|
-
await tick();
|
|
257
|
-
|
|
258
|
-
if (value !== getter()) {
|
|
259
|
-
var start = input.selectionStart;
|
|
260
|
-
var end = input.selectionEnd;
|
|
261
|
-
input.value = value ?? '';
|
|
262
|
-
|
|
263
|
-
// Restore selection
|
|
264
|
-
if (end !== null) {
|
|
265
|
-
input.selectionStart = start;
|
|
266
|
-
input.selectionEnd = Math.min(end, input.value.length);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
258
|
});
|
|
270
259
|
|
|
271
260
|
render(() => {
|
|
@@ -280,7 +269,23 @@ export function bindValue(maybe_tracked, set_func = undefined) {
|
|
|
280
269
|
}
|
|
281
270
|
|
|
282
271
|
if (value !== input.value) {
|
|
283
|
-
|
|
272
|
+
if (selection_restore_needed) {
|
|
273
|
+
var start = input.selectionStart;
|
|
274
|
+
var end = input.selectionEnd;
|
|
275
|
+
|
|
276
|
+
input.value = value ?? '';
|
|
277
|
+
|
|
278
|
+
// Restore selection
|
|
279
|
+
if (end !== null && start !== null) {
|
|
280
|
+
end = Math.min(end, input.value.length);
|
|
281
|
+
start = Math.min(start, end);
|
|
282
|
+
input.selectionStart = start;
|
|
283
|
+
input.selectionEnd = end;
|
|
284
|
+
}
|
|
285
|
+
selection_restore_needed = false;
|
|
286
|
+
} else {
|
|
287
|
+
input.value = value ?? '';
|
|
288
|
+
}
|
|
284
289
|
}
|
|
285
290
|
});
|
|
286
291
|
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
flushSync,
|
|
3
3
|
track,
|
|
4
4
|
effect,
|
|
5
|
+
untrack,
|
|
5
6
|
TrackedObject,
|
|
6
7
|
bindValue,
|
|
7
8
|
bindChecked,
|
|
@@ -239,7 +240,7 @@ describe('use value()', () => {
|
|
|
239
240
|
});
|
|
240
241
|
|
|
241
242
|
it('should update checked on input', () => {
|
|
242
|
-
const logs: string[] = [];
|
|
243
|
+
const logs: (string | boolean)[] = [];
|
|
243
244
|
|
|
244
245
|
component App() {
|
|
245
246
|
const value = track(false);
|
|
@@ -335,7 +336,7 @@ describe('use value()', () => {
|
|
|
335
336
|
});
|
|
336
337
|
|
|
337
338
|
it('should update indeterminate on input', () => {
|
|
338
|
-
const logs: string[] = [];
|
|
339
|
+
const logs: (string | boolean)[] = [];
|
|
339
340
|
|
|
340
341
|
component App() {
|
|
341
342
|
const value = track(false);
|
|
@@ -2533,6 +2534,305 @@ describe('bindNode', () => {
|
|
|
2533
2534
|
|
|
2534
2535
|
expect(input.value).toBe('Set by ref');
|
|
2535
2536
|
});
|
|
2537
|
+
|
|
2538
|
+
it('should accurately reflect values mutated through a tracked setter', () => {
|
|
2539
|
+
component App() {
|
|
2540
|
+
let value = track(
|
|
2541
|
+
'',
|
|
2542
|
+
(val) => {
|
|
2543
|
+
return val;
|
|
2544
|
+
},
|
|
2545
|
+
(next) => {
|
|
2546
|
+
if (next.includes('c')) {
|
|
2547
|
+
next = next.replace(/c/g, '');
|
|
2548
|
+
}
|
|
2549
|
+
return next;
|
|
2550
|
+
},
|
|
2551
|
+
);
|
|
2552
|
+
|
|
2553
|
+
<input type="text" {ref bindValue(value)} />
|
|
2554
|
+
<div>{@value}</div>
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
render(App);
|
|
2558
|
+
flushSync();
|
|
2559
|
+
|
|
2560
|
+
const input = container.querySelector('input') as HTMLInputElement;
|
|
2561
|
+
const div = container.querySelector('div') as HTMLDivElement;
|
|
2562
|
+
|
|
2563
|
+
expect(input.value).toBe('');
|
|
2564
|
+
expect(div.textContent).toBe('');
|
|
2565
|
+
|
|
2566
|
+
input.value = 'abc';
|
|
2567
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
2568
|
+
flushSync();
|
|
2569
|
+
|
|
2570
|
+
expect(input.value).toBe('ab');
|
|
2571
|
+
expect(div.textContent).toBe('ab');
|
|
2572
|
+
});
|
|
2573
|
+
|
|
2574
|
+
it('should accurately reflect values when a getter modifies value', () => {
|
|
2575
|
+
component App() {
|
|
2576
|
+
let value = track(
|
|
2577
|
+
'',
|
|
2578
|
+
(val) => {
|
|
2579
|
+
if (val.includes('c')) {
|
|
2580
|
+
val = val.replace(/c/g, '');
|
|
2581
|
+
}
|
|
2582
|
+
return val;
|
|
2583
|
+
},
|
|
2584
|
+
(next) => {
|
|
2585
|
+
return next;
|
|
2586
|
+
},
|
|
2587
|
+
);
|
|
2588
|
+
|
|
2589
|
+
<input type="text" {ref bindValue(value)} />
|
|
2590
|
+
<div>{@value}</div>
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
render(App);
|
|
2594
|
+
flushSync();
|
|
2595
|
+
|
|
2596
|
+
const input = container.querySelector('input') as HTMLInputElement;
|
|
2597
|
+
const div = container.querySelector('div') as HTMLDivElement;
|
|
2598
|
+
|
|
2599
|
+
expect(input.value).toBe('');
|
|
2600
|
+
expect(div.textContent).toBe('');
|
|
2601
|
+
|
|
2602
|
+
input.value = 'abc';
|
|
2603
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
2604
|
+
flushSync();
|
|
2605
|
+
|
|
2606
|
+
expect(input.value).toBe('ab');
|
|
2607
|
+
expect(div.textContent).toBe('ab');
|
|
2608
|
+
});
|
|
2609
|
+
|
|
2610
|
+
it('should always prefer what getter returns even if setter mutates next', () => {
|
|
2611
|
+
component App() {
|
|
2612
|
+
let value = track(
|
|
2613
|
+
'',
|
|
2614
|
+
(val) => {
|
|
2615
|
+
return val.replace(/[c,b]+/g, '');
|
|
2616
|
+
},
|
|
2617
|
+
(next) => {
|
|
2618
|
+
if (next.includes('c')) {
|
|
2619
|
+
next = next.replace(/c/g, '');
|
|
2620
|
+
}
|
|
2621
|
+
return next;
|
|
2622
|
+
},
|
|
2623
|
+
);
|
|
2624
|
+
|
|
2625
|
+
<input type="text" {ref bindValue(value)} />
|
|
2626
|
+
<div>{@value}</div>
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
render(App);
|
|
2630
|
+
flushSync();
|
|
2631
|
+
|
|
2632
|
+
const input = container.querySelector('input') as HTMLInputElement;
|
|
2633
|
+
const div = container.querySelector('div') as HTMLDivElement;
|
|
2634
|
+
|
|
2635
|
+
expect(input.value).toBe('');
|
|
2636
|
+
expect(div.textContent).toBe('');
|
|
2637
|
+
|
|
2638
|
+
input.value = 'abc';
|
|
2639
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
2640
|
+
flushSync();
|
|
2641
|
+
|
|
2642
|
+
expect(input.value).toBe('a');
|
|
2643
|
+
expect(div.textContent).toBe('a');
|
|
2644
|
+
});
|
|
2645
|
+
|
|
2646
|
+
it(
|
|
2647
|
+
'should accurately reflect values mutated through an effect even after a setter mutation',
|
|
2648
|
+
() => {
|
|
2649
|
+
component App() {
|
|
2650
|
+
let value = track(
|
|
2651
|
+
'',
|
|
2652
|
+
(val) => {
|
|
2653
|
+
return val;
|
|
2654
|
+
},
|
|
2655
|
+
(next) => {
|
|
2656
|
+
if (next.includes('c')) {
|
|
2657
|
+
next = next.replace(/c/g, '');
|
|
2658
|
+
}
|
|
2659
|
+
return next;
|
|
2660
|
+
},
|
|
2661
|
+
);
|
|
2662
|
+
|
|
2663
|
+
effect(() => {
|
|
2664
|
+
@value;
|
|
2665
|
+
|
|
2666
|
+
untrack(() => {
|
|
2667
|
+
if (@value.includes('a')) {
|
|
2668
|
+
@value = @value.replace(/a/g, '');
|
|
2669
|
+
}
|
|
2670
|
+
});
|
|
2671
|
+
});
|
|
2672
|
+
<input type="text" {ref bindValue(value)} />
|
|
2673
|
+
<div>{@value}</div>
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
render(App);
|
|
2677
|
+
flushSync();
|
|
2678
|
+
|
|
2679
|
+
const input = container.querySelector('input') as HTMLInputElement;
|
|
2680
|
+
const div = container.querySelector('div') as HTMLDivElement;
|
|
2681
|
+
|
|
2682
|
+
expect(input.value).toBe('');
|
|
2683
|
+
expect(div.textContent).toBe('');
|
|
2684
|
+
|
|
2685
|
+
input.value = 'abc';
|
|
2686
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
2687
|
+
flushSync();
|
|
2688
|
+
|
|
2689
|
+
expect(input.value).toBe('b');
|
|
2690
|
+
expect(div.textContent).toBe('b');
|
|
2691
|
+
},
|
|
2692
|
+
);
|
|
2693
|
+
|
|
2694
|
+
it('should accurately reflect values mutated through a tracked setter via bind accessors', () => {
|
|
2695
|
+
component App() {
|
|
2696
|
+
let value = track('');
|
|
2697
|
+
const value_accessors = [
|
|
2698
|
+
() => {
|
|
2699
|
+
return @value;
|
|
2700
|
+
},
|
|
2701
|
+
(v: string) => {
|
|
2702
|
+
if (v.includes('c')) {
|
|
2703
|
+
v = v.replace(/c/g, '');
|
|
2704
|
+
}
|
|
2705
|
+
@value = v;
|
|
2706
|
+
},
|
|
2707
|
+
];
|
|
2708
|
+
|
|
2709
|
+
<input type="text" {ref bindValue(...value_accessors)} />
|
|
2710
|
+
}
|
|
2711
|
+
|
|
2712
|
+
render(App);
|
|
2713
|
+
flushSync();
|
|
2714
|
+
|
|
2715
|
+
const input = container.querySelector('input') as HTMLInputElement;
|
|
2716
|
+
|
|
2717
|
+
expect(input.value).toBe('');
|
|
2718
|
+
|
|
2719
|
+
input.value = 'abc';
|
|
2720
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
2721
|
+
flushSync();
|
|
2722
|
+
|
|
2723
|
+
expect(input.value).toBe('ab');
|
|
2724
|
+
});
|
|
2725
|
+
|
|
2726
|
+
it('should prefer what getter returns via bind accessors', () => {
|
|
2727
|
+
component App() {
|
|
2728
|
+
let value = track('');
|
|
2729
|
+
const value_accessors = [
|
|
2730
|
+
() => {
|
|
2731
|
+
if (@value.includes('c')) {
|
|
2732
|
+
return @value.replace(/c/g, '');
|
|
2733
|
+
}
|
|
2734
|
+
return @value;
|
|
2735
|
+
},
|
|
2736
|
+
(v: string) => {
|
|
2737
|
+
@value = v;
|
|
2738
|
+
},
|
|
2739
|
+
];
|
|
2740
|
+
|
|
2741
|
+
<input type="text" {ref bindValue(...value_accessors)} />
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2744
|
+
render(App);
|
|
2745
|
+
flushSync();
|
|
2746
|
+
|
|
2747
|
+
const input = container.querySelector('input') as HTMLInputElement;
|
|
2748
|
+
|
|
2749
|
+
expect(input.value).toBe('');
|
|
2750
|
+
|
|
2751
|
+
input.value = 'abc';
|
|
2752
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
2753
|
+
flushSync();
|
|
2754
|
+
|
|
2755
|
+
expect(input.value).toBe('ab');
|
|
2756
|
+
});
|
|
2757
|
+
|
|
2758
|
+
it(
|
|
2759
|
+
'should always prefer what getter returns even if setter mutates next via bind accessors',
|
|
2760
|
+
() => {
|
|
2761
|
+
component App() {
|
|
2762
|
+
let value = track('');
|
|
2763
|
+
const value_accessors = [
|
|
2764
|
+
() => {
|
|
2765
|
+
return @value.replace(/[c,b]+/g, '');
|
|
2766
|
+
},
|
|
2767
|
+
(v: string) => {
|
|
2768
|
+
if (v.includes('c')) {
|
|
2769
|
+
v = v.replace(/c/g, '');
|
|
2770
|
+
}
|
|
2771
|
+
@value = v;
|
|
2772
|
+
},
|
|
2773
|
+
];
|
|
2774
|
+
|
|
2775
|
+
<input type="text" {ref bindValue(...value_accessors)} />
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
render(App);
|
|
2779
|
+
flushSync();
|
|
2780
|
+
|
|
2781
|
+
const input = container.querySelector('input') as HTMLInputElement;
|
|
2782
|
+
|
|
2783
|
+
expect(input.value).toBe('');
|
|
2784
|
+
|
|
2785
|
+
input.value = 'abc';
|
|
2786
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
2787
|
+
flushSync();
|
|
2788
|
+
|
|
2789
|
+
expect(input.value).toBe('a');
|
|
2790
|
+
},
|
|
2791
|
+
);
|
|
2792
|
+
|
|
2793
|
+
it(
|
|
2794
|
+
'should accurately reflect values mutated through an effect even after a setter mutation via bind accessors',
|
|
2795
|
+
() => {
|
|
2796
|
+
component App() {
|
|
2797
|
+
let value = track('');
|
|
2798
|
+
const value_accessors = [
|
|
2799
|
+
() => {
|
|
2800
|
+
return @value;
|
|
2801
|
+
},
|
|
2802
|
+
(v: string) => {
|
|
2803
|
+
if (v.includes('c')) {
|
|
2804
|
+
v = v.replace(/c/g, '');
|
|
2805
|
+
}
|
|
2806
|
+
@value = v;
|
|
2807
|
+
},
|
|
2808
|
+
];
|
|
2809
|
+
|
|
2810
|
+
effect(() => {
|
|
2811
|
+
@value;
|
|
2812
|
+
|
|
2813
|
+
untrack(() => {
|
|
2814
|
+
if (@value.includes('a')) {
|
|
2815
|
+
@value = @value.replace(/a/g, '');
|
|
2816
|
+
}
|
|
2817
|
+
});
|
|
2818
|
+
});
|
|
2819
|
+
<input type="text" {ref bindValue(...value_accessors)} />
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2822
|
+
render(App);
|
|
2823
|
+
flushSync();
|
|
2824
|
+
|
|
2825
|
+
const input = container.querySelector('input') as HTMLInputElement;
|
|
2826
|
+
|
|
2827
|
+
expect(input.value).toBe('');
|
|
2828
|
+
|
|
2829
|
+
input.value = 'abc';
|
|
2830
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
2831
|
+
flushSync();
|
|
2832
|
+
|
|
2833
|
+
expect(input.value).toBe('b');
|
|
2834
|
+
},
|
|
2835
|
+
);
|
|
2536
2836
|
});
|
|
2537
2837
|
|
|
2538
2838
|
describe('bindFiles', () => {
|
package/types/index.d.ts
CHANGED
|
@@ -112,7 +112,11 @@ export type PropsNoChildren<T extends object = {}> = Expand<T>;
|
|
|
112
112
|
|
|
113
113
|
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
|
|
114
114
|
|
|
115
|
-
type
|
|
115
|
+
type WrapTracked<V> = V extends Tracked<any> ? V : Tracked<V>;
|
|
116
|
+
|
|
117
|
+
type PickKeys<T, K extends readonly (keyof T)[]> = {
|
|
118
|
+
[I in keyof K]: WrapTracked<T[K[I] & keyof T]>;
|
|
119
|
+
};
|
|
116
120
|
|
|
117
121
|
type RestKeys<T, K extends readonly (keyof T)[]> = Expand<Omit<T, K[number]>>;
|
|
118
122
|
|