ripple 0.2.203 → 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 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.203",
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.203"
100
+ "ripple": "0.2.204"
101
101
  }
102
102
  }
@@ -3684,6 +3684,16 @@ function create_tsx_with_typescript_support(comments) {
3684
3684
  context.visit(node.value.body);
3685
3685
  }
3686
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
+ },
3687
3697
  TSObjectKeyword(node, context) {
3688
3698
  if (node.loc) {
3689
3699
  context.location(node.loc.start.line, node.loc.start.column);
@@ -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, tick, untrack } from './runtime.js';
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', async () => {
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
- input.value = value ?? '';
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', () => {