ripple 0.2.86 → 0.2.88

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 (32) hide show
  1. package/package.json +3 -1
  2. package/src/compiler/phases/1-parse/index.js +30 -1
  3. package/src/compiler/phases/2-analyze/index.js +3 -1
  4. package/src/compiler/phases/3-transform/client/index.js +23 -10
  5. package/src/compiler/scope.js +2 -0
  6. package/src/compiler/types/index.d.ts +5 -0
  7. package/src/runtime/array.js +12 -5
  8. package/src/runtime/index-client.js +0 -1
  9. package/src/runtime/index-server.js +18 -3
  10. package/src/runtime/internal/client/blocks.js +209 -205
  11. package/src/runtime/internal/client/composite.js +9 -0
  12. package/src/runtime/internal/client/events.js +219 -189
  13. package/src/runtime/internal/client/for.js +467 -383
  14. package/src/runtime/internal/client/if.js +36 -25
  15. package/src/runtime/internal/client/index.js +2 -0
  16. package/src/runtime/internal/client/operations.js +7 -2
  17. package/src/runtime/internal/client/portal.js +15 -4
  18. package/src/runtime/internal/client/render.js +48 -6
  19. package/src/runtime/internal/client/runtime.js +98 -83
  20. package/src/runtime/internal/client/script.js +16 -0
  21. package/src/runtime/internal/client/template.js +8 -5
  22. package/src/runtime/internal/client/try.js +42 -4
  23. package/src/runtime/internal/client/utils.js +18 -3
  24. package/src/runtime/internal/server/context.js +2 -0
  25. package/src/runtime/internal/server/index.js +39 -3
  26. package/src/runtime/internal/server/types.d.ts +21 -0
  27. package/src/runtime/map.js +37 -2
  28. package/src/runtime/set.js +22 -9
  29. package/src/utils/builders.js +1 -0
  30. package/tests/client/__snapshots__/basic.test.ripple.snap +13 -0
  31. package/tests/client/basic.test.ripple +15 -0
  32. package/tests/client/ref.test.ripple +33 -2
@@ -1,3 +1,5 @@
1
+ /** @import { Block } from '#client' */
2
+
1
3
  import { branch, create_try_block, destroy_block, is_destroyed, resume_block } from './blocks.js';
2
4
  import { TRY_BLOCK } from './constants.js';
3
5
  import { next_sibling } from './operations.js';
@@ -13,13 +15,28 @@ import {
13
15
  tracking,
14
16
  } from './runtime.js';
15
17
 
18
+ /**
19
+ * @param {Node} node
20
+ * @param {(anchor: Node) => void} fn
21
+ * @param {((anchor: Node, error: any) => void) | null} catch_fn
22
+ * @param {((anchor: Node) => void) | null} [pending_fn=null]
23
+ * @returns {void}
24
+ */
16
25
  export function try_block(node, fn, catch_fn, pending_fn = null) {
17
26
  var anchor = node;
27
+ /** @type {Block | null} */
18
28
  var b = null;
29
+ /** @type {Block | null} */
19
30
  var suspended = null;
20
31
  var pending_count = 0;
32
+ /** @type {DocumentFragment | null} */
21
33
  var offscreen_fragment = null;
22
34
 
35
+ /**
36
+ * @param {Block} block
37
+ * @param {DocumentFragment} fragment
38
+ * @returns {void}
39
+ */
23
40
  function move_block(block, fragment) {
24
41
  var state = block.s;
25
42
  var node = state.start;
@@ -42,7 +59,7 @@ export function try_block(node, fn, catch_fn, pending_fn = null) {
42
59
  move_block(b, offscreen_fragment);
43
60
 
44
61
  b = branch(() => {
45
- pending_fn(anchor);
62
+ /** @type {(anchor: Node) => void} */ (pending_fn)(anchor);
46
63
  });
47
64
  }
48
65
  });
@@ -53,22 +70,26 @@ export function try_block(node, fn, catch_fn, pending_fn = null) {
53
70
  if (b !== null) {
54
71
  destroy_block(b);
55
72
  }
56
- anchor.before(offscreen_fragment);
73
+ /** @type {ChildNode} */ (anchor).before(/** @type {DocumentFragment} */ (offscreen_fragment));
57
74
  offscreen_fragment = null;
58
- resume_block(suspended);
75
+ resume_block(/** @type {Block} */ (suspended));
59
76
  b = suspended;
60
77
  suspended = null;
61
78
  }
62
79
  };
63
80
  }
64
81
 
