ripple 0.2.203 → 0.2.205
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/phases/1-parse/index.js +1 -9
- package/src/compiler/phases/2-analyze/index.js +29 -1
- package/src/compiler/phases/3-transform/client/index.js +45 -4
- package/src/compiler/phases/3-transform/segments.js +5 -0
- package/src/compiler/types/index.d.ts +1 -0
- package/src/runtime/date.js +1 -1
- package/src/runtime/internal/client/bindings.js +29 -24
- package/tests/client/input-value.test.ripple +302 -2
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.205",
|
|
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.205"
|
|
101
101
|
}
|
|
102
102
|
}
|
|
@@ -1749,15 +1749,7 @@ function RipplePlugin(config) {
|
|
|
1749
1749
|
jsx_parseAttributeValue() {
|
|
1750
1750
|
switch (this.type) {
|
|
1751
1751
|
case tt.braceL:
|
|
1752
|
-
|
|
1753
|
-
return (
|
|
1754
|
-
t.expression.type === 'JSXEmptyExpression' &&
|
|
1755
|
-
this.raise(
|
|
1756
|
-
/** @type {AST.NodeWithLocation} */ (t).start,
|
|
1757
|
-
'attributes must only be assigned a non-empty expression',
|
|
1758
|
-
),
|
|
1759
|
-
t
|
|
1760
|
-
);
|
|
1752
|
+
return this.jsx_parseExpressionContainer();
|
|
1761
1753
|
case tstt.jsxTagStart:
|
|
1762
1754
|
case tt.string:
|
|
1763
1755
|
return this.parseExprAtom();
|
|
@@ -10,7 +10,10 @@
|
|
|
10
10
|
StyleClasses,
|
|
11
11
|
} from '#compiler';
|
|
12
12
|
*/
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
@import * as AST from 'estree';
|
|
15
|
+
@import * as ESTreeJSX from 'estree-jsx';
|
|
16
|
+
*/
|
|
14
17
|
|
|
15
18
|
import * as b from '../../../utils/builders.js';
|
|
16
19
|
import { walk } from 'zimmerframe';
|
|
@@ -959,6 +962,31 @@ const visitors = {
|
|
|
959
962
|
|
|
960
963
|
for (const attr of node.attributes) {
|
|
961
964
|
if (attr.type === 'Attribute') {
|
|
965
|
+
if (attr.value && attr.value.type === 'JSXEmptyExpression') {
|
|
966
|
+
const value = /** @type {ESTreeJSX.JSXEmptyExpression & AST.NodeWithLocation} */ (
|
|
967
|
+
attr.value
|
|
968
|
+
);
|
|
969
|
+
error(
|
|
970
|
+
'attributes must only be assigned a non-empty expression',
|
|
971
|
+
state.analysis.module.filename,
|
|
972
|
+
{
|
|
973
|
+
...value,
|
|
974
|
+
start: value.start - 1,
|
|
975
|
+
end: value.end + 1,
|
|
976
|
+
loc: {
|
|
977
|
+
start: {
|
|
978
|
+
line: value.loc.start.line,
|
|
979
|
+
column: value.loc.start.column - 1,
|
|
980
|
+
},
|
|
981
|
+
end: {
|
|
982
|
+
line: value.loc.end.line,
|
|
983
|
+
column: value.loc.end.column + 1,
|
|
984
|
+
},
|
|
985
|
+
},
|
|
986
|
+
},
|
|
987
|
+
context.state.loose ? context.state.analysis.errors : undefined,
|
|
988
|
+
);
|
|
989
|
+
}
|
|
962
990
|
if (attr.name.type === 'Identifier') {
|
|
963
991
|
attribute_names.add(attr.name);
|
|
964
992
|
|
|
@@ -2492,11 +2492,16 @@ function transform_ts_child(node, context) {
|
|
|
2492
2492
|
if (attr.type === 'Attribute') {
|
|
2493
2493
|
const metadata = { await: false };
|
|
2494
2494
|
const name = visit(attr.name, { ...state, metadata });
|
|
2495
|
+
const attr_value = /** @type { AST.Expression & AST.NodeWithLocation | null} */ (
|
|
2496
|
+
attr.value
|
|
2497
|
+
);
|
|
2495
2498
|
const value =
|
|
2496
|
-
|
|
2497
|
-
?
|
|
2499
|
+
attr_value === null
|
|
2500
|
+
? // <div attr>, not adding `name` for loc because `jsx_name` below
|
|
2501
|
+
// will take care of the mapping JSXAttribute's JSXIdentifier
|
|
2502
|
+
b.literal(true)
|
|
2498
2503
|
: // reset init, update, final to avoid adding attr value to the component body
|
|
2499
|
-
visit(
|
|
2504
|
+
visit(attr_value, SetStateForOutsideComponent(state, { metadata }));
|
|
2500
2505
|
|
|
2501
2506
|
// Handle both regular identifiers and tracked identifiers
|
|
2502
2507
|
/** @type {string} */
|
|
@@ -2524,7 +2529,23 @@ function transform_ts_child(node, context) {
|
|
|
2524
2529
|
jsx_name,
|
|
2525
2530
|
b.jsx_expression_container(
|
|
2526
2531
|
/** @type {AST.Expression} */ (value),
|
|
2527
|
-
|
|
2532
|
+
attr_value === null
|
|
2533
|
+
? /** @type {AST.NodeWithLocation} */ (value)
|
|
2534
|
+
: // account location for opening and closing braces around the expression
|
|
2535
|
+
/** @type {AST.NodeWithLocation} */ ({
|
|
2536
|
+
start: attr_value.start - 1,
|
|
2537
|
+
end: attr_value.end + 1,
|
|
2538
|
+
loc: {
|
|
2539
|
+
start: {
|
|
2540
|
+
line: attr_value.loc.start.line,
|
|
2541
|
+
column: attr_value.loc.start.column - 1,
|
|
2542
|
+
},
|
|
2543
|
+
end: {
|
|
2544
|
+
line: attr_value.loc.end.line,
|
|
2545
|
+
column: attr_value.loc.end.column + 1,
|
|
2546
|
+
},
|
|
2547
|
+
},
|
|
2548
|
+
}),
|
|
2528
2549
|
),
|
|
2529
2550
|
attr.shorthand ?? false,
|
|
2530
2551
|
/** @type {AST.NodeWithLocation} */ (attr),
|
|
@@ -3587,6 +3608,16 @@ function create_tsx_with_typescript_support(comments) {
|
|
|
3587
3608
|
context.write(node.name);
|
|
3588
3609
|
context.location(loc.end.line, loc.end.column);
|
|
3589
3610
|
},
|
|
3611
|
+
JSXExpressionContainer(node, context) {
|
|
3612
|
+
const loc = /** @type {AST.SourceLocation} */ (node.loc);
|
|
3613
|
+
if (!loc) {
|
|
3614
|
+
base_tsx.JSXExpressionContainer?.(node, context);
|
|
3615
|
+
return;
|
|
3616
|
+
}
|
|
3617
|
+
context.location(loc.start.line, loc.start.column);
|
|
3618
|
+
base_tsx.JSXExpressionContainer?.(node, context);
|
|
3619
|
+
context.location(loc.end.line, loc.end.column);
|
|
3620
|
+
},
|
|
3590
3621
|
MethodDefinition(node, context) {
|
|
3591
3622
|
node.value.metadata.is_method = true;
|
|
3592
3623
|
/** @type {AST.Position | undefined} */
|
|
@@ -3684,6 +3715,16 @@ function create_tsx_with_typescript_support(comments) {
|
|
|
3684
3715
|
context.visit(node.value.body);
|
|
3685
3716
|
}
|
|
3686
3717
|
},
|
|
3718
|
+
TSAsExpression(node, context) {
|
|
3719
|
+
if (!node.loc) {
|
|
3720
|
+
base_tsx.TSAsExpression?.(node, context);
|
|
3721
|
+
return;
|
|
3722
|
+
}
|
|
3723
|
+
const loc = /** @type {AST.SourceLocation} */ (node.loc);
|
|
3724
|
+
context.location(loc.start.line, loc.start.column);
|
|
3725
|
+
base_tsx.TSAsExpression?.(node, context);
|
|
3726
|
+
context.location(loc.end.line, loc.end.column);
|
|
3727
|
+
},
|
|
3687
3728
|
TSObjectKeyword(node, context) {
|
|
3688
3729
|
if (node.loc) {
|
|
3689
3730
|
context.location(node.loc.start.line, node.loc.start.column);
|
|
@@ -652,6 +652,11 @@ export function convert_source_map_to_mappings(
|
|
|
652
652
|
}
|
|
653
653
|
return;
|
|
654
654
|
} else if (node.type === 'JSXExpressionContainer') {
|
|
655
|
+
if (node.loc) {
|
|
656
|
+
mappings.push(
|
|
657
|
+
get_mapping_from_node(node, src_to_gen_map, gen_line_offsets, mapping_data_verify_only),
|
|
658
|
+
);
|
|
659
|
+
}
|
|
655
660
|
// Visit the expression inside {}
|
|
656
661
|
if (node.expression) {
|
|
657
662
|
visit(node.expression);
|
package/src/runtime/date.js
CHANGED
|
@@ -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
|
/**
|
|
@@ -216,11 +216,12 @@ export function bindValue(maybe_tracked, set_func = undefined) {
|
|
|
216
216
|
value = [].map.call(select.querySelectorAll(query), get_option_value);
|
|
217
217
|
} else {
|
|
218
218
|
/** @type {HTMLOptionElement | null} */
|
|
219
|
-
// @ts-ignore
|
|
220
219
|
var selected_option =
|
|
221
|
-
select.querySelector(query) ??
|
|
220
|
+
/** @type {HTMLOptionElement | null} */ (select.querySelector(query)) ??
|
|
222
221
|
// will fall back to first non-disabled option if no option is selected
|
|
223
|
-
|
|
222
|
+
/** @type {HTMLOptionElement | null} */ (
|
|
223
|
+
select.querySelector('option:not([disabled])')
|
|
224
|
+
);
|
|
224
225
|
value = selected_option && get_option_value(selected_option);
|
|
225
226
|
}
|
|
226
227
|
|
|
@@ -234,8 +235,9 @@ export function bindValue(maybe_tracked, set_func = undefined) {
|
|
|
234
235
|
// Mounting and value undefined -> take selection from dom
|
|
235
236
|
if (mounting && value === undefined) {
|
|
236
237
|
/** @type {HTMLOptionElement | null} */
|
|
237
|
-
|
|
238
|
-
|
|
238
|
+
var selected_option = /** @type {HTMLOptionElement | null} */ (
|
|
239
|
+
select.querySelector(':checked')
|
|
240
|
+
);
|
|
239
241
|
if (selected_option !== null) {
|
|
240
242
|
value = get_option_value(selected_option);
|
|
241
243
|
setter(value);
|
|
@@ -246,26 +248,15 @@ export function bindValue(maybe_tracked, set_func = undefined) {
|
|
|
246
248
|
});
|
|
247
249
|
} else {
|
|
248
250
|
var input = /** @type {HTMLInputElement} */ (node);
|
|
251
|
+
var selection_restore_needed = false;
|
|
249
252
|
|
|
250
|
-
clear_event = on(input, 'input',
|
|
253
|
+
clear_event = on(input, 'input', () => {
|
|
254
|
+
selection_restore_needed = true;
|
|
251
255
|
/** @type {any} */
|
|
252
256
|
var value = input.value;
|
|
253
257
|
value = is_numberlike_input(input) ? to_number(value) : value;
|
|
258
|
+
// the setter will schedule a microtask and the render block below will run
|
|
254
259
|
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
260
|
});
|
|
270
261
|
|
|
271
262
|
render(() => {
|
|
@@ -280,7 +271,23 @@ export function bindValue(maybe_tracked, set_func = undefined) {
|
|
|
280
271
|
}
|
|
281
272
|
|
|
282
273
|
if (value !== input.value) {
|
|
283
|
-
|
|
274
|
+
if (selection_restore_needed) {
|
|
275
|
+
var start = input.selectionStart;
|
|
276
|
+
var end = input.selectionEnd;
|
|
277
|
+
|
|
278
|
+
input.value = value ?? '';
|
|
279
|
+
|
|
280
|
+
// Restore selection
|
|
281
|
+
if (end !== null && start !== null) {
|
|
282
|
+
end = Math.min(end, input.value.length);
|
|
283
|
+
start = Math.min(start, end);
|
|
284
|
+
input.selectionStart = start;
|
|
285
|
+
input.selectionEnd = end;
|
|
286
|
+
}
|
|
287
|
+
selection_restore_needed = false;
|
|
288
|
+
} else {
|
|
289
|
+
input.value = value ?? '';
|
|
290
|
+
}
|
|
284
291
|
}
|
|
285
292
|
});
|
|
286
293
|
|
|
@@ -527,11 +534,9 @@ export function bind_content_editable(maybe_tracked, property, set_func = undefi
|
|
|
527
534
|
var value = getter();
|
|
528
535
|
if (element[property] !== value) {
|
|
529
536
|
if (value == null) {
|
|
530
|
-
// @ts-ignore
|
|
531
537
|
var non_null_value = element[property];
|
|
532
538
|
setter(non_null_value);
|
|
533
539
|
} else {
|
|
534
|
-
// @ts-ignore
|
|
535
540
|
element[property] = value + '';
|
|
536
541
|
}
|
|
537
542
|
}
|
|
@@ -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', () => {
|