ripple 0.2.33 → 0.2.34

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 a TypeScript UI framework for the web",
4
4
  "license": "MIT",
5
5
  "author": "Dominic Gannaway",
6
- "version": "0.2.33",
6
+ "version": "0.2.34",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index.js",
9
9
  "main": "src/runtime/index.js",
@@ -148,6 +148,45 @@ const visitors = {
148
148
  return context.next();
149
149
  }
150
150
 
151
+ // Handle array methods that access the array
152
+ if (
153
+ callee.type === 'MemberExpression' &&
154
+ !callee.optional &&
155
+ callee.property.type === 'Identifier'
156
+ ) {
157
+ const name = callee.property.name;
158
+ if (
159
+ // TODO support the missing array methods
160
+ name === 'reduce' ||
161
+ name === 'map' ||
162
+ name === 'forEach' ||
163
+ name === 'join' ||
164
+ name === 'includes' ||
165
+ name === 'indexOf' ||
166
+ name === 'lastIndexOf' ||
167
+ name === 'filter' ||
168
+ name === 'every' ||
169
+ name === 'some' ||
170
+ name === 'toSpliced' ||
171
+ name === 'toSorted' ||
172
+ name === 'toString' ||
173
+ name === 'values' ||
174
+ name === 'entries'
175
+ ) {
176
+ return b.call(
177
+ '$.with_scope',
178
+ b.id('__block'),
179
+ b.thunk(
180
+ b.call(
181
+ '$.array_' + name,
182
+ context.visit(callee.object),
183
+ ...node.arguments.map((arg) => context.visit(arg)),
184
+ ),
185
+ ),
186
+ );
187
+ }
188
+ }
189
+
151
190
  return b.call(
152
191
  '$.with_scope',
153
192
  b.id('__block'),
@@ -975,43 +1014,37 @@ const visitors = {
975
1014
  },
976
1015
 
977
1016
  ArrayExpression(node, context) {
978
- // const elements = [];
979
- // const tracked = [];
980
- // let i = 0;
981
-
982
- // for (const element of node.elements) {
983
- // if (element === null) {
984
- // elements.push(null);
985
- // } else if (element.type === 'Element') {
986
- // const metadata = { tracking: false, await: false };
987
- // const tracked_element = context.visit(element, { ...context.state, metadata });
988
-
989
- // if (metadata.tracking) {
990
- // tracked.push(b.literal(i));
991
- // elements.push(tracked_element);
992
- // } else {
993
- // elements.push(tracked_element);
994
- // }
995
- // } else if (element.type === 'SpreadElement') {
996
- // const metadata = { tracking: false, await: false };
997
- // const tracked_element = context.visit(element, { ...context.state, metadata });
998
-
999
- // if (metadata.tracking) {
1000
- // tracked.push(b.spread(tracked_element.argument));
1001
- // elements.push(tracked_element);
1002
- // } else {
1003
- // elements.push(tracked_element);
1004
- // }
1005
- // } else {
1006
- // const metadata = { tracking: false, await: false };
1007
- // elements.push(context.visit(element, { ...context.state, metadata }));
1008
- // }
1009
- // i++;
1010
- // }
1011
-
1012
- // if (tracked.length > 0) {
1013
- // return b.call('$.tracked_object', { ...node, elements }, b.array(tracked), b.id('__block'));
1014
- // }
1017
+ // TODO we can bail out of all of this if we know we're inside a computed fn expression
1018
+ // as the reactivity will hold from the reference of the $ binding itself
1019
+ const elements = [];
1020
+ const tracked = [];
1021
+ let i = 0;
1022
+
1023
+ for (const element of node.elements) {
1024
+ if (element === null) {
1025
+ elements.push(null);
1026
+ } else if (element.type === 'Identifier' && is_tracked_name(element.name)) {
1027
+ const metadata = { tracking: false, await: false };
1028
+ const tracked_identifier = context.visit(element, { ...context.state, metadata });
1029
+
1030
+ if (metadata.tracking) {
1031
+ tracked.push(b.literal(i));
1032
+ elements.push(
1033
+ b.call('$.computed_property', b.thunk(tracked_identifier), b.id('__block')),
1034
+ );
1035
+ } else {
1036
+ elements.push(tracked_identifier);
1037
+ }
1038
+ } else {
1039
+ const metadata = { tracking: false, await: false };
1040
+ elements.push(context.visit(element, { ...context.state, metadata }));
1041
+ }
1042
+ i++;
1043
+ }
1044
+
1045
+ if (tracked.length > 0) {
1046
+ return b.call('$.tracked_object', { ...node, elements }, b.array(tracked), b.id('__block'));
1047
+ }
1015
1048
 
1016
1049
  context.next();
1017
1050
  },
@@ -0,0 +1,352 @@
1
+ import { TRACKED_OBJECT } from './constants';
2
+ import { get_property } from './runtime';
3
+
4
+ const array_proto = Array.prototype;
5
+
6
+ /**
7
+ * @template T
8
+ * @param {Array<T>} array
9
+ * @param {(previousValue: T, currentValue: T, currentIndex: number, array: Array<T>) => T} callback
10
+ * @param {T} initial_value
11
+ * @returns {T}
12
+ */
13
+ export function array_reduce(array, callback, initial_value) {
14
+ // @ts-expect-error
15
+ var tracked_properties = array[TRACKED_OBJECT];
16
+
17
+ if (tracked_properties === undefined || array.reduce !== array_proto.reduce) {
18
+ return array.reduce(callback, initial_value);
19
+ }
20
+
21
+ let accumulator = initial_value;
22
+
23
+ for (let i = 0; i < array.length; i++) {
24
+ accumulator = callback(accumulator, get_property(array, i), i, array);
25
+ }
26
+
27
+ return accumulator;
28
+ }
29
+
30
+ /**
31
+ * @template T
32
+ * @param {Array<T>} array
33
+ * @param {string} [separator]
34
+ * @returns {string}
35
+ */
36
+ export function array_join(array, separator) {
37
+ // @ts-expect-error
38
+ var tracked_properties = array[TRACKED_OBJECT];
39
+ if (tracked_properties === undefined || array.join !== array_proto.join) {
40
+ return array.join(separator);
41
+ }
42
+
43
+ let result = '';
44
+ for (let i = 0; i < array.length; i++) {
45
+ if (i > 0 && separator !== undefined) {
46
+ result += separator;
47
+ }
48
+ result += String(get_property(array, i));
49
+ }
50
+
51
+ return result;
52
+ }
53
+
54
+ /**
55
+ * @template T
56
+ * @template U
57
+ * @param {Array<T>} array
58
+ * @param {(value: T, index: number, array: Array<T>) => U} callback
59
+ * @returns {Array<U>}
60
+ */
61
+ export function array_map(array, callback) {
62
+ // @ts-expect-error
63
+ var tracked_properties = array[TRACKED_OBJECT];
64
+ if (tracked_properties === undefined || array.map !== array_proto.map) {
65
+ return array.map(callback);
66
+ }
67
+
68
+ const result = [];
69
+ for (let i = 0; i < array.length; i++) {
70
+ if (i in array) {
71
+ result[i] = callback(get_property(array, i), i, array);
72
+ }
73
+ }
74
+
75
+ return result;
76
+ }
77
+
78
+ /**
79
+ * @template T
80
+ * @param {Array<T>} array
81
+ * @param {(value: T, index: number, array: Array<T>) => boolean} callback
82
+ * @returns {Array<T>}
83
+ */
84
+ export function array_filter(array, callback) {
85
+ // @ts-expect-error
86
+ var tracked_properties = array[TRACKED_OBJECT];
87
+ if (tracked_properties === undefined || array.filter !== array_proto.filter) {
88
+ return array.filter(callback);
89
+ }
90
+
91
+ const result = [];
92
+ for (let i = 0; i < array.length; i++) {
93
+ if (i in array) {
94
+ const value = get_property(array, i);
95
+ if (callback(value, i, array)) {
96
+ result.push(value);
97
+ }
98
+ }
99
+ }
100
+
101
+ return result;
102
+ }
103
+
104
+ /**
105
+ * @template T
106
+ * @param {Array<T>} array
107
+ * @param {(value: T, index: number, array: Array<T>) => boolean} callback
108
+ * @returns {void}
109
+ */
110
+ export function array_forEach(array, callback) {
111
+ // @ts-expect-error
112
+ var tracked_properties = array[TRACKED_OBJECT];
113
+ if (tracked_properties === undefined || array.forEach !== array_proto.forEach) {
114
+ return array.forEach(callback);
115
+ }
116
+
117
+ for (let i = 0; i < array.length; i++) {
118
+ if (i in array) {
119
+ callback(get_property(array, i), i, array);
120
+ }
121
+ }
122
+ }
123
+
124
+ /**
125
+ * @template T
126
+ * @param {Array<T>} array
127
+ * @param {T} value
128
+ * @returns {boolean}
129
+ */
130
+ export function array_includes(array, value) {
131
+ // @ts-expect-error
132
+ var tracked_properties = array[TRACKED_OBJECT];
133
+ if (tracked_properties === undefined || array.includes !== array_proto.includes) {
134
+ return array.includes(value);
135
+ }
136
+
137
+ for (let i = 0; i < array.length; i++) {
138
+ if (i in array && get_property(array, i) === value) {
139
+ return true;
140
+ }
141
+ }
142
+
143
+ return false;
144
+ }
145
+
146
+ /**
147
+ * @template T
148
+ * @param {Array<T>} array
149
+ * @param {T} value
150
+ * @returns {number}
151
+ */
152
+ export function array_indexOf(array, value) {
153
+ // @ts-expect-error
154
+ var tracked_properties = array[TRACKED_OBJECT];
155
+ if (tracked_properties === undefined || array.indexOf !== array_proto.indexOf) {
156
+ return array.indexOf(value);
157
+ }
158
+
159
+ for (let i = 0; i < array.length; i++) {
160
+ if (i in array && get_property(array, i) === value) {
161
+ return i;
162
+ }
163
+ }
164
+
165
+ return -1;
166
+ }
167
+
168
+ /**
169
+ * @template T
170
+ * @param {Array<T>} array
171
+ * @param {T} value
172
+ * @returns {number}
173
+ */
174
+ export function array_lastIndexOf(array, value) {
175
+ // @ts-expect-error
176
+ var tracked_properties = array[TRACKED_OBJECT];
177
+ if (tracked_properties === undefined || array.lastIndexOf !== array_proto.lastIndexOf) {
178
+ return array.lastIndexOf(value);
179
+ }
180
+
181
+ for (let i = array.length - 1; i >= 0; i--) {
182
+ if (i in array && get_property(array, i) === value) {
183
+ return i;
184
+ }
185
+ }
186
+
187
+ return -1;
188
+ }
189
+
190
+ /**
191
+ * @template T
192
+ * @param {Array<T>} array
193
+ * @param {(value: T, index: number, array: Array<T>) => boolean} callback
194
+ * @returns {boolean}
195
+ */
196
+ export function array_every(array, callback) {
197
+ // @ts-expect-error
198
+ var tracked_properties = array[TRACKED_OBJECT];
199
+ if (tracked_properties === undefined || array.every !== array_proto.every) {
200
+ return array.every(callback);
201
+ }
202
+
203
+ for (let i = 0; i < array.length; i++) {
204
+ if (i in array && !callback(get_property(array, i), i, array)) {
205
+ return false;
206
+ }
207
+ }
208
+
209
+ return true;
210
+ }
211
+
212
+ /**
213
+ * @template T
214
+ * @param {Array<T>} array
215
+ * @param {(value: T, index: number, array: Array<T>) => boolean} callback
216
+ * @returns {boolean}
217
+ */
218
+ export function array_some(array, callback) {
219
+ // @ts-expect-error
220
+ var tracked_properties = array[TRACKED_OBJECT];
221
+ if (tracked_properties === undefined || array.some !== array_proto.some) {
222
+ return array.some(callback);
223
+ }
224
+
225
+ for (let i = 0; i < array.length; i++) {
226
+ if (i in array && callback(get_property(array, i), i, array)) {
227
+ return true;
228
+ }
229
+ }
230
+
231
+ return false;
232
+ }
233
+
234
+ /**
235
+ * @template T
236
+ * @param {Array<T>} array
237
+ * @returns {string}
238
+ */
239
+ export function array_toString(array) {
240
+ // @ts-expect-error
241
+ var tracked_properties = array[TRACKED_OBJECT];
242
+ if (tracked_properties === undefined || array.toString !== array_proto.toString) {
243
+ return array.toString();
244
+ }
245
+
246
+ let result = '';
247
+ for (let i = 0; i < array.length; i++) {
248
+ if (i > 0) {
249
+ result += ',';
250
+ }
251
+ if (i in array) {
252
+ result += String(get_property(array, i));
253
+ }
254
+ }
255
+
256
+ return result;
257
+ }
258
+
259
+ /**
260
+ * @template T
261
+ * @param {Array<T>} array
262
+ * @param {((a: T, b: T) => number) | undefined} compare_fn
263
+ * @returns {Array<T>}
264
+ */
265
+ export function array_toSorted(array, compare_fn) {
266
+ // @ts-expect-error
267
+ var tracked_properties = array[TRACKED_OBJECT];
268
+ if (tracked_properties === undefined || array.toSorted !== array_proto.toSorted) {
269
+ return array.toSorted(compare_fn);
270
+ }
271
+
272
+ const result = [];
273
+ for (let i = 0; i < array.length; i++) {
274
+ if (i in array) {
275
+ result.push(get_property(array, i));
276
+ }
277
+ }
278
+
279
+ return result.sort(compare_fn);
280
+ }
281
+
282
+ /**
283
+ * @template T
284
+ * @param {Array<T>} array
285
+ * @param {number} start
286
+ * @param {number} delete_count
287
+ * @param {...T} items
288
+ * @returns {Array<T>}
289
+ */
290
+ export function array_toSpliced(array, start, delete_count, ...items) {
291
+ // @ts-expect-error
292
+ var tracked_properties = array[TRACKED_OBJECT];
293
+ if (tracked_properties === undefined || array.toSpliced !== array_proto.toSpliced) {
294
+ return array.toSpliced(start, delete_count, ...items);
295
+ }
296
+
297
+ const result = [];
298
+ for (let i = 0; i < array.length; i++) {
299
+ if (i in array) {
300
+ result.push(get_property(array, i));
301
+ }
302
+ }
303
+
304
+ result.splice(start, delete_count, ...items);
305
+
306
+ return result;
307
+ }
308
+
309
+ /**
310
+ * @template T
311
+ * @param {Array<T>} array
312
+ * @returns {IterableIterator<T>}
313
+ */
314
+ export function array_values(array) {
315
+ // @ts-expect-error
316
+ var tracked_properties = array[TRACKED_OBJECT];
317
+ if (tracked_properties === undefined || array.values !== array_proto.values) {
318
+ return array.values();
319
+ }
320
+
321
+ const result = [];
322
+ for (let i = 0; i < array.length; i++) {
323
+ if (i in array) {
324
+ result.push(get_property(array, i));
325
+ }
326
+ }
327
+
328
+ return result[Symbol.iterator]();
329
+ }
330
+
331
+ /**
332
+ * @template T
333
+ * @param {Array<T>} array
334
+ * @returns {IterableIterator<[number, T]>}
335
+ */
336
+ export function array_entries(array) {
337
+ // @ts-expect-error
338
+ var tracked_properties = array[TRACKED_OBJECT];
339
+ if (tracked_properties === undefined || array.entries !== array_proto.entries) {
340
+ return array.entries();
341
+ }
342
+
343
+ /** @type {Array<[number, T]>} */
344
+ const result = [];
345
+ for (let i = 0; i < array.length; i++) {
346
+ if (i in array) {
347
+ result.push([i, get_property(array, i)]);
348
+ }
349
+ }
350
+
351
+ return result[Symbol.iterator]();
352
+ }
@@ -46,6 +46,24 @@ export {
46
46
  exclude_from_object,
47
47
  } from './runtime.js';
48
48
 
49
+ export {
50
+ array_reduce,
51
+ array_join,
52
+ array_map,
53
+ array_filter,
54
+ array_forEach,
55
+ array_includes,
56
+ array_indexOf,
57
+ array_lastIndexOf,
58
+ array_every,
59
+ array_some,
60
+ array_toString,
61
+ array_toSorted,
62
+ array_toSpliced,
63
+ array_values,
64
+ array_entries,
65
+ } from './array.js';
66
+
49
67
  export { for_block as for } from './for.js';
50
68
 
51
69
  export { if_block as if } from './if.js';
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { mount, flushSync } from 'ripple';
2
+ import { mount, flushSync, effect } from 'ripple';
3
3
 
4
4
  describe('basic', () => {
5
5
  let container;
@@ -832,4 +832,70 @@ describe('basic', () => {
832
832
  expect(paragraphs[0].className).toBe('parent-class');
833
833
  expect(paragraphs[1].className).toBe('nested-class');
834
834
  });
835
+
836
+ it('basic reactivity with standard arrays should work', () => {
837
+ let logs = [];
838
+
839
+ component App() {
840
+ let $first = 0;
841
+ let $second = 0;
842
+ const arr = [$first, $second];
843
+
844
+ const $total = arr.reduce((a, b) => a + b, 0);
845
+
846
+ <button onClick={() => { $first++; }}>{'first:' + $first}</button>
847
+ <button onClick={() => { $second++; }}>{'second: ' + $second}</button>
848
+
849
+ effect(() => {
850
+ let _arr = [];
851
+
852
+ arr.forEach((item) => {
853
+ _arr.push(item);
854
+ });
855
+
856
+ logs.push(_arr.join(', '));
857
+ });
858
+
859
+ effect(() => {
860
+ if (arr.includes(1)) {
861
+ logs.push('arr includes 1');
862
+ }
863
+ });
864
+
865
+ <div>{'Sum: ' + $total}</div>
866
+ <div>{'Comma Separated: ' + arr.join(', ')}</div>
867
+ <div>{'Number to string: ' + arr.map(a => String(a))}</div>
868
+ <div>{'Even numbers: ' + arr.filter(a => a % 2 === 0)}</div>
869
+ }
870
+
871
+ render(App);
872
+ flushSync();
873
+
874
+ const buttons = container.querySelectorAll('button');
875
+ const divs = container.querySelectorAll('div');
876
+
877
+ expect(divs[0].textContent).toBe('Sum: 0');
878
+ expect(divs[1].textContent).toBe('Comma Separated: 0, 0');
879
+ expect(divs[2].textContent).toBe('Number to string: 0,0');
880
+ expect(divs[3].textContent).toBe('Even numbers: 0,0');
881
+ expect(logs).toEqual(['0, 0']);
882
+
883
+ buttons[0].click();
884
+ flushSync();
885
+
886
+ expect(divs[0].textContent).toBe('Sum: 1');
887
+ expect(divs[1].textContent).toBe('Comma Separated: 1, 0');
888
+ expect(divs[2].textContent).toBe('Number to string: 1,0');
889
+ expect(divs[3].textContent).toBe('Even numbers: 0');
890
+ expect(logs).toEqual(['0, 0', '1, 0', 'arr includes 1']);
891
+
892
+ buttons[1].click();
893
+ flushSync();
894
+
895
+ expect(divs[0].textContent).toBe('Sum: 2');
896
+ expect(divs[1].textContent).toBe('Comma Separated: 1, 1');
897
+ expect(divs[2].textContent).toBe('Number to string: 1,1');
898
+ expect(divs[3].textContent).toBe('Even numbers: ');
899
+ expect(logs).toEqual(['0, 0', '1, 0', 'arr includes 1', '1, 1']);
900
+ })
835
901
  });