ripple 0.2.88 → 0.2.89

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.88",
6
+ "version": "0.2.89",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index-client.js",
9
9
  "main": "src/runtime/index-client.js",
@@ -66,6 +66,7 @@
66
66
  "@jridgewell/sourcemap-codec": "^1.5.5",
67
67
  "acorn": "^8.15.0",
68
68
  "acorn-typescript": "^1.4.13",
69
+ "clsx": "^2.1.1",
69
70
  "esrap": "^2.1.0",
70
71
  "is-reference": "^3.0.3",
71
72
  "magic-string": "^0.30.18",
@@ -80,13 +80,17 @@ function RipplePlugin(config) {
80
80
  // Look ahead to see if this is followed by [ for tuple syntax
81
81
  if (this.pos + 1 < this.input.length) {
82
82
  const nextChar = this.input.charCodeAt(this.pos + 1);
83
- if (nextChar === 91) {
84
- // [ character
85
- // This is a tuple literal #[
86
- // Consume both # and [
83
+ if (nextChar === 91 || nextChar === 123) {
84
+ // [ or { character
85
+ // This is a tuple literal #[ or #{
86
+ // Consume both # and [ or {
87
87
  ++this.pos; // consume #
88
- ++this.pos; // consume [
89
- return this.finishToken(tt.bracketL, '#[');
88
+ ++this.pos; // consume [ or {
89
+ if (nextChar === 123) {
90
+ return this.finishToken(tt.braceL, '#{');
91
+ } else {
92
+ return this.finishToken(tt.bracketL, '#[');
93
+ }
90
94
  }
91
95
  }
92
96
  }
@@ -181,6 +185,8 @@ function RipplePlugin(config) {
181
185
  // Check if this is a tuple literal starting with #[
182
186
  if (this.type === tt.bracketL && this.value === '#[') {
183
187
  return this.parseTrackedArrayExpression();
188
+ } else if (this.type === tt.braceL && this.value === '#{') {
189
+ return this.parseTrackedObjectExpression();
184
190
  }
185
191
 
186
192
  return super.parseExprAtom(refDestructuringErrors, forNew, forInit);
@@ -221,6 +227,38 @@ function RipplePlugin(config) {
221
227
  return this.finishNode(node, 'TrackedArrayExpression');
222
228
  }
223
229
 
230
+ parseTrackedObjectExpression() {
231
+ const node = this.startNode();
232
+ this.next(); // consume the '#{'
233
+
234
+ node.properties = [];
235
+
236
+ // Parse object properties similar to regular object parsing
237
+ let first = true;
238
+ while (!this.eat(tt.braceR)) {
239
+ if (!first) {
240
+ this.expect(tt.comma);
241
+ if (this.afterTrailingComma(tt.braceR)) break;
242
+ } else {
243
+ first = false;
244
+ }
245
+
246
+ if (this.type === tt.ellipsis) {
247
+ // Spread property
248
+ const prop = this.parseSpread();
249
+ node.properties.push(prop);
250
+ if (this.type === tt.comma && this.input.charCodeAt(this.pos) === 125) {
251
+ this.raise(this.pos, 'Trailing comma is not permitted after the rest element');
252
+ }
253
+ } else {
254
+ // Regular property
255
+ node.properties.push(this.parseProperty(false, {}));
256
+ }
257
+ }
258
+
259
+ return this.finishNode(node, 'TrackedObjectExpression');
260
+ }
261
+
224
262
  parseExportDefaultDeclaration() {
225
263
  // Check if this is "export default component"
226
264
  if (this.value === 'component') {
@@ -133,7 +133,9 @@ function visit_title_element(node, context) {
133
133
  ),
134
134
  );
135
135
  } else {
136
- debugger;
136
+ context.state.init.push(
137
+ b.stmt(b.assignment('=', b.id('_$_.document.title'), result)),
138
+ );
137
139
  }
138
140
  }
139
141
 
@@ -336,6 +338,25 @@ const visitors = {
336
338
  );
337
339
  },
