ripple 0.2.148 → 0.2.149
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 +45 -38
- package/src/compiler/phases/3-transform/client/index.js +27 -20
- package/src/constants.js +0 -1
- package/src/runtime/internal/client/composite.js +10 -5
- package/src/runtime/internal/client/constants.js +6 -0
- package/src/runtime/internal/client/index.js +1 -0
- package/src/runtime/internal/client/runtime.js +20 -0
- package/src/runtime/internal/client/template.js +11 -9
- package/tests/client/svg.test.ripple +103 -0
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.
|
|
6
|
+
"version": "0.2.149",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"module": "src/runtime/index-client.js",
|
|
9
9
|
"main": "src/runtime/index-client.js",
|
|
@@ -81,6 +81,6 @@
|
|
|
81
81
|
"typescript": "^5.9.2"
|
|
82
82
|
},
|
|
83
83
|
"peerDependencies": {
|
|
84
|
-
"ripple": "0.2.
|
|
84
|
+
"ripple": "0.2.149"
|
|
85
85
|
}
|
|
86
86
|
}
|
|
@@ -39,6 +39,8 @@ const regex_whitespace_only = /\s/;
|
|
|
39
39
|
*/
|
|
40
40
|
function skipWhitespace(parser) {
|
|
41
41
|
const originalStart = parser.start;
|
|
42
|
+
/** @type {acorn.Position | undefined} */
|
|
43
|
+
let lineInfo;
|
|
42
44
|
while (
|
|
43
45
|
parser.start < parser.input.length &&
|
|
44
46
|
regex_whitespace_only.test(parser.input[parser.start])
|
|
@@ -47,11 +49,14 @@ function skipWhitespace(parser) {
|
|
|
47
49
|
}
|
|
48
50
|
// Update line tracking if whitespace was skipped
|
|
49
51
|
if (parser.start !== originalStart) {
|
|
50
|
-
|
|
52
|
+
lineInfo = acorn.getLineInfo(parser.input, parser.start);
|
|
51
53
|
parser.curLine = lineInfo.line;
|
|
52
54
|
parser.lineStart = parser.start - lineInfo.column;
|
|
53
|
-
parser.startLoc = lineInfo;
|
|
54
55
|
}
|
|
56
|
+
|
|
57
|
+
// After skipping whitespace, update startLoc to reflect our actual position
|
|
58
|
+
// so the next node's start location is correct
|
|
59
|
+
parser.startLoc = lineInfo || acorn.getLineInfo(parser.input, parser.start);
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
function isWhitespaceTextNode(node) {
|
|
@@ -73,6 +78,8 @@ function RipplePlugin(config) {
|
|
|
73
78
|
const original = acorn.Parser.prototype;
|
|
74
79
|
const tt = Parser.tokTypes || acorn.tokTypes;
|
|
75
80
|
const tc = Parser.tokContexts || acorn.tokContexts;
|
|
81
|
+
const tstt = Parser.acornTypeScript.tokTypes;
|
|
82
|
+
const tstc = Parser.acornTypeScript.tokContexts;
|
|
76
83
|
|
|
77
84
|
class RippleParser extends Parser {
|
|
78
85
|
/** @type {any[]} */
|
|
@@ -214,9 +221,8 @@ function RipplePlugin(config) {
|
|
|
214
221
|
if (allWhitespace && this.pos + 1 < this.input.length) {
|
|
215
222
|
const nextChar = this.input.charCodeAt(this.pos + 1);
|
|
216
223
|
if (nextChar !== 32 && nextChar !== 9 && nextChar !== 10 && nextChar !== 13) {
|
|
217
|
-
const tokTypes = this.acornTypeScript.tokTypes;
|
|
218
224
|
++this.pos;
|
|
219
|
-
return this.finishToken(
|
|
225
|
+
return this.finishToken(tstt.jsxTagStart);
|
|
220
226
|
}
|
|
221
227
|
}
|
|
222
228
|
}
|
|
@@ -592,7 +598,7 @@ function RipplePlugin(config) {
|
|
|
592
598
|
this.expect(tt.braceL);
|
|
593
599
|
this.enterScope(0);
|
|
594
600
|
while (this.type !== tt.braceR) {
|
|
595
|
-
|
|
601
|
+
const stmt = this.parseStatement(null, true);
|
|
596
602
|
body.body.push(stmt);
|
|
597
603
|
}
|
|
598
604
|
this.next();
|
|
@@ -986,7 +992,7 @@ function RipplePlugin(config) {
|
|
|
986
992
|
}
|
|
987
993
|
|
|
988
994
|
jsx_parseTupleContainer() {
|
|
989
|
-
|
|
995
|
+
const t = this.startNode();
|
|
990
996
|
return (
|
|
991
997
|
this.next(),
|
|
992
998
|
(t.expression =
|
|
@@ -1057,7 +1063,7 @@ function RipplePlugin(config) {
|
|
|
1057
1063
|
if (this.type.label === '@') {
|
|
1058
1064
|
this.next(); // consume @
|
|
1059
1065
|
|
|
1060
|
-
if (this.type === tt.name || this.type
|
|
1066
|
+
if (this.type === tt.name || this.type === tstt.jsxName) {
|
|
1061
1067
|
node.name = this.value;
|
|
1062
1068
|
node.tracked = true;
|
|
1063
1069
|
this.next();
|
|
@@ -1066,14 +1072,14 @@ function RipplePlugin(config) {
|
|
|
1066
1072
|
this.unexpected();
|
|
1067
1073
|
}
|
|
1068
1074
|
} else if (
|
|
1069
|
-
(this.type === tt.name || this.type
|
|
1075
|
+
(this.type === tt.name || this.type === tstt.jsxName) &&
|
|
1070
1076
|
this.value &&
|
|
1071
1077
|
this.value.startsWith('@')
|
|
1072
1078
|
) {
|
|
1073
1079
|
node.name = this.value.substring(1);
|
|
1074
1080
|
node.tracked = true;
|
|
1075
1081
|
this.next();
|
|
1076
|
-
} else if (this.type === tt.name || this.type.keyword || this.type
|
|
1082
|
+
} else if (this.type === tt.name || this.type.keyword || this.type === tstt.jsxName) {
|
|
1077
1083
|
node.name = this.value;
|
|
1078
1084
|
node.tracked = false; // Explicitly mark as not tracked
|
|
1079
1085
|
this.next();
|
|
@@ -1085,7 +1091,7 @@ function RipplePlugin(config) {
|
|
|
1085
1091
|
}
|
|
1086
1092
|
|
|
1087
1093
|
jsx_parseElementName() {
|
|
1088
|
-
if (this.type
|
|
1094
|
+
if (this.type === tstt.jsxTagEnd) {
|
|
1089
1095
|
return '';
|
|
1090
1096
|
}
|
|
1091
1097
|
|
|
@@ -1148,17 +1154,15 @@ function RipplePlugin(config) {
|
|
|
1148
1154
|
}
|
|
1149
1155
|
|
|
1150
1156
|
jsx_parseAttributeValue() {
|
|
1151
|
-
const tok = this.acornTypeScript.tokTypes;
|
|
1152
|
-
|
|
1153
1157
|
switch (this.type) {
|
|
1154
1158
|
case tt.braceL:
|
|
1155
|
-
|
|
1159
|
+
const t = this.jsx_parseExpressionContainer();
|
|
1156
1160
|
return (
|
|
1157
1161
|
'JSXEmptyExpression' === t.expression.type &&
|
|
1158
1162
|
this.raise(t.start, 'attributes must only be assigned a non-empty expression'),
|
|
1159
1163
|
t
|
|
1160
1164
|
);
|
|
1161
|
-
case
|
|
1165
|
+
case tstt.jsxTagStart:
|
|
1162
1166
|
case tt.string:
|
|
1163
1167
|
return this.parseExprAtom();
|
|
1164
1168
|
default:
|
|
@@ -1179,7 +1183,7 @@ function RipplePlugin(config) {
|
|
|
1179
1183
|
}
|
|
1180
1184
|
|
|
1181
1185
|
if (this.type === tt._catch) {
|
|
1182
|
-
|
|
1186
|
+
const clause = this.startNode();
|
|
1183
1187
|
this.next();
|
|
1184
1188
|
if (this.eat(tt.parenL)) {
|
|
1185
1189
|
clause.param = this.parseCatchClauseParam();
|
|
@@ -1209,7 +1213,6 @@ function RipplePlugin(config) {
|
|
|
1209
1213
|
}
|
|
1210
1214
|
let out = '',
|
|
1211
1215
|
chunkStart = this.pos;
|
|
1212
|
-
const tok = this.acornTypeScript.tokTypes;
|
|
1213
1216
|
|
|
1214
1217
|
while (true) {
|
|
1215
1218
|
if (this.pos >= this.input.length) this.raise(this.start, 'Unterminated JSX contents');
|
|
@@ -1220,7 +1223,7 @@ function RipplePlugin(config) {
|
|
|
1220
1223
|
case 123: // '{'
|
|
1221
1224
|
if (ch === 60 && this.exprAllowed) {
|
|
1222
1225
|
++this.pos;
|
|
1223
|
-
return this.finishToken(
|
|
1226
|
+
return this.finishToken(tstt.jsxTagStart);
|
|
1224
1227
|
}
|
|
1225
1228
|
if (ch === 123 && this.exprAllowed) {
|
|
1226
1229
|
return this.getTokenFromCode(ch);
|
|
@@ -1359,7 +1362,6 @@ function RipplePlugin(config) {
|
|
|
1359
1362
|
const inside_head = this.#path.findLast(
|
|
1360
1363
|
(n) => n.type === 'Element' && n.id.type === 'Identifier' && n.id.name === 'head',
|
|
1361
1364
|
);
|
|
1362
|
-
const tok = this.acornTypeScript.tokTypes;
|
|
1363
1365
|
// Adjust the start so we capture the `<` as part of the element
|
|
1364
1366
|
const prev_pos = this.pos;
|
|
1365
1367
|
this.pos = this.start - 1;
|
|
@@ -1444,7 +1446,7 @@ function RipplePlugin(config) {
|
|
|
1444
1446
|
}
|
|
1445
1447
|
this.pos = start + content.length + 1;
|
|
1446
1448
|
|
|
1447
|
-
this.type =
|
|
1449
|
+
this.type = tstt.jsxTagStart;
|
|
1448
1450
|
this.next();
|
|
1449
1451
|
if (this.value === '/') {
|
|
1450
1452
|
this.next();
|
|
@@ -1481,7 +1483,7 @@ function RipplePlugin(config) {
|
|
|
1481
1483
|
}
|
|
1482
1484
|
this.pos = start + content.length + 1;
|
|
1483
1485
|
|
|
1484
|
-
this.type =
|
|
1486
|
+
this.type = tstt.jsxTagStart;
|
|
1485
1487
|
this.next();
|
|
1486
1488
|
if (this.value === '/') {
|
|
1487
1489
|
this.next();
|
|
@@ -1495,10 +1497,9 @@ function RipplePlugin(config) {
|
|
|
1495
1497
|
element.children = [parsed_css];
|
|
1496
1498
|
|
|
1497
1499
|
// Ensure we escape JSX <tag></tag> context
|
|
1498
|
-
const tokContexts = this.acornTypeScript.tokContexts;
|
|
1499
1500
|
const curContext = this.curContext();
|
|
1500
1501
|
|
|
1501
|
-
if (curContext ===
|
|
1502
|
+
if (curContext === tstc.tc_expr) {
|
|
1502
1503
|
this.context.pop();
|
|
1503
1504
|
}
|
|
1504
1505
|
|
|
@@ -1535,7 +1536,7 @@ function RipplePlugin(config) {
|
|
|
1535
1536
|
raise_error();
|
|
1536
1537
|
}
|
|
1537
1538
|
this.next();
|
|
1538
|
-
if (this.type
|
|
1539
|
+
if (this.type !== tstt.jsxTagEnd) {
|
|
1539
1540
|
raise_error();
|
|
1540
1541
|
}
|
|
1541
1542
|
this.next();
|
|
@@ -1551,10 +1552,9 @@ function RipplePlugin(config) {
|
|
|
1551
1552
|
}
|
|
1552
1553
|
}
|
|
1553
1554
|
// Ensure we escape JSX <tag></tag> context
|
|
1554
|
-
const tokContexts = this.acornTypeScript.tokContexts;
|
|
1555
1555
|
const curContext = this.curContext();
|
|
1556
1556
|
|
|
1557
|
-
if (curContext ===
|
|
1557
|
+
if (curContext === tstc.tc_expr) {
|
|
1558
1558
|
this.context.pop();
|
|
1559
1559
|
}
|
|
1560
1560
|
}
|
|
@@ -1584,23 +1584,28 @@ function RipplePlugin(config) {
|
|
|
1584
1584
|
this.exprAllowed = true;
|
|
1585
1585
|
|
|
1586
1586
|
while (true) {
|
|
1587
|
-
const node = super.parseExpression();
|
|
1588
|
-
body.push(node);
|
|
1589
|
-
|
|
1590
1587
|
if (this.input.slice(this.pos, this.pos + 5) === '/tsx:') {
|
|
1591
1588
|
return;
|
|
1592
1589
|
}
|
|
1590
|
+
|
|
1591
|
+
if (this.type === tt.braceL) {
|
|
1592
|
+
const node = this.jsx_parseExpressionContainer();
|
|
1593
|
+
body.push(node);
|
|
1594
|
+
} else {
|
|
1595
|
+
// Parse regular JSX expression (JSXElement, JSXFragment, etc.)
|
|
1596
|
+
const node = super.parseExpression();
|
|
1597
|
+
body.push(node);
|
|
1598
|
+
}
|
|
1593
1599
|
}
|
|
1594
1600
|
}
|
|
1595
|
-
|
|
1596
|
-
if (this.type.label === '{') {
|
|
1601
|
+
if (this.type === tt.braceL) {
|
|
1597
1602
|
const node = this.jsx_parseExpressionContainer();
|
|
1598
1603
|
node.type = node.html ? 'Html' : 'Text';
|
|
1599
1604
|
delete node.html;
|
|
1600
1605
|
body.push(node);
|
|
1601
|
-
} else if (this.type
|
|
1606
|
+
} else if (this.type === tt.braceR) {
|
|
1602
1607
|
return;
|
|
1603
|
-
} else if (this.type
|
|
1608
|
+
} else if (this.type === tstt.jsxTagStart) {
|
|
1604
1609
|
this.next();
|
|
1605
1610
|
if (this.value === '/') {
|
|
1606
1611
|
this.next();
|
|
@@ -1658,7 +1663,7 @@ function RipplePlugin(config) {
|
|
|
1658
1663
|
|
|
1659
1664
|
// Ensure we're not in JSX context before recursing
|
|
1660
1665
|
// This is important when elements are parsed at statement level
|
|
1661
|
-
if (this.curContext() ===
|
|
1666
|
+
if (this.curContext() === tstc.tc_expr) {
|
|
1662
1667
|
this.context.pop();
|
|
1663
1668
|
}
|
|
1664
1669
|
}
|
|
@@ -1667,14 +1672,12 @@ function RipplePlugin(config) {
|
|
|
1667
1672
|
}
|
|
1668
1673
|
|
|
1669
1674
|
parseStatement(context, topLevel, exports) {
|
|
1670
|
-
const tok = this.acornTypeScript.tokContexts;
|
|
1671
|
-
|
|
1672
1675
|
if (
|
|
1673
1676
|
context !== 'for' &&
|
|
1674
1677
|
context !== 'if' &&
|
|
1675
1678
|
this.context.at(-1) === tc.b_stat &&
|
|
1676
1679
|
this.type === tt.braceL &&
|
|
1677
|
-
this.context.some((c) => c ===
|
|
1680
|
+
this.context.some((c) => c === tstt.tc_expr)
|
|
1678
1681
|
) {
|
|
1679
1682
|
this.next();
|
|
1680
1683
|
const node = this.jsx_parseExpressionContainer();
|
|
@@ -1739,7 +1742,7 @@ function RipplePlugin(config) {
|
|
|
1739
1742
|
}
|
|
1740
1743
|
}
|
|
1741
1744
|
|
|
1742
|
-
if (this.type
|
|
1745
|
+
if (this.type === tstt.jsxTagStart) {
|
|
1743
1746
|
this.next();
|
|
1744
1747
|
if (this.value === '/') {
|
|
1745
1748
|
this.unexpected();
|
|
@@ -1925,7 +1928,11 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
1925
1928
|
// Handle JSXEmptyExpression - these represent {/* comment */} in JSX
|
|
1926
1929
|
if (node.type === 'JSXEmptyExpression') {
|
|
1927
1930
|
// Collect all comments that fall within this JSXEmptyExpression
|
|
1928
|
-
while (
|
|
1931
|
+
while (
|
|
1932
|
+
comments[0] &&
|
|
1933
|
+
comments[0].start >= node.start &&
|
|
1934
|
+
comments[0].end <= node.end
|
|
1935
|
+
) {
|
|
1929
1936
|
comment = /** @type {CommentWithLocation} */ (comments.shift());
|
|
1930
1937
|
(node.innerComments ||= []).push(comment);
|
|
1931
1938
|
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
TEMPLATE_SVG_NAMESPACE,
|
|
16
16
|
TEMPLATE_MATHML_NAMESPACE,
|
|
17
17
|
} from '../../../../constants.js';
|
|
18
|
+
import { DEFAULT_NAMESPACE } from '../../../../runtime/internal/client/constants.js';
|
|
18
19
|
import { sanitize_template_string } from '../../../../utils/sanitize_template_string.js';
|
|
19
20
|
import {
|
|
20
21
|
is_inside_component,
|
|
@@ -1236,31 +1237,36 @@ const visitors = {
|
|
|
1236
1237
|
// We visit, but only to gather metadata
|
|
1237
1238
|
b.call(visit(node.id, { ...state, metadata }));
|
|
1238
1239
|
|
|
1240
|
+
// We're calling a component from within svg/mathml context
|
|
1241
|
+
const is_with_ns = state.namespace !== DEFAULT_NAMESPACE;
|
|
1242
|
+
|
|
1239
1243
|
if (metadata.tracking) {
|
|
1244
|
+
const shared = b.call(
|
|
1245
|
+
'_$_.composite',
|
|
1246
|
+
b.thunk(visit(node.id, state)),
|
|
1247
|
+
id,
|
|
1248
|
+
is_spreading
|
|
1249
|
+
? b.call('_$_.spread_props', b.thunk(b.object(props)), b.id('__block'))
|
|
1250
|
+
: b.object(props),
|
|
1251
|
+
);
|
|
1240
1252
|
state.init.push(
|
|
1241
|
-
|
|
1242
|
-
b.call(
|
|
1243
|
-
|
|
1244
|
-
b.thunk(visit(node.id, state)),
|
|
1245
|
-
id,
|
|
1246
|
-
is_spreading
|
|
1247
|
-
? b.call('_$_.spread_props', b.thunk(b.object(props)), b.id('__block'))
|
|
1248
|
-
: b.object(props),
|
|
1249
|
-
),
|
|
1250
|
-
),
|
|
1253
|
+
is_with_ns
|
|
1254
|
+
? b.call('_$_.with_ns', b.literal(state.namespace), b.thunk(shared))
|
|
1255
|
+
: b.stmt(shared),
|
|
1251
1256
|
);
|
|
1252
1257
|
} else {
|
|
1258
|
+
const shared = b.call(
|
|
1259
|
+
visit(node.id, state),
|
|
1260
|
+
id,
|
|
1261
|
+
is_spreading
|
|
1262
|
+
? b.call('_$_.spread_props', b.thunk(b.object(props)), b.id('__block'))
|
|
1263
|
+
: b.object(props),
|
|
1264
|
+
b.id('_$_.active_block'),
|
|
1265
|
+
);
|
|
1253
1266
|
state.init.push(
|
|
1254
|
-
|
|
1255
|
-
b.call(
|
|
1256
|
-
|
|
1257
|
-
id,
|
|
1258
|
-
is_spreading
|
|
1259
|
-
? b.call('_$_.spread_props', b.thunk(b.object(props)), b.id('__block'))
|
|
1260
|
-
: b.object(props),
|
|
1261
|
-
b.id('_$_.active_block'),
|
|
1262
|
-
),
|
|
1263
|
-
),
|
|
1267
|
+
is_with_ns
|
|
1268
|
+
? b.call('_$_.with_ns', b.literal(state.namespace), b.thunk(shared))
|
|
1269
|
+
: b.stmt(shared),
|
|
1264
1270
|
);
|
|
1265
1271
|
}
|
|
1266
1272
|
}
|
|
@@ -2114,6 +2120,7 @@ function transform_children(children, context) {
|
|
|
2114
2120
|
node.type === 'TryStatement' ||
|
|
2115
2121
|
node.type === 'ForOfStatement' ||
|
|
2116
2122
|
node.type === 'SwitchStatement' ||
|
|
2123
|
+
node.type === 'TsxCompat' ||
|
|
2117
2124
|
node.type === 'Html' ||
|
|
2118
2125
|
(node.type === 'Element' &&
|
|
2119
2126
|
(node.id.type !== 'Identifier' || !is_element_dom_element(node))),
|
package/src/constants.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/** @import { Block, Component } from '#client' */
|
|
2
2
|
|
|
3
3
|
import { branch, destroy_block, render, render_spread } from './blocks.js';
|
|
4
|
-
import { COMPOSITE_BLOCK } from './constants.js';
|
|
5
|
-
import { active_block } from './runtime.js';
|
|
4
|
+
import { COMPOSITE_BLOCK, NAMESPACE_URI, DEFAULT_NAMESPACE } from './constants.js';
|
|
5
|
+
import { active_block, active_namespace } from './runtime.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @typedef {((anchor: Node, props: Record<string, any>, block: Block | null) => void)} ComponentFunction
|
|
@@ -36,9 +36,14 @@ export function composite(get_component, node, props) {
|
|
|
36
36
|
b = branch(() => {
|
|
37
37
|
var block = /** @type {Block} */ (active_block);
|
|
38
38
|
|
|
39
|
-
var element =
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
var element =
|
|
40
|
+
active_namespace !== DEFAULT_NAMESPACE
|
|
41
|
+
? document.createElementNS(
|
|
42
|
+
NAMESPACE_URI[active_namespace],
|
|
43
|
+
/** @type {keyof HTMLElementTagNameMap} */ (component),
|
|
44
|
+
)
|
|
45
|
+
: document.createElement(/** @type {keyof HTMLElementTagNameMap} */ (component));
|
|
46
|
+
|
|
42
47
|
/** @type {ChildNode} */ (anchor).before(element);
|
|
43
48
|
|
|
44
49
|
if (block.s === null) {
|
|
@@ -30,3 +30,9 @@ export var REF_PROP = 'ref';
|
|
|
30
30
|
/** @type {unique symbol} */
|
|
31
31
|
export const ARRAY_SET_INDEX_AT = Symbol();
|
|
32
32
|
export const MAX_ARRAY_LENGTH = 2 ** 32 - 1;
|
|
33
|
+
export const DEFAULT_NAMESPACE = 'html';
|
|
34
|
+
export const NAMESPACE_URI = {
|
|
35
|
+
html: 'http://www.w3.org/1999/xhtml',
|
|
36
|
+
svg: 'http://www.w3.org/2000/svg',
|
|
37
|
+
mathml: 'http://www.w3.org/1998/Math/MathML',
|
|
38
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** @import { Block, Component, Dependency, Derived, Tracked } from '#client' */
|
|
2
|
+
/** @import { NAMESPACE_URI } from './constants.js' */
|
|
2
3
|
|
|
3
4
|
import { DEV } from 'esm-env';
|
|
4
5
|
import {
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
UNINITIALIZED,
|
|
27
28
|
REF_PROP,
|
|
28
29
|
TRACKED_OBJECT,
|
|
30
|
+
DEFAULT_NAMESPACE,
|
|
29
31
|
} from './constants.js';
|
|
30
32
|
import { capture, suspend } from './try.js';
|
|
31
33
|
import {
|
|
@@ -48,6 +50,8 @@ export let active_reaction = null;
|
|
|
48
50
|
export let active_scope = null;
|
|
49
51
|
/** @type {null | Component} */
|
|
50
52
|
export let active_component = null;
|
|
53
|
+
/** @type {keyof NAMESPACE_URI} */
|
|
54
|
+
export let active_namespace = DEFAULT_NAMESPACE;
|
|
51
55
|
/** @type {boolean} */
|
|
52
56
|
export let is_mutating_allowed = true;
|
|
53
57
|
|
|
@@ -1163,6 +1167,22 @@ export function pop_component() {
|
|
|
1163
1167
|
active_component = component.p;
|
|
1164
1168
|
}
|
|
1165
1169
|
|
|
1170
|
+
/**
|
|
1171
|
+
* @template T
|
|
1172
|
+
* @param {() => T} fn
|
|
1173
|
+
* @param {keyof NAMESPACE_URI} namespace
|
|
1174
|
+
* @returns {T}
|
|
1175
|
+
*/
|
|
1176
|
+
export function with_ns(namespace, fn) {
|
|
1177
|
+
var previous_namespace = active_namespace;
|
|
1178
|
+
active_namespace = namespace;
|
|
1179
|
+
try {
|
|
1180
|
+
return fn();
|
|
1181
|
+
} finally {
|
|
1182
|
+
active_namespace = previous_namespace;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1166
1186
|
/**
|
|
1167
1187
|
* @returns {symbol}
|
|
1168
1188
|
*/
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
TEMPLATE_MATHML_NAMESPACE,
|
|
8
8
|
} from '../../../constants.js';
|
|
9
9
|
import { first_child, is_firefox } from './operations.js';
|
|
10
|
-
import { active_block } from './runtime.js';
|
|
10
|
+
import { active_block, active_namespace } from './runtime.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Assigns start and end nodes to the active block's state.
|
|
@@ -64,15 +64,16 @@ export function template(content, flags) {
|
|
|
64
64
|
var use_mathml_namespace = (flags & TEMPLATE_MATHML_NAMESPACE) !== 0;
|
|
65
65
|
/** @type {Node | DocumentFragment | undefined} */
|
|
66
66
|
var node;
|
|
67
|
-
var
|
|
67
|
+
var is_comment = content === '<!>';
|
|
68
|
+
var has_start = !is_comment && !content.startsWith('<!>');
|
|
68
69
|
|
|
69
70
|
return () => {
|
|
71
|
+
// If using runtime namespace, check active_namespace
|
|
72
|
+
var svg = !is_comment && (use_svg_namespace || active_namespace === 'svg');
|
|
73
|
+
var mathml = !is_comment && (use_mathml_namespace || active_namespace === 'mathml');
|
|
74
|
+
|
|
70
75
|
if (node === undefined) {
|
|
71
|
-
node = create_fragment_from_html(
|
|
72
|
-
has_start ? content : '<!>' + content,
|
|
73
|
-
use_svg_namespace,
|
|
74
|
-
use_mathml_namespace,
|
|
75
|
-
);
|
|
76
|
+
node = create_fragment_from_html(has_start ? content : '<!>' + content, svg, mathml);
|
|
76
77
|
if (!is_fragment) node = /** @type {Node} */ (first_child(node));
|
|
77
78
|
}
|
|
78
79
|
|
|
@@ -119,8 +120,9 @@ function from_namespace(content, ns = 'svg') {
|
|
|
119
120
|
var root = /** @type {Element} */ (first_child(fragment));
|
|
120
121
|
var result = document.createDocumentFragment();
|
|
121
122
|
|
|
122
|
-
|
|
123
|
-
|
|
123
|
+
var first;
|
|
124
|
+
while ((first = first_child(root))) {
|
|
125
|
+
result.appendChild(/** @type {Node} */ (first));
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
return result;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { track } from 'ripple';
|
|
2
|
+
|
|
1
3
|
describe('SVG namespace handling', () => {
|
|
2
4
|
it('should render static SVG elements with correct namespace', () => {
|
|
3
5
|
component App() {
|
|
@@ -228,6 +230,107 @@ describe('SVG namespace handling', () => {
|
|
|
228
230
|
expect(div.textContent).toBe('HTML inside SVG');
|
|
229
231
|
});
|
|
230
232
|
|
|
233
|
+
it('should render SVG with children as svg elements', () => {
|
|
234
|
+
component SVG({ children }) {
|
|
235
|
+
<svg width={20} height={20} fill="blue" viewBox="0 0 30 10" preserveAspectRatio="none">
|
|
236
|
+
<children />
|
|
237
|
+
</svg>
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
component App() {
|
|
241
|
+
let isDiamond = true;
|
|
242
|
+
<SVG>
|
|
243
|
+
if (isDiamond) {
|
|
244
|
+
<polygon points="0,0 30,0 15,10" />
|
|
245
|
+
} else {
|
|
246
|
+
<polygon points="0,0 30,0 15,10" />
|
|
247
|
+
}
|
|
248
|
+
</SVG>
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
render(App);
|
|
252
|
+
|
|
253
|
+
const svg = container.querySelector('svg');
|
|
254
|
+
const polygon = container.querySelector('polygon');
|
|
255
|
+
|
|
256
|
+
expect(svg.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
257
|
+
expect(polygon.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should render SVG with props as svg elements', () => {
|
|
261
|
+
component SVG({ Polygon }) {
|
|
262
|
+
<svg width={20} height={20} fill="blue" viewBox="0 0 30 10" preserveAspectRatio="none">
|
|
263
|
+
<Polygon />
|
|
264
|
+
</svg>
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
component App() {
|
|
268
|
+
<SVG {Polygon} />
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
component Polygon() {
|
|
272
|
+
<polygon points="0,0 30,0 15,10" />
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
render(App);
|
|
276
|
+
|
|
277
|
+
const svg = container.querySelector('svg');
|
|
278
|
+
const polygon = container.querySelector('polygon');
|
|
279
|
+
|
|
280
|
+
expect(svg.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
281
|
+
expect(polygon.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should render SVG with children as dynamic elements', () => {
|
|
285
|
+
component SVG({ children }) {
|
|
286
|
+
<svg width={20} height={20} fill="blue" viewBox="0 0 30 10" preserveAspectRatio="none">
|
|
287
|
+
<children />
|
|
288
|
+
</svg>
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
component App() {
|
|
292
|
+
let dynTag = track('polygon');
|
|
293
|
+
<SVG>
|
|
294
|
+
<@dynTag points="0,0 30,0 15,10" />
|
|
295
|
+
</SVG>
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
render(App);
|
|
299
|
+
|
|
300
|
+
const svg = container.querySelector('svg');
|
|
301
|
+
const polygon = container.querySelector('polygon');
|
|
302
|
+
|
|
303
|
+
expect(svg.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
304
|
+
expect(polygon.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('should render SVG with children as dynamic components', () => {
|
|
308
|
+
component SVG({ children }) {
|
|
309
|
+
<svg width={20} height={20} fill="blue" viewBox="0 0 30 10" preserveAspectRatio="none">
|
|
310
|
+
<children />
|
|
311
|
+
</svg>
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
component Polygon({ points }) {
|
|
315
|
+
<polygon {points} />
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
component App() {
|
|
319
|
+
let Component = track(() => Polygon);
|
|
320
|
+
<SVG>
|
|
321
|
+
<@Component points="0,0 30,0 15,10" />
|
|
322
|
+
</SVG>
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
render(App);
|
|
326
|
+
|
|
327
|
+
const svg = container.querySelector('svg');
|
|
328
|
+
const polygon = container.querySelector('polygon');
|
|
329
|
+
|
|
330
|
+
expect(svg.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
331
|
+
expect(polygon.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
332
|
+
});
|
|
333
|
+
|
|
231
334
|
it('should compare static vs dynamic SVG rendering (original problem case)', () => {
|
|
232
335
|
component App() {
|
|
233
336
|
const d = [
|