sibujs 3.0.0 → 3.2.0
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/README.md +6 -0
- package/dist/browser.cjs +16 -8
- package/dist/browser.js +6 -5
- package/dist/build.cjs +276 -150
- package/dist/build.js +35 -24
- package/dist/cdn.global.js +7 -7
- package/dist/{chunk-RJIRT46U.js → chunk-2C4E3HBM.js} +5 -5
- package/dist/{chunk-XDKP4T7G.js → chunk-4JCAUOLN.js} +45 -23
- package/dist/{chunk-VSNLICTS.js → chunk-5N74TKLD.js} +1 -1
- package/dist/{chunk-XVYB3J6C.js → chunk-7XDYVJLE.js} +19 -9
- package/dist/{chunk-L52H775O.js → chunk-BGNLPNGV.js} +20 -12
- package/dist/{chunk-6QZO7MMG.js → chunk-C427DVQF.js} +1 -1
- package/dist/{chunk-5WD7BYTZ.js → chunk-FDY42FIU.js} +3 -2
- package/dist/{chunk-4YTVESDX.js → chunk-FOI23UJL.js} +11 -1
- package/dist/{chunk-2RA7SHDA.js → chunk-GOJMFRBL.js} +20 -4
- package/dist/{chunk-2KM2724A.js → chunk-GOUM4JCT.js} +6 -6
- package/dist/chunk-H3SRKIYX.js +17 -0
- package/dist/{chunk-NEWH4O5U.js → chunk-H6PCHJZQ.js} +2 -2
- package/dist/{chunk-UCS6AMJ7.js → chunk-HMJFCBRR.js} +26 -3
- package/dist/{chunk-JYD2PWXH.js → chunk-HXMS4SNP.js} +22 -15
- package/dist/{chunk-DF3GTP4Q.js → chunk-JYXOEYI4.js} +12 -18
- package/dist/{chunk-KZA7ANXP.js → chunk-NFYWLRUO.js} +11 -18
- package/dist/{chunk-KH4OE6WY.js → chunk-NPIEEKPT.js} +20 -11
- package/dist/{chunk-V65KTDZW.js → chunk-OYLPZO4N.js} +33 -15
- package/dist/{chunk-LYTCUZ7H.js → chunk-RDRSWYNP.js} +1 -1
- package/dist/{chunk-UKMXT5T6.js → chunk-RLUJL2MV.js} +7 -12
- package/dist/{chunk-INBOWHQ3.js → chunk-V2MTG5FT.js} +99 -36
- package/dist/{chunk-CNZ35WI2.js → chunk-VJE6DDYM.js} +2 -2
- package/dist/{chunk-2JQUV4Y3.js → chunk-VOCE4NNK.js} +157 -75
- package/dist/{chunk-STFTTMO2.js → chunk-X67UYC74.js} +31 -12
- package/dist/{chunk-YMOIAHWA.js → chunk-YFDGQWDA.js} +1 -1
- package/dist/{chunk-L4DAT4WU.js → chunk-Z2FWAE4B.js} +28 -1
- package/dist/data.cjs +211 -93
- package/dist/data.d.cts +7 -1
- package/dist/data.d.ts +7 -1
- package/dist/data.js +8 -8
- package/dist/devtools.cjs +38 -10
- package/dist/devtools.d.cts +1 -1
- package/dist/devtools.d.ts +1 -1
- package/dist/devtools.js +6 -6
- package/dist/ecosystem.cjs +163 -65
- package/dist/ecosystem.js +9 -9
- package/dist/extras.cjs +420 -198
- package/dist/extras.d.cts +2 -2
- package/dist/extras.d.ts +2 -2
- package/dist/extras.js +27 -24
- package/dist/index.cjs +255 -139
- package/dist/index.d.cts +15 -2
- package/dist/index.d.ts +15 -2
- package/dist/index.js +15 -13
- package/dist/{introspect-BZWKvQUZ.d.ts → introspect-DOZfmC-4.d.ts} +1 -1
- package/dist/{introspect-DsJlDD2T.d.cts → introspect-RjLfIFpL.d.cts} +1 -1
- package/dist/motion.cjs +10 -0
- package/dist/motion.js +3 -3
- package/dist/patterns.cjs +66 -39
- package/dist/patterns.js +8 -7
- package/dist/performance.cjs +101 -25
- package/dist/performance.d.cts +2 -2
- package/dist/performance.d.ts +2 -2
- package/dist/performance.js +8 -7
- package/dist/plugins.cjs +243 -138
- package/dist/plugins.d.cts +1 -1
- package/dist/plugins.d.ts +1 -1
- package/dist/plugins.js +96 -45
- package/dist/{ssr-FXD2PPMC.js → ssr-2QDQ27EV.js} +5 -3
- package/dist/{ssr-CrVNy6Pa.d.cts → ssr-D62yFwuw.d.cts} +8 -1
- package/dist/{ssr-CrVNy6Pa.d.ts → ssr-D62yFwuw.d.ts} +8 -1
- package/dist/ssr.cjs +185 -68
- package/dist/ssr.d.cts +1 -1
- package/dist/ssr.d.ts +1 -1
- package/dist/ssr.js +12 -10
- package/dist/testing.cjs +9 -4
- package/dist/testing.js +3 -3
- package/dist/ui.cjs +76 -39
- package/dist/ui.js +10 -9
- package/dist/widgets.cjs +61 -23
- package/dist/widgets.js +8 -8
- package/package.json +3 -1
package/dist/plugins.cjs
CHANGED
|
@@ -43,8 +43,16 @@ var init_dev = __esm({
|
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
// src/utils/sanitize.ts
|
|
46
|
+
function stripControlChars(value) {
|
|
47
|
+
return value.replace(/[\x00-\x20\x7f-\x9f]+/g, "");
|
|
48
|
+
}
|
|
49
|
+
function isEventHandlerAttr(name) {
|
|
50
|
+
if (name.length < 3) return false;
|
|
51
|
+
const lower = name.toLowerCase();
|
|
52
|
+
return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
|
|
53
|
+
}
|
|
46
54
|
function sanitizeUrl(url) {
|
|
47
|
-
const trimmed = url
|
|
55
|
+
const trimmed = stripControlChars(url).trim();
|
|
48
56
|
if (!trimmed) return "";
|
|
49
57
|
const lower = trimmed.toLowerCase();
|
|
50
58
|
let schemeEnd = -1;
|
|
@@ -93,7 +101,7 @@ function sanitizeCSSValue(value) {
|
|
|
93
101
|
return value;
|
|
94
102
|
}
|
|
95
103
|
function isUrlAttribute(attr) {
|
|
96
|
-
return URL_ATTRIBUTES.has(attr);
|
|
104
|
+
return URL_ATTRIBUTES.has(attr.toLowerCase());
|
|
97
105
|
}
|
|
98
106
|
var SAFE_URL_PROTOCOLS, URL_ATTRIBUTES;
|
|
99
107
|
var init_sanitize = __esm({
|
|
@@ -135,11 +143,15 @@ var init_ssr_context = __esm({
|
|
|
135
143
|
als = null;
|
|
136
144
|
try {
|
|
137
145
|
if (typeof process !== "undefined" && process.versions && process.versions.node) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
146
|
+
let mod = null;
|
|
147
|
+
const getBuiltin = process.getBuiltinModule;
|
|
148
|
+
if (typeof getBuiltin === "function") {
|
|
149
|
+
mod = getBuiltin("node:async_hooks");
|
|
150
|
+
} else {
|
|
151
|
+
const req = Function("return typeof require==='function'?require:null")();
|
|
152
|
+
if (req) mod = req("node:async_hooks");
|
|
142
153
|
}
|
|
154
|
+
if (mod) als = new mod.AsyncLocalStorage();
|
|
143
155
|
}
|
|
144
156
|
} catch {
|
|
145
157
|
als = null;
|
|
@@ -157,6 +169,7 @@ __export(ssr_exports, {
|
|
|
157
169
|
hydrate: () => hydrate,
|
|
158
170
|
hydrateIslands: () => hydrateIslands,
|
|
159
171
|
hydrateProgressively: () => hydrateProgressively,
|
|
172
|
+
isDangerousMetaRefresh: () => isDangerousMetaRefresh,
|
|
160
173
|
island: () => island,
|
|
161
174
|
renderToDocument: () => renderToDocument,
|
|
162
175
|
renderToReadableStream: () => renderToReadableStream,
|
|
@@ -169,14 +182,12 @@ __export(ssr_exports, {
|
|
|
169
182
|
suspenseSwapScript: () => suspenseSwapScript,
|
|
170
183
|
trustHTML: () => trustHTML
|
|
171
184
|
});
|
|
185
|
+
function sanitizeUrlAttr(name, value) {
|
|
186
|
+
return name === "srcset" ? sanitizeSrcset(value) : sanitizeUrl(value);
|
|
187
|
+
}
|
|
172
188
|
function isSafeAttrName(name) {
|
|
173
189
|
return SAFE_ATTR_NAME.test(name);
|
|
174
190
|
}
|
|
175
|
-
function isEventHandlerAttr2(name) {
|
|
176
|
-
if (name.length < 3) return false;
|
|
177
|
-
const lower = name.toLowerCase();
|
|
178
|
-
return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
|
|
179
|
-
}
|
|
180
191
|
function ssrErrorComment(err) {
|
|
181
192
|
if (_isDev8) {
|
|
182
193
|
const msg = escapeHtml(err instanceof Error ? err.message : String(err));
|
|
@@ -217,11 +228,11 @@ function renderToString(element) {
|
|
|
217
228
|
for (const attr of Array.from(element.attributes)) {
|
|
218
229
|
const rawName = attr.name;
|
|
219
230
|
if (!isSafeAttrName(rawName)) continue;
|
|
220
|
-
if (
|
|
231
|
+
if (isEventHandlerAttr(rawName)) continue;
|
|
221
232
|
const lowerName = rawName.toLowerCase();
|
|
222
233
|
let value = attr.value;
|
|
223
234
|
if (URL_ATTRS.has(lowerName)) {
|
|
224
|
-
value =
|
|
235
|
+
value = sanitizeUrlAttr(lowerName, value);
|
|
225
236
|
if (!value) continue;
|
|
226
237
|
}
|
|
227
238
|
html2 += ` ${rawName}="${escapeAttr(value)}"`;
|
|
@@ -358,11 +369,11 @@ function buildAttrString(attrs, { allowEventHandlers = false } = {}) {
|
|
|
358
369
|
for (const rawKey of Object.keys(attrs)) {
|
|
359
370
|
if (!Object.hasOwn(attrs, rawKey)) continue;
|
|
360
371
|
if (!isSafeAttrName(rawKey)) continue;
|
|
361
|
-
if (!allowEventHandlers &&
|
|
372
|
+
if (!allowEventHandlers && isEventHandlerAttr(rawKey)) continue;
|
|
362
373
|
const lowerKey = rawKey.toLowerCase();
|
|
363
374
|
let value = String(attrs[rawKey]);
|
|
364
375
|
if (URL_ATTRS.has(lowerKey)) {
|
|
365
|
-
value =
|
|
376
|
+
value = sanitizeUrlAttr(lowerKey, value);
|
|
366
377
|
if (!value) continue;
|
|
367
378
|
}
|
|
368
379
|
out.push(`${rawKey}="${escapeAttr(value)}"`);
|
|
@@ -370,12 +381,17 @@ function buildAttrString(attrs, { allowEventHandlers = false } = {}) {
|
|
|
370
381
|
return out.join(" ");
|
|
371
382
|
}
|
|
372
383
|
function isDangerousMetaRefresh(metaProps) {
|
|
373
|
-
|
|
384
|
+
let httpEquiv;
|
|
385
|
+
let content;
|
|
386
|
+
for (const k in metaProps) {
|
|
387
|
+
const lk = k.toLowerCase();
|
|
388
|
+
if (lk === "http-equiv") httpEquiv = metaProps[k];
|
|
389
|
+
else if (lk === "content") content = metaProps[k];
|
|
390
|
+
}
|
|
374
391
|
if (typeof httpEquiv !== "string") return false;
|
|
375
392
|
if (httpEquiv.toLowerCase() !== "refresh") return false;
|
|
376
|
-
const content = metaProps.content;
|
|
377
393
|
if (typeof content !== "string") return false;
|
|
378
|
-
const normalized = content
|
|
394
|
+
const normalized = stripControlChars(content).toLowerCase();
|
|
379
395
|
return normalized.includes("url=javascript:") || normalized.includes("url=data:") || normalized.includes("url=vbscript:") || normalized.includes("url=blob:");
|
|
380
396
|
}
|
|
381
397
|
function renderToDocument(component, options = {}) {
|
|
@@ -453,11 +469,11 @@ async function* renderToStream(element) {
|
|
|
453
469
|
for (const attr of Array.from(element.attributes)) {
|
|
454
470
|
const rawName = attr.name;
|
|
455
471
|
if (!isSafeAttrName(rawName)) continue;
|
|
456
|
-
if (
|
|
472
|
+
if (isEventHandlerAttr(rawName)) continue;
|
|
457
473
|
const lowerName = rawName.toLowerCase();
|
|
458
474
|
let value = attr.value;
|
|
459
475
|
if (URL_ATTRS.has(lowerName)) {
|
|
460
|
-
value =
|
|
476
|
+
value = sanitizeUrlAttr(lowerName, value);
|
|
461
477
|
if (!value) continue;
|
|
462
478
|
}
|
|
463
479
|
openTag += ` ${rawName}="${escapeAttr(value)}"`;
|
|
@@ -533,11 +549,11 @@ function hydrateProgressively(container, islands, options) {
|
|
|
533
549
|
(entries) => {
|
|
534
550
|
for (const entry of entries) {
|
|
535
551
|
if (entry.isIntersecting) {
|
|
552
|
+
observer.disconnect();
|
|
536
553
|
const clientTree = factory();
|
|
537
554
|
clientTree.setAttribute("data-sibu-island", id);
|
|
538
555
|
clientTree.setAttribute("data-sibu-hydrated", "true");
|
|
539
556
|
marker2.replaceWith(clientTree);
|
|
540
|
-
observer.disconnect();
|
|
541
557
|
break;
|
|
542
558
|
}
|
|
543
559
|
}
|
|
@@ -901,7 +917,7 @@ function retrack(effectFn, subscriber) {
|
|
|
901
917
|
}
|
|
902
918
|
}
|
|
903
919
|
function track(effectFn, subscriber) {
|
|
904
|
-
if (!subscriber)
|
|
920
|
+
if (!subscriber) return reactiveBinding(effectFn);
|
|
905
921
|
cleanup(subscriber);
|
|
906
922
|
const prev = currentSubscriber;
|
|
907
923
|
currentSubscriber = subscriber;
|
|
@@ -919,6 +935,32 @@ function track(effectFn, subscriber) {
|
|
|
919
935
|
const sub2 = subscriber;
|
|
920
936
|
return sub2._dispose ?? (sub2._dispose = () => cleanup(subscriber));
|
|
921
937
|
}
|
|
938
|
+
function reactiveBinding(commit) {
|
|
939
|
+
const run = () => {
|
|
940
|
+
const s2 = subscriber;
|
|
941
|
+
if (s2._disposed || s2._reentrant) return;
|
|
942
|
+
s2._reentrant = true;
|
|
943
|
+
try {
|
|
944
|
+
retrack(commit, subscriber);
|
|
945
|
+
} finally {
|
|
946
|
+
s2._reentrant = false;
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
const subscriber = run;
|
|
950
|
+
subscriber.depsHead = null;
|
|
951
|
+
subscriber.depsTail = null;
|
|
952
|
+
subscriber._epoch = 0;
|
|
953
|
+
subscriber._structDirty = false;
|
|
954
|
+
subscriber._runEpoch = 0;
|
|
955
|
+
subscriber._runs = 0;
|
|
956
|
+
subscriber._reentrant = false;
|
|
957
|
+
subscriber._disposed = false;
|
|
958
|
+
run();
|
|
959
|
+
return subscriber._dispose ?? (subscriber._dispose = () => {
|
|
960
|
+
subscriber._disposed = true;
|
|
961
|
+
cleanup(subscriber);
|
|
962
|
+
});
|
|
963
|
+
}
|
|
922
964
|
function recordDependency(signal2) {
|
|
923
965
|
if (!currentSubscriber) return;
|
|
924
966
|
const sub2 = currentSubscriber;
|
|
@@ -1075,11 +1117,6 @@ var _isDev3 = isDev();
|
|
|
1075
1117
|
function setProp(el, key, val) {
|
|
1076
1118
|
el[key] = val;
|
|
1077
1119
|
}
|
|
1078
|
-
function isEventHandlerAttr(name) {
|
|
1079
|
-
if (name.length < 3) return false;
|
|
1080
|
-
const lower = name.toLowerCase();
|
|
1081
|
-
return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
|
|
1082
|
-
}
|
|
1083
1120
|
function bindAttribute(el, attr, getter) {
|
|
1084
1121
|
if (isEventHandlerAttr(attr)) {
|
|
1085
1122
|
if (_isDev3)
|
|
@@ -1115,13 +1152,76 @@ function bindAttribute(el, attr, getter) {
|
|
|
1115
1152
|
el.setAttribute(attr, isUrlAttribute(attr) ? sanitizeUrl(str) : str);
|
|
1116
1153
|
}
|
|
1117
1154
|
}
|
|
1118
|
-
|
|
1119
|
-
return teardown;
|
|
1155
|
+
return reactiveBinding(commit);
|
|
1120
1156
|
}
|
|
1121
1157
|
|
|
1122
1158
|
// src/reactivity/bindChildNode.ts
|
|
1123
1159
|
init_dev();
|
|
1160
|
+
|
|
1161
|
+
// src/core/rendering/dispose.ts
|
|
1162
|
+
init_dev();
|
|
1163
|
+
var elementDisposers = /* @__PURE__ */ new WeakMap();
|
|
1124
1164
|
var _isDev4 = isDev();
|
|
1165
|
+
var activeBindingCount = 0;
|
|
1166
|
+
function registerDisposer(node, teardown) {
|
|
1167
|
+
let disposers = elementDisposers.get(node);
|
|
1168
|
+
if (!disposers) {
|
|
1169
|
+
disposers = [];
|
|
1170
|
+
elementDisposers.set(node, disposers);
|
|
1171
|
+
}
|
|
1172
|
+
disposers.push(teardown);
|
|
1173
|
+
if (_isDev4) activeBindingCount++;
|
|
1174
|
+
}
|
|
1175
|
+
function dispose(node) {
|
|
1176
|
+
const stack = [node];
|
|
1177
|
+
const order = [];
|
|
1178
|
+
while (stack.length > 0) {
|
|
1179
|
+
const current = stack.pop();
|
|
1180
|
+
order.push(current);
|
|
1181
|
+
const children = Array.from(current.childNodes);
|
|
1182
|
+
for (let i2 = 0; i2 < children.length; i2++) {
|
|
1183
|
+
stack.push(children[i2]);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
for (let i2 = order.length - 1; i2 >= 0; i2--) {
|
|
1187
|
+
const current = order[i2];
|
|
1188
|
+
const disposers = elementDisposers.get(current);
|
|
1189
|
+
if (disposers) {
|
|
1190
|
+
const snapshot = disposers.slice();
|
|
1191
|
+
elementDisposers.delete(current);
|
|
1192
|
+
if (_isDev4) activeBindingCount -= snapshot.length;
|
|
1193
|
+
for (const d of snapshot) {
|
|
1194
|
+
try {
|
|
1195
|
+
d();
|
|
1196
|
+
} catch (err) {
|
|
1197
|
+
if (_isDev4 && typeof console !== "undefined") {
|
|
1198
|
+
console.warn("[SibuJS] Disposer threw during cleanup:", err);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
let extraPasses = 0;
|
|
1203
|
+
while (extraPasses++ < 8) {
|
|
1204
|
+
const added = elementDisposers.get(current);
|
|
1205
|
+
if (!added || added.length === 0) break;
|
|
1206
|
+
const moreSnapshot = added.slice();
|
|
1207
|
+
elementDisposers.delete(current);
|
|
1208
|
+
if (_isDev4) activeBindingCount -= moreSnapshot.length;
|
|
1209
|
+
for (const d of moreSnapshot) {
|
|
1210
|
+
try {
|
|
1211
|
+
d();
|
|
1212
|
+
} catch (err) {
|
|
1213
|
+
if (_isDev4 && typeof console !== "undefined") {
|
|
1214
|
+
console.warn("[SibuJS] Disposer threw during cleanup:", err);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// src/reactivity/bindChildNode.ts
|
|
1224
|
+
var _isDev5 = isDev();
|
|
1125
1225
|
function bindChildNode(placeholder, getter) {
|
|
1126
1226
|
let lastNodes = [];
|
|
1127
1227
|
function commit() {
|
|
@@ -1129,12 +1229,13 @@ function bindChildNode(placeholder, getter) {
|
|
|
1129
1229
|
try {
|
|
1130
1230
|
result = getter();
|
|
1131
1231
|
} catch (err) {
|
|
1132
|
-
if (
|
|
1232
|
+
if (_isDev5) devWarn(`bindChildNode: getter threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
1133
1233
|
return;
|
|
1134
1234
|
}
|
|
1135
1235
|
if (result == null || typeof result === "boolean") {
|
|
1136
1236
|
for (let i2 = 0; i2 < lastNodes.length; i2++) {
|
|
1137
1237
|
const node = lastNodes[i2];
|
|
1238
|
+
dispose(node);
|
|
1138
1239
|
if (node.parentNode) node.parentNode.removeChild(node);
|
|
1139
1240
|
}
|
|
1140
1241
|
lastNodes.length = 0;
|
|
@@ -1154,7 +1255,7 @@ function bindChildNode(placeholder, getter) {
|
|
|
1154
1255
|
if (item == null || typeof item === "boolean") continue;
|
|
1155
1256
|
const node = item instanceof Node ? item : document.createTextNode(String(item));
|
|
1156
1257
|
if (seen.has(node)) {
|
|
1157
|
-
if (
|
|
1258
|
+
if (_isDev5)
|
|
1158
1259
|
devWarn("bindChildNode: duplicate node reference in array \u2014 only the first occurrence is rendered.");
|
|
1159
1260
|
continue;
|
|
1160
1261
|
}
|
|
@@ -1176,90 +1277,24 @@ function bindChildNode(placeholder, getter) {
|
|
|
1176
1277
|
for (let i2 = 0; i2 < lastNodes.length; i2++) {
|
|
1177
1278
|
const node = lastNodes[i2];
|
|
1178
1279
|
if (reused?.has(node)) continue;
|
|
1280
|
+
dispose(node);
|
|
1179
1281
|
if (node.parentNode) node.parentNode.removeChild(node);
|
|
1180
1282
|
}
|
|
1181
|
-
|
|
1283
|
+
let prev = placeholder;
|
|
1182
1284
|
for (let i2 = 0; i2 < newNodes.length; i2++) {
|
|
1183
1285
|
const node = newNodes[i2];
|
|
1184
|
-
if (
|
|
1185
|
-
|
|
1186
|
-
parent.insertBefore(node, anchor);
|
|
1187
|
-
}
|
|
1188
|
-
} else {
|
|
1189
|
-
parent.insertBefore(node, anchor);
|
|
1286
|
+
if (prev.nextSibling !== node) {
|
|
1287
|
+
parent.insertBefore(node, prev.nextSibling);
|
|
1190
1288
|
}
|
|
1289
|
+
prev = node;
|
|
1191
1290
|
}
|
|
1192
1291
|
lastNodes = newNodes;
|
|
1193
1292
|
}
|
|
1194
|
-
return
|
|
1293
|
+
return reactiveBinding(commit);
|
|
1195
1294
|
}
|
|
1196
1295
|
|
|
1197
1296
|
// src/core/rendering/tagFactory.ts
|
|
1198
1297
|
init_sanitize();
|
|
1199
|
-
|
|
1200
|
-
// src/core/rendering/dispose.ts
|
|
1201
|
-
init_dev();
|
|
1202
|
-
var elementDisposers = /* @__PURE__ */ new WeakMap();
|
|
1203
|
-
var _isDev5 = isDev();
|
|
1204
|
-
var activeBindingCount = 0;
|
|
1205
|
-
function registerDisposer(node, teardown) {
|
|
1206
|
-
let disposers = elementDisposers.get(node);
|
|
1207
|
-
if (!disposers) {
|
|
1208
|
-
disposers = [];
|
|
1209
|
-
elementDisposers.set(node, disposers);
|
|
1210
|
-
}
|
|
1211
|
-
disposers.push(teardown);
|
|
1212
|
-
if (_isDev5) activeBindingCount++;
|
|
1213
|
-
}
|
|
1214
|
-
function dispose(node) {
|
|
1215
|
-
const stack = [node];
|
|
1216
|
-
const order = [];
|
|
1217
|
-
while (stack.length > 0) {
|
|
1218
|
-
const current = stack.pop();
|
|
1219
|
-
order.push(current);
|
|
1220
|
-
const children = Array.from(current.childNodes);
|
|
1221
|
-
for (let i2 = 0; i2 < children.length; i2++) {
|
|
1222
|
-
stack.push(children[i2]);
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
for (let i2 = order.length - 1; i2 >= 0; i2--) {
|
|
1226
|
-
const current = order[i2];
|
|
1227
|
-
const disposers = elementDisposers.get(current);
|
|
1228
|
-
if (disposers) {
|
|
1229
|
-
const snapshot = disposers.slice();
|
|
1230
|
-
elementDisposers.delete(current);
|
|
1231
|
-
if (_isDev5) activeBindingCount -= snapshot.length;
|
|
1232
|
-
for (const d of snapshot) {
|
|
1233
|
-
try {
|
|
1234
|
-
d();
|
|
1235
|
-
} catch (err) {
|
|
1236
|
-
if (_isDev5 && typeof console !== "undefined") {
|
|
1237
|
-
console.warn("[SibuJS] Disposer threw during cleanup:", err);
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
let extraPasses = 0;
|
|
1242
|
-
while (extraPasses++ < 8) {
|
|
1243
|
-
const added = elementDisposers.get(current);
|
|
1244
|
-
if (!added || added.length === 0) break;
|
|
1245
|
-
const moreSnapshot = added.slice();
|
|
1246
|
-
elementDisposers.delete(current);
|
|
1247
|
-
if (_isDev5) activeBindingCount -= moreSnapshot.length;
|
|
1248
|
-
for (const d of moreSnapshot) {
|
|
1249
|
-
try {
|
|
1250
|
-
d();
|
|
1251
|
-
} catch (err) {
|
|
1252
|
-
if (_isDev5 && typeof console !== "undefined") {
|
|
1253
|
-
console.warn("[SibuJS] Disposer threw during cleanup:", err);
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
// src/core/rendering/tagFactory.ts
|
|
1263
1298
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
1264
1299
|
var _isDev6 = isDev();
|
|
1265
1300
|
var BLOCKED_TAGS = /* @__PURE__ */ new Set(["script", "iframe", "object", "embed", "frame", "frameset"]);
|
|
@@ -1285,6 +1320,18 @@ var CLOBBER_RISKY_IDS = /* @__PURE__ */ new Set([
|
|
|
1285
1320
|
function setProp2(el, key, val) {
|
|
1286
1321
|
el[key] = val;
|
|
1287
1322
|
}
|
|
1323
|
+
function looksLikeClassList(s2) {
|
|
1324
|
+
const t2 = s2.trim();
|
|
1325
|
+
if (!t2) return false;
|
|
1326
|
+
const tokens = t2.split(/\s+/);
|
|
1327
|
+
let sawClassish = false;
|
|
1328
|
+
for (let i2 = 0; i2 < tokens.length; i2++) {
|
|
1329
|
+
const tok = tokens[i2];
|
|
1330
|
+
if (!/^-?[A-Za-z_][A-Za-z0-9_:/.-]*$/.test(tok)) return false;
|
|
1331
|
+
if (/[-:/0-9]/.test(tok)) sawClassish = true;
|
|
1332
|
+
}
|
|
1333
|
+
return sawClassish;
|
|
1334
|
+
}
|
|
1288
1335
|
var kebabCache = /* @__PURE__ */ new Map();
|
|
1289
1336
|
function toKebab(prop) {
|
|
1290
1337
|
let cached = kebabCache.get(prop);
|
|
@@ -1420,6 +1467,11 @@ var tagFactory = (tag, ns) => {
|
|
|
1420
1467
|
appendChildren(el, second);
|
|
1421
1468
|
return el;
|
|
1422
1469
|
}
|
|
1470
|
+
if (_isDev6 && looksLikeClassList(first)) {
|
|
1471
|
+
devWarn(
|
|
1472
|
+
`tagFactory: lone string "${first}" looks like a class list but is being rendered as TEXT. For a class, use ${tag}({ class: "${first}" }) \u2014 or ${tag}("${first}", children) to set the class AND add children.`
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
1423
1475
|
el.textContent = first;
|
|
1424
1476
|
return el;
|
|
1425
1477
|
}
|
|
@@ -1477,7 +1529,7 @@ var tagFactory = (tag, ns) => {
|
|
|
1477
1529
|
const value = props[key];
|
|
1478
1530
|
if (value == null) continue;
|
|
1479
1531
|
const lkey = key.toLowerCase();
|
|
1480
|
-
if (
|
|
1532
|
+
if (isEventHandlerAttr(key)) continue;
|
|
1481
1533
|
if (typeof value === "function") {
|
|
1482
1534
|
registerDisposer(el, bindAttribute(el, key, value));
|
|
1483
1535
|
} else if (typeof value === "boolean") {
|
|
@@ -1841,6 +1893,7 @@ function effect(effectFn, options) {
|
|
|
1841
1893
|
ctx.fn(ctx.onCleanup);
|
|
1842
1894
|
};
|
|
1843
1895
|
const sub2 = (() => {
|
|
1896
|
+
if (ctx.disposed) return;
|
|
1844
1897
|
if (ctx.running) {
|
|
1845
1898
|
ctx.rerunPending = true;
|
|
1846
1899
|
return;
|
|
@@ -1880,7 +1933,9 @@ function effect(effectFn, options) {
|
|
|
1880
1933
|
init_sanitize();
|
|
1881
1934
|
function isSafeNavigationTarget(path2) {
|
|
1882
1935
|
if (path2 === "") return true;
|
|
1883
|
-
|
|
1936
|
+
const normalized = stripControlChars(path2).replace(/\\/g, "/");
|
|
1937
|
+
if (normalized.startsWith("//")) return false;
|
|
1938
|
+
return sanitizeUrl(normalized) !== "";
|
|
1884
1939
|
}
|
|
1885
1940
|
var LRUCache = class {
|
|
1886
1941
|
constructor(maxSize = 100) {
|
|
@@ -2017,8 +2072,13 @@ var RouteMatcher = class {
|
|
|
2017
2072
|
if (match) {
|
|
2018
2073
|
const params = {};
|
|
2019
2074
|
compiled.keys.forEach((key, i2) => {
|
|
2020
|
-
|
|
2021
|
-
|
|
2075
|
+
const raw = match[i2 + 1];
|
|
2076
|
+
if (raw !== void 0) {
|
|
2077
|
+
try {
|
|
2078
|
+
params[key] = decodeURIComponent(raw);
|
|
2079
|
+
} catch {
|
|
2080
|
+
params[key] = raw;
|
|
2081
|
+
}
|
|
2022
2082
|
}
|
|
2023
2083
|
});
|
|
2024
2084
|
return { params };
|
|
@@ -2069,14 +2129,23 @@ var RouteMatcher = class {
|
|
|
2069
2129
|
}
|
|
2070
2130
|
}
|
|
2071
2131
|
removeRoute(path2) {
|
|
2072
|
-
this.routeTrie.delete(path2);
|
|
2073
|
-
this.parentChain.delete(path2);
|
|
2074
2132
|
this.compiledPatterns.clear();
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2133
|
+
const root = this.routeTrie.get(path2);
|
|
2134
|
+
if (!root) return;
|
|
2135
|
+
const removed = /* @__PURE__ */ new Set();
|
|
2136
|
+
const collect = (r) => {
|
|
2137
|
+
removed.add(r);
|
|
2138
|
+
if (r.children) for (const child of r.children) collect(child);
|
|
2139
|
+
};
|
|
2140
|
+
collect(root);
|
|
2141
|
+
for (const [key, route2] of [...this.routeTrie]) {
|
|
2142
|
+
if (removed.has(route2)) this.routeTrie.delete(key);
|
|
2143
|
+
}
|
|
2144
|
+
for (const [key, chain] of [...this.parentChain]) {
|
|
2145
|
+
if (chain.length > 0 && removed.has(chain[chain.length - 1])) this.parentChain.delete(key);
|
|
2146
|
+
}
|
|
2147
|
+
for (const [name, route2] of [...this.namedRoutes]) {
|
|
2148
|
+
if (removed.has(route2)) this.namedRoutes.delete(name);
|
|
2080
2149
|
}
|
|
2081
2150
|
}
|
|
2082
2151
|
};
|
|
@@ -2188,42 +2257,61 @@ var GuardManager = class {
|
|
|
2188
2257
|
this.afterEachHooks = [];
|
|
2189
2258
|
}
|
|
2190
2259
|
};
|
|
2191
|
-
var
|
|
2260
|
+
var _ComponentLoader = class _ComponentLoader {
|
|
2192
2261
|
constructor(cacheSize = 50, retryDelay = 1e3) {
|
|
2193
2262
|
this.errorCache = /* @__PURE__ */ new Map();
|
|
2194
2263
|
this.loadingPromises = /* @__PURE__ */ new Map();
|
|
2264
|
+
// Stable per-route-definition id. Caching by the RESOLVED path (e.g.
|
|
2265
|
+
// /users/123) gave the cache one entry per visited URL, so parameterized
|
|
2266
|
+
// routes thrashed/evicted and the component was reloaded every navigation.
|
|
2267
|
+
// Keying by route-definition identity makes the cache effective again.
|
|
2268
|
+
this.routeKeys = /* @__PURE__ */ new WeakMap();
|
|
2269
|
+
this.keyCounter = 0;
|
|
2195
2270
|
this.componentCache = new LRUCache(cacheSize);
|
|
2196
2271
|
this.retryDelay = retryDelay;
|
|
2197
2272
|
}
|
|
2273
|
+
keyFor(route2) {
|
|
2274
|
+
let key = this.routeKeys.get(route2);
|
|
2275
|
+
if (key === void 0) {
|
|
2276
|
+
key = `route#${this.keyCounter++}`;
|
|
2277
|
+
this.routeKeys.set(route2, key);
|
|
2278
|
+
}
|
|
2279
|
+
return key;
|
|
2280
|
+
}
|
|
2198
2281
|
async loadComponent(route2, routePath) {
|
|
2199
2282
|
if (!("component" in route2)) {
|
|
2200
2283
|
throw new Error(`Route ${routePath} does not have a component`);
|
|
2201
2284
|
}
|
|
2202
2285
|
const comp = route2.component;
|
|
2203
|
-
const
|
|
2286
|
+
const cacheKey = this.keyFor(route2);
|
|
2287
|
+
const cached = this.componentCache.get(cacheKey);
|
|
2204
2288
|
if (cached) return cached;
|
|
2205
|
-
const existingPromise = this.loadingPromises.get(
|
|
2289
|
+
const existingPromise = this.loadingPromises.get(cacheKey);
|
|
2206
2290
|
if (existingPromise) return existingPromise;
|
|
2207
|
-
const errorInfo = this.errorCache.get(
|
|
2291
|
+
const errorInfo = this.errorCache.get(cacheKey);
|
|
2208
2292
|
if (errorInfo && Date.now() - errorInfo.timestamp < this.retryDelay) {
|
|
2209
2293
|
throw new Error(`Component loading failed recently, retry in ${this.retryDelay}ms`);
|
|
2210
2294
|
}
|
|
2211
2295
|
const loadingPromise = this.doLoadComponent(comp, routePath);
|
|
2212
|
-
this.loadingPromises.set(
|
|
2296
|
+
this.loadingPromises.set(cacheKey, loadingPromise);
|
|
2213
2297
|
try {
|
|
2214
2298
|
const component = await loadingPromise;
|
|
2215
|
-
this.componentCache.set(
|
|
2216
|
-
this.errorCache.delete(
|
|
2299
|
+
this.componentCache.set(cacheKey, component);
|
|
2300
|
+
this.errorCache.delete(cacheKey);
|
|
2217
2301
|
return component;
|
|
2218
2302
|
} catch (error) {
|
|
2219
|
-
const currentError = this.errorCache.get(
|
|
2220
|
-
this.errorCache.
|
|
2303
|
+
const currentError = this.errorCache.get(cacheKey) || { timestamp: 0, count: 0 };
|
|
2304
|
+
if (!this.errorCache.has(cacheKey) && this.errorCache.size >= _ComponentLoader.MAX_ERROR_ENTRIES) {
|
|
2305
|
+
const oldest = this.errorCache.keys().next().value;
|
|
2306
|
+
if (oldest !== void 0) this.errorCache.delete(oldest);
|
|
2307
|
+
}
|
|
2308
|
+
this.errorCache.set(cacheKey, {
|
|
2221
2309
|
timestamp: Date.now(),
|
|
2222
2310
|
count: currentError.count + 1
|
|
2223
2311
|
});
|
|
2224
2312
|
throw error;
|
|
2225
2313
|
} finally {
|
|
2226
|
-
this.loadingPromises.delete(
|
|
2314
|
+
this.loadingPromises.delete(cacheKey);
|
|
2227
2315
|
}
|
|
2228
2316
|
}
|
|
2229
2317
|
async doLoadComponent(comp, routePath) {
|
|
@@ -2277,6 +2365,8 @@ var ComponentLoader = class {
|
|
|
2277
2365
|
this.loadingPromises.clear();
|
|
2278
2366
|
}
|
|
2279
2367
|
};
|
|
2368
|
+
_ComponentLoader.MAX_ERROR_ENTRIES = 256;
|
|
2369
|
+
var ComponentLoader = _ComponentLoader;
|
|
2280
2370
|
var _SibuRouter = class _SibuRouter {
|
|
2281
2371
|
constructor(routes, options = {}) {
|
|
2282
2372
|
// Event listeners cleanup
|
|
@@ -2342,7 +2432,7 @@ var _SibuRouter = class _SibuRouter {
|
|
|
2342
2432
|
return window.location.hash.slice(1) || "/";
|
|
2343
2433
|
}
|
|
2344
2434
|
let path2 = window.location.pathname;
|
|
2345
|
-
if (base2 && path2.startsWith(base2)) {
|
|
2435
|
+
if (base2 && (path2 === base2 || path2.startsWith(`${base2}/`))) {
|
|
2346
2436
|
path2 = path2.slice(base2.length);
|
|
2347
2437
|
}
|
|
2348
2438
|
return (path2 || "/") + window.location.search + window.location.hash;
|
|
@@ -2475,7 +2565,7 @@ var _SibuRouter = class _SibuRouter {
|
|
|
2475
2565
|
}
|
|
2476
2566
|
if (to.params) {
|
|
2477
2567
|
for (const [key, value] of Object.entries(to.params)) {
|
|
2478
|
-
path2 = path2.replace(`:${key}`, encodeURIComponent(value));
|
|
2568
|
+
path2 = path2.replace(new RegExp(`:${key}(?=[/?#]|$)`, "g"), encodeURIComponent(value));
|
|
2479
2569
|
}
|
|
2480
2570
|
}
|
|
2481
2571
|
if (to.query && Object.keys(to.query).length > 0) {
|
|
@@ -2905,7 +2995,8 @@ function KeepAliveRoute(options) {
|
|
|
2905
2995
|
return;
|
|
2906
2996
|
}
|
|
2907
2997
|
if (!("component" in routeDef)) return;
|
|
2908
|
-
const
|
|
2998
|
+
const queryStr = Object.keys(route2.query).length > 0 ? `?${new URLSearchParams(route2.query).toString()}` : "";
|
|
2999
|
+
const cacheKey = `${route2.path}${queryStr}${route2.hash ? `#${route2.hash}` : ""}`;
|
|
2909
3000
|
const shouldCache = !includeNames || routeDef.name != null && includeNames.includes(routeDef.name);
|
|
2910
3001
|
if (cacheKey === currentKey && currentNode) return;
|
|
2911
3002
|
isUpdating = true;
|
|
@@ -2995,7 +3086,8 @@ function RouterLink(props) {
|
|
|
2995
3086
|
const { to, replace: replace2 = false, activeClass, exactActiveClass, nodes, target, rel, class: classAttr, ...attrs } = props;
|
|
2996
3087
|
const baseClass = typeof classAttr === "string" ? classAttr : "";
|
|
2997
3088
|
const routeGetter = globalRouter.routeGetter;
|
|
2998
|
-
const
|
|
3089
|
+
const rawHref = globalRouter["resolvePath"](to);
|
|
3090
|
+
const href = isSafeNavigationTarget(rawHref) ? rawHref : "#";
|
|
2999
3091
|
const hrefPath = href.split("?")[0].split("#")[0];
|
|
3000
3092
|
const link2 = document.createElement("a");
|
|
3001
3093
|
link2.href = href;
|
|
@@ -3027,9 +3119,18 @@ function RouterLink(props) {
|
|
|
3027
3119
|
});
|
|
3028
3120
|
registerDisposer(link2, effectCleanup);
|
|
3029
3121
|
Object.entries(attrs).forEach(([key, value]) => {
|
|
3030
|
-
|
|
3122
|
+
const lkey = key.toLowerCase();
|
|
3123
|
+
if (lkey === "href" || lkey[0] === "o" && lkey[1] === "n") return;
|
|
3031
3124
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
3032
|
-
|
|
3125
|
+
const str = String(value);
|
|
3126
|
+
if (isUrlAttribute(lkey)) {
|
|
3127
|
+
const safe = sanitizeUrl(str);
|
|
3128
|
+
if (safe) link2.setAttribute(key, safe);
|
|
3129
|
+
} else if (lkey === "style") {
|
|
3130
|
+
link2.setAttribute(key, sanitizeCSSValue(str));
|
|
3131
|
+
} else {
|
|
3132
|
+
link2.setAttribute(key, str);
|
|
3133
|
+
}
|
|
3033
3134
|
}
|
|
3034
3135
|
});
|
|
3035
3136
|
if (typeof nodes === "string") {
|
|
@@ -3280,10 +3381,14 @@ function createMemoryRouter(routes, _initialPath = "/") {
|
|
|
3280
3381
|
|
|
3281
3382
|
// src/plugins/routerSSR.ts
|
|
3282
3383
|
init_ssr();
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3384
|
+
|
|
3385
|
+
// src/utils/guards.ts
|
|
3386
|
+
var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
3387
|
+
function isUnsafeKey(key) {
|
|
3388
|
+
return UNSAFE_KEYS.has(key);
|
|
3286
3389
|
}
|
|
3390
|
+
|
|
3391
|
+
// src/plugins/routerSSR.ts
|
|
3287
3392
|
function safeDecode(raw) {
|
|
3288
3393
|
try {
|
|
3289
3394
|
return decodeURIComponent(raw);
|
|
@@ -3324,7 +3429,7 @@ function parseURL(url) {
|
|
|
3324
3429
|
key = safeDecode(pair.slice(0, eqIndex));
|
|
3325
3430
|
value = safeDecode(pair.slice(eqIndex + 1));
|
|
3326
3431
|
}
|
|
3327
|
-
if (
|
|
3432
|
+
if (isUnsafeKey(key)) continue;
|
|
3328
3433
|
query[key] = value;
|
|
3329
3434
|
}
|
|
3330
3435
|
}
|
|
@@ -3386,7 +3491,7 @@ function matchRoute(path2, routes, parentPath = "", parentChain = []) {
|
|
|
3386
3491
|
const params = nullObject();
|
|
3387
3492
|
for (let i2 = 0; i2 < compiled.keys.length; i2++) {
|
|
3388
3493
|
const key = compiled.keys[i2];
|
|
3389
|
-
if (
|
|
3494
|
+
if (isUnsafeKey(key)) continue;
|
|
3390
3495
|
if (match[i2 + 1] !== void 0) {
|
|
3391
3496
|
params[key] = safeDecode(match[i2 + 1]);
|
|
3392
3497
|
}
|
|
@@ -3483,7 +3588,7 @@ function renderRouteToString(url, routes, _options) {
|
|
|
3483
3588
|
function renderRouteToDocument(url, routes, options) {
|
|
3484
3589
|
const { html: html2, state } = renderRouteToString(url, routes, options);
|
|
3485
3590
|
const opts = options || {};
|
|
3486
|
-
const metaTags = (opts.meta || []).map((attrs) => {
|
|
3591
|
+
const metaTags = (opts.meta || []).filter((attrs) => !isDangerousMetaRefresh(attrs)).map((attrs) => {
|
|
3487
3592
|
const pairs = buildSafeAttrString(attrs);
|
|
3488
3593
|
return pairs ? `<meta ${pairs} />` : "";
|
|
3489
3594
|
}).filter(Boolean).join("\n ");
|
|
@@ -3563,7 +3668,7 @@ var SAFE_ATTR_NAME2 = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
|
|
|
3563
3668
|
function isSafeAttrName2(name) {
|
|
3564
3669
|
return SAFE_ATTR_NAME2.test(name);
|
|
3565
3670
|
}
|
|
3566
|
-
function
|
|
3671
|
+
function isEventHandlerAttr2(name) {
|
|
3567
3672
|
if (name.length < 3) return false;
|
|
3568
3673
|
const lower = name.toLowerCase();
|
|
3569
3674
|
return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
|
|
@@ -3596,7 +3701,7 @@ function buildSafeAttrString(attrs) {
|
|
|
3596
3701
|
for (const rawKey of Object.keys(attrs)) {
|
|
3597
3702
|
if (!Object.hasOwn(attrs, rawKey)) continue;
|
|
3598
3703
|
if (!isSafeAttrName2(rawKey)) continue;
|
|
3599
|
-
if (
|
|
3704
|
+
if (isEventHandlerAttr2(rawKey)) continue;
|
|
3600
3705
|
const lowerKey = rawKey.toLowerCase();
|
|
3601
3706
|
let value = String(attrs[rawKey]);
|
|
3602
3707
|
if (URL_ATTRS2.has(lowerKey)) {
|