ripple 0.3.2 → 0.3.4

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.
Files changed (128) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/package.json +2 -2
  3. package/src/compiler/identifier-utils.js +0 -2
  4. package/src/compiler/phases/1-parse/index.js +101 -195
  5. package/src/compiler/phases/2-analyze/index.js +82 -174
  6. package/src/compiler/phases/2-analyze/prune.js +2 -2
  7. package/src/compiler/phases/3-transform/client/index.js +174 -264
  8. package/src/compiler/phases/3-transform/segments.js +0 -22
  9. package/src/compiler/phases/3-transform/server/index.js +185 -42
  10. package/src/compiler/types/index.d.ts +14 -33
  11. package/src/compiler/utils.js +32 -20
  12. package/src/runtime/index-client.js +0 -17
  13. package/src/runtime/internal/client/bindings.js +118 -7
  14. package/src/runtime/internal/client/render.js +5 -1
  15. package/src/runtime/internal/client/runtime.js +1 -1
  16. package/src/runtime/internal/client/types.d.ts +4 -0
  17. package/tests/client/array/array.copy-within.test.ripple +7 -7
  18. package/tests/client/array/array.derived.test.ripple +24 -24
  19. package/tests/client/array/array.iteration.test.ripple +7 -7
  20. package/tests/client/array/array.mutations.test.ripple +17 -17
  21. package/tests/client/array/array.to-methods.test.ripple +4 -4
  22. package/tests/client/async-suspend.test.ripple +3 -3
  23. package/tests/client/basic/basic.attributes.test.ripple +31 -31
  24. package/tests/client/basic/basic.collections.test.ripple +6 -6
  25. package/tests/client/basic/basic.components.test.ripple +8 -8
  26. package/tests/client/basic/basic.errors.test.ripple +31 -34
  27. package/tests/client/basic/basic.events.test.ripple +11 -11
  28. package/tests/client/basic/basic.get-set.test.ripple +18 -18
  29. package/tests/client/basic/basic.reactivity.test.ripple +36 -36
  30. package/tests/client/basic/basic.rendering.test.ripple +7 -7
  31. package/tests/client/basic/basic.utilities.test.ripple +4 -4
  32. package/tests/client/boundaries.test.ripple +7 -7
  33. package/tests/client/compiler/__snapshots__/compiler.typescript.test.ripple.snap +24 -0
  34. package/tests/client/compiler/compiler.assignments.test.ripple +12 -10
  35. package/tests/client/compiler/compiler.basic.test.ripple +58 -60
  36. package/tests/client/compiler/compiler.tracked-access.test.ripple +14 -8
  37. package/tests/client/compiler/compiler.typescript.test.ripple +31 -0
  38. package/tests/client/composite/composite.dynamic-components.test.ripple +6 -6
  39. package/tests/client/composite/composite.props.test.ripple +9 -9
  40. package/tests/client/composite/composite.reactivity.test.ripple +23 -23
  41. package/tests/client/composite/composite.render.test.ripple +52 -4
  42. package/tests/client/computed-properties.test.ripple +3 -3
  43. package/tests/client/context.test.ripple +3 -3
  44. package/tests/client/css/global-additional-cases.test.ripple +5 -2
  45. package/tests/client/css/style-identifier.test.ripple +40 -49
  46. package/tests/client/date.test.ripple +39 -39
  47. package/tests/client/dynamic-elements.test.ripple +37 -37
  48. package/tests/client/events.test.ripple +25 -25
  49. package/tests/client/for.test.ripple +8 -8
  50. package/tests/client/head.test.ripple +7 -7
  51. package/tests/client/html.test.ripple +2 -2
  52. package/tests/client/input-value.test.ripple +376 -177
  53. package/tests/client/lazy-destructuring.test.ripple +185 -0
  54. package/tests/client/map.test.ripple +20 -20
  55. package/tests/client/media-query.test.ripple +4 -4
  56. package/tests/client/object.test.ripple +5 -5
  57. package/tests/client/portal.test.ripple +4 -4
  58. package/tests/client/ref.test.ripple +3 -3
  59. package/tests/client/return.test.ripple +17 -17
  60. package/tests/client/set.test.ripple +10 -10
  61. package/tests/client/svg.test.ripple +6 -5
  62. package/tests/client/switch.test.ripple +10 -10
  63. package/tests/client/tracked-expression.test.ripple +3 -1
  64. package/tests/client/try.test.ripple +4 -4
  65. package/tests/client/url/url.derived.test.ripple +6 -7
  66. package/tests/client/url/url.parsing.test.ripple +9 -9
  67. package/tests/client/url/url.partial-removal.test.ripple +9 -9
  68. package/tests/client/url/url.reactivity.test.ripple +16 -16
  69. package/tests/client/url/url.serialization.test.ripple +3 -3
  70. package/tests/client/url-search-params/url-search-params.derived.test.ripple +7 -8
  71. package/tests/client/url-search-params/url-search-params.initialization.test.ripple +6 -4
  72. package/tests/client/url-search-params/url-search-params.iteration.test.ripple +12 -12
  73. package/tests/client/url-search-params/url-search-params.mutation.test.ripple +18 -18
  74. package/tests/client/url-search-params/url-search-params.retrieval.test.ripple +16 -16
  75. package/tests/client/url-search-params/url-search-params.serialization.test.ripple +4 -4
  76. package/tests/client/url-search-params/url-search-params.tracked-url.test.ripple +3 -3
  77. package/tests/hydration/build-components.js +4 -10
  78. package/tests/hydration/compiled/client/basic.js +4 -4
  79. package/tests/hydration/compiled/client/events.js +2 -0
  80. package/tests/hydration/compiled/client/for.js +2 -0
  81. package/tests/hydration/compiled/client/head.js +13 -11
  82. package/tests/hydration/compiled/client/hmr.js +4 -2
  83. package/tests/hydration/compiled/client/html.js +82 -95
  84. package/tests/hydration/compiled/client/if-children.js +8 -9
  85. package/tests/hydration/compiled/client/if.js +2 -0
  86. package/tests/hydration/compiled/client/mixed-control-flow.js +4 -2
  87. package/tests/hydration/compiled/client/portal.js +1 -1
  88. package/tests/hydration/compiled/client/reactivity.js +2 -0
  89. package/tests/hydration/compiled/client/return.js +2 -0
  90. package/tests/hydration/compiled/client/switch.js +2 -0
  91. package/tests/hydration/compiled/server/composite.js +2 -2
  92. package/tests/hydration/compiled/server/events.js +2 -0
  93. package/tests/hydration/compiled/server/for.js +2 -0
  94. package/tests/hydration/compiled/server/head.js +13 -11
  95. package/tests/hydration/compiled/server/hmr.js +2 -0
  96. package/tests/hydration/compiled/server/html.js +2 -0
  97. package/tests/hydration/compiled/server/if-children.js +2 -0
  98. package/tests/hydration/compiled/server/if.js +2 -0
  99. package/tests/hydration/compiled/server/mixed-control-flow.js +2 -0
  100. package/tests/hydration/compiled/server/portal.js +1 -1
  101. package/tests/hydration/compiled/server/reactivity.js +2 -0
  102. package/tests/hydration/compiled/server/return.js +2 -0
  103. package/tests/hydration/compiled/server/switch.js +2 -0
  104. package/tests/hydration/components/composite.ripple +1 -1
  105. package/tests/hydration/components/events.ripple +10 -8
  106. package/tests/hydration/components/for.ripple +22 -20
  107. package/tests/hydration/components/head.ripple +8 -6
  108. package/tests/hydration/components/hmr.ripple +3 -1
  109. package/tests/hydration/components/html.ripple +3 -1
  110. package/tests/hydration/components/if-children.ripple +9 -7
  111. package/tests/hydration/components/if.ripple +7 -5
  112. package/tests/hydration/components/mixed-control-flow.ripple +5 -3
  113. package/tests/hydration/components/portal.ripple +2 -2
  114. package/tests/hydration/components/reactivity.ripple +11 -9
  115. package/tests/hydration/components/return.ripple +13 -11
  116. package/tests/hydration/components/switch.ripple +6 -4
  117. package/tests/server/__snapshots__/compiler.test.ripple.snap +22 -0
  118. package/tests/server/await.test.ripple +2 -2
  119. package/tests/server/basic.attributes.test.ripple +21 -19
  120. package/tests/server/basic.components.test.ripple +5 -4
  121. package/tests/server/basic.test.ripple +21 -20
  122. package/tests/server/compiler.test.ripple +36 -5
  123. package/tests/server/composite.props.test.ripple +7 -6
  124. package/tests/server/context.test.ripple +3 -1
  125. package/tests/server/dynamic-elements.test.ripple +24 -24
  126. package/tests/server/head.test.ripple +7 -5
  127. package/tests/server/style-identifier.test.ripple +95 -16
  128. package/types/index.d.ts +4 -1
