ripple 0.2.145 → 0.2.147
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 +36 -12
- package/src/compiler/phases/3-transform/client/index.js +135 -19
- package/src/compiler/utils.js +17 -0
- package/src/runtime/internal/client/blocks.js +3 -2
- package/src/runtime/internal/client/composite.js +46 -42
- package/src/runtime/internal/client/for.js +34 -26
- package/src/runtime/internal/client/head.js +1 -1
- package/src/runtime/internal/client/if.js +11 -7
- package/src/runtime/internal/client/operations.js +0 -2
- package/src/runtime/internal/client/render.js +19 -37
- package/src/runtime/internal/client/runtime.js +42 -38
- package/src/runtime/internal/client/switch.js +16 -12
- package/tests/client/svg.test.ripple +72 -25
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.147",
|
|
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.147"
|
|
85
85
|
}
|
|
86
86
|
}
|
|
@@ -39,7 +39,10 @@ const regex_whitespace_only = /\s/;
|
|
|
39
39
|
*/
|
|
40
40
|
function skipWhitespace(parser) {
|
|
41
41
|
const originalStart = parser.start;
|
|
42
|
-
while (
|
|
42
|
+
while (
|
|
43
|
+
parser.start < parser.input.length &&
|
|
44
|
+
regex_whitespace_only.test(parser.input[parser.start])
|
|
45
|
+
) {
|
|
43
46
|
parser.start++;
|
|
44
47
|
}
|
|
45
48
|
// Update line tracking if whitespace was skipped
|
|
@@ -973,6 +976,15 @@ function RipplePlugin(config) {
|
|
|
973
976
|
return this.finishNode(node, 'JSXExpressionContainer');
|
|
974
977
|
}
|
|
975
978
|
|
|
979
|
+
jsx_parseEmptyExpression() {
|
|
980
|
+
// Override to properly handle the range for JSXEmptyExpression
|
|
981
|
+
// The range should be from after { to before }
|
|
982
|
+
const node = this.startNodeAt(this.lastTokEnd, this.lastTokEndLoc);
|
|
983
|
+
node.end = this.start;
|
|
984
|
+
node.loc.end = this.startLoc;
|
|
985
|
+
return this.finishNodeAt(node, 'JSXEmptyExpression', this.start, this.startLoc);
|
|
986
|
+
}
|
|
987
|
+
|
|
976
988
|
jsx_parseTupleContainer() {
|
|
977
989
|
var t = this.startNode();
|
|
978
990
|
return (
|
|
@@ -1093,7 +1105,8 @@ function RipplePlugin(config) {
|
|
|
1093
1105
|
// Check if the next character after @ is [
|
|
1094
1106
|
const nextChar = this.input.charCodeAt(this.pos);
|
|
1095
1107
|
|
|
1096
|
-
if (nextChar === 91) {
|
|
1108
|
+
if (nextChar === 91) {
|
|
1109
|
+
// [ character
|
|
1097
1110
|
memberExpr.computed = true;
|
|
1098
1111
|
|
|
1099
1112
|
// Consume the @ token
|
|
@@ -1142,7 +1155,7 @@ function RipplePlugin(config) {
|
|
|
1142
1155
|
var t = this.jsx_parseExpressionContainer();
|
|
1143
1156
|
return (
|
|
1144
1157
|
'JSXEmptyExpression' === t.expression.type &&
|
|
1145
|
-
|
|
1158
|
+
this.raise(t.start, 'attributes must only be assigned a non-empty expression'),
|
|
1146
1159
|
t
|
|
1147
1160
|
);
|
|
1148
1161
|
case tok.jsxTagStart:
|
|
@@ -1315,14 +1328,14 @@ function RipplePlugin(config) {
|
|
|
1315
1328
|
this.raise(
|
|
1316
1329
|
this.pos,
|
|
1317
1330
|
'Unexpected token `' +
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1331
|
+
this.input[this.pos] +
|
|
1332
|
+
'`. Did you mean `' +
|
|
1333
|
+
(ch === 62 ? '>' : '}') +
|
|
1334
|
+
'` or ' +
|
|
1335
|
+
'`{"' +
|
|
1336
|
+
this.input[this.pos] +
|
|
1337
|
+
'"}' +
|
|
1338
|
+
'`?',
|
|
1326
1339
|
);
|
|
1327
1340
|
}
|
|
1328
1341
|
|
|
@@ -1909,6 +1922,17 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
1909
1922
|
return;
|
|
1910
1923
|
}
|
|
1911
1924
|
}
|
|
1925
|
+
// Handle JSXEmptyExpression - these represent {/* comment */} in JSX
|
|
1926
|
+
if (node.type === 'JSXEmptyExpression') {
|
|
1927
|
+
// Collect all comments that fall within this JSXEmptyExpression
|
|
1928
|
+
while (comments[0] && comments[0].start >= node.start && comments[0].end <= node.end) {
|
|
1929
|
+
comment = /** @type {CommentWithLocation} */ (comments.shift());
|
|
1930
|
+
(node.innerComments ||= []).push(comment);
|
|
1931
|
+
}
|
|
1932
|
+
if (node.innerComments && node.innerComments.length > 0) {
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1912
1936
|
// Handle empty Element nodes the same way as empty BlockStatements
|
|
1913
1937
|
if (node.type === 'Element' && (!node.children || node.children.length === 0)) {
|
|
1914
1938
|
if (comments[0].start < node.end && comments[0].end < node.end) {
|
|
@@ -1979,7 +2003,7 @@ function get_comment_handlers(source, comments, index = 0) {
|
|
|
1979
2003
|
const nextChar = getNextNonWhitespaceCharacter(source, potentialComment.end);
|
|
1980
2004
|
if (nextChar === ')') {
|
|
1981
2005
|
(node.trailingComments ||= []).push(
|
|
1982
|
-
/** @type {CommentWithLocation} */(comments.shift()),
|
|
2006
|
+
/** @type {CommentWithLocation} */ (comments.shift()),
|
|
1983
2007
|
);
|
|
1984
2008
|
continue;
|
|
1985
2009
|
}
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
normalize_children,
|
|
36
36
|
build_getter,
|
|
37
37
|
determine_namespace_for_children,
|
|
38
|
+
index_to_key,
|
|
38
39
|
} from '../../../utils.js';
|
|
39
40
|
import is_reference from 'is-reference';
|
|
40
41
|
import { object } from '../../../../utils/ast.js';
|
|
@@ -127,7 +128,77 @@ function visit_head_element(node, context) {
|
|
|
127
128
|
|
|
128
129
|
if (init.length > 0 || update.length > 0 || final.length > 0) {
|
|
129
130
|
context.state.init.push(
|
|
130
|
-
b.call(
|
|
131
|
+
b.call(
|
|
132
|
+
'_$_.head',
|
|
133
|
+
b.arrow(
|
|
134
|
+
[b.id('__anchor')],
|
|
135
|
+
b.block([
|
|
136
|
+
...init,
|
|
137
|
+
...update.map((u) => {
|
|
138
|
+
debugger;
|
|
139
|
+
}),
|
|
140
|
+
...final,
|
|
141
|
+
]),
|
|
142
|
+
),
|
|
143
|
+
),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function apply_updates(init, update) {
|
|
149
|
+
if (update.length === 1) {
|
|
150
|
+
init.push(
|
|
151
|
+
b.stmt(
|
|
152
|
+
b.call(
|
|
153
|
+
'_$_.render',
|
|
154
|
+
b.thunk(
|
|
155
|
+
b.block(
|
|
156
|
+
update.map((u) => {
|
|
157
|
+
if (u.initial) {
|
|
158
|
+
return u.operation(u.expression);
|
|
159
|
+
}
|
|
160
|
+
return u.operation;
|
|
161
|
+
}),
|
|
162
|
+
),
|
|
163
|
+
!!update.async,
|
|
164
|
+
),
|
|
165
|
+
),
|
|
166
|
+
),
|
|
167
|
+
);
|
|
168
|
+
} else {
|
|
169
|
+
const index_map = new Map();
|
|
170
|
+
const initial = [];
|
|
171
|
+
const render_statements = [];
|
|
172
|
+
let index = 0;
|
|
173
|
+
|
|
174
|
+
for (const u of update) {
|
|
175
|
+
if (u.initial) {
|
|
176
|
+
const key = index_to_key(index);
|
|
177
|
+
index_map.set(u.operation, key);
|
|
178
|
+
initial.push(b.prop('init', b.id(key), u.initial));
|
|
179
|
+
render_statements.push(
|
|
180
|
+
b.var('__' + key, u.expression),
|
|
181
|
+
b.if(
|
|
182
|
+
b.binary('!==', b.member(b.id('__prev'), b.id(key)), b.id('__' + key)),
|
|
183
|
+
b.block([
|
|
184
|
+
u.operation(b.assignment('=', b.member(b.id('__prev'), b.id(key)), b.id('__' + key))),
|
|
185
|
+
]),
|
|
186
|
+
),
|
|
187
|
+
);
|
|
188
|
+
index++;
|
|
189
|
+
} else {
|
|
190
|
+
render_statements.push(u.operation);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
init.push(
|
|
195
|
+
b.stmt(
|
|
196
|
+
b.call(
|
|
197
|
+
'_$_.render',
|
|
198
|
+
b.arrow([b.id('__prev')], b.block(render_statements), !!update.async),
|
|
199
|
+
b.object(initial),
|
|
200
|
+
),
|
|
201
|
+
),
|
|
131
202
|
);
|
|
132
203
|
}
|
|
133
204
|
}
|
|
@@ -862,7 +933,7 @@ const visitors = {
|
|
|
862
933
|
const expression = visit(attr.value, { ...state, metadata });
|
|
863
934
|
|
|
864
935
|
if (metadata.tracking) {
|
|
865
|
-
local_updates.push(b.stmt(b.call('_$_.set_value', id, expression)));
|
|
936
|
+
local_updates.push({ operation: b.stmt(b.call('_$_.set_value', id, expression)) });
|
|
866
937
|
} else {
|
|
867
938
|
state.init.push(b.stmt(b.call('_$_.set_value', id, expression)));
|
|
868
939
|
}
|
|
@@ -876,7 +947,9 @@ const visitors = {
|
|
|
876
947
|
const expression = visit(attr.value, { ...state, metadata });
|
|
877
948
|
|
|
878
949
|
if (name === '$checked' || metadata.tracking) {
|
|
879
|
-
local_updates.push(
|
|
950
|
+
local_updates.push({
|
|
951
|
+
operation: b.stmt(b.call('_$_.set_checked', id, expression)),
|
|
952
|
+
});
|
|
880
953
|
} else {
|
|
881
954
|
state.init.push(b.stmt(b.call('_$_.set_checked', id, expression)));
|
|
882
955
|
}
|
|
@@ -889,7 +962,9 @@ const visitors = {
|
|
|
889
962
|
const expression = visit(attr.value, { ...state, metadata });
|
|
890
963
|
|
|
891
964
|
if (metadata.tracking) {
|
|
892
|
-
local_updates.push(
|
|
965
|
+
local_updates.push({
|
|
966
|
+
operation: b.stmt(b.call('_$_.set_selected', id, expression)),
|
|
967
|
+
});
|
|
893
968
|
} else {
|
|
894
969
|
state.init.push(b.stmt(b.call('_$_.set_selected', id, expression)));
|
|
895
970
|
}
|
|
@@ -968,11 +1043,15 @@ const visitors = {
|
|
|
968
1043
|
const id = state.flush_node();
|
|
969
1044
|
|
|
970
1045
|
if (is_dom_property(attribute)) {
|
|
971
|
-
local_updates.push(
|
|
1046
|
+
local_updates.push({
|
|
1047
|
+
operation: b.stmt(b.assignment('=', b.member(id, attribute), expression)),
|
|
1048
|
+
});
|
|
972
1049
|
} else {
|
|
973
|
-
local_updates.push(
|
|
974
|
-
b.stmt(
|
|
975
|
-
|
|
1050
|
+
local_updates.push({
|
|
1051
|
+
operation: b.stmt(
|
|
1052
|
+
b.call('_$_.set_attribute', id, b.literal(attribute), expression),
|
|
1053
|
+
),
|
|
1054
|
+
});
|
|
976
1055
|
}
|
|
977
1056
|
} else {
|
|
978
1057
|
const id = state.flush_node();
|
|
@@ -1012,9 +1091,12 @@ const visitors = {
|
|
|
1012
1091
|
const is_html = context.state.namespace === 'html' && node.id.name !== 'svg';
|
|
1013
1092
|
|
|
1014
1093
|
if (metadata.tracking) {
|
|
1015
|
-
local_updates.push(
|
|
1016
|
-
|
|
1017
|
-
|
|
1094
|
+
local_updates.push({
|
|
1095
|
+
operation: (key) =>
|
|
1096
|
+
b.stmt(b.call('_$_.set_class', id, key, hash_arg, b.literal(is_html))),
|
|
1097
|
+
expression,
|
|
1098
|
+
initial: b.literal(''),
|
|
1099
|
+
});
|
|
1018
1100
|
} else {
|
|
1019
1101
|
state.init.push(
|
|
1020
1102
|
b.stmt(b.call('_$_.set_class', id, expression, hash_arg, b.literal(is_html))),
|
|
@@ -1037,7 +1119,7 @@ const visitors = {
|
|
|
1037
1119
|
const statement = b.stmt(b.call('_$_.set_attribute', id, b.literal(name), expression));
|
|
1038
1120
|
|
|
1039
1121
|
if (metadata.tracking) {
|
|
1040
|
-
local_updates.push(statement);
|
|
1122
|
+
local_updates.push({ operation: statement });
|
|
1041
1123
|
} else {
|
|
1042
1124
|
state.init.push(statement);
|
|
1043
1125
|
}
|
|
@@ -1069,7 +1151,7 @@ const visitors = {
|
|
|
1069
1151
|
|
|
1070
1152
|
if (update.length > 0) {
|
|
1071
1153
|
if (state.scope.parent.declarations.size > 0) {
|
|
1072
|
-
init
|
|
1154
|
+
apply_updates(init, update);
|
|
1073
1155
|
} else {
|
|
1074
1156
|
state.update.push(...update);
|
|
1075
1157
|
}
|
|
@@ -2168,8 +2250,8 @@ function transform_children(children, context) {
|
|
|
2168
2250
|
context.state.template.push('<!>');
|
|
2169
2251
|
|
|
2170
2252
|
const id = flush_node();
|
|
2171
|
-
state.update.push(
|
|
2172
|
-
b.stmt(
|
|
2253
|
+
state.update.push({
|
|
2254
|
+
operation: b.stmt(
|
|
2173
2255
|
b.call(
|
|
2174
2256
|
'_$_.html',
|
|
2175
2257
|
id,
|
|
@@ -2178,7 +2260,7 @@ function transform_children(children, context) {
|
|
|
2178
2260
|
state.namespace === 'mathml' && b.true,
|
|
2179
2261
|
),
|
|
2180
2262
|
),
|
|
2181
|
-
);
|
|
2263
|
+
});
|
|
2182
2264
|
} else if (node.type === 'Text') {
|
|
2183
2265
|
const metadata = { tracking: false, await: false };
|
|
2184
2266
|
const expression = visit(node.expression, { ...state, metadata });
|
|
@@ -2186,7 +2268,11 @@ function transform_children(children, context) {
|
|
|
2186
2268
|
if (metadata.tracking) {
|
|
2187
2269
|
state.template.push(' ');
|
|
2188
2270
|
const id = flush_node();
|
|
2189
|
-
state.update.push(
|
|
2271
|
+
state.update.push({
|
|
2272
|
+
operation: (key) => b.stmt(b.call('_$_.set_text', id, key)),
|
|
2273
|
+
expression,
|
|
2274
|
+
initial: b.literal(' '),
|
|
2275
|
+
});
|
|
2190
2276
|
if (metadata.await) {
|
|
2191
2277
|
state.update.async = true;
|
|
2192
2278
|
}
|
|
@@ -2203,7 +2289,11 @@ function transform_children(children, context) {
|
|
|
2203
2289
|
// Handle Text nodes in fragments
|
|
2204
2290
|
state.template.push(' ');
|
|
2205
2291
|
const id = flush_node();
|
|
2206
|
-
state.update.push(
|
|
2292
|
+
state.update.push({
|
|
2293
|
+
operation: (key) => b.stmt(b.call('_$_.set_text', id, key)),
|
|
2294
|
+
expression,
|
|
2295
|
+
initial: b.literal(' '),
|
|
2296
|
+
});
|
|
2207
2297
|
if (metadata.await) {
|
|
2208
2298
|
state.update.async = true;
|
|
2209
2299
|
}
|
|
@@ -2279,7 +2369,7 @@ function transform_body(body, { visit, state }) {
|
|
|
2279
2369
|
// In TypeScript mode, just add the update statements directly
|
|
2280
2370
|
body_state.init.push(...body_state.update);
|
|
2281
2371
|
} else {
|
|
2282
|
-
body_state.init
|
|
2372
|
+
apply_updates(body_state.init, body_state.update);
|
|
2283
2373
|
}
|
|
2284
2374
|
}
|
|
2285
2375
|
|
|
@@ -2335,6 +2425,32 @@ function create_tsx_with_typescript_support() {
|
|
|
2335
2425
|
|
|
2336
2426
|
return {
|
|
2337
2427
|
...base_tsx,
|
|
2428
|
+
// Custom handler for Property nodes to prevent method shorthand for components
|
|
2429
|
+
// When a component is transformed to a FunctionExpression, we want to preserve
|
|
2430
|
+
// the explicit syntax (key: function name() {}) instead of method shorthand (key() {})
|
|
2431
|
+
// This ensures proper source mapping for the 'function'/'component' keyword
|
|
2432
|
+
Property(node, context) {
|
|
2433
|
+
// Check if the value is a function that was originally a component
|
|
2434
|
+
const isComponent =
|
|
2435
|
+
node.value?.type === 'FunctionExpression' && node.value.metadata?.was_component;
|
|
2436
|
+
|
|
2437
|
+
if (isComponent) {
|
|
2438
|
+
// Manually print as non-method property to preserve 'function' keyword
|
|
2439
|
+
// This ensures esrap creates proper source map entries for the component->function transformation
|
|
2440
|
+
if (node.computed) {
|
|
2441
|
+
context.write('[');
|
|
2442
|
+
context.visit(node.key);
|
|
2443
|
+
context.write(']');
|
|
2444
|
+
} else {
|
|
2445
|
+
context.visit(node.key);
|
|
2446
|
+
}
|
|
2447
|
+
context.write(': ');
|
|
2448
|
+
context.visit(node.value);
|
|
2449
|
+
} else {
|
|
2450
|
+
// Use default handler for non-component properties
|
|
2451
|
+
base_tsx.Property(node, context);
|
|
2452
|
+
}
|
|
2453
|
+
},
|
|
2338
2454
|
// Custom handler for ArrayPattern to ensure typeAnnotation is visited
|
|
2339
2455
|
// esrap's TypeScript handler doesn't visit typeAnnotation for ArrayPattern (only for ObjectPattern)
|
|
2340
2456
|
ArrayPattern(node, context) {
|
package/src/compiler/utils.js
CHANGED
|
@@ -913,3 +913,20 @@ export function determine_namespace_for_children(element_name, current_namespace
|
|
|
913
913
|
|
|
914
914
|
return current_namespace;
|
|
915
915
|
}
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* Converts and index to a key string, where the starting character is a
|
|
919
|
+
* letter.
|
|
920
|
+
* @param {number} index
|
|
921
|
+
*/
|
|
922
|
+
export function index_to_key(index) {
|
|
923
|
+
const letters = 'abcdefghijklmnopqrstuvwxyz';
|
|
924
|
+
let key = '';
|
|
925
|
+
|
|
926
|
+
do {
|
|
927
|
+
key = letters[index % 26] + key;
|
|
928
|
+
index = Math.floor(index / 26) - 1;
|
|
929
|
+
} while (index >= 0);
|
|
930
|
+
|
|
931
|
+
return key;
|
|
932
|
+
}
|
|
@@ -60,10 +60,11 @@ export function effect(fn) {
|
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
62
|
* @param {Function} fn
|
|
63
|
+
* @param {any} [state]
|
|
63
64
|
* @param {number} [flags]
|
|
64
65
|
*/
|
|
65
|
-
export function render(fn, flags = 0) {
|
|
66
|
-
return block(RENDER_BLOCK | flags, fn);
|
|
66
|
+
export function render(fn, state, flags = 0) {
|
|
67
|
+
return block(RENDER_BLOCK | flags, fn, state);
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
/**
|
|
@@ -16,46 +16,50 @@ export function composite(get_component, node, props) {
|
|
|
16
16
|
/** @type {Block | null} */
|
|
17
17
|
var b = null;
|
|
18
18
|
|
|
19
|
-
render(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
block.s
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
19
|
+
render(
|
|
20
|
+
() => {
|
|
21
|
+
var component = get_component();
|
|
22
|
+
|
|
23
|
+
if (b !== null) {
|
|
24
|
+
destroy_block(b);
|
|
25
|
+
b = null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (typeof component === 'function') {
|
|
29
|
+
// Handle as regular component
|
|
30
|
+
b = branch(() => {
|
|
31
|
+
var block = active_block;
|
|
32
|
+
/** @type {ComponentFunction} */ (component)(anchor, props, block);
|
|
33
|
+
});
|
|
34
|
+
} else {
|
|
35
|
+
// Custom element
|
|
36
|
+
b = branch(() => {
|
|
37
|
+
var block = /** @type {Block} */ (active_block);
|
|
38
|
+
|
|
39
|
+
var element = document.createElement(
|
|
40
|
+
/** @type {keyof HTMLElementTagNameMap} */ (component),
|
|
41
|
+
);
|
|
42
|
+
/** @type {ChildNode} */ (anchor).before(element);
|
|
43
|
+
|
|
44
|
+
if (block.s === null) {
|
|
45
|
+
block.s = {
|
|
46
|
+
start: element,
|
|
47
|
+
end: element,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
render_spread(element, () => props || {});
|
|
52
|
+
|
|
53
|
+
if (typeof props?.children === 'function') {
|
|
54
|
+
var child_anchor = document.createComment('');
|
|
55
|
+
element.appendChild(child_anchor);
|
|
56
|
+
|
|
57
|
+
props?.children?.(child_anchor, {}, block);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
null,
|
|
63
|
+
COMPOSITE_BLOCK,
|
|
64
|
+
);
|
|
61
65
|
}
|
|
@@ -114,15 +114,19 @@ export function for_block(node, get_collection, render_fn, flags) {
|
|
|
114
114
|
anchor = node.appendChild(create_text());
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
render(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
117
|
+
render(
|
|
118
|
+
() => {
|
|
119
|
+
var block = /** @type {Block} */ (active_block);
|
|
120
|
+
var collection = get_collection();
|
|
121
|
+
var array = collection_to_array(collection);
|
|
122
|
+
|
|
123
|
+
untrack(() => {
|
|
124
|
+
reconcile_by_ref(anchor, block, array, render_fn, is_controlled, is_indexed);
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
null,
|
|
128
|
+
FOR_BLOCK,
|
|
129
|
+
);
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
/**
|
|
@@ -144,23 +148,27 @@ export function for_block_keyed(node, get_collection, render_fn, flags, get_key)
|
|
|
144
148
|
anchor = node.appendChild(create_text());
|
|
145
149
|
}
|
|
146
150
|
|
|
147
|
-
render(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
151
|
+
render(
|
|
152
|
+
() => {
|
|
153
|
+
var block = /** @type {Block} */ (active_block);
|
|
154
|
+
var collection = get_collection();
|
|
155
|
+
var array = collection_to_array(collection);
|
|
156
|
+
|
|
157
|
+
untrack(() => {
|
|
158
|
+
reconcile_by_key(
|
|
159
|
+
anchor,
|
|
160
|
+
block,
|
|
161
|
+
array,
|
|
162
|
+
render_fn,
|
|
163
|
+
is_controlled,
|
|
164
|
+
is_indexed,
|
|
165
|
+
/** @type {(item: V) => K} */ (get_key),
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
},
|
|
169
|
+
null,
|
|
170
|
+
FOR_BLOCK,
|
|
171
|
+
);
|
|
164
172
|
}
|
|
165
173
|
|
|
166
174
|
/**
|
|
@@ -36,11 +36,15 @@ export function if_block(node, fn) {
|
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
render(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
render(
|
|
40
|
+
() => {
|
|
41
|
+
has_branch = false;
|
|
42
|
+
fn(set_branch);
|
|
43
|
+
if (!has_branch) {
|
|
44
|
+
update_branch(null, null);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
null,
|
|
48
|
+
IF_BLOCK,
|
|
49
|
+
);
|
|
46
50
|
}
|
|
@@ -28,8 +28,6 @@ export function init_operations() {
|
|
|
28
28
|
// @ts-expect-error
|
|
29
29
|
element_prototype.__click = undefined;
|
|
30
30
|
// @ts-expect-error
|
|
31
|
-
element_prototype.__className = '';
|
|
32
|
-
// @ts-expect-error
|
|
33
31
|
element_prototype.__attributes = null;
|
|
34
32
|
// @ts-expect-error
|
|
35
33
|
element_prototype.__styles = null;
|
|
@@ -188,51 +188,33 @@ function set_attribute_helper(element, key, value) {
|
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
/**
|
|
192
|
-
* @param {import('clsx').ClassValue} value
|
|
193
|
-
* @param {string} [hash]
|
|
194
|
-
* @returns {string}
|
|
195
|
-
*/
|
|
196
|
-
function to_class(value, hash) {
|
|
197
|
-
return value == null
|
|
198
|
-
? (hash ?? '')
|
|
199
|
-
: // Fast-path for string values
|
|
200
|
-
typeof value === 'string'
|
|
201
|
-
? value + (hash ? ' ' + hash : '')
|
|
202
|
-
: clsx([value, hash]);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
191
|
/**
|
|
206
192
|
* @param {HTMLElement} dom
|
|
207
|
-
* @param {
|
|
193
|
+
* @param {string} value
|
|
208
194
|
* @param {string} [hash]
|
|
209
195
|
* @param {boolean} [is_html]
|
|
210
196
|
* @returns {void}
|
|
211
197
|
*/
|
|
212
198
|
export function set_class(dom, value, hash, is_html = true) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
199
|
+
var class_value =
|
|
200
|
+
value == null
|
|
201
|
+
? (hash ?? '')
|
|
202
|
+
: // Fast-path for string values
|
|
203
|
+
typeof value === 'string'
|
|
204
|
+
? value + (hash ? ' ' + hash : '')
|
|
205
|
+
: clsx([value, hash]);
|
|
206
|
+
|
|
207
|
+
// Removing the attribute when the value is only an empty string causes
|
|
208
|
+
// peformance issues vs simply making the className an empty string. So
|
|
209
|
+
// we should only remove the class if the the value is nullish.
|
|
210
|
+
if (value == null && hash === undefined) {
|
|
211
|
+
dom.removeAttribute('class');
|
|
212
|
+
} else {
|
|
213
|
+
if (is_html) {
|
|
214
|
+
dom.className = class_value;
|
|
215
|
+
} else {
|
|
216
|
+
dom.setAttribute('class', class_value);
|
|
232
217
|
}
|
|
233
|
-
|
|
234
|
-
// @ts-expect-error need to add __className to patched prototype
|
|
235
|
-
dom.__className = value;
|
|
236
218
|
}
|
|
237
219
|
}
|
|
238
220
|
|
|
@@ -255,7 +255,7 @@ export function run_block(block) {
|
|
|
255
255
|
|
|
256
256
|
tracking = (block.f & (ROOT_BLOCK | BRANCH_BLOCK)) === 0;
|
|
257
257
|
active_dependency = null;
|
|
258
|
-
var res = block.fn();
|
|
258
|
+
var res = block.fn(block.s);
|
|
259
259
|
|
|
260
260
|
if (typeof res === 'function') {
|
|
261
261
|
block.t = res;
|
|
@@ -499,51 +499,55 @@ export function async_computed(fn, block) {
|
|
|
499
499
|
/** @type {Map<Tracked, {v: any, c: number}>} */
|
|
500
500
|
var new_values = new Map();
|
|
501
501
|
|
|
502
|
-
render(
|
|
503
|
-
|
|
502
|
+
render(
|
|
503
|
+
() => {
|
|
504
|
+
var [current, deferred] = capture_deferred(() => (promise = fn()));
|
|
504
505
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
506
|
+
var restore = capture();
|
|
507
|
+
/** @type {(() => void) | undefined} */
|
|
508
|
+
var unuspend;
|
|
508
509
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
510
|
+
if (deferred === null) {
|
|
511
|
+
unuspend = suspend();
|
|
512
|
+
} else {
|
|
513
|
+
for (var i = 0; i < deferred.length; i++) {
|
|
514
|
+
var tracked = deferred[i];
|
|
515
|
+
new_values.set(tracked, { v: tracked.__v, c: tracked.c });
|
|
516
|
+
}
|
|
515
517
|
}
|
|
516
|
-
}
|
|
517
518
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
519
|
+
promise.then((v) => {
|
|
520
|
+
if (parent && is_destroyed(/** @type {Block} */ (parent))) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
if (promise === current && t.__v !== v) {
|
|
524
|
+
restore();
|
|
524
525
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
526
|
+
if (t.__v === UNINITIALIZED) {
|
|
527
|
+
t.__v = v;
|
|
528
|
+
} else {
|
|
529
|
+
set(t, v);
|
|
530
|
+
}
|
|
529
531
|
}
|
|
530
|
-
}
|
|
531
532
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
533
|
+
if (deferred === null) {
|
|
534
|
+
unuspend?.();
|
|
535
|
+
} else if (promise === current) {
|
|
536
|
+
for (var i = 0; i < deferred.length; i++) {
|
|
537
|
+
var tracked = deferred[i];
|
|
538
|
+
var stored = /** @type {{ v: any, c: number }} */ (new_values.get(tracked));
|
|
539
|
+
var { v, c } = stored;
|
|
540
|
+
tracked.__v = v;
|
|
541
|
+
tracked.c = c;
|
|
542
|
+
schedule_update(tracked.b);
|
|
543
|
+
}
|
|
544
|
+
new_values.clear();
|
|
542
545
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
546
|
+
});
|
|
547
|
+
},
|
|
548
|
+
null,
|
|
549
|
+
ASYNC_BLOCK,
|
|
550
|
+
);
|
|
547
551
|
|
|
548
552
|
return new Promise(async (resolve) => {
|
|
549
553
|
var p;
|
|
@@ -15,18 +15,22 @@ export function switch_block(node, fn) {
|
|
|
15
15
|
/** @type {Block | null} */
|
|
16
16
|
var b = null;
|
|
17
17
|
|
|
18
|
-
render(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
render(
|
|
19
|
+
() => {
|
|
20
|
+
const branch_fn = fn() ?? null;
|
|
21
|
+
if (current_branch === branch_fn) return;
|
|
22
|
+
current_branch = branch_fn;
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
if (b !== null) {
|
|
25
|
+
destroy_block(b);
|
|
26
|
+
b = null;
|
|
27
|
+
}
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
if (branch_fn !== null) {
|
|
30
|
+
b = branch(() => branch_fn(anchor));
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
null,
|
|
34
|
+
SWITCH_BLOCK,
|
|
35
|
+
);
|
|
32
36
|
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
describe('SVG namespace handling', () => {
|
|
2
2
|
it('should render static SVG elements with correct namespace', () => {
|
|
3
3
|
component App() {
|
|
4
|
-
<svg
|
|
4
|
+
<svg
|
|
5
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
6
|
+
width="24"
|
|
7
|
+
height="24"
|
|
8
|
+
fill="none"
|
|
9
|
+
stroke="currentColor"
|
|
10
|
+
>
|
|
5
11
|
<path d="m14 12 4 4 4-4" />
|
|
6
12
|
<circle cx="12" cy="12" r="4" />
|
|
7
13
|
<rect x="4" y="4" width="16" height="16" />
|
|
@@ -32,9 +38,20 @@ describe('SVG namespace handling', () => {
|
|
|
32
38
|
|
|
33
39
|
it('should render dynamic SVG paths with for loop (original issue)', () => {
|
|
34
40
|
component App() {
|
|
35
|
-
const d = [
|
|
41
|
+
const d = [
|
|
42
|
+
'm14 12 4 4 4-4',
|
|
43
|
+
'M18 16V7',
|
|
44
|
+
'm2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16',
|
|
45
|
+
'M3.304 13h6.392',
|
|
46
|
+
];
|
|
36
47
|
|
|
37
|
-
<svg
|
|
48
|
+
<svg
|
|
49
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
50
|
+
width="24"
|
|
51
|
+
height="24"
|
|
52
|
+
fill="none"
|
|
53
|
+
stroke="currentColor"
|
|
54
|
+
>
|
|
38
55
|
for (const pathData of d) {
|
|
39
56
|
<path d={pathData} />
|
|
40
57
|
}
|
|
@@ -52,7 +69,12 @@ describe('SVG namespace handling', () => {
|
|
|
52
69
|
|
|
53
70
|
// Critical test: dynamic paths should have correct SVG namespace
|
|
54
71
|
expect(paths.length).toBe(4);
|
|
55
|
-
const expectedPaths = [
|
|
72
|
+
const expectedPaths = [
|
|
73
|
+
'm14 12 4 4 4-4',
|
|
74
|
+
'M18 16V7',
|
|
75
|
+
'm2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16',
|
|
76
|
+
'M3.304 13h6.392',
|
|
77
|
+
];
|
|
56
78
|
paths.forEach((path, i) => {
|
|
57
79
|
expect(path.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
58
80
|
expect(path.getAttribute('d')).toBe(expectedPaths[i]);
|
|
@@ -60,7 +82,7 @@ describe('SVG namespace handling', () => {
|
|
|
60
82
|
|
|
61
83
|
// Verify paths are actually SVG elements (should have getBBox method)
|
|
62
84
|
// Note: getBBox might not work in test environment, so just check namespace
|
|
63
|
-
paths.forEach(path => {
|
|
85
|
+
paths.forEach((path) => {
|
|
64
86
|
expect(path.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
65
87
|
expect(path.tagName.toLowerCase()).toBe('path');
|
|
66
88
|
});
|
|
@@ -68,9 +90,15 @@ describe('SVG namespace handling', () => {
|
|
|
68
90
|
|
|
69
91
|
it('should handle mixed static and dynamic SVG elements', () => {
|
|
70
92
|
component App() {
|
|
71
|
-
const dynamicPaths = [
|
|
72
|
-
|
|
73
|
-
<svg
|
|
93
|
+
const dynamicPaths = ['M12 2L2 7v10c0 5.55 3.84 10 9 11 5.16-1 9-5.45 9-11V7l-10-5z'];
|
|
94
|
+
|
|
95
|
+
<svg
|
|
96
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
97
|
+
width="24"
|
|
98
|
+
height="24"
|
|
99
|
+
fill="none"
|
|
100
|
+
stroke="currentColor"
|
|
101
|
+
>
|
|
74
102
|
<circle cx="12" cy="12" r="10" />
|
|
75
103
|
for (const pathData of dynamicPaths) {
|
|
76
104
|
<path d={pathData} />
|
|
@@ -93,14 +121,16 @@ describe('SVG namespace handling', () => {
|
|
|
93
121
|
expect(rect.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
94
122
|
|
|
95
123
|
// Verify content
|
|
96
|
-
expect(path.getAttribute('d')).toBe(
|
|
124
|
+
expect(path.getAttribute('d')).toBe(
|
|
125
|
+
'M12 2L2 7v10c0 5.55 3.84 10 9 11 5.16-1 9-5.45 9-11V7l-10-5z',
|
|
126
|
+
);
|
|
97
127
|
});
|
|
98
128
|
|
|
99
129
|
it('should handle nested SVG groups with for loops', () => {
|
|
100
130
|
component App() {
|
|
101
131
|
const items = [
|
|
102
|
-
{ x:
|
|
103
|
-
{ x:
|
|
132
|
+
{ x: '10', y: '10', width: '20', height: '20' },
|
|
133
|
+
{ x: '40', y: '40', width: '20', height: '20' },
|
|
104
134
|
];
|
|
105
135
|
|
|
106
136
|
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100">
|
|
@@ -123,7 +153,7 @@ describe('SVG namespace handling', () => {
|
|
|
123
153
|
expect(g.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
124
154
|
expect(rects.length).toBe(2);
|
|
125
155
|
|
|
126
|
-
rects.forEach(rect => {
|
|
156
|
+
rects.forEach((rect) => {
|
|
127
157
|
expect(rect.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
128
158
|
});
|
|
129
159
|
|
|
@@ -200,22 +230,39 @@ describe('SVG namespace handling', () => {
|
|
|
200
230
|
|
|
201
231
|
it('should compare static vs dynamic SVG rendering (original problem case)', () => {
|
|
202
232
|
component App() {
|
|
203
|
-
const d = [
|
|
233
|
+
const d = [
|
|
234
|
+
'm14 12 4 4 4-4',
|
|
235
|
+
'M18 16V7',
|
|
236
|
+
'm2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16',
|
|
237
|
+
'M3.304 13h6.392',
|
|
238
|
+
];
|
|
204
239
|
|
|
205
240
|
<div class="container">
|
|
206
|
-
|
|
207
|
-
|
|
241
|
+
<svg
|
|
242
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
243
|
+
width="24"
|
|
244
|
+
height="24"
|
|
245
|
+
fill="none"
|
|
246
|
+
stroke="currentColor"
|
|
247
|
+
class="dynamic-svg"
|
|
248
|
+
>
|
|
208
249
|
for (const path of d) {
|
|
209
250
|
<path d={path} />
|
|
210
251
|
}
|
|
211
252
|
</svg>
|
|
212
253
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
254
|
+
<svg
|
|
255
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
256
|
+
width="24"
|
|
257
|
+
height="24"
|
|
258
|
+
fill="none"
|
|
259
|
+
stroke="currentColor"
|
|
260
|
+
class="static-svg"
|
|
261
|
+
>
|
|
262
|
+
<path d="m14 12 4 4 4-4" />
|
|
263
|
+
<path d="M18 16V7" />
|
|
264
|
+
<path d="m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16" />
|
|
265
|
+
<path d="M3.304 13h6.392" />
|
|
219
266
|
</svg>
|
|
220
267
|
</div>
|
|
221
268
|
}
|
|
@@ -236,10 +283,10 @@ describe('SVG namespace handling', () => {
|
|
|
236
283
|
expect(staticPaths.length).toBe(4);
|
|
237
284
|
|
|
238
285
|
// All paths should have SVG namespace
|
|
239
|
-
dynamicPaths.forEach(path => {
|
|
286
|
+
dynamicPaths.forEach((path) => {
|
|
240
287
|
expect(path.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
241
288
|
});
|
|
242
|
-
staticPaths.forEach(path => {
|
|
289
|
+
staticPaths.forEach((path) => {
|
|
243
290
|
expect(path.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
244
291
|
});
|
|
245
292
|
|
|
@@ -249,11 +296,11 @@ describe('SVG namespace handling', () => {
|
|
|
249
296
|
});
|
|
250
297
|
|
|
251
298
|
// Critical test: all paths should be proper SVG elements
|
|
252
|
-
dynamicPaths.forEach(path => {
|
|
299
|
+
dynamicPaths.forEach((path) => {
|
|
253
300
|
expect(path.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
254
301
|
expect(path.tagName.toLowerCase()).toBe('path');
|
|
255
302
|
});
|
|
256
|
-
staticPaths.forEach(path => {
|
|
303
|
+
staticPaths.forEach((path) => {
|
|
257
304
|
expect(path.namespaceURI).toBe('http://www.w3.org/2000/svg');
|
|
258
305
|
expect(path.tagName.toLowerCase()).toBe('path');
|
|
259
306
|
});
|