82
+ /**
83
+ * @param {any} error
84
+ * @returns {void}
85
+ */
65
86
  function handle_error(error) {
66
87
  if (b !== null) {
67
88
  destroy_block(b);
68
89
  }
69
90
 
70
91
  b = branch(() => {
71
- catch_fn(anchor, error);
92
+ /** @type {(anchor: Node, error: any) => void} */ (catch_fn)(anchor, error);
72
93
  });
73
94
  }
74
95
 
@@ -84,6 +105,9 @@ export function try_block(node, fn, catch_fn, pending_fn = null) {
84
105
  }, state);
85
106
  }
86
107
 
108
+ /**
109
+ * @returns {() => void}
110
+ */
87
111
  export function suspend() {
88
112
  var current = active_block;
89
113
 
@@ -98,6 +122,9 @@ export function suspend() {
98
122
  throw new Error('Missing parent `try { ... } pending { ... }` statement');
99
123
  }
100
124
 
125
+ /**
126
+ * @returns {void}
127
+ */
101
128
  function exit() {
102
129
  set_tracking(false);
103
130
  set_active_reaction(null);
@@ -105,6 +132,9 @@ function exit() {
105
132
  set_active_component(null);
106
133
  }
107
134
 
135
+ /**
136
+ * @returns {() => void}
137
+ */
108
138
  export function capture() {
109
139
  var previous_tracking = tracking;
110
140
  var previous_block = active_block;
@@ -121,6 +151,9 @@ export function capture() {
121
151
  };
122
152
  }
123
153
 
154
+ /**
155
+ * @returns {boolean}
156
+ */
124
157
  export function aborted() {
125
158
  if (active_block === null) {
126
159
  return true;
@@ -128,6 +161,11 @@ export function aborted() {
128
161
  return is_destroyed(active_block);
129
162
  }
130
163
 
164
+ /**
165
+ * @template T
166
+ * @param {Promise<T>} promise
167
+ * @returns {Promise<() => T>}
168
+ */
131
169
  export async function resume_context(promise) {
132
170
  var restore = capture();
133
171
  var value = await promise;
@@ -1,18 +1,33 @@
1
+ /** @type {typeof Object.getOwnPropertyDescriptor} */
1
2
  export var get_descriptor = Object.getOwnPropertyDescriptor;
3
+ /** @type {typeof Object.getOwnPropertyDescriptors} */
2
4
  export var get_descriptors = Object.getOwnPropertyDescriptors;
5
+ /** @type {typeof Array.from} */
3
6
  export var array_from = Array.from;
7
+ /** @type {typeof Array.isArray} */
4
8
  export var is_array = Array.isArray;
9
+ /** @type {typeof Object.defineProperty} */
5
10
  export var define_property = Object.defineProperty;
11
+ /** @type {typeof Object.getPrototypeOf} */
6
12
  export var get_prototype_of = Object.getPrototypeOf;
13
+ /** @type {typeof Object.values} */
7
14
  export var object_values = Object.values;
15
+ /** @type {typeof Object.entries} */
8
16
  export var object_entries = Object.entries;
17
+ /** @type {typeof Object.keys} */
9
18
  export var object_keys = Object.keys;
19
+ /** @type {typeof Object.getOwnPropertySymbols} */
10
20
  export var get_own_property_symbols = Object.getOwnPropertySymbols;
21
+ /** @type {typeof structuredClone} */
11
22
  export var structured_clone = structuredClone;
12
23
 
24
+ /**
25
+ * Creates a text node that serves as an anchor point in the DOM.
26
+ * @returns {Text}
27
+ */
13
28
  export function create_anchor() {
14
29
  var t = document.createTextNode('');
15
- t.__t = '';
30
+ /** @type {any} */ (t).__t = '';
16
31
  return t;
17
32
  }
18
33
 
@@ -26,9 +41,9 @@ export function is_positive_integer(value) {
26
41
 
27
42
  /**
28
43
  * Checks if an object is a tracked object (has a numeric 'f' property).
29
- * @param {object} v - The object to check.
44
+ * @param {any} v - The object to check.
30
45
  * @returns {boolean}
31
46
  */
32
47
  export function is_tracked_object(v) {
33
- return typeof v === 'object' && v !== null && typeof v.f === 'number';
48
+ return typeof v === 'object' && v !== null && typeof /** @type {any} */ (v).f === 'number';
34
49
  }
@@ -1,3 +1,5 @@
1
+ /** @import { Component } from '#server' */
2
+
1
3
  import { active_component } from './index.js';
2
4
 
3
5
  /**
@@ -1,4 +1,4 @@
1
- /** @import { Derived } from '#client' */
1
+ /** @import { Component, Derived } from '#server' */
2
2
 
3
3
  import { DERIVED, UNINITIALIZED } from '../client/constants';
4
4
  import { is_tracked_object } from '../client/utils';
@@ -8,6 +8,7 @@ import { is_boolean_attribute } from '../../../compiler/utils';
8
8
 
9
9
  export { escape };
10
10
 
11
+ /** @type {Component | null} */
11
12
  export let active_component = null;
12
13
 
13
14
  /**
@@ -26,17 +27,29 @@ const replacements = {
26
27
  class Output {
27
28
  head = '';
28
29
  body = '';
30
+ /** @type {Output | null} */
29
31
  #parent = null;
30
32
 
33
+ /**
34
+ * @param {Output | null} parent
35
+ */
31
36
  constructor(parent) {
32
37
  this.#parent = parent;
33
38
  }
34
39
 
40
+ /**
41
+ * @param {string} str
42
+ * @returns {void}
43
+ */
35
44
  push(str) {
36
45
  this.body += str;
37
46
  }
38
47
  }
39
48
 
49
+ /**
50
+ * @param {((output: Output, props: Record<string, any>) => void | Promise<void>) & { async?: boolean }} component
51
+ * @returns {Promise<{head: string, body: string}>}
52
+ */
40
53
  export async function render(component) {
41
54
  const output = new Output(null);
42
55
 
@@ -52,6 +65,9 @@ export async function render(component) {
52
65
  return { head, body };
53
66
  }
54
67
 
68
+ /**
69
+ * @returns {void}
70
+ */
55
71
  export function push_component() {
56
72
  var component = {
57
73
  c: null,
@@ -60,15 +76,26 @@ export function push_component() {
60
76
  active_component = component;
61
77
  }
62
78
 
79
+ /**
80
+ * @returns {void}
81
+ */
63
82
  export function pop_component() {
64
- var component = active_component;
65
- active_component = component.p;
83
+ var component = /** @type {Component} */ (active_component);
84
+ active_component = component;
66
85
  }
67
86
 
87
+ /**
88
+ * @param {() => any} fn
89
+ * @returns {Promise<void>}
90
+ */
68
91
  export async function async(fn) {
69
92
  // TODO
70
93
  }
71
94
 
95
+ /**
96
+ * @param {Derived} tracked
97
+ * @returns {any}
98
+ */
72
99
  function get_derived(tracked) {
73
100
  let v = tracked.v;
74
101
 
@@ -79,6 +106,10 @@ function get_derived(tracked) {
79
106
  return v;
80
107
  }
81
108
 
109
+ /**
110
+ * @param {any} tracked
111
+ * @returns {any}
112
+ */
82
113
  export function get(tracked) {
83
114
  // reflect back the value if it's not boxed
84
115
  if (!is_tracked_object(tracked)) {
@@ -105,6 +136,11 @@ export function attr(name, value, is_boolean = false) {
105
136
  return ` ${name}${assignment}`;
106
137
  }
107
138
 
139
+ /**
140
+ * @param {Record<string, any>} attrs
141
+ * @param {string | undefined} css_hash
142
+ * @returns {string}
143
+ */
108
144
  export function spread_attrs(attrs, css_hash) {
109
145
  let attr_str = '';
110
146
  let name;
@@ -0,0 +1,21 @@
1
+ import type { Context } from './context.js';
2
+
3
+ export type Component = {
4
+ c: null | Map<Context<any>, any>;
5
+ p: null | Component;
6
+ };
7
+
8
+
9
+ export type Derived = {
10
+ a: { get?: Function, set?: Function };
11
+ co: null | Component;
12
+ f: number;
13
+ fn: Function;
14
+ v: any;
15
+ };
16
+
17
+ export type Tracked = {
18
+ a: { get?: Function, set?: Function };
19
+ f: number;
20
+ v: any;
21
+ };
@@ -41,20 +41,27 @@ export class TrackedMap extends Map {
41
41
  }
42
42
  }
43
43
 
44
+ /**
45
+ * @returns {void}
46
+ */
44
47
  #init() {
45
48
  var proto = TrackedMap.prototype;
46
49
  var map_proto = Map.prototype;
47
50
 
48
51
  for (const method of introspect_methods) {
49
- proto[method] = function (...v) {
52
+ /** @type {any} */ (proto)[method] = function (/** @type {...any} */ ...v) {
50
53
  this.size;
51
54
  this.#read_all();
52
55
 
53
- return map_proto[method].apply(this, v);
56
+ return /** @type {any} */ (map_proto)[method].apply(this, v);
54
57
  };
55
58
  }
56
59
  }
57
60
 
61
+ /**
62
+ * @param {K} key
63
+ * @returns {V | undefined}
64
+ */
58
65
  get(key) {
59
66
  var tracked_items = this.#tracked_items;
60
67
  var t = tracked_items.get(key);
@@ -69,6 +76,10 @@ export class TrackedMap extends Map {
69
76
  return super.get(key);
70
77
  }
71
78
 
79
+ /**
80
+ * @param {K} key
81
+ * @returns {boolean}
82
+ */
72
83
  has(key) {
73
84
  var has = super.has(key);
74
85
  var tracked_items = this.#tracked_items;
@@ -87,6 +98,11 @@ export class TrackedMap extends Map {
87
98
  return has;
88
99
  }
89
100
 
101
+ /**
102
+ * @param {K} key
103
+ * @param {V} value
104
+ * @returns {this}
105
+ */
90
106
  set(key, value) {
91
107
  var block = this.#block;
92
108
  var tracked_items = this.#tracked_items;
@@ -105,6 +121,10 @@ export class TrackedMap extends Map {
105
121
  return this;
106
122
  }
107
123
 
124
+ /**
125
+ * @param {K} key
126
+ * @returns {boolean}
127
+ */
108
128
  delete(key) {
109
129
  var block = this.#block;
110
130
  var tracked_items = this.#tracked_items;
@@ -120,6 +140,9 @@ export class TrackedMap extends Map {
120
140
  return result;
121
141
  }
122
142
 
143
+ /**
144
+ * @returns {void}
145
+ */
123
146
  clear() {
124
147
  var block = this.#block;
125
148
 
@@ -136,21 +159,33 @@ export class TrackedMap extends Map {
136
159
  set(this.#tracked_size, 0, block);
137
160
  }
138
161
 
162
+ /**
163
+ * @returns {MapIterator<K>}
164
+ */
139
165
  keys() {
140
166
  this.size;
141
167
  return super.keys();
142
168
  }
143
169
 
170
+ /**
171
+ * @returns {void}
172
+ */
144
173
  #read_all() {
145
174
  for (const [, t] of this.#tracked_items) {
146
175
  get(t);
147
176
  }
148
177
  }
149
178
 
179
+ /**
180
+ * @returns {number}
181
+ */
150
182
  get size() {
151
183
  return get(this.#tracked_size);
152
184
  }
153
185
 
186
+ /**
187
+ * @returns {Array<[K, V]>}
188
+ */
154
189
  toJSON() {
155
190
  this.size;
156
191
  this.#read_all();
@@ -23,7 +23,7 @@ export class TrackedSet extends Set {
23
23
  #block;
24
24
 
25
25
  /**
26
- * @param {Iterable<T>} iterable
26
+ * @param {Iterable<T>} [iterable]
27
27
  */
28
28
  constructor(iterable) {
29
29
  super();
@@ -45,6 +45,9 @@ export class TrackedSet extends Set {
45
45
  }
46
46
  }
47
47
 
48
+ /**
49
+ * @returns {void}
50
+ */
48
51
  #init() {
49
52
  var proto = TrackedSet.prototype;
50
53
  var set_proto = Set.prototype;
@@ -54,11 +57,10 @@ export class TrackedSet extends Set {
54
57
  continue;
55
58
  }
56
59
 
57
- /** @param {...any} v */
58
- proto[method] = function (...v) {
60
+ /** @type {any} */ (proto)[method] = function (/** @type {...any} */ ...v) {
59
61
  this.size;
60
62
 
61
- return set_proto[method].apply(this, v);
63
+ return /** @type {any} */ (set_proto)[method].apply(this, v);
62
64
  };
63
65
  }
64
66
 
@@ -67,14 +69,14 @@ export class TrackedSet extends Set {
67
69
  continue;
68
70
  }
69
71
 
70
- proto[method] = function (other, ...v) {
72
+ /** @type {any} */ (proto)[method] = function (/** @type {any} */ other, /** @type {...any} */ ...v) {
71
73
  this.size;
72
74
 
73
75
  if (other instanceof TrackedSet) {
74
76
  other.size;
75
77
  }
76
78
 
77
- return set_proto[method].apply(this, [other, ...v]);
79
+ return /** @type {any} */ (set_proto)[method].apply(this, [other, ...v]);
78
80
  };
79
81
  }
80
82
 
@@ -83,14 +85,14 @@ export class TrackedSet extends Set {
83
85
  continue;
84
86
  }
85
87
 
86
- proto[method] = function (other, ...v) {
88
+ /** @type {any} */ (proto)[method] = function (/** @type {any} */ other, /** @type {...any} */ ...v) {
87
89
  this.size;
88
90
 
89
91
  if (other instanceof TrackedSet) {
90
92
  other.size;
91
93
  }
92
94
 
93
- return new TrackedSet(set_proto[method].apply(this, [other, ...v]));
95
+ return new TrackedSet(/** @type {any} */ (set_proto)[method].apply(this, [other, ...v]));
94
96
  };
95
97
  }
96
98
  }
@@ -124,7 +126,9 @@ export class TrackedSet extends Set {
124
126
 
125
127
  var t = this.#tracked_items.get(value);
126
128
 
127
- increment(t, block);
129
+ if (t) {
130
+ increment(t, block);
131
+ }
128
132
  this.#tracked_items.delete(value);
129
133
  set(this.#tracked_size, super.size, block);
130
134
 
@@ -154,6 +158,9 @@ export class TrackedSet extends Set {
154
158
  return has;
155
159
  }
156
160
 
161
+ /**
162
+ * @returns {void}
163
+ */
157
164
  clear() {
158
165
  var block = this.#block;
159
166
 
@@ -170,10 +177,16 @@ export class TrackedSet extends Set {
170
177
  set(this.#tracked_size, 0, block);
171
178
  }
172
179
 
180
+ /**
181
+ * @returns {number}
182
+ */
173
183
  get size() {
174
184
  return get(this.#tracked_size);
175
185
  }
176
186
 
187
+ /**
188
+ * @returns {T[]}
189
+ */
177
190
  toJSON() {
178
191
  this.size;
179
192
 
@@ -1,4 +1,5 @@
1
1
  /** @import * as ESTree from 'estree' */
2
+
2
3
  import { regex_is_valid_identifier } from './patterns.js';
3
4
  import { sanitize_template_string } from './sanitize_template_string.js';
4
5
 
@@ -37,6 +37,19 @@ exports[`basic client > handles boolean attributes with no prop value provides 1
37
37
  </div>
38
38
  `;
39
39
 
40
+ exports[`basic client > handles boolean props correctly 1`] = `
41
+ <div>
42
+ <div
43
+ data-disabled=""
44
+ />
45
+ <input
46
+ disabled=""
47
+ />
48
+ <!---->
49
+
50
+ </div>
51
+ `;
52
+
40
53
  exports[`basic client > render semi-dynamic text 1`] = `
41
54
  <div>
42
55
  <div>
@@ -1547,5 +1547,20 @@ describe('basic client', () => {
1547
1547
  expect(pre1.textContent).toBe('4');
1548
1548
  expect(pre2.textContent).toBe('2');
1549
1549
  });
1550
+
1551
+ it('handles boolean props correctly', () => {
1552
+ component App() {
1553
+ <div data-disabled />
1554
+
1555
+ <Child isDisabled />
1556
+ }
1557
+
1558
+ component Child({ isDisabled }) {
1559
+ <input disabled={isDisabled} />
1560
+ }
1561
+
1562
+ render(App);
1563
+ expect(container).toMatchSnapshot();
1564
+ });
1550
1565
  });
1551
1566
 
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { mount, flushSync, TrackedArray } from 'ripple';
2
+ import { mount, flushSync, TrackedArray, track, createRefKey } from 'ripple';
3
3
 
4
4
  describe('refs', () => {
5
5
  let container;
@@ -54,5 +54,36 @@ describe('refs', () => {
54
54
  flushSync();
55
55
 
56
56
  expect(_node).toBe(document.querySelector('pre'));
57
- })
57
+ });
58
+
59
+ it('should handle spreading into composite refs', () => {
60
+ let logs: string[] = [];
61
+
62
+ component App() {
63
+ let value = track('test');
64
+
65
+ function inputRef(node) {
66
+ logs.push('ref called');
67
+ }
68
+
69
+ const props = {
70
+ id: "example",
71
+ @value,
72
+ [createRefKey()]: inputRef
73
+ };
74
+
75
+ <input type="text" {...props} />
76
+
77
+ <Input {...props} />
78
+ }
79
+
80
+ component Input({ id, value, ...rest }) {
81
+ <input type="text" {id} {value} {...rest} />
82
+ }
83
+
84
+ render(App);
85
+ flushSync();
86
+
87
+ expect(logs).toEqual(['ref called', 'ref called']);
88
+ });
58
89
  });