@@ -1,24 +1,28 @@
1
1
  import type { GetFunction, SetFunction } from 'ripple';
2
2
  import {
3
- flushSync,
3
+ RippleArray,
4
4
  RippleObject,
5
- bindValue,
5
+ bindBorderBoxSize,
6
6
  bindChecked,
7
- bindIndeterminate,
8
- bindGroup,
9
- bindClientWidth,
10
7
  bindClientHeight,
11
- bindOffsetWidth,
12
- bindOffsetHeight,
13
- bindContentRect,
8
+ bindClientWidth,
14
9
  bindContentBoxSize,
15
- bindBorderBoxSize,
10
+ bindContentRect,
16
11
  bindDevicePixelContentBoxSize,
12
+ bindFiles,
13
+ bindGroup,
14
+ bindIndeterminate,
17
15
  bindInnerHTML,
18
16
  bindInnerText,
19
- bindTextContent,
20
17
  bindNode,
21
- bindFiles,
18
+ bindOffsetHeight,
19
+ bindOffsetWidth,
20
+ bindTextContent,
21
+ bindValue,
22
+ effect,
23
+ flushSync,
24
+ track,
25
+ untrack,
22
26
  } from 'ripple';
23
27
 
24
28
  // Mock ResizeObserver for testing
@@ -102,9 +106,9 @@ describe('use value()', () => {
102
106
  const logs: string[] = [];
103
107
 
104
108
  component App() {
105
- const text = #ripple.track('');
109
+ const text = track('');
106
110
 
107
- #ripple.effect(() => {
111
+ effect(() => {
108
112
  logs.push('text changed', @text);
109
113
  });
110
114
 
@@ -125,9 +129,9 @@ describe('use value()', () => {
125
129
  const logs: string[] = [];
126
130
 
127
131
  component App() {
128
- const text = #ripple{ value: '' };
132
+ const text = new RippleObject({ value: '' });
129
133
 
130
- #ripple.effect(() => {
134
+ effect(() => {
131
135
  logs.push('text changed', text.value);
132
136
  });
133
137
 
@@ -148,9 +152,9 @@ describe('use value()', () => {
148
152
  const logs: string[] = [];
149
153
 
150
154
  component App() {
151
- const text = #ripple.track('foo');
155
+ const text = track('foo');
152
156
 
153
- #ripple.effect(() => {
157
+ effect(() => {
154
158
  logs.push('text changed', @text);
155
159
  });
156
160
 
@@ -172,8 +176,8 @@ describe('use value()', () => {
172
176
  const logs: string[] = [];
173
177
 
174
178
  component App() {
175
- const text = #ripple{ value: 'foo' };
176
- #ripple.effect(() => {
179
+ const text = new RippleObject({ value: 'foo' });
180
+ effect(() => {
177
181
  logs.push('text changed', text.value);
178
182
  });
179
183
 
@@ -193,7 +197,7 @@ describe('use value()', () => {
193
197
 
194
198
  it('should update text input element when tracked value changes', () => {
195
199
  component App() {
196
- const text = #ripple.track('initial');
200
+ const text = track('initial');
197
201
 
198
202
  <div>
199
203
  <input type="text" {ref bindValue(text)} />
@@ -216,7 +220,7 @@ describe('use value()', () => {
216
220
 
217
221
  it('should update text input element when tracked value changes with a getter and setter', () => {
218
222
  component App() {
219
- const text = #ripple{ value: 'initial' };
223
+ const text = new RippleObject({ value: 'initial' });
220
224
 
221
225
  <div>
222
226
  <input type="text" {ref bindValue(() => text.value, (v) => (text.value = v))} />
@@ -241,9 +245,9 @@ describe('use value()', () => {
241
245
  const logs: (string | boolean)[] = [];
242
246
 
243
247
  component App() {
244
- const value = #ripple.track(false);
248
+ const value = track(false);
245
249
 
246
- #ripple.effect(() => {
250
+ effect(() => {
247
251
  logs.push('checked changed', @value);
248
252
  });
249
253
 
@@ -265,8 +269,8 @@ describe('use value()', () => {
265
269
  const logs: (string | boolean)[] = [];
266
270
 
267
271
  component App() {
268
- const obj = #ripple{ value: false };
269
- #ripple.effect(() => {
272
+ const obj = new RippleObject({ value: false });
273
+ effect(() => {
270
274
  logs.push('checked changed', obj.value);
271
275
  });
272
276
 
@@ -286,7 +290,7 @@ describe('use value()', () => {
286
290
 
287
291
  it('should update checkbox element when tracked value changes', () => {
288
292
  component App() {
289
- const value = #ripple.track(false);
293
+ const value = track(false);
290
294
 
291
295
  <div>
292
296
  <input type="checkbox" {ref bindChecked(value)} />
@@ -309,7 +313,7 @@ describe('use value()', () => {
309
313
 
310
314
  it('should update checkbox element when tracked value changes with a getter and setter', () => {
311
315
  component App() {
312
- const obj = #ripple{ value: false };
316
+ const obj = new RippleObject({ value: false });
313
317
 
314
318
  <div>
315
319
  <input
@@ -337,9 +341,9 @@ describe('use value()', () => {
337
341
  const logs: (string | boolean)[] = [];
338
342
 
339
343
  component App() {
340
- const value = #ripple.track(false);
344
+ const value = track(false);
341
345
 
342
- #ripple.effect(() => {
346
+ effect(() => {
343
347
  logs.push('indeterminate changed', @value);
344
348
  });
345
349
 
@@ -363,9 +367,9 @@ describe('use value()', () => {
363
367
  const logs: (string | boolean)[] = [];
364
368
 
365
369
  component App() {
366
- const obj = #ripple{ value: false };
370
+ const obj = new RippleObject({ value: false });
367
371
 
368
- #ripple.effect(() => {
372
+ effect(() => {
369
373
  logs.push('indeterminate changed', obj.value);
370
374
  });
371
375
 
@@ -390,7 +394,7 @@ describe('use value()', () => {
390
394
 
391
395
  it('should update checkbox indeterminate element when tracked value changes', () => {
392
396
  component App() {
393
- const value = #ripple.track(false);
397
+ const value = track(false);
394
398
 
395
399
  <div>
396
400
  <input type="checkbox" {ref bindIndeterminate(value)} />
@@ -415,7 +419,7 @@ describe('use value()', () => {
415
419
  'should update checkbox indeterminate element when tracked value changes with a getter and setter',
416
420
  () => {
417
421
  component App() {
418
- const obj = #ripple{ value: false };
422
+ const obj = new RippleObject({ value: false });
419
423
 
420
424
  <div>
421
425
  <input
@@ -444,9 +448,9 @@ describe('use value()', () => {
444
448
  const logs: string[] = [];
445
449
 
446
450
  component App() {
447
- const select = #ripple.track('2');
451
+ const select = track('2');
448
452
 
449
- #ripple.effect(() => {
453
+ effect(() => {
450
454
  logs.push('select changed', @select);
451
455
  });
452
456
 
@@ -473,8 +477,8 @@ describe('use value()', () => {
473
477
  const logs: string[] = [];
474
478
 
475
479
  component App() {
476
- const select = #ripple{ value: '2' };
477
- #ripple.effect(() => {
480
+ const select = new RippleObject({ value: '2' });
481
+ effect(() => {
478
482
  logs.push('select changed', select.value);
479
483
  });
480
484
 
@@ -499,7 +503,7 @@ describe('use value()', () => {
499
503
 
500
504
  it('should update select element when tracked value changes', () => {
501
505
  component App() {
502
- const select = #ripple.track('1');
506
+ const select = track('1');
503
507
 
504
508
  <div>
505
509
  <select {ref bindValue(select)}>
@@ -527,7 +531,7 @@ describe('use value()', () => {
527
531
 
528
532
  it('should update select element when tracked value changes with a getter and setter', () => {
529
533
  component App() {
530
- const select = #ripple{ value: '1' };
534
+ const select = new RippleObject({ value: '1' });
531
535
 
532
536
  <div>
533
537
  <select {ref bindValue(() => select.value, (v) => (select.value = v))}>
@@ -557,9 +561,9 @@ describe('use value()', () => {
557
561
  const logs: string[] = [];
558
562
 
559
563
  component App() {
560
- const selected = #ripple.track(['b']);
564
+ const selected = track(['b']);
561
565
 
562
- #ripple.effect(() => {
566
+ effect(() => {
563
567
  logs.push('selected changed', JSON.stringify(@selected));
564
568
  });
565
569
 
@@ -605,9 +609,9 @@ describe('use value()', () => {
605
609
  const logs: string[] = [];
606
610
 
607
611
  component App() {
608
- const obj = #ripple{ selected: ['b'] };
612
+ const obj = new RippleObject({ selected: ['b'] });
609
613
 
610
- #ripple.effect(() => {
614
+ effect(() => {
611
615
  logs.push('selected changed', JSON.stringify(obj.selected));
612
616
  });
613
617
 
@@ -665,9 +669,9 @@ describe('use value()', () => {
665
669
  const logs: string[] = [];
666
670
 
667
671
  component App() {
668
- const selected = #ripple.track('b');
672
+ const selected = track('b');
669
673
 
670
- #ripple.effect(() => {
674
+ effect(() => {
671
675
  logs.push('selected changed', @selected);
672
676
  });
673
677
 
@@ -712,9 +716,9 @@ describe('use value()', () => {
712
716
  const logs: string[] = [];
713
717
 
714
718
  component App() {
715
- const selected = #ripple{ value: 'b' };
719
+ const selected = new RippleObject({ value: 'b' });
716
720
 
717
- #ripple.effect(() => {
721
+ effect(() => {
718
722
  logs.push('selected changed', selected.value);
719
723
  });
720
724
 
@@ -772,7 +776,7 @@ describe('use value()', () => {
772
776
 
773
777
  it('should update checkbox group from tracked value change', () => {
774
778
  component App() {
775
- const selected = #ripple.track(['a']);
779
+ const selected = track(['a']);
776
780
 
777
781
  <div>
778
782
  <input type="checkbox" value="a" {ref bindGroup(selected)} />
@@ -802,7 +806,7 @@ describe('use value()', () => {
802
806
 
803
807
  it('should update checkbox group from tracked value change with a getter and setter', () => {
804
808
  component App() {
805
- const selected = #ripple{ value: ['a'] };
809
+ const selected = new RippleObject({ value: ['a'] });
806
810
 
807
811
  <div>
808
812
  <input
@@ -844,7 +848,7 @@ describe('use value()', () => {
844
848
 
845
849
  it('should update radio group from tracked value change', () => {
846
850
  component App() {
847
- const selected = #ripple.track('a');
851
+ const selected = track('a');
848
852
 
849
853
  <div>
850
854
  <input type="radio" name="test" value="a" {ref bindGroup(selected)} />
@@ -874,7 +878,7 @@ describe('use value()', () => {
874
878
 
875
879
  it('should update radio group from tracked value change with a getter and setter', () => {
876
880
  component App() {
877
- const selected = #ripple{ value: 'a' };
881
+ const selected = new RippleObject({ value: 'a' });
878
882
 
879
883
  <div>
880
884
  <input
@@ -919,7 +923,7 @@ describe('use value()', () => {
919
923
 
920
924
  it('should handle checkbox group with initial empty array', () => {
921
925
  component App() {
922
- const selected = #ripple.track([]);
926
+ const selected = track([]);
923
927
 
924
928
  <div>
925
929
  <input type="checkbox" value="a" {ref bindGroup(selected)} />
@@ -945,7 +949,7 @@ describe('use value()', () => {
945
949
 
946
950
  it('should handle checkbox group with initial empty array with a getter and setter', () => {
947
951
  component App() {
948
- const selected: RippleObject<{ value: string[] }> = #ripple{ value: [] };
952
+ const selected: RippleObject<{ value: string[] }> = new RippleObject({ value: [] });
949
953
 
950
954
  <div>
951
955
  <input
@@ -979,7 +983,7 @@ describe('use value()', () => {
979
983
 
980
984
  it('should handle number input type', () => {
981
985
  component App() {
982
- const value = #ripple.track(42);
986
+ const value = track(42);
983
987
 
984
988
  <input type="number" {ref bindValue(value)} />
985
989
  }
@@ -999,7 +1003,7 @@ describe('use value()', () => {
999
1003
 
1000
1004
  it('should handle number input type with a getter and setter', () => {
1001
1005
  component App() {
1002
- const obj = #ripple{ value: 42 };
1006
+ const obj = new RippleObject({ value: 42 });
1003
1007
 
1004
1008
  <input type="number" {ref bindValue(() => obj.value, (v) => (obj.value = v))} />
1005
1009
  }
@@ -1019,7 +1023,7 @@ describe('use value()', () => {
1019
1023
 
1020
1024
  it('should update number input element when tracked value changes', () => {
1021
1025
  component App() {
1022
- const value = #ripple.track(10);
1026
+ const value = track(10);
1023
1027
 
1024
1028
  <div>
1025
1029
  <input type="number" {ref bindValue(value)} />
@@ -1045,7 +1049,7 @@ describe('use value()', () => {
1045
1049
  'should update number input element when tracked value changes with a getter and setter',
1046
1050
  () => {
1047
1051
  component App() {
1048
- const obj = #ripple{ value: 10 };
1052
+ const obj = new RippleObject({ value: 10 });
1049
1053
 
1050
1054
  <div>
1051
1055
  <input type="number" {ref bindValue(() => obj.value, (v) => (obj.value = v))} />
@@ -1070,7 +1074,7 @@ describe('use value()', () => {
1070
1074
 
1071
1075
  it('should handle range input type', () => {
1072
1076
  component App() {
1073
- const value = #ripple.track(50);
1077
+ const value = track(50);
1074
1078
 
1075
1079
  <input type="range" min="0" max="100" {ref bindValue(value)} />
1076
1080
  }
@@ -1090,7 +1094,7 @@ describe('use value()', () => {
1090
1094
 
1091
1095
  it('should handle range input type with a getter and setter', () => {
1092
1096
  component App() {
1093
- const obj = #ripple{ value: 50 };
1097
+ const obj = new RippleObject({ value: 50 });
1094
1098
 
1095
1099
  <input
1096
1100
  type="range"
@@ -1115,7 +1119,7 @@ describe('use value()', () => {
1115
1119
 
1116
1120
  it('should update range input element when tracked value changes', () => {
1117
1121
  component App() {
1118
- const value = #ripple.track(25);
1122
+ const value = track(25);
1119
1123
 
1120
1124
  <div>
1121
1125
  <input type="range" min="0" max="100" {ref bindValue(value)} />
@@ -1141,7 +1145,7 @@ describe('use value()', () => {
1141
1145
  'should update range input element when tracked value changes with a getter and setter',
1142
1146
  () => {
1143
1147
  component App() {
1144
- const obj = #ripple{ value: 25 };
1148
+ const obj = new RippleObject({ value: 25 });
1145
1149
 
1146
1150
  <div>
1147
1151
  <input
@@ -1171,7 +1175,7 @@ describe('use value()', () => {
1171
1175
 
1172
1176
  it('should handle empty number input as null', () => {
1173
1177
  component App() {
1174
- const value = #ripple.track(null);
1178
+ const value = track(null);
1175
1179
 
1176
1180
  <input type="number" {ref bindValue(value)} />
1177
1181
  }
@@ -1191,7 +1195,7 @@ describe('use value()', () => {
1191
1195
 
1192
1196
  it('should handle empty number input as null with a getter and setter', () => {
1193
1197
  component App() {
1194
- const obj = #ripple{ value: null };
1198
+ const obj = new RippleObject({ value: null });
1195
1199
 
1196
1200
  <input type="number" {ref bindValue(() => obj.value, (v) => (obj.value = v))} />
1197
1201
  }
@@ -1211,7 +1215,7 @@ describe('use value()', () => {
1211
1215
 
1212
1216
  it('should handle date input type', () => {
1213
1217
  component App() {
1214
- const value = #ripple.track('2025-11-14');
1218
+ const value = track('2025-11-14');
1215
1219
 
1216
1220
  <input type="date" {ref bindValue(value)} />
1217
1221
  }
@@ -1231,7 +1235,7 @@ describe('use value()', () => {
1231
1235
 
1232
1236
  it('should handle date input type with a getter and setter', () => {
1233
1237
  component App() {
1234
- const obj = #ripple{ value: '2025-11-14' };
1238
+ const obj = new RippleObject({ value: '2025-11-14' });
1235
1239
 
1236
1240
  <input type="date" {ref bindValue(() => obj.value, (v) => (obj.value = v))} />
1237
1241
  }
@@ -1251,7 +1255,7 @@ describe('use value()', () => {
1251
1255
 
1252
1256
  it('should update date input element when tracked value changes', () => {
1253
1257
  component App() {
1254
- const value = #ripple.track('2025-01-01');
1258
+ const value = track('2025-01-01');
1255
1259
 
1256
1260
  <div>
1257
1261
  <input type="date" {ref bindValue(value)} />
@@ -1275,7 +1279,7 @@ describe('use value()', () => {
1275
1279
 
1276
1280
  it('should update date input element when tracked value changes with a getter and setter', () => {
1277
1281
  component App() {
1278
- const obj = #ripple{ value: '2025-01-01' };
1282
+ const obj = new RippleObject({ value: '2025-01-01' });
1279
1283
 
1280
1284
  <div>
1281
1285
  <input type="date" {ref bindValue(() => obj.value, (v) => (obj.value = v))} />
@@ -1299,7 +1303,7 @@ describe('use value()', () => {
1299
1303
 
1300
1304
  it('should handle select with multiple attribute', () => {
1301
1305
  component App() {
1302
- const selected = #ripple.track(['2', '3']);
1306
+ const selected = track(['2', '3']);
1303
1307
 
1304
1308
  <select multiple {ref bindValue(selected)}>
1305
1309
  <option value="1">{'One'}</option>
@@ -1333,7 +1337,7 @@ describe('use value()', () => {
1333
1337
 
1334
1338
  it('should handle select with multiple attribute with a getter and setter', () => {
1335
1339
  component App() {
1336
- const selected = #ripple.track(['2', '3']);
1340
+ const selected = track(['2', '3']);
1337
1341
 
1338
1342
  <select multiple {ref bindValue(() => @selected, (v) => (@selected = v))}>
1339
1343
  <option value="1">{'One'}</option>
@@ -1367,7 +1371,7 @@ describe('use value()', () => {
1367
1371
 
1368
1372
  it('should update multiple select element when tracked value changes', () => {
1369
1373
  component App() {
1370
- const selected = #ripple.track(['1']);
1374
+ const selected = track(['1']);
1371
1375
 
1372
1376
  <div>
1373
1377
  <select multiple {ref bindValue(selected)}>
@@ -1405,7 +1409,7 @@ describe('use value()', () => {
1405
1409
  'should update multiple select element when tracked value changes with a getter and setter',
1406
1410
  () => {
1407
1411
  component App() {
1408
- const selected = #ripple.track(['1']);
1412
+ const selected = track(['1']);
1409
1413
 
1410
1414
  <div>
1411
1415
  <select multiple {ref bindValue(() => @selected, (v) => (@selected = v))}>
@@ -1442,7 +1446,7 @@ describe('use value()', () => {
1442
1446
 
1443
1447
  it('should handle select without initial value and fall back to first option', () => {
1444
1448
  component App() {
1445
- const selected = #ripple.track();
1449
+ const selected = track();
1446
1450
 
1447
1451
  <select {ref bindValue(selected)}>
1448
1452
  <option value="1">{'One'}</option>
@@ -1462,7 +1466,7 @@ describe('use value()', () => {
1462
1466
  'should handle select without initial value and fall back to first option with a getter and setter',
1463
1467
  () => {
1464
1468
  component App() {
1465
- const selected = #ripple.track();
1469
+ const selected = track();
1466
1470
 
1467
1471
  <select {ref bindValue(() => @selected, (v) => (@selected = v))}>
1468
1472
  <option value="1">{'One'}</option>
@@ -1481,7 +1485,7 @@ describe('use value()', () => {
1481
1485
 
1482
1486
  it('should handle select with disabled options', () => {
1483
1487
  component App() {
1484
- const selected = #ripple.track();
1488
+ const selected = track();
1485
1489
 
1486
1490
  <select {ref bindValue(selected)}>
1487
1491
  <option value="1" disabled>{'One'}</option>
@@ -1499,7 +1503,7 @@ describe('use value()', () => {
1499
1503
 
1500
1504
  it('should handle select with disabled options with a getter and setter', () => {
1501
1505
  component App() {
1502
- const selected = #ripple.track();
1506
+ const selected = track();
1503
1507
 
1504
1508
  <select {ref bindValue(() => @selected, (v) => (@selected = v))}>
1505
1509
  <option value="1" disabled>{'One'}</option>
@@ -1515,9 +1519,150 @@ describe('use value()', () => {
1515
1519
  expect(select.options[1].selected).toBe(true);
1516
1520
  });
1517
1521
 
1522
+ it('should preselect numeric option values in select bindValue', () => {
1523
+ component App() {
1524
+ const selected = track(2);
1525
+
1526
+ <select {ref bindValue(selected)}>
1527
+ <option value={1}>{'One'}</option>
1528
+ <option value={2}>{'Two'}</option>
1529
+ <option value={3}>{'Three'}</option>
1530
+ </select>
1531
+ }
1532
+
1533
+ render(App);
1534
+ flushSync();
1535
+
1536
+ const select = container.querySelector('select') as HTMLSelectElement;
1537
+
1538
+ expect(select.value).toBe('2');
1539
+ expect(select.options[0].selected).toBe(false);
1540
+ expect(select.options[1].selected).toBe(true);
1541
+ expect(select.options[2].selected).toBe(false);
1542
+ });
1543
+
1544
+ it('should preselect numeric option values in select bindValue with a getter and setter', () => {
1545
+ component App() {
1546
+ const selected = track(2);
1547
+
1548
+ <select {ref bindValue(() => @selected, (v) => (@selected = v))}>
1549
+ <option value={1}>{'One'}</option>
1550
+ <option value={2}>{'Two'}</option>
1551
+ <option value={3}>{'Three'}</option>
1552
+ </select>
1553
+ }
1554
+
1555
+ render(App);
1556
+ flushSync();
1557
+
1558
+ const select = container.querySelector('select') as HTMLSelectElement;
1559
+
1560
+ expect(select.value).toBe('2');
1561
+ expect(select.options[0].selected).toBe(false);
1562
+ expect(select.options[1].selected).toBe(true);
1563
+ expect(select.options[2].selected).toBe(false);
1564
+ });
1565
+
1566
+ it('should preserve numeric select values on change', () => {
1567
+ const logs: number[] = [];
1568
+
1569
+ component App() {
1570
+ const selected = track(2);
1571
+
1572
+ effect(() => {
1573
+ logs.push(@selected);
1574
+ });
1575
+
1576
+ <select {ref bindValue(selected)}>
1577
+ <option value={1}>{'One'}</option>
1578
+ <option value={2}>{'Two'}</option>
1579
+ <option value={3}>{'Three'}</option>
1580
+ </select>
1581
+ }
1582
+
1583
+ render(App);
1584
+ flushSync();
1585
+
1586
+ const select = container.querySelector('select') as HTMLSelectElement;
1587
+ select.options[2].selected = true;
1588
+ select.dispatchEvent(new Event('change', { bubbles: true }));
1589
+ flushSync();
1590
+
1591
+ expect(select.value).toBe('3');
1592
+ expect(logs).toEqual([2, 3]);
1593
+ });
1594
+
1595
+ it('should reselect tracked value when matching option is added later', async () => {
1596
+ component App() {
1597
+ const selected = track(5);
1598
+ const options = RippleArray([
1599
+ { id: 1, text: 'One' },
1600
+ { id: 2, text: 'Two' },
1601
+ ]);
1602
+
1603
+ <div>
1604
+ <select {ref bindValue(selected)}>
1605
+ for (const option of options) {
1606
+ <option value={option.id}>{option.text}</option>
1607
+ }
1608
+ </select>
1609
+ <button onClick={() => options.push({ id: 5, text: 'Five' })}>{'Add'}</button>
1610
+ </div>
1611
+ }
1612
+
1613
+ render(App);
1614
+ flushSync();
1615
+
1616
+ const select = container.querySelector('select') as HTMLSelectElement;
1617
+ const button = container.querySelector('button') as HTMLButtonElement;
1618
+
1619
+ expect(select.value).toBe('');
1620
+
1621
+ button.click();
1622
+ flushSync();
1623
+ await new Promise((resolve) => setTimeout(resolve, 0));
1624
+ flushSync();
1625
+
1626
+ expect(select.value).toBe('5');
1627
+ expect(select.selectedOptions[0].value).toBe('5');
1628
+ });
1629
+
1630
+ it('should reselect tracked value when option lists are replaced later', async () => {
1631
+ component App() {
1632
+ const selected = track(22);
1633
+ const options = RippleArray([
1634
+ { id: 1, text: 'One' },
1635
+ ]);
1636
+
1637
+ effect(() => {
1638
+ const timeout = setTimeout(() => {
1639
+ options.splice(0, options.length, { id: 21, text: 'Tokyo' }, { id: 22, text: 'Osaka' });
1640
+ }, 0);
1641
+
1642
+ return () => clearTimeout(timeout);
1643
+ });
1644
+
1645
+ <select {ref bindValue(selected)}>
1646
+ for (const option of options) {
1647
+ <option value={option.id}>{option.text}</option>
1648
+ }
1649
+ </select>
1650
+ }
1651
+
1652
+ render(App);
1653
+ flushSync();
1654
+ await new Promise((resolve) => setTimeout(resolve, 0));
1655
+ flushSync();
1656
+
1657
+ const select = container.querySelector('select') as HTMLSelectElement;
1658
+
1659
+ expect(select.value).toBe('22');
1660
+ expect(select.options[1].selected).toBe(true);
1661
+ });
1662
+
1518
1663
  it('should accurately reflect values mutated through a tracked setter', () => {
1519
1664
  component App() {
1520
- let value = #ripple.track(
1665
+ let value = track(
1521
1666
  '',
1522
1667
  (val) => {
1523
1668
  return val;
@@ -1553,7 +1698,7 @@ describe('use value()', () => {
1553
1698
 
1554
1699
  it('should accurately reflect values when a getter modifies value', () => {
1555
1700
  component App() {
1556
- let value = #ripple.track(
1701
+ let value = track(
1557
1702
  '',
1558
1703
  (val) => {
1559
1704
  if (val.includes('c')) {
@@ -1589,7 +1734,7 @@ describe('use value()', () => {
1589
1734
 
1590
1735
  it('should always prefer what getter returns even if setter mutates next', () => {
1591
1736
  component App() {
1592
- let value = #ripple.track(
1737
+ let value = track(
1593
1738
  '',
1594
1739
  (val) => {
1595
1740
  return val.replace(/[c,b]+/g, '');
@@ -1627,7 +1772,7 @@ describe('use value()', () => {
1627
1772
  'should accurately reflect values mutated through an effect even after a setter mutation',
1628
1773
  () => {
1629
1774
  component App() {
1630
- let value = #ripple.track(
1775
+ let value = track(
1631
1776
  '',
1632
1777
  (val) => {
1633
1778
  return val;
@@ -1640,10 +1785,10 @@ describe('use value()', () => {
1640
1785
  },
1641
1786
  );
1642
1787
 
1643
- #ripple.effect(() => {
1788
+ effect(() => {
1644
1789
  @value;
1645
1790
 
1646
- #ripple.untrack(() => {
1791
+ untrack(() => {
1647
1792
  if (@value.includes('a')) {
1648
1793
  @value = @value.replace(/a/g, '');
1649
1794
  }
@@ -1673,7 +1818,7 @@ describe('use value()', () => {
1673
1818
 
1674
1819
  it('should accurately reflect values mutated through a tracked setter via bind accessors', () => {
1675
1820
  component App() {
1676
- let value = #ripple.track('');
1821
+ let value = track('');
1677
1822
  const value_accessors: [GetFunction<string>, SetFunction<string>] = [
1678
1823
  () => {
1679
1824
  return @value;
@@ -1705,7 +1850,7 @@ describe('use value()', () => {
1705
1850
 
1706
1851
  it('should prefer what getter returns via bind accessors', () => {
1707
1852
  component App() {
1708
- let value = #ripple.track('');
1853
+ let value = track('');
1709
1854
  const value_accessors: [GetFunction<string>, SetFunction<string>] = [
1710
1855
  () => {
1711
1856
  if (@value.includes('c')) {
@@ -1739,7 +1884,7 @@ describe('use value()', () => {
1739
1884
  'should always prefer what getter returns even if setter mutates next via bind accessors',
1740
1885
  () => {
1741
1886
  component App() {
1742
- let value = #ripple.track('');
1887
+ let value = track('');
1743
1888
  const value_accessors: [GetFunction<string>, SetFunction<string>] = [
1744
1889
  () => {
1745
1890
  return @value.replace(/[c,b]+/g, '');
@@ -1774,7 +1919,7 @@ describe('use value()', () => {
1774
1919
  'should accurately reflect values mutated through an effect even after a setter mutation via bind accessors',
1775
1920
  () => {
1776
1921
  component App() {
1777
- let value = #ripple.track('');
1922
+ let value = track('');
1778
1923
  const value_accessors: [GetFunction<string>, SetFunction<string>] = [
1779
1924
  () => {
1780
1925
  return @value;
@@ -1787,10 +1932,10 @@ describe('use value()', () => {
1787
1932
  },
1788
1933
  ];
1789
1934
 
1790
- #ripple.effect(() => {
1935
+ effect(() => {
1791
1936
  @value;
1792
1937
 
1793
- #ripple.untrack(() => {
1938
+ untrack(() => {
1794
1939
  if (@value.includes('a')) {
1795
1940
  @value = @value.replace(/a/g, '');
1796
1941
  }
@@ -1818,7 +1963,7 @@ describe('use value()', () => {
1818
1963
  'should keep the input.value unchanged and synced with the tracked when the setter blocks updates to the tracked via bind accessors',
1819
1964
  () => {
1820
1965
  component App() {
1821
- let value = #ripple.track('');
1966
+ let value = track('');
1822
1967
  const value_accessors: [GetFunction<string>, SetFunction<string>] = [
1823
1968
  () => {
1824
1969
  return @value;
@@ -1853,7 +1998,7 @@ describe('use value()', () => {
1853
1998
  'should keep the input.value unchanged and synced with the tracked when the setter blocks updates to the tracked via track get / set',
1854
1999
  () => {
1855
2000
  component App() {
1856
- let value = #ripple.track(
2001
+ let value = track(
1857
2002
  '',
1858
2003
  (v) => {
1859
2004
  return v;
@@ -1890,9 +2035,9 @@ describe('bindClientWidth and bindClientHeight', () => {
1890
2035
  const logs: number[] = [];
1891
2036
 
1892
2037
  component App() {
1893
- const width = #ripple.track(0);
2038
+ const width = track(0);
1894
2039
 
1895
- #ripple.effect(() => {
2040
+ effect(() => {
1896
2041
  logs.push(@width);
1897
2042
  });
1898
2043
 
@@ -1921,9 +2066,9 @@ describe('bindClientWidth and bindClientHeight', () => {
1921
2066
  const logs: number[] = [];
1922
2067
 
1923
2068
  component App() {
1924
- const width = #ripple{ value: 0 };
2069
+ const width = new RippleObject({ value: 0 });
1925
2070
 
1926
- #ripple.effect(() => {
2071
+ effect(() => {
1927
2072
  logs.push(width.value);
1928
2073
  });
1929
2074
 
@@ -1952,9 +2097,9 @@ describe('bindClientWidth and bindClientHeight', () => {
1952
2097
  const logs: number[] = [];
1953
2098
 
1954
2099
  component App() {
1955
- const height = #ripple.track(0);
2100
+ const height = track(0);
1956
2101
 
1957
- #ripple.effect(() => {
2102
+ effect(() => {
1958
2103
  logs.push(@height);
1959
2104
  });
1960
2105
 
@@ -1983,8 +2128,8 @@ describe('bindClientWidth and bindClientHeight', () => {
1983
2128
  const logs: number[] = [];
1984
2129
 
1985
2130
  component App() {
1986
- const height = #ripple{ value: 0 };
1987
- #ripple.effect(() => {
2131
+ const height = new RippleObject({ value: 0 });
2132
+ effect(() => {
1988
2133
  logs.push(height.value);
1989
2134
  });
1990
2135
 
@@ -2015,9 +2160,9 @@ describe('bindOffsetWidth and bindOffsetHeight', () => {
2015
2160
  const logs: number[] = [];
2016
2161
 
2017
2162
  component App() {
2018
- const width = #ripple.track(0);
2163
+ const width = track(0);
2019
2164
 
2020
- #ripple.effect(() => {
2165
+ effect(() => {
2021
2166
  logs.push(@width);
2022
2167
  });
2023
2168
 
@@ -2046,9 +2191,9 @@ describe('bindOffsetWidth and bindOffsetHeight', () => {
2046
2191
  const logs: number[] = [];
2047
2192
 
2048
2193
  component App() {
2049
- const width = #ripple{ value: 0 };
2194
+ const width = new RippleObject({ value: 0 });
2050
2195
 
2051
- #ripple.effect(() => {
2196
+ effect(() => {
2052
2197
  logs.push(width.value);
2053
2198
  });
2054
2199
 
@@ -2077,9 +2222,9 @@ describe('bindOffsetWidth and bindOffsetHeight', () => {
2077
2222
  const logs: number[] = [];
2078
2223
 
2079
2224
  component App() {
2080
- const height = #ripple.track(0);
2225
+ const height = track(0);
2081
2226
 
2082
- #ripple.effect(() => {
2227
+ effect(() => {
2083
2228
  logs.push(@height);
2084
2229
  });
2085
2230
 
@@ -2108,8 +2253,8 @@ describe('bindOffsetWidth and bindOffsetHeight', () => {
2108
2253
  const logs: number[] = [];
2109
2254
 
2110
2255
  component App() {
2111
- const height = #ripple{ value: 0 };
2112
- #ripple.effect(() => {
2256
+ const height = new RippleObject({ value: 0 });
2257
+ effect(() => {
2113
2258
  logs.push(height.value);
2114
2259
  });
2115
2260
 
@@ -2140,9 +2285,9 @@ describe('bindContentRect', () => {
2140
2285
  const logs: DOMRectReadOnly[] = [];
2141
2286
 
2142
2287
  component App() {
2143
- const rect = #ripple.track(null);
2288
+ const rect = track(null);
2144
2289
 
2145
- #ripple.effect(() => {
2290
+ effect(() => {
2146
2291
  if (@rect) logs.push(@rect);
2147
2292
  });
2148
2293
 
@@ -2170,8 +2315,10 @@ describe('bindContentRect', () => {
2170
2315
  const logs: DOMRectReadOnly[] = [];
2171
2316
 
2172
2317
  component App() {
2173
- const rect: RippleObject<{ value: DOMRectReadOnly | null }> = #ripple{ value: null };
2174
- #ripple.effect(() => {
2318
+ const rect: RippleObject<{ value: DOMRectReadOnly | null }> = new RippleObject({
2319
+ value: null,
2320
+ });
2321
+ effect(() => {
2175
2322
  if (rect.value) logs.push(rect.value);
2176
2323
  });
2177
2324
 
@@ -2206,9 +2353,9 @@ describe('bindContentBoxSize', () => {
2206
2353
  const logs: any[] = [];
2207
2354
 
2208
2355
  component App() {
2209
- const boxSize = #ripple.track(null);
2356
+ const boxSize = track(null);
2210
2357
 
2211
- #ripple.effect(() => {
2358
+ effect(() => {
2212
2359
  if (@boxSize) logs.push(@boxSize);
2213
2360
  });
2214
2361
 
@@ -2239,9 +2386,11 @@ describe('bindContentBoxSize', () => {
2239
2386
  ];
2240
2387
 
2241
2388
  component App() {
2242
- const boxSize: RippleObject<{ value: typeof mockBoxSize | null }> = #ripple{ value: null };
2389
+ const boxSize: RippleObject<{ value: typeof mockBoxSize | null }> = new RippleObject({
2390
+ value: null,
2391
+ });
2243
2392
 
2244
- #ripple.effect(() => {
2393
+ effect(() => {
2245
2394
  if (boxSize.value) logs.push(boxSize.value);
2246
2395
  });
2247
2396
 
@@ -2273,9 +2422,9 @@ describe('bindBorderBoxSize', () => {
2273
2422
  const logs: any[] = [];
2274
2423
 
2275
2424
  component App() {
2276
- const boxSize = #ripple.track(null);
2425
+ const boxSize = track(null);
2277
2426
 
2278
- #ripple.effect(() => {
2427
+ effect(() => {
2279
2428
  if (@boxSize) logs.push(@boxSize);
2280
2429
  });
2281
2430
 
@@ -2306,9 +2455,11 @@ describe('bindBorderBoxSize', () => {
2306
2455
  ];
2307
2456
 
2308
2457
  component App() {
2309
- const boxSize: RippleObject<{ value: typeof mockBoxSize | null }> = #ripple{ value: null };
2458
+ const boxSize: RippleObject<{ value: typeof mockBoxSize | null }> = new RippleObject({
2459
+ value: null,
2460
+ });
2310
2461
 
2311
- #ripple.effect(() => {
2462
+ effect(() => {
2312
2463
  if (boxSize.value) logs.push(boxSize.value);
2313
2464
  });
2314
2465
 
@@ -2340,9 +2491,9 @@ describe('bindDevicePixelContentBoxSize', () => {
2340
2491
  const logs: any[] = [];
2341
2492
 
2342
2493
  component App() {
2343
- const boxSize = #ripple.track(null);
2494
+ const boxSize = track(null);
2344
2495
 
2345
- #ripple.effect(() => {
2496
+ effect(() => {
2346
2497
  if (@boxSize) logs.push(@boxSize);
2347
2498
  });
2348
2499
 
@@ -2373,9 +2524,11 @@ describe('bindDevicePixelContentBoxSize', () => {
2373
2524
  ];
2374
2525
 
2375
2526
  component App() {
2376
- const boxSize: RippleObject<{ value: typeof mockBoxSize | null }> = #ripple{ value: null };
2527
+ const boxSize: RippleObject<{ value: typeof mockBoxSize | null }> = new RippleObject({
2528
+ value: null,
2529
+ });
2377
2530
 
2378
- #ripple.effect(() => {
2531
+ effect(() => {
2379
2532
  if (boxSize.value) logs.push(boxSize.value);
2380
2533
  });
2381
2534
 
@@ -2407,9 +2560,9 @@ describe('bindInnerHTML', () => {
2407
2560
  const logs: string[] = [];
2408
2561
 
2409
2562
  component App() {
2410
- const html = #ripple.track('<strong>Hello</strong>');
2563
+ const html = track('<strong>Hello</strong>');
2411
2564
 
2412
- #ripple.effect(() => {
2565
+ effect(() => {
2413
2566
  logs.push(@html);
2414
2567
  });
2415
2568
 
@@ -2433,9 +2586,9 @@ describe('bindInnerHTML', () => {
2433
2586
  const logs: string[] = [];
2434
2587
 
2435
2588
  component App() {
2436
- const html = #ripple{ value: '<strong>Hello</strong>' };
2589
+ const html = new RippleObject({ value: '<strong>Hello</strong>' });
2437
2590
 
2438
- #ripple.effect(() => {
2591
+ effect(() => {
2439
2592
  logs.push(html.value);
2440
2593
  });
2441
2594
 
@@ -2460,7 +2613,7 @@ describe('bindInnerHTML', () => {
2460
2613
 
2461
2614
  it('should update innerHTML when tracked value changes', () => {
2462
2615
  component App() {
2463
- const html = #ripple.track('<p>Initial</p>');
2616
+ const html = track('<p>Initial</p>');
2464
2617
 
2465
2618
  <div>
2466
2619
  <div contenteditable="true" {ref bindInnerHTML(html)} />
@@ -2484,7 +2637,7 @@ describe('bindInnerHTML', () => {
2484
2637
 
2485
2638
  it('should update innerHTML when tracked value changes with a getter and setter', () => {
2486
2639
  component App() {
2487
- const html = #ripple{ value: '<p>Initial</p>' };
2640
+ const html = new RippleObject({ value: '<p>Initial</p>' });
2488
2641
 
2489
2642
  <div>
2490
2643
  <div
@@ -2511,7 +2664,7 @@ describe('bindInnerHTML', () => {
2511
2664
 
2512
2665
  it('should handle null innerHTML value', () => {
2513
2666
  component App() {
2514
- const html = #ripple.track(null);
2667
+ const html = track(null);
2515
2668
 
2516
2669
  <div contenteditable="true" {ref bindInnerHTML(html)} />
2517
2670
  }
@@ -2526,7 +2679,7 @@ describe('bindInnerHTML', () => {
2526
2679
 
2527
2680
  it('should handle null innerHTML value with a getter and setter', () => {
2528
2681
  component App() {
2529
- const html: RippleObject<{ value: null | string }> = #ripple{ value: null };
2682
+ const html: RippleObject<{ value: null | string }> = new RippleObject({ value: null });
2530
2683
 
2531
2684
  <div
2532
2685
  contenteditable="true"
@@ -2548,9 +2701,9 @@ describe('bindInnerText', () => {
2548
2701
  const logs: string[] = [];
2549
2702
 
2550
2703
  component App() {
2551
- const text = #ripple.track('Hello World');
2704
+ const text = track('Hello World');
2552
2705
 
2553
- #ripple.effect(() => {
2706
+ effect(() => {
2554
2707
  logs.push(@text);
2555
2708
  });
2556
2709
 
@@ -2574,8 +2727,8 @@ describe('bindInnerText', () => {
2574
2727
  const logs: string[] = [];
2575
2728
 
2576
2729
  component App() {
2577
- const text = #ripple{ value: 'Hello World' };
2578
- #ripple.effect(() => {
2730
+ const text = new RippleObject({ value: 'Hello World' });
2731
+ effect(() => {
2579
2732
  logs.push(text.value);
2580
2733
  });
2581
2734
 
@@ -2600,7 +2753,7 @@ describe('bindInnerText', () => {
2600
2753
 
2601
2754
  it('should update innerText when tracked value changes', () => {
2602
2755
  component App() {
2603
- const text = #ripple.track('Before');
2756
+ const text = track('Before');
2604
2757
 
2605
2758
  <div>
2606
2759
  <div contenteditable="true" {ref bindInnerText(text)} />
@@ -2624,7 +2777,7 @@ describe('bindInnerText', () => {
2624
2777
 
2625
2778
  it('should update innerText when tracked value changes with a getter and setter', () => {
2626
2779
  component App() {
2627
- const text = #ripple{ value: 'Before' };
2780
+ const text = new RippleObject({ value: 'Before' });
2628
2781
 
2629
2782
  <div>
2630
2783
  <div
@@ -2655,9 +2808,9 @@ describe('bindTextContent', () => {
2655
2808
  const logs: string[] = [];
2656
2809
 
2657
2810
  component App() {
2658
- const text = #ripple.track('Sample text');
2811
+ const text = track('Sample text');
2659
2812
 
2660
- #ripple.effect(() => {
2813
+ effect(() => {
2661
2814
  logs.push(@text);
2662
2815
  });
2663
2816
 
@@ -2681,9 +2834,9 @@ describe('bindTextContent', () => {
2681
2834
  const logs: string[] = [];
2682
2835
 
2683
2836
  component App() {
2684
- const text = #ripple{ value: 'Sample text' };
2837
+ const text = new RippleObject({ value: 'Sample text' });
2685
2838
 
2686
- #ripple.effect(() => {
2839
+ effect(() => {
2687
2840
  logs.push(text.value);
2688
2841
  });
2689
2842
 
@@ -2708,7 +2861,7 @@ describe('bindTextContent', () => {
2708
2861
 
2709
2862
  it('should update textContent when tracked value changes', () => {
2710
2863
  component App() {
2711
- const text = #ripple.track('Start');
2864
+ const text = track('Start');
2712
2865
 
2713
2866
  <div>
2714
2867
  <div contenteditable="true" {ref bindTextContent(text)} />
@@ -2732,7 +2885,7 @@ describe('bindTextContent', () => {
2732
2885
 
2733
2886
  it('should update textContent when tracked value changes with a getter and setter', () => {
2734
2887
  component App() {
2735
- const text = #ripple{ value: 'Start' };
2888
+ const text = new RippleObject({ value: 'Start' });
2736
2889
 
2737
2890
  <div>
2738
2891
  <div
@@ -2759,7 +2912,7 @@ describe('bindTextContent', () => {
2759
2912
 
2760
2913
  it('should handle null textContent value', () => {
2761
2914
  component App() {
2762
- const text = #ripple.track(null);
2915
+ const text = track(null);
2763
2916
 
2764
2917
  <div contenteditable="true" {ref bindTextContent(text)} />
2765
2918
  }
@@ -2774,7 +2927,7 @@ describe('bindTextContent', () => {
2774
2927
 
2775
2928
  it('should handle null textContent value with a getter and setter', () => {
2776
2929
  component App() {
2777
- const text: RippleObject<{ value: string | null }> = #ripple{ value: null };
2930
+ const text: RippleObject<{ value: string | null }> = new RippleObject({ value: null });
2778
2931
 
2779
2932
  <div
2780
2933
  contenteditable="true"
@@ -2799,9 +2952,9 @@ describe('bindNode', () => {
2799
2952
  let capturedNode: HTMLElement | null = null;
2800
2953
 
2801
2954
  component App() {
2802
- const nodeRef = #ripple.track(null);
2955
+ const nodeRef = track(null);
2803
2956
 
2804
- #ripple.effect(() => {
2957
+ effect(() => {
2805
2958
  capturedNode = @nodeRef;
2806
2959
  });
2807
2960
 
@@ -2819,9 +2972,11 @@ describe('bindNode', () => {
2819
2972
  let capturedNode: HTMLElement | null = null;
2820
2973
 
2821
2974
  component App() {
2822
- const nodeRef: RippleObject<{ value: HTMLElement | null }> = #ripple{ value: null };
2975
+ const nodeRef: RippleObject<{ value: HTMLElement | null }> = new RippleObject({
2976
+ value: null,
2977
+ });
2823
2978
 
2824
- #ripple.effect(() => {
2979
+ effect(() => {
2825
2980
  capturedNode = nodeRef.value;
2826
2981
  });
2827
2982
 
@@ -2837,7 +2992,7 @@ describe('bindNode', () => {
2837
2992
 
2838
2993
  it('should allow access to bound element', () => {
2839
2994
  component App() {
2840
- const inputRef = #ripple.track<HTMLInputElement | null>(null);
2995
+ const inputRef = track<HTMLInputElement | null>(null);
2841
2996
 
2842
2997
  <div>
2843
2998
  <input type="text" {ref bindNode(inputRef)} />
@@ -2869,7 +3024,9 @@ describe('bindNode', () => {
2869
3024
 
2870
3025
  it('should allow access to bound element with a getter and setter', () => {
2871
3026
  component App() {
2872
- const inputRef: RippleObject<{ node: HTMLInputElement | null }> = #ripple{ node: null };
3027
+ const inputRef: RippleObject<{ node: HTMLInputElement | null }> = new RippleObject({
3028
+ node: null,
3029
+ });
2873
3030
 
2874
3031
  <div>
2875
3032
  <input
@@ -2908,9 +3065,9 @@ describe('bindFiles', () => {
2908
3065
  const logs: FileList[] = [];
2909
3066
 
2910
3067
  component App() {
2911
- const files = #ripple.track(null);
3068
+ const files = track(null);
2912
3069
 
2913
- #ripple.effect(() => {
3070
+ effect(() => {
2914
3071
  @files;
2915
3072
  if (@files) logs.push(@files);
2916
3073
  });
@@ -2949,9 +3106,9 @@ describe('bindFiles', () => {
2949
3106
  const logs: FileList[] = [];
2950
3107
 
2951
3108
  component App() {
2952
- const files: RippleObject<{ items: FileList | null }> = #ripple{ items: null };
3109
+ const files: RippleObject<{ items: FileList | null }> = new RippleObject({ items: null });
2953
3110
 
2954
- #ripple.effect(() => {
3111
+ effect(() => {
2955
3112
  files.items;
2956
3113
  if (files.items) logs.push(files.items);
2957
3114
  });
@@ -2994,9 +3151,9 @@ describe('bindFiles', () => {
2994
3151
  let capturedFiles: FileList | null = null;
2995
3152
 
2996
3153
  component App() {
2997
- const files = #ripple.track<FileList | null>(null);
3154
+ const files = track<FileList | null>(null);
2998
3155
 
2999
- #ripple.effect(() => {
3156
+ effect(() => {
3000
3157
  capturedFiles = @files;
3001
3158
  });
3002
3159
 
@@ -3029,9 +3186,9 @@ describe('bindFiles', () => {
3029
3186
  let capturedFiles: FileList | null = null;
3030
3187
 
3031
3188
  component App() {
3032
- const files: RippleObject<{ items: FileList | null }> = #ripple{ items: null };
3189
+ const files: RippleObject<{ items: FileList | null }> = new RippleObject({ items: null });
3033
3190
 
3034
- #ripple.effect(() => {
3191
+ effect(() => {
3035
3192
  capturedFiles = files.items;
3036
3193
  });
3037
3194
 
@@ -3067,10 +3224,10 @@ describe('bindFiles', () => {
3067
3224
  let capturedFiles: FileList | null = null;
3068
3225
 
3069
3226
  component App() {
3070
- const files = #ripple.track<FileList | null>(null);
3071
- const input = #ripple.track<HTMLInputElement | null>(null);
3227
+ const files = track<FileList | null>(null);
3228
+ const input = track<HTMLInputElement | null>(null);
3072
3229
 
3073
- #ripple.effect(() => {
3230
+ effect(() => {
3074
3231
  capturedFiles = @files;
3075
3232
  });
3076
3233
 
@@ -3120,10 +3277,10 @@ describe('bindFiles', () => {
3120
3277
  let capturedFiles: FileList | null = null;
3121
3278
 
3122
3279
  component App() {
3123
- const files: RippleObject<{ items: FileList | null }> = #ripple{ items: null };
3124
- const input = #ripple.track<HTMLInputElement | null>(null);
3280
+ const files: RippleObject<{ items: FileList | null }> = new RippleObject({ items: null });
3281
+ const input = track<HTMLInputElement | null>(null);
3125
3282
 
3126
- #ripple.effect(() => {
3283
+ effect(() => {
3127
3284
  capturedFiles = files.items;
3128
3285
  });
3129
3286
 
@@ -3177,9 +3334,9 @@ describe('bindFiles', () => {
3177
3334
  const fileCounts: number[] = [];
3178
3335
 
3179
3336
  component App() {
3180
- const files = #ripple.track<FileList | null>(null);
3337
+ const files = track<FileList | null>(null);
3181
3338
 
3182
- #ripple.effect(() => {
3339
+ effect(() => {
3183
3340
  @files;
3184
3341
  if (@files) {
3185
3342
  fileCounts.push(@files.length);
@@ -3226,8 +3383,8 @@ describe('bindFiles', () => {
3226
3383
  const fileCounts: number[] = [];
3227
3384
 
3228
3385
  component App() {
3229
- const files: RippleObject<{ items: FileList | null }> = #ripple{ items: null };
3230
- #ripple.effect(() => {
3386
+ const files: RippleObject<{ items: FileList | null }> = new RippleObject({ items: null });
3387
+ effect(() => {
3231
3388
  files.items;
3232
3389
  if (files.items) {
3233
3390
  fileCounts.push(files.items.length);
@@ -3278,9 +3435,9 @@ describe('bindFiles', () => {
3278
3435
  let capturedFile: File | null = null;
3279
3436
 
3280
3437
  component App() {
3281
- const files = #ripple.track<FileList | null>(null);
3438
+ const files = track<FileList | null>(null);
3282
3439
 
3283
- #ripple.effect(() => {
3440
+ effect(() => {
3284
3441
  @files;
3285
3442
  if (@files && @files.length > 0) {
3286
3443
  capturedFile = @files[0];
@@ -3315,8 +3472,8 @@ describe('bindFiles', () => {
3315
3472
  let capturedFile: File | null = null;
3316
3473
 
3317
3474
  component App() {
3318
- const files: RippleObject<{ items: FileList | null }> = #ripple{ items: null };
3319
- #ripple.effect(() => {
3475
+ const files: RippleObject<{ items: FileList | null }> = new RippleObject({ items: null });
3476
+ effect(() => {
3320
3477
  files.items;
3321
3478
  if (files.items && files.items.length > 0) {
3322
3479
  capturedFile = files.items[0];
@@ -3354,9 +3511,9 @@ describe('bindFiles', () => {
3354
3511
  const logs: (FileList | null)[] = [];
3355
3512
 
3356
3513
  component App() {
3357
- const files = #ripple.track(null);
3514
+ const files = track(null);
3358
3515
 
3359
- #ripple.effect(() => {
3516
+ effect(() => {
3360
3517
  logs.push(@files);
3361
3518
  });
3362
3519
 
@@ -3396,8 +3553,8 @@ describe('bindFiles', () => {
3396
3553
  const logs: (FileList | null)[] = [];
3397
3554
 
3398
3555
  component App() {
3399
- const files: RippleObject<{ items: FileList | null }> = #ripple{ items: null };
3400
- #ripple.effect(() => {
3556
+ const files: RippleObject<{ items: FileList | null }> = new RippleObject({ items: null });
3557
+ effect(() => {
3401
3558
  logs.push(files.items);
3402
3559
  });
3403
3560
 
@@ -3611,7 +3768,7 @@ describe('error handling', () => {
3611
3768
  it('should throw error when bindValue receives a getter but not a setter', () => {
3612
3769
  expect(() => {
3613
3770
  component App() {
3614
- const value = #ripple.track('');
3771
+ const value = track('');
3615
3772
  <input type="text" {ref bindValue(() => @value)} />
3616
3773
  }
3617
3774
  render(App);
@@ -3623,7 +3780,7 @@ describe('error handling', () => {
3623
3780
  it('should throw error when bindValue receives a getter and setter not a function', () => {
3624
3781
  expect(() => {
3625
3782
  component App() {
3626
- const value = #ripple.track('');
3783
+ const value = track('');
3627
3784
  // @ts-expect-error invalid argument
3628
3785
  <input type="text" {ref bindValue(() => @value, 5)} />
3629
3786
  }
@@ -3633,6 +3790,48 @@ describe('error handling', () => {
3633
3790
  );
3634
3791
  });
3635
3792
 
3793
+ it(
3794
+ 'should throw error when bindValue on select multiple receives a non-array tracked value',
3795
+ () => {
3796
+ expect(() => {
3797
+ component App() {
3798
+ const value = track('2');
3799
+
3800
+ <select multiple {ref bindValue(value)}>
3801
+ <option value="1">{'One'}</option>
3802
+ <option value="2">{'Two'}</option>
3803
+ </select>
3804
+ }
3805
+
3806
+ render(App);
3807
+ flushSync();
3808
+ }).toThrow(
3809
+ 'Reactive bound value of a `<select multiple>` element should be an array, but it received a non-array value.',
3810
+ );
3811
+ },
3812
+ );
3813
+
3814
+ it(
3815
+ 'should throw error when bindValue on select multiple receives a non-array getter value',
3816
+ () => {
3817
+ expect(() => {
3818
+ component App() {
3819
+ const value = track('2');
3820
+
3821
+ <select multiple {ref bindValue(() => @value, (v) => (@value = v))}>
3822
+ <option value="1">{'One'}</option>
3823
+ <option value="2">{'Two'}</option>
3824
+ </select>
3825
+ }
3826
+
3827
+ render(App);
3828
+ flushSync();
3829
+ }).toThrow(
3830
+ 'Reactive bound value of a `<select multiple>` element should be an array, but it received a non-array value.',
3831
+ );
3832
+ },
3833
+ );
3834
+
3636
3835
  it(
3637
3836
  'should throw error when bindChecked receives non-tracked object with a getter but not a setter',
3638
3837
  () => {