ripple 0.2.73 → 0.2.74

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.73",
6
+ "version": "0.2.74",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index.js",
9
9
  "main": "src/runtime/index.js",
@@ -50,7 +50,8 @@
50
50
  },
51
51
  "imports": {
52
52
  "#client": "./src/runtime/internal/client/types.d.ts",
53
- "#client/constants": "./src/internal/client/constants.js"
53
+ "#client/constants": "./src/internal/client/constants.js",
54
+ "#public": "./types/index.d.ts"
54
55
  },
55
56
  "dependencies": {
56
57
  "@jridgewell/sourcemap-codec": "^1.5.5",
@@ -192,6 +192,8 @@ const visitors = {
192
192
 
193
193
  if (!context.state.to_ts && is_ripple_track_call(callee, context)) {
194
194
  if (node.arguments.length === 0) {
195
+ node.arguments.push(b.void0, b.void0);
196
+ } else if (node.arguments.length === 1) {
195
197
  node.arguments.push(b.void0);
196
198
  }
197
199
  return {
@@ -36,19 +36,7 @@ export function mount(component, options) {
36
36
 
37
37
  export { create_context as createContext } from './internal/client/context.js';
38
38
 
39
- export { flush_sync as flushSync, untrack, deferred } from './internal/client/runtime.js';
40
-
41
- /**
42
- * @param {any} v
43
- * @param {Block} b
44
- * @returns {Tracked | Derived}
45
- */
46
- export function track(v, b) {
47
- if (typeof v === 'function') {
48
- return derived(v, b);
49
- }
50
- return tracked(v, b);
51
- }
39
+ export { flush_sync as flushSync, track, untrack, deferred } from './internal/client/runtime.js';
52
40
 
53
41
  export { TrackedArray } from './array.js';
54
42
 
@@ -39,10 +39,14 @@ function move(block, anchor) {
39
39
  anchor.before(node);
40
40
  return;
41
41
  }
42
- while (node !== end) {
42
+ while (node !== null) {
43
43
  var next_node = /** @type {TemplateNode} */ (next_sibling(node));
44
44
  anchor.before(node);
45
45
  node = next_node;
46
+ if (node === end) {
47
+ anchor.before(end);
48
+ break;
49
+ }
46
50
  }
47
51
  }
48
52
 
@@ -167,6 +171,8 @@ function reconcile(anchor, block, b, render_fn, is_controlled, is_indexed) {
167
171
  }
168
172
  }
169
173
 
174
+ var fast_path_removal = false;
175
+
170
176
  if (j > a_end) {
171
177
  if (j <= b_end) {
172
178
  while (j <= b_end) {
@@ -185,13 +191,14 @@ function reconcile(anchor, block, b, render_fn, is_controlled, is_indexed) {
185
191
  var b_start = j;
186
192
  var a_left = a_end - j + 1;
187
193
  var b_left = b_end - j + 1;
188
- var fast_path_removal = is_controlled && a_left === a_length;
189
194
  var sources = new Int32Array(b_left + 1);
190
195
  var moved = false;
191
196
  var pos = 0;
192
197
  var patched = 0;
193
198
  var i = 0;
194
199
 
200
+ fast_path_removal = is_controlled && a_left === a_length;
201
+
195
202
  // When sizes are small, just loop them through
196
203
  if (b_length < 4 || (a_left | b_left) < 32) {
197
204
  for (i = a_start; i <= a_end; ++i) {
@@ -244,8 +251,8 @@ function reconcile(anchor, block, b, render_fn, is_controlled, is_indexed) {
244
251
  if (j !== undefined) {
245
252
  if (fast_path_removal) {
246
253
  fast_path_removal = false;
247
- // while (i > aStart) {
248
- // remove(a[aStart++], dom, animations);
254
+ // while (i > a_start) {
255
+ // destroy_block(a[a_start++]);
249
256
  // }
250
257
  }
251
258
  sources[j - b_start] = i + 1;
@@ -385,6 +392,16 @@ export function keyed(collection, key_fn) {
385
392
  if (block === null || (block.f & FOR_BLOCK) === 0) {
386
393
  throw new Error('keyed() must be used inside a for block');
387
394
  }
395
+
396
+ var b_array = collection_to_array(collection);
397
+ var b_keys = b_array.map(key_fn);
398
+
399
+ // We only need to do this in DEV
400
+ var b = new Set(b_keys);
401
+ if (b.size !== b_keys.length) {
402
+ throw new Error('Duplicate keys are not allowed');
403
+ }
404
+
388
405
  var state = block.s;
389
406
 
390
407
  if (state === null) {
@@ -403,15 +420,6 @@ export function keyed(collection, key_fn) {
403
420
  throw new Error('Duplicate keys are not allowed');
404
421
  }
405
422
 
406
- var b_array = collection_to_array(collection);
407
- var b_keys = b_array.map(key_fn);
408
-
409
- // We only need to do this in DEV
410
- var b = new Set(b_keys);
411
- if (b.size !== b_keys.length) {
412
- throw new Error('Duplicate keys are not allowed');
413
- }
414
-
415
423
  for (let i = 0; i < b_keys.length; i++) {
416
424
  var b_val = b_keys[i];
417
425
  var index = a.get(b_val);
@@ -1,4 +1,5 @@
1
1
  /** @import { Block, Component, Dependency, Derived, Tracked } from '#client' */
2
+ /** @import { TrackOptions } from '#public' */
2
3
 
3
4
  import {
4
5
  destroy_block,
@@ -26,7 +27,7 @@ import {
26
27
  REF_PROP,
27
28
  } from './constants.js';
28
29
  import { capture, suspend } from './try.js';
29
- import { define_property, is_tracked_object } from './utils.js';
30
+ import { define_property, get_descriptors, is_array, is_tracked_object } from './utils.js';
30
31
 
31
32
  const FLUSH_MICROTASK = 0;
32
33
  const FLUSH_SYNC = 1;
@@ -278,6 +279,65 @@ export function derived(fn, block) {
278
279
  };
279
280
  }
280
281
 
282
+ /**
283
+ * @param {any} v
284
+ * @param {TrackOptions | undefined} o
285
+ * @param {Block} b
286
+ * @returns {Tracked | Derived | Tracked[]}
287
+ */
288
+ export function track(v, o, b) {
289
+ var is_tracked = is_tracked_object(v);
290
+
291
+ if (o === undefined) {
292
+ if (is_tracked) {
293
+ return v;
294
+ }
295
+
296
+ if (typeof v === 'function') {
297
+ return derived(v, b);
298
+ }
299
+ return tracked(v, b);
300
+ }
301
+
302
+ if (is_tracked || typeof v !== "object" || v === null || is_array(v)) {
303
+ throw new TypeError('Invalid value: expected a non-tracked object');
304
+ }
305
+
306
+ var list = o.split ?? [];
307
+ /** @type {Tracked[]} */
308
+ var out = [];
309
+ /** @type {Record<string|symbol, any>} */
310
+ var rest = {};
311
+ /** @type {Record<PropertyKey, any | null>} */
312
+ var descriptors = get_descriptors(v);
313
+
314
+ for (let i = 0, key, t; i < list.length; i++) {
315
+ key = list[i];
316
+
317
+ if (is_tracked_object(v[key])) {
318
+ t = v[key];
319
+ } else {
320
+ t = define_property(tracked(undefined, b), 'v', descriptors[key]);
321
+ }
322
+
323
+ out[i] = t;
324
+ descriptors[key] = null;
325
+ }
326
+
327
+ var props = Reflect.ownKeys(descriptors);
328
+ for (let i = 0, key; i < props.length; i++) {
329
+ key = props[i];
330
+ if (descriptors[key] === null) {
331
+ continue;
332
+ }
333
+ define_property(rest, key, descriptors[key]);
334
+ }
335
+
336
+ out.push(tracked(rest, b));
337
+
338
+ return out;
339
+ }
340
+
281
341
  /**
282
342
  * @param {Tracked} tracked
283
343
  * @returns {Dependency}
@@ -474,13 +474,13 @@ describe('basic', () => {
474
474
  component Basic() {
475
475
  let count = track(0);
476
476
 
477
- const minus = {
477
+ const minus = {
478
478
  onClick() {
479
479
  @count--
480
480
  }
481
481
  }
482
482
 
483
- const plus = {
483
+ const plus = {
484
484
  onClick() {
485
485
  @count++
486
486
  }
@@ -524,7 +524,7 @@ describe('basic', () => {
524
524
  let clickCount = track(0);
525
525
  let focusCount = track(0);
526
526
 
527
- const mixedHandler = {
527
+ const mixedHandler = {
528
528
  onClick() { // Delegated event
529
529
  @clickCount++
530
530
  },
@@ -980,7 +980,7 @@ describe('basic', () => {
980
980
  div {
981
981
  animation-name: anim;
982
982
  }
983
-
983
+
984
984
  @keyframes anim {}
985
985
 
986
986
  p {
@@ -1272,18 +1272,133 @@ describe('basic', () => {
1272
1272
  expect(container).toMatchSnapshot();
1273
1273
  });
1274
1274
 
1275
- it('correctly renders adjacent text nodes', () => {
1275
+ it('can retain reactivity for destructure rest via track split', () => {
1276
+ let logs = [];
1277
+
1276
1278
  component App() {
1277
- let a = 1;
1278
- let b = 1;
1279
- <div>{1}{1}</div>
1280
- <div>{a}{b}</div>
1281
- <div>{true}{true}</div>
1282
- <div>{true}{true}</div>
1279
+ let count = track(0);
1280
+ let name = track('Click Me');
1281
+
1282
+ function buttonRef(el) {
1283
+ logs.push('ref called');
1284
+ return () => {
1285
+ logs.push('cleanup ref');
1286
+ };
1287
+ }
1288
+
1289
+ <Child
1290
+ class="my-button"
1291
+ onClick={() => @name === 'Click Me' ? @name = 'Clicked' : @name = 'Click Me'}
1292
+ count:={() => @count, (v) => {logs.push('inside setter'); @count++}}
1293
+ {ref buttonRef}
1294
+ >{@name}</Child>;
1295
+ }
1296
+
1297
+ component Child(props: PropsWithChildren<{ count: Tracked<number> }>) {
1298
+ const [children, count, rest] = track(props, {split: ['children', 'count']});
1299
+
1300
+ if (@count < 2) {
1301
+ <button {...@rest}><@children /></button>
1302
+ }
1303
+ <pre>{@count}</pre>
1304
+ <button onClick={() => @count++}>{'Increment Count'}</button>
1283
1305
  }
1284
1306
 
1285
1307
  render(App);
1308
+ flushSync();
1286
1309
 
1287
- expect(container).toMatchSnapshot();
1310
+ const buttonClickMe = container.querySelectorAll('button')[0];
1311
+ const buttonIncrement = container.querySelectorAll('button')[1];
1312
+ const countPre = container.querySelector('pre');
1313
+
1314
+ expect(buttonClickMe.textContent).toBe('Click Me');
1315
+ expect(countPre.textContent).toBe('0');
1316
+ expect(logs).toEqual(['ref called']);
1317
+
1318
+
1319
+ buttonClickMe.click();
1320
+ buttonIncrement.click();
1321
+ flushSync();
1322
+
1323
+ expect(buttonClickMe.textContent).toBe('Clicked');
1324
+ expect(countPre.textContent).toBe('1');
1325
+ expect(logs).toEqual(['ref called','inside setter']);
1326
+
1327
+ buttonIncrement.click();
1328
+ flushSync();
1329
+
1330
+ expect(logs).toEqual(['ref called','inside setter','inside setter','cleanup ref']);
1331
+ });
1332
+
1333
+ it('errors on invalid value as null for track with split', () => {
1334
+ component App() {
1335
+ let message = track('');
1336
+
1337
+ try{
1338
+ const [a, b, rest] = track(null, { split: ['a', 'b'] });
1339
+ } catch(e) {
1340
+ @message = e.message;
1341
+ }
1342
+
1343
+ <pre>{@message}</pre>
1344
+ }
1345
+
1346
+ render(App);
1347
+
1348
+ const pre = container.querySelectorAll('pre')[0];
1349
+ expect(pre.textContent).toBe('Invalid value: expected a non-tracked object');
1350
+ });
1351
+
1352
+ it('errors on invalid value as array for track with split', () => {
1353
+ component App() {
1354
+ let message = track('');
1355
+
1356
+ try{
1357
+ const [a, b, rest] = track([1, 2, 3], { split: ['a', 'b'] });
1358
+ } catch(e) {
1359
+ @message = e.message;
1360
+ }
1361
+
1362
+ <pre>{@message}</pre>
1363
+ }
1364
+
1365
+ render(App);
1366
+
1367
+ const pre = container.querySelectorAll('pre')[0];
1368
+ expect(pre.textContent).toBe('Invalid value: expected a non-tracked object');
1369
+ });
1370
+
1371
+ it('errors on invalid value as tracked for track with split', () => {
1372
+ component App() {
1373
+ const t = track({a: 1, b: 2, c: 3});
1374
+ let message = track('');
1375
+
1376
+ try{
1377
+ const [a, b, rest] = track(t, { split: ['a', 'b'] });
1378
+ } catch(e) {
1379
+ @message = e.message;
1380
+ }
1381
+
1382
+ <pre>{@message}</pre>
1383
+ }
1384
+
1385
+ render(App);
1386
+
1387
+ const pre = container.querySelectorAll('pre')[0];
1388
+ expect(pre.textContent).toBe('Invalid value: expected a non-tracked object');
1389
+ });
1390
+
1391
+ it('returns the same tracked object if plain track is called with a tracked object', () => {
1392
+ component App() {
1393
+ const t = track({a: 1, b: 2, c: 3});
1394
+ const doublet = track(t);
1395
+
1396
+ <pre>{t === doublet}</pre>
1397
+ }
1398
+
1399
+ render(App);
1400
+
1401
+ const pre = container.querySelectorAll('pre')[0];
1402
+ expect(pre.textContent).toBe('true');
1288
1403
  });
1289
1404
  });
@@ -105,4 +105,4 @@ describe('passing reactivity between boundaries tests', () => {
105
105
  expect(container.querySelector('div').textContent).toBe('Double: 4');
106
106
  expect(log).toEqual(['Count:0', 'Count:1', 'Count:2']);
107
107
  });
108
- });
108
+ });
package/tsconfig.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "allowSyntheticDefaultImports": true,
12
12
  "verbatimModuleSyntax": true,
13
13
  "types": ["node"],
14
- "jsx": "preserve",
14
+ "jsx": "preserve",
15
15
  "jsxImportSource": "ripple",
16
16
  "strict": true,
17
17
  "allowJs": true,
@@ -25,4 +25,4 @@
25
25
  "./src/",
26
26
  "./tests/*/test.ripple",
27
27
  ]
28
- }
28
+ }
package/types/index.d.ts CHANGED
@@ -29,7 +29,7 @@ export type Context<T> = {
29
29
 
30
30
  export declare function createContext<T>(initialValue: T): Context<T>;
31
31
 
32
- export class TrackedSet<T> extends Set<T> {
32
+ export declare class TrackedSet<T> extends Set<T> {
33
33
  isDisjointFrom(other: TrackedSet<T> | Set<T>): boolean;
34
34
  isSubsetOf(other: TrackedSet<T> | Set<T>): boolean;
35
35
  isSupersetOf(other: TrackedSet<T> | Set<T>): boolean;
@@ -40,7 +40,7 @@ export class TrackedSet<T> extends Set<T> {
40
40
  toJSON(): T[];
41
41
  }
42
42
 
43
- export class TrackedMap<K, V> extends Map<K, V> {
43
+ export declare class TrackedMap<K, V> extends Map<K, V> {
44
44
  toJSON(): [K, V][];
45
45
  }
46
46
 
@@ -56,7 +56,7 @@ declare global {
56
56
  * Ripple runtime namespace - injected by the compiler
57
57
  * These functions are available in compiled Ripple components for TypeScript analysis
58
58
  */
59
- var $: {
59
+ var _$_: {
60
60
  tracked<T>(value: T, block?: any): T;
61
61
  computed<T>(fn: () => T, block?: any): T;
62
62
  scope(): any;
@@ -69,10 +69,35 @@ declare global {
69
69
 
70
70
  export declare function createRefKey(): symbol;
71
71
 
72
- type Tracked<V> = { '#v': V };
72
+ export type Tracked<V> = { '#v': V };
73
+
74
+ export type Props<K extends PropertyKey = any, V = unknown> = Record<K, V>;
75
+ export type PropsWithExtras<T extends object> = Props & T & Record<string, unknown>;
76
+ export type PropsWithChildren<T extends object = {}> =
77
+ Expand<Omit<Props, 'children'> & { children: Component } & T>;
78
+
79
+ type UnwrapTracked<T> = [T] extends [Tracked<infer V>] ? T : Tracked<T>;
80
+
81
+ // force ts to evaluate and expand a type fully
82
+ type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
83
+
84
+ type PickKeys<T, K extends readonly (keyof T)[]> =
85
+ { [I in keyof K]: UnwrapTracked<T[K[I] & keyof T]> };
86
+
87
+ type RestKeys<T, K extends readonly (keyof T)[]> = Expand<Omit<T, K[number]>>;
88
+
89
+ type SplitResult<T extends Props, K extends readonly (keyof T)[]> =
90
+ [...PickKeys<T, K>, UnwrapTracked<RestKeys<T, K>>];
91
+
92
+ type TrackOptions = { split?: readonly (string | number | symbol)[] };
73
93
 
74
94
  export declare function track<V>(value?: V | (() => V)): Tracked<V>;
75
95
 
96
+ export declare function track<V extends Props, const K extends readonly (keyof V)[]>(
97
+ value: V,
98
+ options: TrackOptions
99
+ ): SplitResult<V, K>;
100
+
76
101
  export function on<Type extends keyof WindowEventMap>(
77
102
  window: Window,
78
103
  type: Type,
@@ -107,3 +132,27 @@ export function on(
107
132
  handler: EventListener,
108
133
  options?: AddEventListenerOptions | undefined
109
134
  ): () => void;
135
+
136
+ export type TrackedObjectShallow<T> = {
137
+ [K in keyof T]: T[K] | Tracked<T[K]>;
138
+ };
139
+
140
+ export type TrackedObjectDeep<T> =
141
+ T extends string | number | boolean | null | undefined | symbol | bigint
142
+ ? T | Tracked<T>
143
+ : T extends TrackedArray<infer U>
144
+ ? TrackedArray<U> | Tracked<TrackedArray<U>>
145
+ : T extends TrackedSet<infer U>
146
+ ? TrackedSet<U> | Tracked<TrackedSet<U>>
147
+ : T extends TrackedMap<infer K, infer V>
148
+ ? TrackedMap<K, V> | Tracked<TrackedMap<K, V>>
149
+ : T extends Array<infer U>
150
+ ? Array<TrackedObjectDeep<U>> | Tracked<Array<TrackedObjectDeep<U>>>
151
+ : T extends Set<infer U>
152
+ ? Set<TrackedObjectDeep<U>> | Tracked<Set<TrackedObjectDeep<U>>>
153
+ : T extends Map<infer K, infer V>
154
+ ? Map<TrackedObjectDeep<K>, TrackedObjectDeep<V>> |
155
+ Tracked<Map<TrackedObjectDeep<K>, TrackedObjectDeep<V>>>
156
+ : T extends object
157
+ ? { [K in keyof T]: TrackedObjectDeep<T[K]> | Tracked<TrackedObjectDeep<T[K]>> }
158
+ : T | Tracked<T>;