338
340
 
341
+ TrackedObjectExpression(node, context) {
342
+ if (context.state.to_ts) {
343
+ if (!context.state.imports.has(`import { TrackedObject } from 'ripple'`)) {
344
+ context.state.imports.add(`import { TrackedObject } from 'ripple'`);
345
+ }
346
+
347
+ return b.new(
348
+ b.id('TrackedObject'),
349
+ b.object(node.properties.map((prop) => context.visit(prop)))
350
+ );
351
+ }
352
+
353
+ return b.call(
354
+ '_$_.tracked_object',
355
+ b.object(node.properties.map((prop) => context.visit(prop))),
356
+ b.id('__block'),
357
+ );
358
+ },
359
+
339
360
  MemberExpression(node, context) {
340
361
  const parent = context.path.at(-1);
341
362
 
@@ -702,18 +723,18 @@ const visitors = {
702
723
  const metadata = { tracking: false, await: false };
703
724
  let expression = visit(class_attribute.value, { ...state, metadata });
704
725
 
705
- if (node.metadata.scoped && state.component.css) {
706
- expression = b.binary('+', expression, b.literal(' ' + state.component.css.hash));
707
- }
726
+ const hash_arg = node.metadata.scoped && state.component.css
727
+ ? b.literal(state.component.css.hash)
728
+ : undefined;
708
729
  const is_html = context.state.metadata.namespace === 'html' && node.id.name !== 'svg';
709
730
 
710
731
  if (metadata.tracking) {
711
732
  local_updates.push(
712
- b.stmt(b.call('_$_.set_class', id, expression, undefined, b.literal(is_html))),
733
+ b.stmt(b.call('_$_.set_class', id, expression, hash_arg, b.literal(is_html))),
713
734
  );
714
735
  } else {
715
736
  state.init.push(
716
- b.stmt(b.call('_$_.set_class', id, expression, undefined, b.literal(is_html))),
737
+ b.stmt(b.call('_$_.set_class', id, expression, hash_arg, b.literal(is_html))),
717
738
  );
718
739
  }
719
740
  }
@@ -1611,7 +1632,8 @@ function transform_children(children, context) {
1611
1632
  for (const head_element of head_elements) {
1612
1633
  visit_head_element(head_element, context);
1613
1634
  }
1614
- if (context.state.inside_head) {
1635
+
1636
+ if (context.state.inside_head) {
1615
1637
  const title_element = children.find(
1616
1638
  (node) =>
1617
1639
  node.type === 'Element' && node.id.type === 'Identifier' && node.id.name === 'title',
@@ -1,7 +1,6 @@
1
1
  /** @import { Block } from '#client' */
2
- import { MAX_ARRAY_LENGTH, TRACKED_ARRAY, UNINITIALIZED } from './internal/client/constants.js';
3
- import { get, safe_scope, set, tracked } from './internal/client/runtime.js';
4
- import { get_descriptor } from './internal/client/utils.js';
2
+ import { safe_scope } from './internal/client/runtime.js';
3
+ import { array_proxy } from './proxy.js';
5
4
 
6
5
  /**
7
6
  * @template T
@@ -15,8 +14,7 @@ export function TrackedArray(...elements) {
15
14
  }
16
15
 
17
16
  var block = safe_scope();
18
-
19
- return proxy({ elements, block });
17
+ return array_proxy({ elements, block });
20
18
  }
21
19
 
22
20
  /**
@@ -29,7 +27,7 @@ export function TrackedArray(...elements) {
29
27
  TrackedArray.from = function (arrayLike, mapFn, thisArg) {
30
28
  var block = safe_scope();
31
29
  var elements = mapFn ? Array.from(arrayLike, mapFn, thisArg) : Array.from(arrayLike);
32
- return proxy({ elements, block, from_static: true });
30
+ return array_proxy({ elements, block, from_static: true });
33
31
  };
34
32
 
35
33
  /**
@@ -40,7 +38,7 @@ TrackedArray.from = function (arrayLike, mapFn, thisArg) {
40
38
  TrackedArray.of = function (...items) {
41
39
  var block = safe_scope();
42
40
  var elements = Array.of(...items);
43
- return proxy({ elements, block, from_static: true });
41
+ return array_proxy({ elements, block, from_static: true });
44
42
  };
45
43
 
46
44
  /**
@@ -55,246 +53,15 @@ TrackedArray.fromAsync = async function (arrayLike, mapFn, thisArg) {
55
53
  var elements = mapFn
56
54
  ? await Array.fromAsync(arrayLike, mapFn, thisArg)
57
55
  : await Array.fromAsync(arrayLike);
58
- return proxy({ elements, block, from_static: true });
56
+ return array_proxy({ elements, block, from_static: true });
59
57
  };
60
58
 
61
59
  /**
62
60
  * @template T
63
- * @param {{
64
- * elements: Iterable<T>,
65
- * block: Block,
66
- * from_static?: boolean,
67
- * use_array?: boolean
68
- * }} params
69
- * @returns {TrackedArray<T>}
70
- */
71
- function proxy({ elements, block, from_static = false, use_array = false }) {
72
- /** @type {T[]} */
73
- var arr;
74
- var first;
75
-
76
- if (
77
- from_static &&
78
- (first = get_first_if_length(/** @type {Array<T>} */ (elements))) !== undefined
79
- ) {
80
- arr = new Array();
81
- arr[0] = /** @type {T} */ (/** @type {unknown} */ (first));
82
- } else if (use_array) {
83
- arr = /** @type {T[]} */ (elements);
84
- } else {
85
- arr = new Array(...elements);
86
- }
87
-
88
- var tracked_elements = new Map();
89
- var tracked_len = tracked(arr.length, block);
90
- tracked_elements.set('length', tracked_len);
91
-
92
- return new Proxy(arr, {
93
- get(target, prop, receiver) {
94
- var t = tracked_elements.get(prop);
95
- var exists = prop in target;
96
-
97
- if (t === undefined && (!exists || get_descriptor(target, prop)?.writable)) {
98
- t = tracked(exists ? /** @type {any} */ (target)[prop] : UNINITIALIZED, block);
99
- tracked_elements.set(prop, t);
100
- }
101
-
102
- if (t !== undefined) {
103
- var v = get(t);
104
- return v === UNINITIALIZED ? undefined : v;
105
- }
106
-
107
- var result = Reflect.get(target, prop, receiver);
108
-
109
- if (typeof result === 'function') {
110
- if (methods_returning_arrays.has(/** @type {string} */ (prop))) {
111
- /** @type {(this: any, ...args: any[]) => any} */
112
- return function (...args) {
113
- var output = Reflect.apply(result, receiver, args);
114
-
115
- if (Array.isArray(output) && output !== target) {
116
- return proxy({ elements: output, block, use_array: true });
117
- }
118
-
119
- return output;
120
- };
121
- }
122
-
123
- // When generating an iterator, we need to ensure that length is tracked
124
- if (prop === 'entries' || prop === 'values' || prop === 'keys') {
125
- receiver.length;
126
- }
127
- }
128
-
129
- return result;
130
- },
131
-
132
- set(target, prop, value, receiver) {
133
- var t = tracked_elements.get(prop);
134
- var exists = prop in target;
135
-
136
- if (prop === 'length') {
137
- for (var i = value; i < tracked_len.v; i += 1) {
138
- var other_t = tracked_elements.get(i + '');
139
- if (other_t !== undefined) {
140
- set(other_t, UNINITIALIZED, block);
141
- } else if (i in target) {
142
- // If the item exists in the original, we need to create a uninitialized tracked,
143
- // else a later read of the property would result in a tracked being created with
144
- // the value of the original item at that index.
145
- other_t = tracked(UNINITIALIZED, block);
146
- tracked_elements.set(i + '', other_t);
147
- }
148
- }
149
- }
150
-
151
- // If we haven't yet created a tracked for this property, we need to ensure
152
- // we do so otherwise if we read it later, then the write won't be tracked and
153
- // the heuristics of effects will be different vs if we had read the proxied
154
- // object property before writing to that property.
155
- if (t === undefined) {
156
- if (!exists || get_descriptor(target, prop)?.writable) {
157
- t = tracked(undefined, block);
158
- set(t, value, block);
159
-
160
- tracked_elements.set(prop, t);
161
- }
162
- } else {
163
- exists = t.v !== UNINITIALIZED;
164
-
165
- set(t, value, block);
166
- }
167
-
168
- var result = Reflect.set(target, prop, value, receiver);
169
-
170
- if (!exists) {
171
- // If we have mutated an array directly, we might need to
172
- // signal that length has also changed. Do it before updating metadata
173
- // to ensure that iterating over the array as a result of a metadata update
174
- // will not cause the length to be out of sync.
175
- if (typeof prop === 'string') {
176
- var n = Number(prop);
177
-
178
- if (Number.isInteger(n) && n >= tracked_len.v) {
179
- set(tracked_len, n + 1, block);
180
- }
181
- }
182
- }
183
-
184
- return result;
185
- },
186
-
187
- setPrototypeOf() {
188
- throw new Error(`Cannot set prototype of \`TrackedArray\``);
189
- },
190
-
191
- deleteProperty(target, prop) {
192
- var t = tracked_elements.get(prop);
193
-
194
- if (t === undefined) {
195
- if (prop in target) {
196
- const t = tracked(UNINITIALIZED, block);
197
- tracked_elements.set(prop, t);
198
- }
199
- } else {
200
- set(t, UNINITIALIZED, block);
201
- }
202
-
203
- return Reflect.deleteProperty(target, prop);
204
- },
205
-
206
- has(target, prop) {
207
- if (prop === TRACKED_ARRAY) {
208
- return true;
209
- }
210
- var t = tracked_elements.get(prop);
211
- var exists = (t !== undefined && t.v !== UNINITIALIZED) || Reflect.has(target, prop);
212
-
213
- if (t !== undefined || !exists || get_descriptor(target, prop)?.writable) {
214
- if (t === undefined) {
215
- t = tracked(exists ? /** @type {any} */ (target)[prop] : UNINITIALIZED, block);
216
-
217
- tracked_elements.set(prop, t);
218
- }
219
-
220
- var value = get(t);
221
- if (value === UNINITIALIZED) {
222
- return false;
223
- }
224
- }
225
-
226
- return exists;
227
- },
228
-
229
- defineProperty(_, prop, descriptor) {
230
- if (
231
- !('value' in descriptor) ||
232
- descriptor.configurable === false ||
233
- descriptor.enumerable === false ||
234
- descriptor.writable === false
235
- ) {
236
- // we disallow non-basic descriptors, because unless they are applied to the
237
- // target object — which we avoid, so that state can be forked — we will run
238
- // afoul of the various invariants
239
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/getOwnPropertyDescriptor#invariants
240
- throw new Error(
241
- 'Only basic property descriptors are supported with value and configurable, enumerable, and writable set to true',
242
- );
243
- }
244
-
245
- var t = tracked_elements.get(prop);
246
-
247
- if (t === undefined) {
248
- t = tracked(descriptor.value, block);
249
- tracked_elements.set(prop, t);
250
- } else {
251
- set(t, descriptor.value, block);
252
- }
253
-
254
- return true;
255
- },
256
- });
257
- }
258
-
259
- /**
260
- * @template T
261
- * @param {Array<T>} array
262
- * @returns {number | void}
263
- */
264
- function get_first_if_length(array) {
265
- var first = array[0];
266
-
267
- if (
268
- array.length === 1 &&
269
- 0 in array &&
270
- Number.isInteger(first) &&
271
- /** @type {number} */ (first) >= 0 &&
272
- /** @type {number} */ (first) <= MAX_ARRAY_LENGTH
273
- ) {
274
- return /** @type {number} */ (first);
275
- }
276
- }
277
-
278
- const methods_returning_arrays = new Set([
279
- 'concat',
280
- 'filter',
281
- 'flat',
282
- 'flatMap',
283
- 'map',
284
- 'slice',
285
- 'splice',
286
- 'toReversed',
287
- 'toSorted',
288
- 'toSpliced',
289
- 'with',
290
- ]);
291
-
292
- /**
293
- * @template T
294
- * @param {Iterable<T>} elements
61
+ * @param {Array<T>} elements
295
62
  * @param {Block} block
296
63
  * @returns {TrackedArray<T>}
297
64
  */
298
65
  export function tracked_array(elements, block) {
299
- return proxy({ elements, block, from_static: true });
300
- }
66
+ return array_proxy({ elements, block, from_static: true });
67
+ }
@@ -45,12 +45,14 @@ export { create_context as createContext } from './internal/client/context.js';
45
45
  export {
46
46
  flush_sync as flushSync,
47
47
  track,
48
- trackSplit,
48
+ track_split as trackSplit,
49
49
  untrack,
50
50
  } from './internal/client/runtime.js';
51
51
 
52
52
  export { TrackedArray } from './array.js';
53
53
 
54
+ export { TrackedObject } from './object.js';
55
+
54
56
  export { TrackedSet } from './set.js';
55
57
 
56
58
  export { TrackedMap } from './map.js';
@@ -22,6 +22,8 @@ export var CONTROL_FLOW_BLOCK = FOR_BLOCK | IF_BLOCK | TRY_BLOCK | COMPOSITE_BLO
22
22
  export var UNINITIALIZED = Symbol();
23
23
  /** @type {unique symbol} */
24
24
  export const TRACKED_ARRAY = Symbol();
25
+ /** @type {unique symbol} */
26
+ export const TRACKED_OBJECT = Symbol();
25
27
  export var COMPUTED_PROPERTY = Symbol();
26
28
  export var REF_PROP = 'ref';
27
29
  /** @type {unique symbol} */
@@ -60,6 +60,8 @@ export { template, append } from './template.js';
60
60
 
61
61
  export { tracked_array } from '../../array.js';
62
62
 
63
+ export { tracked_object } from '../../object.js';
64
+
63
65
  export { head } from './head.js';
64
66
 
65
67
  export { script } from './script.js';
@@ -14,7 +14,6 @@ export var is_firefox;
14
14
  export function init_operations() {
15
15
  var node_prototype = Node.prototype;
16
16
  var element_prototype = Element.prototype;
17
- var object_prototype = Object.prototype;
18
17
  var event_target_prototype = EventTarget.prototype;
19
18
 
20
19
  is_firefox = /Firefox/.test(navigator.userAgent);
@@ -15,6 +15,7 @@ import {
15
15
  is_event_attribute,
16
16
  } from '../../../utils/events.js';
17
17
  import { get } from './runtime.js';
18
+ import { clsx } from 'clsx';
18
19
 
19
20
  /**
20
21
  * @param {Text} text
@@ -131,18 +132,17 @@ export function set_attributes(element, attributes) {
131
132
  }
132
133
 
133
134
  /**
134
- * @template V
135
- * @param {V} value
135
+ * @param {import('clsx').ClassValue} value
136
136
  * @param {string} [hash]
137
- * @returns {string | V}
137
+ * @returns {string}
138
138
  */
139
139
  function to_class(value, hash) {
140
- return (value == null ? '' : value) + (hash ? ' ' + hash : '');
140
+ return value == null ? hash ?? '' : clsx([value, hash]);
141
141
  }
142
142
 
143
143
  /**
144
144
  * @param {HTMLElement} dom
145
- * @param {string} value
145
+ * @param {import('clsx').ClassValue} value
146
146
  * @param {string} [hash]
147
147
  * @param {boolean} [is_html]
148
148
  * @returns {void}
@@ -325,7 +325,7 @@ export function track(v, get, set, b) {
325
325
  * @param {Block} b
326
326
  * @returns {Tracked[]}
327
327
  */
328
- export function trackSplit(v, l, b) {
328
+ export function track_split(v, l, b) {
329
329
  var is_tracked = is_tracked_object(v);
330
330
 
331
331
  if (is_tracked || typeof v !== 'object' || v === null || is_array(v)) {
@@ -1089,7 +1089,7 @@ export function exclude_from_object(obj, exclude_keys) {
1089
1089
  var keys = object_keys(obj);
1090
1090
  /** @type {Record<string | symbol, unknown>} */
1091
1091
  var new_obj = {};
1092
-
1092
+
1093
1093
  for (const key of keys) {
1094
1094
  if (!exclude_keys.includes(key)) {
1095
1095
  new_obj[key] = obj[key];
@@ -20,6 +20,10 @@ export var object_keys = Object.keys;
20
20
  export var get_own_property_symbols = Object.getOwnPropertySymbols;
21
21
  /** @type {typeof structuredClone} */
22
22
  export var structured_clone = structuredClone;
23
+ /** @type {typeof Object.prototype} */
24
+ export var object_prototype = Object.prototype;
25
+ /** @type {typeof Array.prototype} */
26
+ export var array_prototype = Array.prototype;
23
27
 
24
28
  /**
25
29
  * Creates a text node that serves as an anchor point in the DOM.
@@ -6,6 +6,8 @@ import { is_tracked_object } from '../client/utils';
6
6
  import { escape } from '../../../utils/escaping.js';
7
7
  import { is_boolean_attribute } from '../../../compiler/utils';
8
8
 
9
+ import { clsx } from 'clsx';
10
+
9
11
  export { escape };
10
12
 
11
13
  /** @type {Component | null} */
@@ -132,7 +134,8 @@ export function attr(name, value, is_boolean = false) {
132
134
  }
133
135
  if (value == null || (!value && is_boolean)) return '';
134
136
  const normalized = (name in replacements && replacements[name].get(value)) || value;
135
- const assignment = is_boolean ? '' : `="${escape(normalized, true)}"`;
137
+ const value_to_escape = name === 'class' ? clsx(normalized) : normalized;
138
+ const assignment = is_boolean ? '' : `="${escape(value_to_escape, true)}"`;
136
139
  return ` ${name}${assignment}`;
137
140
  }
138
141
 
@@ -155,7 +158,7 @@ export function spread_attrs(attrs, css_hash) {
155
158
  }
156
159
 
157
160
  if (name === 'class' && css_hash) {
158
- value = (value == null ? '' : value) + ' ' + css_hash;
161
+ value = value == null ? css_hash : [value, css_hash];
159
162
  }
160
163
 
161
164
  attr_str += attr(name, value, is_boolean_attribute(name));
@@ -0,0 +1,29 @@
1
+ /** @import { Block } from '#client' */
2
+ import { safe_scope } from './internal/client/runtime.js';
3
+ import { object_proxy } from './proxy.js';
4
+
5
+ /**
6
+ * @template {object} T
7
+ * @constructor
8
+ * @param {T} obj
9
+ * @returns {TrackedObject<T>}
10
+ */
11
+ export function TrackedObject(obj) {
12
+ if (!new.target) {
13
+ throw new Error("TrackedObject must be called with 'new'");
14
+ }
15
+
16
+ var block = safe_scope();
17
+
18
+ return object_proxy(obj, block);
19
+ }
20
+
21
+ /**
22
+ * @template {object} T
23
+ * @param {T} obj
24
+ * @param {Block} block
25
+ * @returns {TrackedObject<T>}
26
+ */
27
+ export function tracked_object(obj, block) {
28
+ return object_proxy(obj, block);
29
+ }