ripple 0.2.82 → 0.2.83

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.82",
6
+ "version": "0.2.83",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -75,6 +75,21 @@ function RipplePlugin(config) {
75
75
  }
76
76
  }
77
77
 
78
+ if (code === 35) {
79
+ // # character
80
+ // Look ahead to see if this is followed by [ for tuple syntax
81
+ if (this.pos + 1 < this.input.length) {
82
+ const nextChar = this.input.charCodeAt(this.pos + 1);
83
+ if (nextChar === 91) { // [ character
84
+ // This is a tuple literal #[
85
+ // Consume both # and [
86
+ ++this.pos; // consume #
87
+ ++this.pos; // consume [
88
+ return this.finishToken(tt.bracketL, '#[');
89
+ }
90
+ }
91
+ }
92
+
78
93
  if (code === 64) {
79
94
  // @ character
80
95
  // Look ahead to see if this is followed by a valid identifier character
@@ -161,6 +176,50 @@ function RipplePlugin(config) {
161
176
  return node;
162
177
  }
163
178
 
179
+ parseExprAtom(refDestructuringErrors, forNew, forInit) {
180
+ // Check if this is a tuple literal starting with #[
181
+ if (this.type === tt.bracketL && this.value === '#[') {
182
+ return this.parseTrackedArrayExpression();
183
+ }
184
+
185
+ return super.parseExprAtom(refDestructuringErrors, forNew, forInit);
186
+ }
187
+
188
+ parseTrackedArrayExpression() {
189
+ const node = this.startNode();
190
+ this.next(); // consume the '#['
191
+
192
+ node.elements = [];
193
+
194
+ // Parse array elements similar to regular array parsing
195
+ let first = true;
196
+ while (!this.eat(tt.bracketR)) {
197
+ if (!first) {
198
+ this.expect(tt.comma);
199
+ if (this.afterTrailingComma(tt.bracketR)) break;
200
+ } else {
201
+ first = false;
202
+ }
203
+
204
+ if (this.type === tt.comma) {
205
+ // Hole in array
206
+ node.elements.push(null);
207
+ } else if (this.type === tt.ellipsis) {
208
+ // Spread element
209
+ const element = this.parseSpread();
210
+ node.elements.push(element);
211
+ if (this.type === tt.comma && this.input.charCodeAt(this.pos) === 93) {
212
+ this.raise(this.pos, "Trailing comma is not permitted after the rest element");
213
+ }
214
+ } else {
215
+ // Regular element
216
+ node.elements.push(this.parseMaybeAssign(false));
217
+ }
218
+ }
219
+
220
+ return this.finishNode(node, 'TrackedArrayExpression');
221
+ }
222
+
164
223
  parseExportDefaultDeclaration() {
165
224
  // Check if this is "export default component"
166
225
  if (this.value === 'component') {
@@ -269,6 +269,27 @@ const visitors = {
269
269
  );
270
270
  },
271
271
 
272
+ TrackedArrayExpression(node, context) {
273
+ if (context.state.to_ts) {
274
+ if (!context.state.imports.has(`import { TrackedArray } from 'ripple'`)) {
275
+ context.state.imports.add(`import { TrackedArray } from 'ripple'`);
276
+ }
277
+
278
+ return b.new(
279
+ b.call(
280
+ b.member(b.id('TrackedArray'), b.id('from')),
281
+ node.elements.map((el) => context.visit(el)),
282
+ ),
283
+ );
284
+ }
285
+
286
+ return b.call(
287
+ '_$_.tracked_array',
288
+ b.array(node.elements.map((el) => context.visit(el))),
289
+ b.id('__block'),
290
+ );
291
+ },
292
+
272
293
  MemberExpression(node, context) {
273
294
  const parent = context.path.at(-1);
274
295
 
@@ -287,3 +287,7 @@ const methods_returning_arrays = new Set([
287
287
  'toSpliced',
288
288
  'with',
289
289
  ]);
290
+
291
+ export function tracked_array(elements, block) {
292
+ return proxy({ elements, block, from_static: true });
293
+ }
@@ -52,3 +52,5 @@ export { if_block as if } from './if.js';
52
52
  export { try_block as try, aborted } from './try.js';
53
53
 
54
54
  export { template, append } from './template.js';
55
+
56
+ export { tracked_array } from '../../array.js';
@@ -326,17 +326,23 @@ export function track(v, o, b) {
326
326
  /** @type {Record<PropertyKey, any | null>} */
327
327
  var descriptors = get_descriptors(v);
328
328
 
329
- for (let i = 0, key, t; i < list.length; i++) {
329
+ for (let i = 0, key, t, exists = true; i < list.length; i++) {
330
330
  key = list[i];
331
331
 
332
332
  if (is_tracked_object(v[key])) {
333
333
  t = v[key];
334
334
  } else {
335
- t = define_property(tracked(undefined, b), 'v', descriptors[key]);
335
+ t = tracked(undefined, b);
336
+ exists = !!descriptors[key];
337
+ if (exists) {
338
+ t = define_property(t, 'v', descriptors[key]);
339
+ }
336
340
  }
337
341
 
338
342
  out[i] = t;
339
- descriptors[key] = null;
343
+ if (exists) {
344
+ descriptors[key] = null;
345
+ }
340
346
  }
341
347
 
342
348
  var props = Reflect.ownKeys(descriptors);
@@ -790,4 +790,5 @@ export {
790
790
  null_instane as null,
791
791
  debugger_builder as debugger,
792
792
  try_builder as try,
793
+ new_builder as new,
793
794
  };
@@ -379,8 +379,7 @@ describe('basic client', () => {
379
379
  expect(container.querySelector('.count').textContent).toBe('0');
380
380
  });
381
381
 
382
- // TODO
383
- it.skip('renders multiple reactive lexical blocks with complexity', () => {
382
+ it('renders multiple reactive lexical blocks with complexity', () => {
384
383
 
385
384
  component Basic() {
386
385
  const count = 'count';
@@ -1276,12 +1275,7 @@ describe('basic client', () => {
1276
1275
  let logs = [];
1277
1276
 
1278
1277
  component App() {
1279
- let count = track(0, {
1280
- set(v) {
1281
- logs.push('inside setter');
1282
- return v;
1283
- }
1284
- });
1278
+ let count = track(0);
1285
1279
  let name = track('Click Me');
1286
1280
 
1287
1281
  function buttonRef(el) {
@@ -1294,9 +1288,11 @@ describe('basic client', () => {
1294
1288
  <Child
1295
1289
  class="my-button"
1296
1290
  onClick={() => @name === 'Click Me' ? @name = 'Clicked' : @name = 'Click Me'}
1297
- {count}
1291
+ {@count}
1298
1292
  {ref buttonRef}
1299
- >{@name}</Child>;
1293
+ >{@name}</Child>
1294
+
1295
+ <button onClick={() => @count++}>{'Increment Count'}</button>
1300
1296
  }
1301
1297
 
1302
1298
  component Child(props: PropsWithChildren<{ count: Tracked<number> }>) {
@@ -1306,7 +1302,6 @@ describe('basic client', () => {
1306
1302
  <button {...@rest}><@children /></button>
1307
1303
  }
1308
1304
  <pre>{@count}</pre>
1309
- <button onClick={() => @count++}>{'Increment Count'}</button>
1310
1305
  }
1311
1306
 
1312
1307
  render(App);
@@ -1327,12 +1322,11 @@ describe('basic client', () => {
1327
1322
 
1328
1323
  expect(buttonClickMe.textContent).toBe('Clicked');
1329
1324
  expect(countPre.textContent).toBe('1');
1330
- expect(logs).toEqual(['ref called','inside setter']);
1331
1325
 
1332
1326
  buttonIncrement.click();
1333
1327
  flushSync();
1334
1328
 
1335
- expect(logs).toEqual(['ref called','inside setter','inside setter','cleanup ref']);
1329
+ expect(logs).toEqual(['ref called','cleanup ref']);
1336
1330
  });
1337
1331
 
1338
1332
  it('errors on invalid value as null for track with split', () => {
@@ -1393,6 +1387,26 @@ describe('basic client', () => {
1393
1387
  expect(pre.textContent).toBe('Invalid value: expected a non-tracked object');
1394
1388
  });
1395
1389
 
1390
+ it('returns undefined for non-existent props in track with split', () => {
1391
+ component App() {
1392
+ const [a, b, rest] = track({a: 1, c: 1}, { split: ['a', 'b'] });
1393
+
1394
+ <pre>{@a}</pre>
1395
+ <pre>{String(@b)}</pre>
1396
+ <pre>{@rest.c}</pre>
1397
+ }
1398
+
1399
+ render(App);
1400
+
1401
+ const preA = container.querySelectorAll('pre')[0];
1402
+ const preB = container.querySelectorAll('pre')[1];
1403
+ const preC = container.querySelectorAll('pre')[2];
1404
+
1405
+ expect(preA.textContent).toBe('1');
1406
+ expect(preB.textContent).toBe('undefined');
1407
+ expect(preC.textContent).toBe('1');
1408
+ });
1409
+
1396
1410
  it('returns the same tracked object if plain track is called with a tracked object', () => {
1397
1411
  component App() {
1398
1412
  const t = track({a: 1, b: 2, c: 3});