ripple 0.2.134 → 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.
- package/package.json +2 -2
- package/src/compiler/phases/1-parse/index.js +290 -69
- package/src/compiler/phases/3-transform/client/index.js +212 -59
- package/src/runtime/internal/client/blocks.js +17 -2
- package/src/utils/builders.js +17 -0
- package/tests/client/basic/basic.components.test.ripple +30 -20
- package/tests/client/compiler/compiler.basic.test.ripple +93 -64
- package/tests/client/dynamic-elements.test.ripple +120 -1
|
@@ -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(
|
|
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(
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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,18 +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
|
|
|
559
565
|
JSXExpressionContainer(node, context) {
|
|
566
|
+
if (context.state.to_ts) {
|
|
567
|
+
return context.next();
|
|
568
|
+
}
|
|
560
569
|
return context.visit(node.expression);
|
|
561
570
|
},
|
|
562
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
|
+
|
|
563
610
|
JSXElement(node, context) {
|
|
611
|
+
if (context.state.to_ts) {
|
|
612
|
+
return context.next();
|
|
613
|
+
}
|
|
564
614
|
const name = node.openingElement.name;
|
|
565
615
|
const attributes = node.openingElement.attributes;
|
|
566
616
|
const normalized_children = node.children.filter((child) => {
|
|
@@ -577,7 +627,7 @@ const visitors = {
|
|
|
577
627
|
}),
|
|
578
628
|
);
|
|
579
629
|
|
|
580
|
-
if (
|
|
630
|
+
if (normalized_children.length > 0) {
|
|
581
631
|
props.properties.push(
|
|
582
632
|
b.prop(
|
|
583
633
|
'init',
|
|
@@ -590,7 +640,7 @@ const visitors = {
|
|
|
590
640
|
}
|
|
591
641
|
|
|
592
642
|
return b.call(
|
|
593
|
-
'__compat.jsx',
|
|
643
|
+
normalized_children.length > 1 ? '__compat.jsxs' : '__compat.jsx',
|
|
594
644
|
name.type === 'JSXIdentifier' && name.name[0].toLowerCase() === name.name[0]
|
|
595
645
|
? b.literal(name.name)
|
|
596
646
|
: context.visit(name),
|
|
@@ -682,6 +732,29 @@ const visitors = {
|
|
|
682
732
|
const local_updates = [];
|
|
683
733
|
const is_void = is_void_element(node.id.name);
|
|
684
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
|
+
|
|
685
758
|
state.template.push(`<${node.id.name}`);
|
|
686
759
|
|
|
687
760
|
for (const attr of node.attributes) {
|
|
@@ -851,8 +924,8 @@ const visitors = {
|
|
|
851
924
|
if (class_attribute.value.type === 'Literal') {
|
|
852
925
|
let value = class_attribute.value.value;
|
|
853
926
|
|
|
854
|
-
if (
|
|
855
|
-
value = `${
|
|
927
|
+
if (scoping_hash) {
|
|
928
|
+
value = `${scoping_hash} ${value}`;
|
|
856
929
|
}
|
|
857
930
|
|
|
858
931
|
handle_static_attr(class_attribute.name.name, value);
|
|
@@ -861,10 +934,7 @@ const visitors = {
|
|
|
861
934
|
const metadata = { tracking: false, await: false };
|
|
862
935
|
let expression = visit(class_attribute.value, { ...state, metadata });
|
|
863
936
|
|
|
864
|
-
const hash_arg =
|
|
865
|
-
node.metadata.scoped && state.component.css
|
|
866
|
-
? b.literal(state.component.css.hash)
|
|
867
|
-
: undefined;
|
|
937
|
+
const hash_arg = scoping_hash ? b.literal(scoping_hash) : undefined;
|
|
868
938
|
const is_html = context.state.metadata.namespace === 'html' && node.id.name !== 'svg';
|
|
869
939
|
|
|
870
940
|
if (metadata.tracking) {
|
|
@@ -877,10 +947,8 @@ const visitors = {
|
|
|
877
947
|
);
|
|
878
948
|
}
|
|
879
949
|
}
|
|
880
|
-
} else if (
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
handle_static_attr(is_spreading ? '#class' : 'class', value);
|
|
950
|
+
} else if (scoping_hash) {
|
|
951
|
+
handle_static_attr(is_spreading ? '#class' : 'class', scoping_hash);
|
|
884
952
|
}
|
|
885
953
|
|
|
886
954
|
if (style_attribute !== null) {
|
|
@@ -1019,7 +1087,14 @@ const visitors = {
|
|
|
1019
1087
|
|
|
1020
1088
|
if (children_filtered.length > 0) {
|
|
1021
1089
|
const component_scope = context.state.scopes.get(node);
|
|
1022
|
-
const
|
|
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, {
|
|
1023
1098
|
...context.state,
|
|
1024
1099
|
scope: component_scope,
|
|
1025
1100
|
namespace: child_namespace,
|
|
@@ -1143,7 +1218,7 @@ const visitors = {
|
|
|
1143
1218
|
const operator = node.operator;
|
|
1144
1219
|
const right = node.right;
|
|
1145
1220
|
|
|
1146
|
-
if (operator !== '=') {
|
|
1221
|
+
if (operator !== '=' && context.state.metadata?.tracking === false) {
|
|
1147
1222
|
context.state.metadata.tracking = true;
|
|
1148
1223
|
}
|
|
1149
1224
|
|
|
@@ -1197,7 +1272,9 @@ const visitors = {
|
|
|
1197
1272
|
(argument.tracked || (argument.property.type === 'Identifier' && argument.property.tracked))
|
|
1198
1273
|
) {
|
|
1199
1274
|
add_ripple_internal_import(context);
|
|
1200
|
-
context.state.metadata
|
|
1275
|
+
if (context.state.metadata?.tracking === false) {
|
|
1276
|
+
context.state.metadata.tracking = true;
|
|
1277
|
+
}
|
|
1201
1278
|
|
|
1202
1279
|
return b.call(
|
|
1203
1280
|
node.prefix ? '_$_.update_pre_property' : '_$_.update_property',
|
|
@@ -1613,7 +1690,17 @@ function transform_ts_child(node, context) {
|
|
|
1613
1690
|
state.init.push(b.stmt(visit(node.expression, { ...state })));
|
|
1614
1691
|
} else if (node.type === 'Element') {
|
|
1615
1692
|
// Use capitalized name for dynamic components/elements in TypeScript output
|
|
1616
|
-
|
|
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
|
+
}
|
|
1617
1704
|
const children = [];
|
|
1618
1705
|
let has_children_props = false;
|
|
1619
1706
|
|
|
@@ -1651,9 +1738,7 @@ function transform_ts_child(node, context) {
|
|
|
1651
1738
|
const createRefKeyAlias = import_from_ripple_if_needed('createRefKey', context);
|
|
1652
1739
|
const metadata = { await: false };
|
|
1653
1740
|
const argument = visit(attr.argument, { ...state, metadata });
|
|
1654
|
-
const wrapper = b.object(
|
|
1655
|
-
[b.prop('init', b.call(createRefKeyAlias), argument, true)]
|
|
1656
|
-
);
|
|
1741
|
+
const wrapper = b.object([b.prop('init', b.call(createRefKeyAlias), argument, true)]);
|
|
1657
1742
|
return b.jsx_spread_attribute(wrapper);
|
|
1658
1743
|
}
|
|
1659
1744
|
});
|
|
@@ -1678,33 +1763,40 @@ function transform_ts_child(node, context) {
|
|
|
1678
1763
|
}
|
|
1679
1764
|
}
|
|
1680
1765
|
|
|
1681
|
-
|
|
1682
|
-
// Use node.id.loc if available, otherwise create a loc based on the element's position
|
|
1683
|
-
opening_type.loc = node.id.loc || {
|
|
1684
|
-
start: {
|
|
1685
|
-
line: node.loc.start.line,
|
|
1686
|
-
column: node.loc.start.column + 2, // After "<@"
|
|
1687
|
-
},
|
|
1688
|
-
end: {
|
|
1689
|
-
line: node.loc.start.line,
|
|
1690
|
-
column: node.loc.start.column + 2 + type.length,
|
|
1691
|
-
},
|
|
1692
|
-
};
|
|
1693
|
-
|
|
1694
|
-
let closing_type = undefined;
|
|
1766
|
+
let opening_type, closing_type;
|
|
1695
1767
|
|
|
1696
|
-
if (
|
|
1697
|
-
|
|
1698
|
-
|
|
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 || {
|
|
1699
1777
|
start: {
|
|
1700
|
-
line: node.loc.
|
|
1701
|
-
column: node.loc.
|
|
1778
|
+
line: node.loc.start.line,
|
|
1779
|
+
column: node.loc.start.column + 2, // After "<@"
|
|
1702
1780
|
},
|
|
1703
1781
|
end: {
|
|
1704
|
-
line: node.loc.
|
|
1705
|
-
column: node.loc.
|
|
1782
|
+
line: node.loc.start.line,
|
|
1783
|
+
column: node.loc.start.column + 2 + type_expression.length,
|
|
1706
1784
|
},
|
|
1707
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
|
+
}
|
|
1708
1800
|
}
|
|
1709
1801
|
|
|
1710
1802
|
const jsxElement = b.jsx_element(
|
|
@@ -1819,8 +1911,14 @@ function transform_ts_child(node, context) {
|
|
|
1819
1911
|
state.init.push(component);
|
|
1820
1912
|
} else if (node.type === 'BreakStatement') {
|
|
1821
1913
|
state.init.push(b.break);
|
|
1822
|
-
} 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
|
+
|
|
1823
1919
|
debugger;
|
|
1920
|
+
state.init.push(b.stmt(b.jsx_fragment(children)));
|
|
1921
|
+
} else {
|
|
1824
1922
|
throw new Error('TODO');
|
|
1825
1923
|
}
|
|
1826
1924
|
}
|
|
@@ -2077,6 +2175,56 @@ function create_tsx_with_typescript_support() {
|
|
|
2077
2175
|
context.visit(node.typeAnnotation);
|
|
2078
2176
|
context.write(')');
|
|
2079
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
|
+
},
|
|
2080
2228
|
// Override the ArrowFunctionExpression handler to support TypeScript return types
|
|
2081
2229
|
ArrowFunctionExpression(node, context) {
|
|
2082
2230
|
if (node.async) context.write('async ');
|
|
@@ -2108,7 +2256,7 @@ function create_tsx_with_typescript_support() {
|
|
|
2108
2256
|
} else {
|
|
2109
2257
|
context.visit(node.body);
|
|
2110
2258
|
}
|
|
2111
|
-
}
|
|
2259
|
+
},
|
|
2112
2260
|
};
|
|
2113
2261
|
}
|
|
2114
2262
|
|
|
@@ -2124,7 +2272,12 @@ export function transform_client(filename, source, analysis, to_ts) {
|
|
|
2124
2272
|
const ripple_user_imports = new Map(); // exported -> local
|
|
2125
2273
|
if (analysis && analysis.ast && Array.isArray(analysis.ast.body)) {
|
|
2126
2274
|
for (const stmt of analysis.ast.body) {
|
|
2127
|
-
if (
|
|
2275
|
+
if (
|
|
2276
|
+
stmt &&
|
|
2277
|
+
stmt.type === 'ImportDeclaration' &&
|
|
2278
|
+
stmt.source &&
|
|
2279
|
+
stmt.source.value === 'ripple'
|
|
2280
|
+
) {
|
|
2128
2281
|
for (const spec of stmt.specifiers || []) {
|
|
2129
2282
|
if (spec.type === 'ImportSpecifier' && spec.imported && spec.local) {
|
|
2130
2283
|
ripple_user_imports.set(spec.imported.name, spec.local.name);
|
|
@@ -124,17 +124,32 @@ export function ref(element, get_fn) {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
/**
|
|
127
|
-
* @param {() => void} fn
|
|
127
|
+
* @param {() => (void | (() => void))} fn
|
|
128
128
|
* @param {CompatOptions} [compat]
|
|
129
129
|
* @returns {Block}
|
|
130
130
|
*/
|
|
131
131
|
export function root(fn, compat) {
|
|
132
|
+
var target_fn = fn;
|
|
133
|
+
|
|
132
134
|
if (compat != null) {
|
|
135
|
+
/** @type {Array<void | (() => void)>} */
|
|
136
|
+
var unmounts = [];
|
|
133
137
|
for (var key in compat) {
|
|
134
138
|
var api = compat[key];
|
|
135
|
-
api.createRoot();
|
|
139
|
+
unmounts.push(api.createRoot());
|
|
136
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
|
+
};
|
|
137
151
|
}
|
|
152
|
+
|
|
138
153
|
return block(ROOT_BLOCK, fn, { compat });
|
|
139
154
|
}
|
|
140
155
|
|
package/src/utils/builders.js
CHANGED
|
@@ -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}
|
|
@@ -3,7 +3,7 @@ import { track, flushSync } from 'ripple';
|
|
|
3
3
|
describe('basic client > components & composition', () => {
|
|
4
4
|
it('renders with component composition and children', () => {
|
|
5
5
|
component Card(props) {
|
|
6
|
-
<div class=
|
|
6
|
+
<div class="card">
|
|
7
7
|
<props.children />
|
|
8
8
|
</div>
|
|
9
9
|
}
|
|
@@ -27,16 +27,14 @@ describe('basic client > components & composition', () => {
|
|
|
27
27
|
|
|
28
28
|
it('renders with nested components and prop passing', () => {
|
|
29
29
|
component Button(props) {
|
|
30
|
-
<button class={props.variant} onClick={props.onClick}>
|
|
31
|
-
{props.label}
|
|
32
|
-
</button>
|
|
30
|
+
<button class={props.variant} onClick={props.onClick}>{props.label}</button>
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
component Card(props) {
|
|
36
|
-
<div class=
|
|
34
|
+
<div class="card">
|
|
37
35
|
<h3>{props.title}</h3>
|
|
38
36
|
<p>{props.content}</p>
|
|
39
|
-
<Button variant=
|
|
37
|
+
<Button variant="primary" label={props.buttonText} onClick={props.onAction} />
|
|
40
38
|
</div>
|
|
41
39
|
}
|
|
42
40
|
|
|
@@ -44,12 +42,12 @@ describe('basic client > components & composition', () => {
|
|
|
44
42
|
let clicked = track(false);
|
|
45
43
|
|
|
46
44
|
<Card
|
|
47
|
-
title=
|
|
48
|
-
content=
|
|
49
|
-
buttonText=
|
|
45
|
+
title="Test Card"
|
|
46
|
+
content="This is a test card"
|
|
47
|
+
buttonText="Click me"
|
|
50
48
|
onAction={() => @clicked = true}
|
|
51
49
|
/>
|
|
52
|
-
<div class=
|
|
50
|
+
<div class="status">{@clicked ? 'Clicked' : 'Not clicked'}</div>
|
|
53
51
|
}
|
|
54
52
|
|
|
55
53
|
render(Basic);
|
|
@@ -74,8 +72,8 @@ describe('basic client > components & composition', () => {
|
|
|
74
72
|
|
|
75
73
|
it('renders with reactive component props', () => {
|
|
76
74
|
component ChildComponent(props) {
|
|
77
|
-
<div class=
|
|
78
|
-
<div class=
|
|
75
|
+
<div class="child-content">{props.@text}</div>
|
|
76
|
+
<div class="child-count">{props.@count}</div>
|
|
79
77
|
}
|
|
80
78
|
|
|
81
79
|
component Basic() {
|
|
@@ -83,10 +81,14 @@ describe('basic client > components & composition', () => {
|
|
|
83
81
|
let number = track(1);
|
|
84
82
|
|
|
85
83
|
<ChildComponent text={message} count={number} />
|
|
86
|
-
<button
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
84
|
+
<button
|
|
85
|
+
onClick={() => {
|
|
86
|
+
@message = @message === 'Hello' ? 'Goodbye' : 'Hello';
|
|
87
|
+
@number++;
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
{'Update Props'}
|
|
91
|
+
</button>
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
render(Basic);
|
|
@@ -139,7 +141,9 @@ describe('basic client > components & composition', () => {
|
|
|
139
141
|
@hasError = true;
|
|
140
142
|
}
|
|
141
143
|
}}
|
|
142
|
-
>
|
|
144
|
+
>
|
|
145
|
+
{'Nonexistent'}
|
|
146
|
+
</button>
|
|
143
147
|
<button
|
|
144
148
|
onClick={() => {
|
|
145
149
|
@hasError = false;
|
|
@@ -149,7 +153,9 @@ describe('basic client > components & composition', () => {
|
|
|
149
153
|
@hasError = true;
|
|
150
154
|
}
|
|
151
155
|
}}
|
|
152
|
-
>
|
|
156
|
+
>
|
|
157
|
+
{'Nonexistent chaining'}
|
|
158
|
+
</button>
|
|
153
159
|
<button
|
|
154
160
|
onClick={() => {
|
|
155
161
|
@hasError = false;
|
|
@@ -159,7 +165,9 @@ describe('basic client > components & composition', () => {
|
|
|
159
165
|
@hasError = true;
|
|
160
166
|
}
|
|
161
167
|
}}
|
|
162
|
-
>
|
|
168
|
+
>
|
|
169
|
+
{'Object null'}
|
|
170
|
+
</button>
|
|
163
171
|
<button
|
|
164
172
|
onClick={() => {
|
|
165
173
|
@hasError = false;
|
|
@@ -169,7 +177,9 @@ describe('basic client > components & composition', () => {
|
|
|
169
177
|
@hasError = true;
|
|
170
178
|
}
|
|
171
179
|
}}
|
|
172
|
-
>
|
|
180
|
+
>
|
|
181
|
+
{'Object null chained'}
|
|
182
|
+
</button>
|
|
173
183
|
<button onClick={() => obj.arr[obj.arr.length - 1]()}>{'BinaryExpression prop'}</button>
|
|
174
184
|
|
|
175
185
|
<span>{obj.@count}</span>
|