ripple 0.2.133 → 0.2.135

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.
@@ -317,21 +317,21 @@ const visitors = {
317
317
  if (!context.state.to_ts) {
318
318
  return b.empty;
319
319
  }
320
- context.next();
320
+ return context.next();
321
321
  },
322
322
 
323
323
  TSInterfaceDeclaration(_, context) {
324
324
  if (!context.state.to_ts) {
325
325
  return b.empty;
326
326
  }
327
- context.next();
327
+ return context.next();
328
328
  },
329
329
 
330
330
  TSMappedType(_, context) {
331
331
  if (!context.state.to_ts) {
332
332
  return b.empty;
333
333
  }
334
- context.next();
334
+ return context.next();
335
335
  },
336
336
 
337
337
  NewExpression(node, context) {
@@ -384,7 +384,7 @@ const visitors = {
384
384
 
385
385
  TrackedArrayExpression(node, context) {
386
386
  if (context.state.to_ts) {
387
- const arrayAlias = import_from_ripple_if_needed("TrackedArray", context);
387
+ const arrayAlias = import_from_ripple_if_needed('TrackedArray', context);
388
388
 
389
389
  return b.call(
390
390
  b.member(b.id(arrayAlias), b.id('from')),
@@ -401,12 +401,9 @@ const visitors = {
401
401
 
402
402
  TrackedObjectExpression(node, context) {
403
403
  if (context.state.to_ts) {
404
- const objectAlias = import_from_ripple_if_needed("TrackedObject", context);
404
+ const objectAlias = import_from_ripple_if_needed('TrackedObject', context);
405
405
 
406
- return b.new(
407
- b.id(objectAlias),
408
- b.object(node.properties.map((prop) => context.visit(prop))),
409
- );
406
+ return b.new(b.id(objectAlias), b.object(node.properties.map((prop) => context.visit(prop))));
410
407
  }
411
408
 
412
409
  return b.call(
@@ -466,14 +463,17 @@ const visitors = {
466
463
  }
467
464
 
468
465
  if (node.tracked || (node.property.type === 'Identifier' && node.property.tracked)) {
466
+ // In TypeScript mode, skip the transformation and let transform_ts_child handle it
469
467
  add_ripple_internal_import(context);
470
468
 
471
- return b.call(
472
- '_$_.get_property',
473
- context.visit(node.object),
474
- node.computed ? context.visit(node.property) : b.literal(node.property.name),
475
- node.optional ? b.true : undefined,
476
- );
469
+ if (!context.state.to_ts) {
470
+ return b.call(
471
+ '_$_.get_property',
472
+ context.visit(node.object),
473
+ node.computed ? context.visit(node.property) : b.literal(node.property.name),
474
+ node.optional ? b.true : undefined,
475
+ );
476
+ }
477
477
  }
478
478
 
479
479
  if (node.object.type === 'MemberExpression' && node.object.optional) {
@@ -499,7 +499,7 @@ const visitors = {
499
499
  }
500
500
  }
501
501
  } else {
502
- context.next();
502
+ return context.next();
503
503
  }
504
504
  },
505
505
 
@@ -549,14 +549,68 @@ const visitors = {
549
549
  },
550
550
 
551
551
  JSXText(node, context) {
552
+ if (context.state.to_ts) {
553
+ return context.next();
554
+ }
552
555
  return b.literal(node.value + '');
553
556
  },
554
557
 
555
558
  JSXIdentifier(node, context) {
559
+ if (context.state.to_ts) {
560
+ return context.next();
561
+ }
556
562
  return b.id(node.name);
557
563
  },
558
564
 
565
+ JSXExpressionContainer(node, context) {
566
+ if (context.state.to_ts) {
567
+ return context.next();
568
+ }
569
+ return context.visit(node.expression);
570
+ },
571
+
572
+ JSXFragment(node, context) {
573
+ if (context.state.to_ts) {
574
+ return context.next();
575
+ }
576
+ const attributes = node.openingFragment.attributes;
577
+ const normalized_children = node.children.filter((child) => {
578
+ return child.type !== 'JSXText' || child.value.trim() !== '';
579
+ });
580
+
581
+ const props = b.object(
582
+ attributes.map((attr) => {
583
+ if (attr.type === 'JSXAttribute') {
584
+ return b.prop('init', context.visit(attr.name), context.visit(attr.value));
585
+ } else if (attr.type === 'JSXSpreadAttribute') {
586
+ return b.spread(context.visit(attr.argument));
587
+ }
588
+ }),
589
+ );
590
+
591
+ if (normalized_children.length > 0) {
592
+ props.properties.push(
593
+ b.prop(
594
+ 'init',
595
+ b.id('children'),
596
+ normalized_children.length === 1
597
+ ? context.visit(normalized_children[0])
598
+ : b.array(normalized_children.map((child) => context.visit(child))),
599
+ ),
600
+ );
601
+ }
602
+
603
+ return b.call(
604
+ normalized_children.length > 1 ? '__compat.jsxs' : '__compat.jsx',
605
+ b.id('__compat.Fragment'),
606
+ props,
607
+ );
608
+ },
609
+
559
610
  JSXElement(node, context) {
611
+ if (context.state.to_ts) {
612
+ return context.next();
613
+ }
560
614
  const name = node.openingElement.name;
561
615
  const attributes = node.openingElement.attributes;
562
616
  const normalized_children = node.children.filter((child) => {
@@ -573,7 +627,7 @@ const visitors = {
573
627
  }),
574
628
  );
575
629
 
576
- if (normalize_children.length > 0) {
630
+ if (normalized_children.length > 0) {
577
631
  props.properties.push(
578
632
  b.prop(
579
633
  'init',
@@ -586,7 +640,7 @@ const visitors = {
586
640
  }
587
641
 
588
642
  return b.call(
589
- '_$_jsx',
643
+ normalized_children.length > 1 ? '__compat.jsxs' : '__compat.jsx',
590
644
  name.type === 'JSXIdentifier' && name.name[0].toLowerCase() === name.name[0]
591
645
  ? b.literal(name.name)
592
646
  : context.visit(name),
@@ -678,6 +732,29 @@ const visitors = {
678
732
  const local_updates = [];
679
733
  const is_void = is_void_element(node.id.name);
680
734
 
735
+ let scoping_hash = null;
736
+ if (node.metadata.scoped && state.component.css) {
737
+ scoping_hash = state.component.css.hash;
738
+ } else {
739
+ let inside_dynamic_children = false;
740
+ for (let i = context.path.length - 1; i >= 0; i--) {
741
+ const anc = context.path[i];
742
+ if (anc && anc.type === 'Component' && anc.metadata && anc.metadata.inherited_css) {
743
+ inside_dynamic_children = true;
744
+ break;
745
+ }
746
+ }
747
+ if (inside_dynamic_children) {
748
+ for (let i = context.path.length - 1; i >= 0; i--) {
749
+ const anc = context.path[i];
750
+ if (anc && anc.type === 'Component' && anc.css) {
751
+ scoping_hash = anc.css.hash;
752
+ break;
753
+ }
754
+ }
755
+ }
756
+ }
757
+
681
758
  state.template.push(`<${node.id.name}`);
682
759
 
683
760
  for (const attr of node.attributes) {
@@ -847,8 +924,8 @@ const visitors = {
847
924
  if (class_attribute.value.type === 'Literal') {
848
925
  let value = class_attribute.value.value;
849
926
 
850
- if (node.metadata.scoped && state.component.css) {
851
- value = `${state.component.css.hash} ${value}`;
927
+ if (scoping_hash) {
928
+ value = `${scoping_hash} ${value}`;
852
929
  }
853
930
 
854
931
  handle_static_attr(class_attribute.name.name, value);
@@ -857,10 +934,7 @@ const visitors = {
857
934
  const metadata = { tracking: false, await: false };
858
935
  let expression = visit(class_attribute.value, { ...state, metadata });
859
936
 
860
- const hash_arg =
861
- node.metadata.scoped && state.component.css
862
- ? b.literal(state.component.css.hash)
863
- : undefined;
937
+ const hash_arg = scoping_hash ? b.literal(scoping_hash) : undefined;
864
938
  const is_html = context.state.metadata.namespace === 'html' && node.id.name !== 'svg';
865
939
 
866
940
  if (metadata.tracking) {
@@ -873,10 +947,8 @@ const visitors = {
873
947
  );
874
948
  }
875
949
  }
876
- } else if (node.metadata.scoped && state.component.css) {
877
- const value = state.component.css.hash;
878
-
879
- handle_static_attr(is_spreading ? '#class' : 'class', value);
950
+ } else if (scoping_hash) {
951
+ handle_static_attr(is_spreading ? '#class' : 'class', scoping_hash);
880
952
  }
881
953
 
882
954
  if (style_attribute !== null) {
@@ -1015,7 +1087,14 @@ const visitors = {
1015
1087
 
1016
1088
  if (children_filtered.length > 0) {
1017
1089
  const component_scope = context.state.scopes.get(node);
1018
- const children = visit(b.component(b.id('children'), [], children_filtered), {
1090
+ const children_component = b.component(b.id('children'), [], children_filtered);
1091
+
1092
+ children_component.metadata = {
1093
+ ...(children_component.metadata || {}),
1094
+ inherited_css: true,
1095
+ };
1096
+
1097
+ const children = visit(children_component, {
1019
1098
  ...context.state,
1020
1099
  scope: component_scope,
1021
1100
  namespace: child_namespace,
@@ -1139,7 +1218,7 @@ const visitors = {
1139
1218
  const operator = node.operator;
1140
1219
  const right = node.right;
1141
1220
 
1142
- if (operator !== '=') {
1221
+ if (operator !== '=' && context.state.metadata?.tracking === false) {
1143
1222
  context.state.metadata.tracking = true;
1144
1223
  }
1145
1224
 
@@ -1193,7 +1272,9 @@ const visitors = {
1193
1272
  (argument.tracked || (argument.property.type === 'Identifier' && argument.property.tracked))
1194
1273
  ) {
1195
1274
  add_ripple_internal_import(context);
1196
- context.state.metadata.tracking = true;
1275
+ if (context.state.metadata?.tracking === false) {
1276
+ context.state.metadata.tracking = true;
1277
+ }
1197
1278
 
1198
1279
  return b.call(
1199
1280
  node.prefix ? '_$_.update_pre_property' : '_$_.update_property',
@@ -1609,7 +1690,17 @@ function transform_ts_child(node, context) {
1609
1690
  state.init.push(b.stmt(visit(node.expression, { ...state })));
1610
1691
  } else if (node.type === 'Element') {
1611
1692
  // Use capitalized name for dynamic components/elements in TypeScript output
1612
- const type = node.metadata?.ts_name || node.id.name;
1693
+ // If node.id is not an Identifier (e.g., MemberExpression like props.children),
1694
+ // we need to visit it to get the proper expression
1695
+ let type_expression;
1696
+ let type_is_expression = false;
1697
+ if (node.id.type === 'MemberExpression') {
1698
+ // For MemberExpressions, we need to create a JSXExpression, not a JSXIdentifier
1699
+ type_expression = visit(node.id, state);
1700
+ type_is_expression = true;
1701
+ } else {
1702
+ type_expression = node.metadata?.ts_name || node.id.name;
1703
+ }
1613
1704
  const children = [];
1614
1705
  let has_children_props = false;
1615
1706
 
@@ -1647,9 +1738,7 @@ function transform_ts_child(node, context) {
1647
1738
  const createRefKeyAlias = import_from_ripple_if_needed('createRefKey', context);
1648
1739
  const metadata = { await: false };
1649
1740
  const argument = visit(attr.argument, { ...state, metadata });
1650
- const wrapper = b.object(
1651
- [b.prop('init', b.call(createRefKeyAlias), argument, true)]
1652
- );
1741
+ const wrapper = b.object([b.prop('init', b.call(createRefKeyAlias), argument, true)]);
1653
1742
  return b.jsx_spread_attribute(wrapper);
1654
1743
  }
1655
1744
  });
@@ -1674,33 +1763,40 @@ function transform_ts_child(node, context) {
1674
1763
  }
1675
1764
  }
1676
1765
 
1677
- const opening_type = b.jsx_id(type);
1678
- // Use node.id.loc if available, otherwise create a loc based on the element's position
1679
- opening_type.loc = node.id.loc || {
1680
- start: {
1681
- line: node.loc.start.line,
1682
- column: node.loc.start.column + 2, // After "<@"
1683
- },
1684
- end: {
1685
- line: node.loc.start.line,
1686
- column: node.loc.start.column + 2 + type.length,
1687
- },
1688
- };
1689
-
1690
- let closing_type = undefined;
1766
+ let opening_type, closing_type;
1691
1767
 
1692
- if (!node.selfClosing) {
1693
- closing_type = b.jsx_id(type);
1694
- closing_type.loc = {
1768
+ if (type_is_expression) {
1769
+ // For dynamic/expression-based components (e.g., props.children),
1770
+ // use JSX expression instead of identifier
1771
+ opening_type = type_expression;
1772
+ closing_type = node.selfClosing ? undefined : type_expression;
1773
+ } else {
1774
+ opening_type = b.jsx_id(type_expression);
1775
+ // Use node.id.loc if available, otherwise create a loc based on the element's position
1776
+ opening_type.loc = node.id.loc || {
1695
1777
  start: {
1696
- line: node.loc.end.line,
1697
- column: node.loc.end.column - type.length - 1,
1778
+ line: node.loc.start.line,
1779
+ column: node.loc.start.column + 2, // After "<@"
1698
1780
  },
1699
1781
  end: {
1700
- line: node.loc.end.line,
1701
- column: node.loc.end.column - 1,
1782
+ line: node.loc.start.line,
1783
+ column: node.loc.start.column + 2 + type_expression.length,
1702
1784
  },
1703
1785
  };
1786
+
1787
+ if (!node.selfClosing) {
1788
+ closing_type = b.jsx_id(type_expression);
1789
+ closing_type.loc = {
1790
+ start: {
1791
+ line: node.loc.end.line,
1792
+ column: node.loc.end.column - type_expression.length - 1,
1793
+ },
1794
+ end: {
1795
+ line: node.loc.end.line,
1796
+ column: node.loc.end.column - 1,
1797
+ },
1798
+ };
1799
+ }
1704
1800
  }
1705
1801
 
1706
1802
  const jsxElement = b.jsx_element(
@@ -1815,8 +1911,14 @@ function transform_ts_child(node, context) {
1815
1911
  state.init.push(component);
1816
1912
  } else if (node.type === 'BreakStatement') {
1817
1913
  state.init.push(b.break);
1818
- } else {
1914
+ } else if (node.type === 'TsxCompat') {
1915
+ const children = node.children
1916
+ .map((child) => visit(child, state))
1917
+ .filter((child) => child.type !== 'JSXText' || child.value.trim() !== '');
1918
+
1819
1919
  debugger;
1920
+ state.init.push(b.stmt(b.jsx_fragment(children)));
1921
+ } else {
1820
1922
  throw new Error('TODO');
1821
1923
  }
1822
1924
  }
@@ -2064,9 +2166,66 @@ function transform_body(body, { visit, state }) {
2064
2166
  function create_tsx_with_typescript_support() {
2065
2167
  const base_tsx = tsx();
2066
2168
 
2067
- // Override the ArrowFunctionExpression handler to support TypeScript return types
2169
+ // Add custom TypeScript node handlers that aren't in tsx
2068
2170
  return {
2069
2171
  ...base_tsx,
2172
+ // Custom handler for TSParenthesizedType: (Type)
2173
+ TSParenthesizedType(node, context) {
2174
+ context.write('(');
2175
+ context.visit(node.typeAnnotation);
2176
+ context.write(')');
2177
+ },
2178
+ // Custom handler for TSMappedType: { [K in keyof T]: T[K] }
2179
+ TSMappedType(node, context) {
2180
+ context.write('{ ');
2181
+ if (node.readonly) {
2182
+ if (node.readonly === '+' || node.readonly === true) {
2183
+ context.write('readonly ');
2184
+ } else if (node.readonly === '-') {
2185
+ context.write('-readonly ');
2186
+ }
2187
+ }
2188
+ context.write('[');
2189
+ // Visit the entire type parameter (TSTypeParameter node)
2190
+ if (node.typeParameter) {
2191
+ context.visit(node.typeParameter);
2192
+ }
2193
+ context.write(']');
2194
+ if (node.optional) {
2195
+ if (node.optional === '+' || node.optional === true) {
2196
+ context.write('?');
2197
+ } else if (node.optional === '-') {
2198
+ context.write('-?');
2199
+ }
2200
+ }
2201
+ context.write(': ');
2202
+ // Visit the value type - could be either typeAnnotation or nameType
2203
+ if (node.typeAnnotation) {
2204
+ context.visit(node.typeAnnotation);
2205
+ } else if (node.nameType) {
2206
+ context.visit(node.nameType);
2207
+ }
2208
+ context.write(' }');
2209
+ },
2210
+ // Custom handler for TSTypeParameter: K in T (for mapped types)
2211
+ // acord ts has a bug where `in` is printed as `extends`, so we override it here
2212
+ TSTypeParameter(node, context) {
2213
+ // For mapped types, the name is just a string, not an Identifier node
2214
+ if (typeof node.name === 'string') {
2215
+ context.write(node.name);
2216
+ } else if (node.name && node.name.name) {
2217
+ context.write(node.name.name);
2218
+ }
2219
+ if (node.constraint) {
2220
+ context.write(' in ');
2221
+ context.visit(node.constraint);
2222
+ }
2223
+ if (node.default) {
2224
+ context.write(' = ');
2225
+ context.visit(node.default);
2226
+ }
2227
+ },
2228
+ // Override the ArrowFunctionExpression handler to support TypeScript return types
2070
2229
  ArrowFunctionExpression(node, context) {
2071
2230
  if (node.async) context.write('async ');
2072
2231
 
@@ -2097,7 +2256,7 @@ function create_tsx_with_typescript_support() {
2097
2256
  } else {
2098
2257
  context.visit(node.body);
2099
2258
  }
2100
- }
2259
+ },
2101
2260
  };
2102
2261
  }
2103
2262
 
@@ -2113,7 +2272,12 @@ export function transform_client(filename, source, analysis, to_ts) {
2113
2272
  const ripple_user_imports = new Map(); // exported -> local
2114
2273
  if (analysis && analysis.ast && Array.isArray(analysis.ast.body)) {
2115
2274
  for (const stmt of analysis.ast.body) {
2116
- if (stmt && stmt.type === 'ImportDeclaration' && stmt.source && stmt.source.value === 'ripple') {
2275
+ if (
2276
+ stmt &&
2277
+ stmt.type === 'ImportDeclaration' &&
2278
+ stmt.source &&
2279
+ stmt.source.value === 'ripple'
2280
+ ) {
2117
2281
  for (const spec of stmt.specifiers || []) {
2118
2282
  if (spec.type === 'ImportSpecifier' && spec.imported && spec.local) {
2119
2283
  ripple_user_imports.set(spec.imported.name, spec.local.name);
@@ -1,4 +1,4 @@
1
- /** @import { Block } from '#client' */
1
+ /** @import { Block, CompatOptions } from '#client' */
2
2
 
3
3
  import { destroy_block, root } from './internal/client/blocks.js';
4
4
  import { handle_root_events } from './internal/client/events.js';
@@ -12,7 +12,7 @@ export { jsx, jsxs, Fragment } from '../jsx-runtime.js';
12
12
 
13
13
  /**
14
14
  * @param {(anchor: Node, props: Record<string, any>, active_block: Block | null) => void} component
15
- * @param {{ props?: Record<string, any>, target: HTMLElement }} options
15
+ * @param {{ props?: Record<string, any>, target: HTMLElement, compat?: CompatOptions }} options
16
16
  * @returns {() => void}
17
17
  */
18
18
  export function mount(component, options) {
@@ -34,7 +34,7 @@ export function mount(component, options) {
34
34
 
35
35
  const _root = root(() => {
36
36
  component(anchor, props, active_block);
37
- });
37
+ }, options.compat);
38
38
 
39
39
  return () => {
40
40
  cleanup_events();
@@ -1,4 +1,4 @@
1
- /** @import { Block, Derived } from '#client' */
1
+ /** @import { Block, Derived, CompatOptions } from '#client' */
2
2
 
3
3
  import {
4
4
  BLOCK_HAS_RUN,
@@ -124,11 +124,33 @@ export function ref(element, get_fn) {
124
124
  }
125
125
 
126
126
  /**
127
- * @param {() => void} fn
127
+ * @param {() => (void | (() => void))} fn
128
+ * @param {CompatOptions} [compat]
128
129
  * @returns {Block}
129
130
  */
130
- export function root(fn) {
131
- return block(ROOT_BLOCK, fn);
131
+ export function root(fn, compat) {
132
+ var target_fn = fn;
133
+
134
+ if (compat != null) {
135
+ /** @type {Array<void | (() => void)>} */
136
+ var unmounts = [];
137
+ for (var key in compat) {
138
+ var api = compat[key];
139
+ unmounts.push(api.createRoot());
140
+ }
141
+ target_fn = () => {
142
+ var component_unmount = fn();
143
+
144
+ return () => {
145
+ component_unmount?.();
146
+ for (var unmount of unmounts) {
147
+ unmount?.();
148
+ }
149
+ };
150
+ };
151
+ }
152
+
153
+ return block(ROOT_BLOCK, fn, { compat });
132
154
  }
133
155
 
134
156
  /**
@@ -1,8 +1,40 @@
1
+ /** @import { CompatApi } from '#client' */
2
+
3
+ import { ROOT_BLOCK } from "./constants";
4
+ import { active_block } from "./runtime";
5
+
1
6
  /**
2
- * @param {string} kind
3
- * @param {Node} node
4
- * @param {() => JSX.Element[]} children_fn
7
+ * @param {string} kind
8
+ * @returns {CompatApi | null}
9
+ */
10
+ function get_compat_from_root(kind) {
11
+ var current = active_block;
12
+
13
+ while (current !== null) {
14
+ if ((current.f & ROOT_BLOCK) !== 0) {
15
+ var api = current.s.compat[kind];
16
+
17
+ if (api != null) {
18
+ return api;
19
+ }
20
+ }
21
+ current = current.p;
22
+ }
23
+
24
+ return null;
25
+ }
26
+
27
+ /**
28
+ * @param {string} kind
29
+ * @param {Node} node
30
+ * @param {() => JSX.Element[]} children_fn
5
31
  */
6
32
  export function tsx_compat(kind, node, children_fn) {
7
- throw new Error("Not implemented yet");
8
- }
33
+ var compat = get_compat_from_root(kind);
34
+
35
+ if (compat == null) {
36
+ throw new Error(`No compat API found for kind "${kind}"`);
37
+ }
38
+
39
+ compat.createComponent(node, children_fn);
40
+ }
@@ -53,3 +53,13 @@ export type Block = {
53
53
  // teardown function
54
54
  t: (() => {}) | null;
55
55
  };
56
+
57
+ export type CompatApi = {
58
+ createRoot: () => void;
59
+ createComponent: (node: any, children_fn: () => any) => void;
60
+ jsx: (type: any, props: any) => any;
61
+ }
62
+
63
+ export type CompatOptions = {
64
+ [key: string]: CompatApi;
65
+ }
@@ -753,6 +753,23 @@ export function jsx_element(
753
753
  return element;
754
754
  }
755
755
 
756
+ /**
757
+ * @param {Array<ESTree.JSXText | ESTree.JSXExpressionContainer | ESTree.JSXSpreadChild | ESTree.JSXElement | ESTree.JSXFragment>} children
758
+ * @returns {ESTree.JSXFragment}
759
+ */
760
+ export function jsx_fragment(children = []) {
761
+ return {
762
+ type: 'JSXFragment',
763
+ openingFragment: {
764
+ type: 'JSXOpeningFragment',
765
+ },
766
+ closingFragment: {
767
+ type: 'JSXClosingFragment',
768
+ },
769
+ children,
770
+ };
771
+ }
772
+
756
773
  /**
757
774
  * @param {ESTree.Expression | ESTree.JSXEmptyExpression} expression
758
775
  * @returns {ESTree.JSXExpressionContainer}