ripple 0.2.68 → 0.2.69

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.68",
6
+ "version": "0.2.69",
7
7
  "type": "module",
8
8
  "module": "src/runtime/index.js",
9
9
  "main": "src/runtime/index.js",
@@ -192,6 +192,135 @@ function RipplePlugin(config) {
192
192
  return super.parseExportDefaultDeclaration();
193
193
  }
194
194
 
195
+ parseForStatement(node) {
196
+ this.next()
197
+ let awaitAt = (this.options.ecmaVersion >= 9 && this.canAwait && this.eatContextual("await")) ? this.lastTokStart : -1
198
+ this.labels.push({kind: "loop"})
199
+ this.enterScope(0)
200
+ this.expect(tt.parenL)
201
+
202
+ if (this.type === tt.semi) {
203
+ if (awaitAt > -1) this.unexpected(awaitAt)
204
+ return this.parseFor(node, null)
205
+ }
206
+
207
+ let isLet = this.isLet()
208
+ if (this.type === tt._var || this.type === tt._const || isLet) {
209
+ let init = this.startNode(), kind = isLet ? "let" : this.value
210
+ this.next()
211
+ this.parseVar(init, true, kind)
212
+ this.finishNode(init, "VariableDeclaration")
213
+ return this.parseForAfterInitWithIndex(node, init, awaitAt)
214
+ }
215
+
216
+ // Handle other cases like using declarations if they exist
217
+ let startsWithLet = this.isContextual("let"), isForOf = false
218
+ let usingKind = (this.isUsing && this.isUsing(true)) ? "using" : (this.isAwaitUsing && this.isAwaitUsing(true)) ? "await using" : null
219
+ if (usingKind) {
220
+ let init = this.startNode()
221
+ this.next()
222
+ if (usingKind === "await using") {
223
+ if (!this.canAwait) {
224
+ this.raise(this.start, "Await using cannot appear outside of async function")
225
+ }
226
+ this.next()
227
+ }
228
+ this.parseVar(init, true, usingKind)
229
+ this.finishNode(init, "VariableDeclaration")
230
+ return this.parseForAfterInitWithIndex(node, init, awaitAt)
231
+ }
232
+
233
+ let containsEsc = this.containsEsc
234
+ let refDestructuringErrors = {}
235
+ let initPos = this.start
236
+ let init = awaitAt > -1
237
+ ? this.parseExprSubscripts(refDestructuringErrors, "await")
238
+ : this.parseExpression(true, refDestructuringErrors)
239
+
240
+ if (this.type === tt._in || (isForOf = this.options.ecmaVersion >= 6 && this.isContextual("of"))) {
241
+ if (awaitAt > -1) { // implies `ecmaVersion >= 9`
242
+ if (this.type === tt._in) this.unexpected(awaitAt)
243
+ node.await = true
244
+ } else if (isForOf && this.options.ecmaVersion >= 8) {
245
+ if (init.start === initPos && !containsEsc && init.type === "Identifier" && init.name === "async") this.unexpected()
246
+ else if (this.options.ecmaVersion >= 9) node.await = false
247
+ }
248
+ if (startsWithLet && isForOf) this.raise(init.start, "The left-hand side of a for-of loop may not start with 'let'.")
249
+ this.toAssignable(init, false, refDestructuringErrors)
250
+ this.checkLValPattern(init)
251
+ return this.parseForInWithIndex(node, init)
252
+ } else {
253
+ this.checkExpressionErrors(refDestructuringErrors, true)
254
+ }
255
+
256
+ if (awaitAt > -1) this.unexpected(awaitAt)
257
+ return this.parseFor(node, init)
258
+ }
259
+
260
+ parseForAfterInitWithIndex(node, init, awaitAt) {
261
+ if ((this.type === tt._in || (this.options.ecmaVersion >= 6 && this.isContextual("of"))) && init.declarations.length === 1) {
262
+ if (this.options.ecmaVersion >= 9) {
263
+ if (this.type === tt._in) {
264
+ if (awaitAt > -1) this.unexpected(awaitAt)
265
+ } else node.await = awaitAt > -1
266
+ }
267
+ return this.parseForInWithIndex(node, init)
268
+ }
269
+ if (awaitAt > -1) this.unexpected(awaitAt)
270
+ return this.parseFor(node, init)
271
+ }
272
+
273
+ parseForInWithIndex(node, init) {
274
+ const isForIn = this.type === tt._in
275
+ this.next()
276
+
277
+ if (
278
+ init.type === "VariableDeclaration" &&
279
+ init.declarations[0].init != null &&
280
+ (
281
+ !isForIn ||
282
+ this.options.ecmaVersion < 8 ||
283
+ this.strict ||
284
+ init.kind !== "var" ||
285
+ init.declarations[0].id.type !== "Identifier"
286
+ )
287
+ ) {
288
+ this.raise(
289
+ init.start,
290
+ `${isForIn ? "for-in" : "for-of"} loop variable declaration may not have an initializer`
291
+ )
292
+ }
293
+
294
+ node.left = init
295
+ node.right = isForIn ? this.parseExpression() : this.parseMaybeAssign()
296
+
297
+ // Check for our extended syntax: "; index varName"
298
+ if (!isForIn && this.type === tt.semi) {
299
+ this.next() // consume ';'
300
+
301
+ if (this.isContextual('index')) {
302
+ this.next() // consume 'index'
303
+
304
+ if (this.type === tt.name) {
305
+ node.index = this.parseIdent()
306
+ } else {
307
+ this.raise(this.start, 'Expected identifier after "index" keyword')
308
+ }
309
+ } else {
310
+ this.raise(this.start, 'Expected "index" keyword after semicolon in for-of loop')
311
+ }
312
+ } else if (!isForIn) {
313
+ // Set index to null for standard for-of loops
314
+ node.index = null
315
+ }
316
+
317
+ this.expect(tt.parenR)
318
+ node.body = this.parseStatement("for")
319
+ this.exitScope()
320
+ this.labels.pop()
321
+ return this.finishNode(node, isForIn ? "ForInStatement" : "ForOfStatement")
322
+ }
323
+
195
324
  shouldParseExportStatement() {
196
325
  if (super.shouldParseExportStatement()) {
197
326
  return true;
@@ -791,6 +920,8 @@ function RipplePlugin(config) {
791
920
  return node;
792
921
  }
793
922
 
923
+
924
+
794
925
  if (this.type.label === '@') {
795
926
  // Try to parse as an expression statement first using tryParse
796
927
  // This allows us to handle Ripple @ syntax like @count++ without
@@ -5,7 +5,7 @@ import {
5
5
  get_delegated_event,
6
6
  is_element_dom_element,
7
7
  is_inside_component,
8
- is_ripple_import,
8
+ is_ripple_track_call,
9
9
  is_void_element,
10
10
  } from '../../utils.js';
11
11
  import { extract_paths } from '../../../utils/ast.js';
@@ -139,16 +139,7 @@ const visitors = {
139
139
  CallExpression(node, context) {
140
140
  const callee = node.callee;
141
141
 
142
- if (
143
- context.state.function_depth === 0 &&
144
- ((callee.type === 'Identifier' && callee.name === 'track') ||
145
- (callee.type === 'MemberExpression' &&
146
- callee.object.type === 'Identifier' &&
147
- callee.property.type === 'Identifier' &&
148
- callee.property.name === 'track' &&
149
- !callee.computed)) &&
150
- is_ripple_import(callee, context)
151
- ) {
142
+ if (context.state.function_depth === 0 && is_ripple_track_call(callee, context)) {
152
143
  error(
153
144
  '`track` can only be used within a reactive context, such as a component, function or class that is used or created from a component',
154
145
  context.state.analysis.module.filename,
@@ -278,6 +269,21 @@ const visitors = {
278
269
  return context.next();
279
270
  }
280
271
 
272
+ if (node.index) {
273
+ const state = context.state;
274
+ const scope = state.scopes.get(node);
275
+ const binding = scope.get(node.index.name);
276
+ binding.kind = 'index'
277
+
278
+ if (binding !== null) {
279
+ binding.transform = {
280
+ read: (node) => {
281
+ return b.call('_$_.get', node)
282
+ },
283
+ };
284
+ }
285
+ }
286
+
281
287
  node.metadata = {
282
288
  has_template: false,
283
289
  };
@@ -3,7 +3,13 @@ import path from 'node:path';
3
3
  import { print } from 'esrap';
4
4
  import tsx from 'esrap/languages/tsx';
5
5
  import * as b from '../../../utils/builders.js';
6
- import { IS_CONTROLLED, TEMPLATE_FRAGMENT } from '../../../constants.js';
6
+ import {
7
+ IS_CONTROLLED,
8
+ IS_INDEXED,
9
+ TEMPLATE_FRAGMENT,
10
+ TEMPLATE_SVG_NAMESPACE,
11
+ TEMPLATE_MATHML_NAMESPACE,
12
+ } from '../../../constants.js';
7
13
  import { sanitize_template_string } from '../../../utils/sanitize_template_string.js';
8
14
  import {
9
15
  build_hoisted_params,
@@ -13,7 +19,6 @@ import {
13
19
  escape_html,
14
20
  is_boolean_attribute,
15
21
  is_dom_property,
16
- is_ripple_import,
17
22
  is_declared_function_within_component,
18
23
  is_inside_call_expression,
19
24
  is_value_static,
@@ -21,6 +26,7 @@ import {
21
26
  is_component_level_function,
22
27
  is_element_dom_element,
23
28
  is_top_level_await,
29
+ is_ripple_track_call,
24
30
  } from '../../utils.js';
25
31
  import is_reference from 'is-reference';
26
32
  import { object } from '../../../utils/ast.js';
@@ -104,6 +110,22 @@ function build_getter(node, context) {
104
110
  return node;
105
111
  }
106
112
 
113
+ function determine_namespace_for_children(element_name, current_namespace) {
114
+ if (element_name === 'foreignObject') {
115
+ return 'html';
116
+ }
117
+
118
+ if (element_name === 'svg') {
119
+ return 'svg';
120
+ }
121
+
122
+ if (element_name === 'math') {
123
+ return 'mathml';
124
+ }
125
+
126
+ return current_namespace;
127
+ }
128
+
107
129
  const visitors = {
108
130
  _: function set_scope(node, { next, state }) {
109
131
  const scope = state.scopes.get(node);
@@ -128,7 +150,10 @@ const visitors = {
128
150
  if (
129
151
  (context.state.metadata?.tracking === false ||
130
152
  (parent.type !== 'AssignmentExpression' && parent.type !== 'UpdateExpression')) &&
131
- (node.tracked || binding?.kind === 'prop' || binding?.kind === 'prop_fallback') &&
153
+ (node.tracked ||
154
+ binding?.kind === 'prop' ||
155
+ binding?.kind === 'index' ||
156
+ binding?.kind === 'prop_fallback') &&
132
157
  binding?.node !== node
133
158
  ) {
134
159
  if (context.state.metadata?.tracking === false) {
@@ -165,16 +190,7 @@ const visitors = {
165
190
  context.state.metadata.tracking = true;
166
191
  }
167
192
 
168
- if (
169
- !context.state.to_ts &&
170
- ((callee.type === 'Identifier' && callee.name === 'track') ||
171
- (callee.type === 'MemberExpression' &&
172
- callee.object.type === 'Identifier' &&
173
- callee.property.type === 'Identifier' &&
174
- callee.property.name === 'track' &&
175
- !callee.computed)) &&
176
- is_ripple_import(callee, context)
177
- ) {
193
+ if (!context.state.to_ts && is_ripple_track_call(callee, context)) {
178
194
  if (node.arguments.length === 0) {
179
195
  node.arguments.push(b.void0);
180
196
  }
@@ -427,6 +443,10 @@ const visitors = {
427
443
  const is_spreading = node.attributes.some((attr) => attr.type === 'SpreadAttribute');
428
444
  const spread_attributes = is_spreading ? [] : null;
429
445
 
446
+ const child_namespace = is_dom_element
447
+ ? determine_namespace_for_children(node.id.name, state.namespace)
448
+ : state.namespace;
449
+
430
450
  const handle_static_attr = (name, value) => {
431
451
  const attr_value = b.literal(
432
452
  ` ${name}${
@@ -629,12 +649,16 @@ const visitors = {
629
649
  if (node.metadata.scoped && state.component.css) {
630
650
  expression = b.binary('+', b.literal(state.component.css.hash + ' '), expression);
631
651
  }
632
- const is_html = context.state.metadata.namespace === 'html' && node.id.name !== 'svg'
652
+ const is_html = context.state.metadata.namespace === 'html' && node.id.name !== 'svg';
633
653
 
634
- if (metadata.tracking) {
635
- local_updates.push(b.stmt(b.call('_$_.set_class', id, expression, is_html)));
654
+ if (class_attribute.name.name === '$class' || metadata.tracking) {
655
+ local_updates.push(
656
+ b.stmt(b.call('_$_.set_class', id, expression, undefined, b.literal(is_html))),
657
+ );
636
658
  } else {
637
- state.init.push(b.stmt(b.call('_$_.set_class', id, expression, is_html)));
659
+ state.init.push(
660
+ b.stmt(b.call('_$_.set_class', id, expression, undefined, b.literal(is_html))),
661
+ );
638
662
  }
639
663
  }
640
664
  } else if (node.metadata.scoped && state.component.css) {
@@ -658,7 +682,7 @@ const visitors = {
658
682
  if (!is_void) {
659
683
  transform_children(node.children, {
660
684
  visit,
661
- state: { ...state, init, update },
685
+ state: { ...state, init, update, namespace: child_namespace },
662
686
  root: false,
663
687
  });
664
688
  state.template.push(`</${node.id.name}>`);
@@ -769,7 +793,7 @@ const visitors = {
769
793
  for (const child of node.children) {
770
794
  if (child.type === 'Component') {
771
795
  const id = child.id;
772
- props.push(b.prop('init', id, visit(child, state)));
796
+ props.push(b.prop('init', id, visit(child, { ...state, namespace: child_namespace })));
773
797
  } else {
774
798
  children_filtered.push(child);
775
799
  }
@@ -780,6 +804,7 @@ const visitors = {
780
804
  const children = visit(b.component(b.id('children'), [], children_filtered), {
781
805
  ...context.state,
782
806
  scope: component_scope,
807
+ namespace: child_namespace,
783
808
  });
784
809
 
785
810
  if (children_prop) {
@@ -1001,6 +1026,12 @@ const visitors = {
1001
1026
  return;
1002
1027
  }
1003
1028
  const is_controlled = node.is_controlled;
1029
+ const index = node.index;
1030
+ let flags = is_controlled ? IS_CONTROLLED : 0;
1031
+
1032
+ if (index !== null) {
1033
+ flags |= IS_INDEXED;
1034
+ }
1004
1035
 
1005
1036
  // do only if not controller
1006
1037
  if (!is_controlled) {
@@ -1018,15 +1049,15 @@ const visitors = {
1018
1049
  id,
1019
1050
  b.thunk(context.visit(node.right)),
1020
1051
  b.arrow(
1021
- [b.id('__anchor'), pattern],
1052
+ index ? [b.id('__anchor'), pattern, index] : [b.id('__anchor'), pattern],
1022
1053
  b.block(
1023
1054
  transform_body(node.body.body, {
1024
1055
  ...context,
1025
- state: { ...context.state, scope: body_scope },
1056
+ state: { ...context.state, scope: body_scope, namespace: context.state.namespace },
1026
1057
  }),
1027
1058
  ),
1028
1059
  ),
1029
- b.literal(is_controlled ? IS_CONTROLLED : 0),
1060
+ b.literal(flags),
1030
1061
  ),
1031
1062
  ),
1032
1063
  );
@@ -1470,9 +1501,15 @@ function transform_children(children, context) {
1470
1501
 
1471
1502
  if (child.type === 'Text' && prev_child?.type === 'Text') {
1472
1503
  if (child.expression.type === 'Literal' && prev_child.expression.type === 'Literal') {
1473
- prev_child.expression = b.literal(prev_child.expression.value + child.expression.value);
1504
+ prev_child.expression = b.literal(
1505
+ prev_child.expression.value + String(child.expression.value),
1506
+ );
1474
1507
  } else {
1475
- prev_child.expression = b.binary('+', prev_child.expression, child.expression);
1508
+ prev_child.expression = b.binary(
1509
+ '+',
1510
+ prev_child.expression,
1511
+ b.call('String', child.expression),
1512
+ );
1476
1513
  }
1477
1514
  normalized.splice(i, 1);
1478
1515
  }
@@ -1540,7 +1577,7 @@ function transform_children(children, context) {
1540
1577
  prev = flush_node;
1541
1578
 
1542
1579
  if (node.type === 'Element') {
1543
- visit(node, { ...state, flush_node });
1580
+ visit(node, { ...state, flush_node, namespace: state.namespace });
1544
1581
  } else if (node.type === 'Text') {
1545
1582
  const metadata = { tracking: false, await: false };
1546
1583
  const expression = visit(node.expression, { ...state, metadata });
@@ -1576,26 +1613,33 @@ function transform_children(children, context) {
1576
1613
  } else if (node.type === 'ForOfStatement') {
1577
1614
  const is_controlled = normalized.length === 1;
1578
1615
  node.is_controlled = is_controlled;
1579
- visit(node, { ...state, flush_node });
1616
+ visit(node, { ...state, flush_node, namespace: state.namespace });
1580
1617
  } else if (node.type === 'IfStatement') {
1581
1618
  const is_controlled = normalized.length === 1;
1582
1619
  node.is_controlled = is_controlled;
1583
- visit(node, { ...state, flush_node });
1620
+ visit(node, { ...state, flush_node, namespace: state.namespace });
1584
1621
  } else if (node.type === 'TryStatement') {
1585
1622
  const is_controlled = normalized.length === 1;
1586
1623
  node.is_controlled = is_controlled;
1587
- visit(node, { ...state, flush_node });
1624
+ visit(node, { ...state, flush_node, namespace: state.namespace });
1588
1625
  } else {
1589
1626
  debugger;
1590
1627
  }
1591
1628
  }
1592
1629
  }
1593
1630
 
1631
+ const template_namespace = state.namespace || 'html';
1632
+
1594
1633
  if (root && initial !== null && template_id !== null) {
1595
- const flags = is_fragment ? b.literal(TEMPLATE_FRAGMENT) : b.literal(0);
1634
+ let flags = is_fragment ? TEMPLATE_FRAGMENT : 0;
1635
+ if (template_namespace === 'svg') {
1636
+ flags |= TEMPLATE_SVG_NAMESPACE;
1637
+ } else if (template_namespace === 'mathml') {
1638
+ flags |= TEMPLATE_MATHML_NAMESPACE;
1639
+ }
1596
1640
  state.final.push(b.stmt(b.call('_$_.append', b.id('__anchor'), initial)));
1597
1641
  state.hoisted.push(
1598
- b.var(template_id, b.call('_$_.template', join_template(state.template), flags)),
1642
+ b.var(template_id, b.call('_$_.template', join_template(state.template), b.literal(flags))),
1599
1643
  );
1600
1644
  }
1601
1645
  }
@@ -1609,6 +1653,7 @@ function transform_body(body, { visit, state }) {
1609
1653
  update: [],
1610
1654
  final: [],
1611
1655
  metadata: state.metadata,
1656
+ namespace: state.namespace || 'html', // Preserve namespace context
1612
1657
  };
1613
1658
 
1614
1659
  transform_children(body, { visit, state: body_state, root: true });
@@ -1637,7 +1682,9 @@ export function transform(filename, source, analysis, to_ts) {
1637
1682
  to_ts,
1638
1683
  };
1639
1684
 
1640
- const program = /** @type {ESTree.Program} */ (walk(analysis.ast, state, visitors));
1685
+ const program = /** @type {ESTree.Program} */ (
1686
+ walk(analysis.ast, { ...state, namespace: 'html' }, visitors)
1687
+ );
1641
1688
 
1642
1689
  for (const hoisted of state.hoisted) {
1643
1690
  program.body.unshift(hoisted);