dolphin-client 1.1.1 → 1.1.3
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/.vscode/dolphin-tags.json +8 -0
- package/.vscode/settings.json +5 -0
- package/dist/dolphin-client.js +283 -180
- package/dist/dolphin-client.min.js +15 -15
- package/dist/index.cjs +283 -180
- package/dist/index.js +283 -180
- package/dist/testing.d.ts +5 -5
- package/fulltutorial.md +55 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -972,6 +972,82 @@ function attachDOMBinding(clientProto) {
|
|
|
972
972
|
function escapeRegExp(str) {
|
|
973
973
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
974
974
|
}
|
|
975
|
+
function evaluateExpression(expr, ctx) {
|
|
976
|
+
if (!ctx || typeof ctx !== "object") return void 0;
|
|
977
|
+
try {
|
|
978
|
+
const safeCtx = new Proxy(ctx, {
|
|
979
|
+
has(target, prop) {
|
|
980
|
+
return true;
|
|
981
|
+
},
|
|
982
|
+
get(target, prop) {
|
|
983
|
+
if (typeof prop === "string") {
|
|
984
|
+
if (prop in target) return target[prop];
|
|
985
|
+
if (typeof globalThis !== "undefined" && prop in globalThis) return globalThis[prop];
|
|
986
|
+
if (typeof window !== "undefined" && prop in window) return window[prop];
|
|
987
|
+
}
|
|
988
|
+
return void 0;
|
|
989
|
+
}
|
|
990
|
+
});
|
|
991
|
+
const fn = new Function("ctx", `with(ctx) { return (${expr}); }`);
|
|
992
|
+
return fn(safeCtx);
|
|
993
|
+
} catch {
|
|
994
|
+
return ctx[expr];
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
function splitByUnquotedChar(str, char) {
|
|
998
|
+
const parts = [];
|
|
999
|
+
let current = "";
|
|
1000
|
+
let inSingleQuote = false;
|
|
1001
|
+
let inDoubleQuote = false;
|
|
1002
|
+
let inBacktick = false;
|
|
1003
|
+
let depth = 0;
|
|
1004
|
+
for (let i = 0; i < str.length; i++) {
|
|
1005
|
+
const c = str[i];
|
|
1006
|
+
if (c === "'" && !inDoubleQuote && !inBacktick) {
|
|
1007
|
+
inSingleQuote = !inSingleQuote;
|
|
1008
|
+
} else if (c === '"' && !inSingleQuote && !inBacktick) {
|
|
1009
|
+
inDoubleQuote = !inDoubleQuote;
|
|
1010
|
+
} else if (c === "`" && !inSingleQuote && !inDoubleQuote) {
|
|
1011
|
+
inBacktick = !inBacktick;
|
|
1012
|
+
} else if (c === "(" || c === "[" || c === "{") {
|
|
1013
|
+
if (!inSingleQuote && !inDoubleQuote && !inBacktick) depth++;
|
|
1014
|
+
} else if (c === ")" || c === "]" || c === "}") {
|
|
1015
|
+
if (!inSingleQuote && !inDoubleQuote && !inBacktick) depth--;
|
|
1016
|
+
}
|
|
1017
|
+
if (c === char && !inSingleQuote && !inDoubleQuote && !inBacktick && depth === 0) {
|
|
1018
|
+
parts.push(current);
|
|
1019
|
+
current = "";
|
|
1020
|
+
} else {
|
|
1021
|
+
current += c;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
parts.push(current);
|
|
1025
|
+
return parts;
|
|
1026
|
+
}
|
|
1027
|
+
function splitFirstUnquotedColon(str) {
|
|
1028
|
+
let inSingleQuote = false;
|
|
1029
|
+
let inDoubleQuote = false;
|
|
1030
|
+
let inBacktick = false;
|
|
1031
|
+
let depth = 0;
|
|
1032
|
+
for (let i = 0; i < str.length; i++) {
|
|
1033
|
+
const c = str[i];
|
|
1034
|
+
if (c === "'" && !inDoubleQuote && !inBacktick) {
|
|
1035
|
+
inSingleQuote = !inSingleQuote;
|
|
1036
|
+
} else if (c === '"' && !inSingleQuote && !inBacktick) {
|
|
1037
|
+
inDoubleQuote = !inDoubleQuote;
|
|
1038
|
+
} else if (c === "`" && !inSingleQuote && !inDoubleQuote) {
|
|
1039
|
+
inBacktick = !inBacktick;
|
|
1040
|
+
} else if (c === "(" || c === "[" || c === "{") {
|
|
1041
|
+
if (!inSingleQuote && !inDoubleQuote && !inBacktick) depth++;
|
|
1042
|
+
} else if (c === ")" || c === "]" || c === "}") {
|
|
1043
|
+
if (!inSingleQuote && !inDoubleQuote && !inBacktick) depth--;
|
|
1044
|
+
}
|
|
1045
|
+
if (c === ":" && !inSingleQuote && !inDoubleQuote && !inBacktick && depth === 0) {
|
|
1046
|
+
return [str.slice(0, i), str.slice(i + 1)];
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
return null;
|
|
1050
|
+
}
|
|
975
1051
|
function resolveTemplate(el) {
|
|
976
1052
|
const template = el.getAttribute("data-rt-template");
|
|
977
1053
|
if (!template) return null;
|
|
@@ -988,9 +1064,26 @@ function attachDOMBinding(clientProto) {
|
|
|
988
1064
|
if (!templateStr.includes("{#if") && !templateStr.includes("{#each")) {
|
|
989
1065
|
let result = templateStr;
|
|
990
1066
|
for (let key in context) {
|
|
991
|
-
const escapedKey = key.replace(/[
|
|
992
|
-
result = result.replace(new RegExp(
|
|
993
|
-
}
|
|
1067
|
+
const escapedKey = key.replace(/[.*+?^$${}()|[\]\\]/g, "\\$&");
|
|
1068
|
+
result = result.replace(new RegExp("\\{\\{" + escapedKey + "\\}}", "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
|
|
1069
|
+
}
|
|
1070
|
+
result = result.replace(/\{\{([\s\S]*?)\}\}/g, (match, expr) => {
|
|
1071
|
+
const trimmed = expr.trim();
|
|
1072
|
+
if (!trimmed) return "";
|
|
1073
|
+
if (/^[a-zA-Z_$][a-zA-Z0-9_$]*(?:\??\.[a-zA-Z_$][a-zA-Z0-9_$]*)+$/.test(trimmed)) {
|
|
1074
|
+
const parts = trimmed.split(/\??\./);
|
|
1075
|
+
let val = context;
|
|
1076
|
+
for (const part of parts) {
|
|
1077
|
+
if (val === void 0 || val === null) {
|
|
1078
|
+
val = void 0;
|
|
1079
|
+
break;
|
|
1080
|
+
}
|
|
1081
|
+
val = val[part];
|
|
1082
|
+
}
|
|
1083
|
+
return val !== void 0 && val !== null ? val : "";
|
|
1084
|
+
}
|
|
1085
|
+
return match;
|
|
1086
|
+
});
|
|
994
1087
|
return result;
|
|
995
1088
|
}
|
|
996
1089
|
try {
|
|
@@ -1110,7 +1203,9 @@ function attachDOMBinding(clientProto) {
|
|
|
1110
1203
|
if (typeof document === "undefined") return html;
|
|
1111
1204
|
try {
|
|
1112
1205
|
const parser = new DOMParser();
|
|
1113
|
-
const
|
|
1206
|
+
const hasBodyOrHtml = /<\s*(?:body|html)\b/i.test(html);
|
|
1207
|
+
const parseString = hasBodyOrHtml ? html : `<body>${html}</body>`;
|
|
1208
|
+
const doc = parser.parseFromString(parseString, "text/html");
|
|
1114
1209
|
const body = doc.body;
|
|
1115
1210
|
const sanitizeNode = (el) => {
|
|
1116
1211
|
const tag = el.tagName.toLowerCase();
|
|
@@ -1224,7 +1319,7 @@ function attachDOMBinding(clientProto) {
|
|
|
1224
1319
|
});
|
|
1225
1320
|
}
|
|
1226
1321
|
}
|
|
1227
|
-
clientProto.setStoreState = function(storeName, key, val) {
|
|
1322
|
+
clientProto.setStoreState = function(storeName, key, val, originEl) {
|
|
1228
1323
|
this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
|
|
1229
1324
|
if (!this.uiStores.has(storeName)) {
|
|
1230
1325
|
this.uiStores.set(storeName, {});
|
|
@@ -1237,6 +1332,7 @@ function attachDOMBinding(clientProto) {
|
|
|
1237
1332
|
if (typeof document !== "undefined") {
|
|
1238
1333
|
const readElements = document.querySelectorAll(`[data-store-read="${storeName}.${key}"]`);
|
|
1239
1334
|
readElements.forEach((el) => {
|
|
1335
|
+
if (el === originEl) return;
|
|
1240
1336
|
if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
|
|
1241
1337
|
if (el.type === "checkbox") {
|
|
1242
1338
|
el.checked = !!val;
|
|
@@ -1260,6 +1356,73 @@ function attachDOMBinding(clientProto) {
|
|
|
1260
1356
|
};
|
|
1261
1357
|
clientProto._scanStoreBinds = function() {
|
|
1262
1358
|
if (typeof document === "undefined") return;
|
|
1359
|
+
const storeElements = document.querySelectorAll("dolphin-store");
|
|
1360
|
+
storeElements.forEach((el) => {
|
|
1361
|
+
if (typeof el.getAttribute !== "function") return;
|
|
1362
|
+
const storeName = el.getAttribute("name") || el.getAttribute("data-store");
|
|
1363
|
+
if (!storeName) return;
|
|
1364
|
+
const hasChildren = el.children && el.children.length > 0;
|
|
1365
|
+
if (hasChildren) {
|
|
1366
|
+
if (typeof el.setAttribute === "function") {
|
|
1367
|
+
el.setAttribute("data-rt-bind", `store/${storeName}`);
|
|
1368
|
+
el.setAttribute("data-rt-type", "context");
|
|
1369
|
+
}
|
|
1370
|
+
} else {
|
|
1371
|
+
if (el.style) {
|
|
1372
|
+
el.style.display = "none";
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
if (!hasChildren) {
|
|
1376
|
+
const content = el.textContent ? el.textContent.trim() : "";
|
|
1377
|
+
if (content && content.startsWith("{")) {
|
|
1378
|
+
try {
|
|
1379
|
+
const parsed = JSON.parse(content);
|
|
1380
|
+
if (parsed && typeof parsed === "object") {
|
|
1381
|
+
Object.keys(parsed).forEach((key) => {
|
|
1382
|
+
this.setStoreState(storeName, key, parsed[key]);
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
} catch (err) {
|
|
1386
|
+
console.error(`[Dolphin Store Init Error] Failed to parse JSON inside <dolphin-store name="${storeName}">:`, err);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
const templateSelector = el.getAttribute("template");
|
|
1391
|
+
if (el.attributes) {
|
|
1392
|
+
const excludeAttrs = ["name", "data-store", "style", "data-rt-bind", "data-rt-type", "template"];
|
|
1393
|
+
Array.from(el.attributes).forEach((attr) => {
|
|
1394
|
+
if (!excludeAttrs.includes(attr.name)) {
|
|
1395
|
+
let val = attr.value;
|
|
1396
|
+
if (val === "true") val = true;
|
|
1397
|
+
else if (val === "false") val = false;
|
|
1398
|
+
else if (val === "null") val = null;
|
|
1399
|
+
else if (!isNaN(Number(val)) && val.trim() !== "") val = Number(val);
|
|
1400
|
+
this.setStoreState(storeName, attr.name, val);
|
|
1401
|
+
}
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
if (templateSelector && !hasChildren && el.parentNode && typeof document !== "undefined") {
|
|
1405
|
+
const markerId = `_ds_${storeName}_${templateSelector.replace(/[^a-z0-9]/gi, "_")}`;
|
|
1406
|
+
let wrapper = document.querySelector(`[data-ds-wired="${markerId}"]`);
|
|
1407
|
+
if (!wrapper) {
|
|
1408
|
+
wrapper = document.createElement("div");
|
|
1409
|
+
wrapper.setAttribute("data-rt-bind", `store/${storeName}`);
|
|
1410
|
+
wrapper.setAttribute("data-rt-template", templateSelector);
|
|
1411
|
+
wrapper.setAttribute("data-ds-wired", markerId);
|
|
1412
|
+
el.parentNode.insertBefore(wrapper, el.nextSibling);
|
|
1413
|
+
}
|
|
1414
|
+
if (typeof this._updateDOM === "function") {
|
|
1415
|
+
this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
|
|
1416
|
+
const currentStore = this.uiStores.get(storeName) || {};
|
|
1417
|
+
this._updateDOM(`store/${storeName}`, currentStore);
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
if (hasChildren && typeof this._updateDOM === "function") {
|
|
1421
|
+
this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
|
|
1422
|
+
const currentStore = this.uiStores.get(storeName) || {};
|
|
1423
|
+
this._updateDOM(`store/${storeName}`, currentStore);
|
|
1424
|
+
}
|
|
1425
|
+
});
|
|
1263
1426
|
const writeEls = document.querySelectorAll("[data-store-write]");
|
|
1264
1427
|
writeEls.forEach((el) => {
|
|
1265
1428
|
const writeBind = el.getAttribute("data-store-write");
|
|
@@ -1312,20 +1475,49 @@ function attachDOMBinding(clientProto) {
|
|
|
1312
1475
|
if (key) return ctx[key];
|
|
1313
1476
|
return ctx;
|
|
1314
1477
|
}
|
|
1315
|
-
current = current.parentElement;
|
|
1478
|
+
current = current.parentElement || current.parentNode;
|
|
1316
1479
|
}
|
|
1317
1480
|
return null;
|
|
1318
1481
|
};
|
|
1319
1482
|
clientProto._executeStoreAction = function(expression, element) {
|
|
1320
1483
|
this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
|
|
1484
|
+
const parentCtx = element && typeof this.getClosestContext === "function" ? this.getClosestContext(element) : null;
|
|
1321
1485
|
const context = new Proxy({}, {
|
|
1322
1486
|
has: (target, prop) => {
|
|
1323
1487
|
return true;
|
|
1324
1488
|
},
|
|
1325
1489
|
get: (target, prop) => {
|
|
1326
1490
|
if (typeof prop === "string") {
|
|
1491
|
+
if (prop === "log") {
|
|
1492
|
+
return (arg) => {
|
|
1493
|
+
if (arg === void 0) {
|
|
1494
|
+
const allStores = {};
|
|
1495
|
+
this.uiStores.forEach((val, key) => {
|
|
1496
|
+
allStores[key] = { ...val };
|
|
1497
|
+
});
|
|
1498
|
+
console.log(`%c\u{1F4CA} [Dolphin All UI Stores]:`, "color: #06b6d4; font-weight: bold;", allStores);
|
|
1499
|
+
} else if (arg && typeof arg === "object" && arg.__isStoreProxy__) {
|
|
1500
|
+
const storeName = arg.__storeName__;
|
|
1501
|
+
const store = this.uiStores.get(storeName);
|
|
1502
|
+
console.log(`%c\u{1F4CA} [Dolphin Store: ${storeName}]:`, "color: #06b6d4; font-weight: bold;", store ? { ...store } : {});
|
|
1503
|
+
} else {
|
|
1504
|
+
console.log(`%c\u{1F4CA} [Dolphin Log]:`, "color: #06b6d4; font-weight: bold;", arg);
|
|
1505
|
+
}
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
if (parentCtx && parentCtx[prop] !== void 0) {
|
|
1509
|
+
return parentCtx[prop];
|
|
1510
|
+
}
|
|
1511
|
+
if (typeof globalThis !== "undefined" && prop in globalThis) {
|
|
1512
|
+
return globalThis[prop];
|
|
1513
|
+
}
|
|
1514
|
+
if (typeof window !== "undefined" && prop in window) {
|
|
1515
|
+
return window[prop];
|
|
1516
|
+
}
|
|
1327
1517
|
return new Proxy({}, {
|
|
1328
1518
|
get: (subTarget, subProp) => {
|
|
1519
|
+
if (subProp === "__storeName__") return prop;
|
|
1520
|
+
if (subProp === "__isStoreProxy__") return true;
|
|
1329
1521
|
if (typeof subProp === "string") {
|
|
1330
1522
|
return this.getStoreState(prop, subProp);
|
|
1331
1523
|
}
|
|
@@ -1352,86 +1544,6 @@ function attachDOMBinding(clientProto) {
|
|
|
1352
1544
|
console.error("%cFailed Expression:", "color: #3b82f6; font-style: italic;", expression);
|
|
1353
1545
|
}
|
|
1354
1546
|
};
|
|
1355
|
-
clientProto._showAlert = function(id, duration) {
|
|
1356
|
-
if (typeof document === "undefined") return;
|
|
1357
|
-
const el = document.getElementById(id) || document.querySelector(`[id="${id}"]`);
|
|
1358
|
-
if (!el) return;
|
|
1359
|
-
el.removeAttribute("hidden");
|
|
1360
|
-
el.style.display = "";
|
|
1361
|
-
if (duration && duration > 0) {
|
|
1362
|
-
setTimeout(() => {
|
|
1363
|
-
el.setAttribute("hidden", "");
|
|
1364
|
-
}, duration);
|
|
1365
|
-
}
|
|
1366
|
-
};
|
|
1367
|
-
clientProto._showToast = function(message, type = "success") {
|
|
1368
|
-
if (typeof document === "undefined") return;
|
|
1369
|
-
const colors = {
|
|
1370
|
-
success: { bg: "rgba(16,185,129,0.15)", border: "#10b981", icon: "\u2705" },
|
|
1371
|
-
error: { bg: "rgba(239,68,68,0.15)", border: "#ef4444", icon: "\u274C" },
|
|
1372
|
-
info: { bg: "rgba(59,130,246,0.15)", border: "#3b82f6", icon: "\u2139\uFE0F" }
|
|
1373
|
-
};
|
|
1374
|
-
const c = colors[type] || colors.success;
|
|
1375
|
-
const toast = document.createElement("div");
|
|
1376
|
-
toast.setAttribute("data-dolphin-toast", "");
|
|
1377
|
-
const existing = document.querySelectorAll("[data-dolphin-toast]");
|
|
1378
|
-
const offset = existing.length * 72;
|
|
1379
|
-
toast.style.cssText = [
|
|
1380
|
-
"position:fixed",
|
|
1381
|
-
`bottom:${24 + offset}px`,
|
|
1382
|
-
"right:24px",
|
|
1383
|
-
"z-index:2147483647",
|
|
1384
|
-
`background:${c.bg}`,
|
|
1385
|
-
`border:1px solid ${c.border}`,
|
|
1386
|
-
"color:#fff",
|
|
1387
|
-
"padding:14px 20px",
|
|
1388
|
-
"border-radius:14px",
|
|
1389
|
-
"font-size:14px",
|
|
1390
|
-
"font-weight:600",
|
|
1391
|
-
"font-family:system-ui,sans-serif",
|
|
1392
|
-
"box-shadow:0 8px 32px rgba(0,0,0,0.4)",
|
|
1393
|
-
"display:flex",
|
|
1394
|
-
"align-items:center",
|
|
1395
|
-
"gap:10px",
|
|
1396
|
-
"max-width:380px",
|
|
1397
|
-
"word-break:break-word",
|
|
1398
|
-
"transform:translateY(80px)",
|
|
1399
|
-
"opacity:0",
|
|
1400
|
-
"transition:transform 0.35s cubic-bezier(0.34,1.56,0.64,1),opacity 0.3s ease",
|
|
1401
|
-
"pointer-events:auto",
|
|
1402
|
-
"backdrop-filter:blur(12px)"
|
|
1403
|
-
].join(";");
|
|
1404
|
-
toast.innerHTML = `<span style="font-size:18px">${c.icon}</span><span>${message}</span>`;
|
|
1405
|
-
document.body.appendChild(toast);
|
|
1406
|
-
const restack = () => {
|
|
1407
|
-
const remaining = document.querySelectorAll("[data-dolphin-toast]");
|
|
1408
|
-
remaining.forEach((el, i) => {
|
|
1409
|
-
el.style.bottom = `${24 + i * 72}px`;
|
|
1410
|
-
});
|
|
1411
|
-
};
|
|
1412
|
-
const raf = typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : (cb) => setTimeout(cb, 0);
|
|
1413
|
-
raf(() => raf(() => {
|
|
1414
|
-
toast.style.transform = "translateY(0)";
|
|
1415
|
-
toast.style.opacity = "1";
|
|
1416
|
-
}));
|
|
1417
|
-
const removeToast = () => {
|
|
1418
|
-
clearTimeout(toast._removeTimer);
|
|
1419
|
-
if (toast.parentNode) {
|
|
1420
|
-
toast.parentNode.removeChild(toast);
|
|
1421
|
-
restack();
|
|
1422
|
-
}
|
|
1423
|
-
};
|
|
1424
|
-
const hideTimer = setTimeout(() => {
|
|
1425
|
-
toast.style.transform = "translateY(80px)";
|
|
1426
|
-
toast.style.opacity = "0";
|
|
1427
|
-
toast._removeTimer = setTimeout(removeToast, 400);
|
|
1428
|
-
}, 3500);
|
|
1429
|
-
toast._hideTimer = hideTimer;
|
|
1430
|
-
toast.addEventListener("click", () => {
|
|
1431
|
-
clearTimeout(toast._hideTimer);
|
|
1432
|
-
removeToast();
|
|
1433
|
-
}, { once: true });
|
|
1434
|
-
};
|
|
1435
1547
|
clientProto._initDOMBinding = function() {
|
|
1436
1548
|
if (this._domInitialized) return;
|
|
1437
1549
|
this._domInitialized = true;
|
|
@@ -1447,7 +1559,7 @@ function attachDOMBinding(clientProto) {
|
|
|
1447
1559
|
const storeName = parts[0];
|
|
1448
1560
|
const key = parts[1];
|
|
1449
1561
|
const val = e.target.type === "checkbox" ? e.target.checked : e.target.value;
|
|
1450
|
-
this.setStoreState(storeName, key, val);
|
|
1562
|
+
this.setStoreState(storeName, key, val, e.target);
|
|
1451
1563
|
}
|
|
1452
1564
|
}
|
|
1453
1565
|
const rules = e.target.getAttribute("data-rt-validate");
|
|
@@ -1530,11 +1642,6 @@ function attachDOMBinding(clientProto) {
|
|
|
1530
1642
|
resolvedTopic = resolvedTopic.replace(new RegExp(`\\{\\{${escapedK}\\}\\}`, "g"), parentCtx[k] !== void 0 && parentCtx[k] !== null ? parentCtx[k] : "");
|
|
1531
1643
|
}
|
|
1532
1644
|
this.publish(resolvedTopic, data);
|
|
1533
|
-
const rtSuccessId = e.target.getAttribute("data-rt-api-success");
|
|
1534
|
-
if (rtSuccessId) {
|
|
1535
|
-
const rtSuccessEl = document.getElementById(rtSuccessId);
|
|
1536
|
-
if (rtSuccessEl) this._showToast(rtSuccessEl.textContent || rtSuccessEl.innerText || "", "success");
|
|
1537
|
-
}
|
|
1538
1645
|
} else if (apiTarget) {
|
|
1539
1646
|
let resolvedTarget = apiTarget;
|
|
1540
1647
|
for (const k in parentCtx) {
|
|
@@ -1551,40 +1658,16 @@ function attachDOMBinding(clientProto) {
|
|
|
1551
1658
|
const result = await this.api.request(method, path, data);
|
|
1552
1659
|
const resultBind = e.target.getAttribute("data-api-result");
|
|
1553
1660
|
if (resultBind) this._updateDOM(resultBind, result);
|
|
1554
|
-
const successToast = e.target.getAttribute("data-api-toast");
|
|
1555
|
-
if (successToast) this._showToast(successToast, "success");
|
|
1556
|
-
const successElId = e.target.getAttribute("data-rt-api-success");
|
|
1557
|
-
if (successElId) {
|
|
1558
|
-
const duration = parseInt(e.target.getAttribute("data-rt-alert-duration") || "0", 10);
|
|
1559
|
-
this._showAlert(successElId, duration);
|
|
1560
|
-
const errElId = e.target.getAttribute("data-rt-api-error");
|
|
1561
|
-
if (errElId) {
|
|
1562
|
-
const errEl = document.getElementById(errElId);
|
|
1563
|
-
if (errEl) errEl.setAttribute("hidden", "");
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
1661
|
const redirect = e.target.getAttribute("data-api-redirect");
|
|
1567
1662
|
if (redirect) window.location.href = redirect;
|
|
1568
1663
|
if (e.target.hasAttribute("data-api-reload")) window.location.reload();
|
|
1569
1664
|
} catch (err) {
|
|
1570
1665
|
console.error("[Dolphin] API Submit Error:", err);
|
|
1571
|
-
const errorToast = e.target.getAttribute("data-api-error-toast");
|
|
1572
|
-
if (errorToast) this._showToast(errorToast, "error");
|
|
1573
|
-
const errorElId = e.target.getAttribute("data-rt-api-error");
|
|
1574
|
-
if (errorElId) {
|
|
1575
|
-
const duration = parseInt(e.target.getAttribute("data-rt-alert-duration") || "0", 10);
|
|
1576
|
-
this._showAlert(errorElId, duration);
|
|
1577
|
-
const sucElId = e.target.getAttribute("data-rt-api-success");
|
|
1578
|
-
if (sucElId) {
|
|
1579
|
-
const sucEl = document.getElementById(sucElId);
|
|
1580
|
-
if (sucEl) sucEl.setAttribute("hidden", "");
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
1666
|
}
|
|
1584
1667
|
}
|
|
1585
1668
|
}
|
|
1586
1669
|
});
|
|
1587
|
-
const INTERACTION_EVENTS = ["click", "change", "input", "keydown", "keyup", "dblclick", "focus", "blur", "mouseenter", "mouseleave"];
|
|
1670
|
+
const INTERACTION_EVENTS = ["click", "change", "submit", "input", "keydown", "keyup", "dblclick", "focus", "blur", "mouseenter", "mouseleave"];
|
|
1588
1671
|
INTERACTION_EVENTS.forEach((evtName) => {
|
|
1589
1672
|
this.addDomListener(document, evtName, async (e) => {
|
|
1590
1673
|
if (!e.target || !e.target.closest) return;
|
|
@@ -1615,12 +1698,7 @@ function attachDOMBinding(clientProto) {
|
|
|
1615
1698
|
const apiTarget = apiBtn.getAttribute(`data-api-${evtName}`);
|
|
1616
1699
|
const actionData = apiBtn.getAttribute("data-api-payload");
|
|
1617
1700
|
const parentCtx = this.getClosestContext(apiBtn) || {};
|
|
1618
|
-
|
|
1619
|
-
for (const k in parentCtx) {
|
|
1620
|
-
const escapedK = escapeRegExp(k);
|
|
1621
|
-
resolvedApiTarget = resolvedApiTarget.replace(new RegExp(`\\{\\{${escapedK}\\}\\}`, "g"), parentCtx[k] !== void 0 && parentCtx[k] !== null ? parentCtx[k] : "");
|
|
1622
|
-
}
|
|
1623
|
-
const parts = resolvedApiTarget.trim().split(" ");
|
|
1701
|
+
const parts = apiTarget.trim().split(" ");
|
|
1624
1702
|
const method = parts.length > 1 ? parts[0].toUpperCase() : "POST";
|
|
1625
1703
|
const path = parts.length > 1 ? parts[1] : parts[0];
|
|
1626
1704
|
let payload = null;
|
|
@@ -1640,35 +1718,11 @@ function attachDOMBinding(clientProto) {
|
|
|
1640
1718
|
const result = await this.api.request(method, path, payload);
|
|
1641
1719
|
const resultBind = apiBtn.getAttribute("data-api-result");
|
|
1642
1720
|
if (resultBind) this._updateDOM(resultBind, result);
|
|
1643
|
-
const successToast = apiBtn.getAttribute("data-api-toast");
|
|
1644
|
-
if (successToast) this._showToast(successToast, "success");
|
|
1645
|
-
const successElId = apiBtn.getAttribute("data-rt-api-success");
|
|
1646
|
-
if (successElId) {
|
|
1647
|
-
const duration = parseInt(apiBtn.getAttribute("data-rt-alert-duration") || "0", 10);
|
|
1648
|
-
this._showAlert(successElId, duration);
|
|
1649
|
-
const errElId = apiBtn.getAttribute("data-rt-api-error");
|
|
1650
|
-
if (errElId) {
|
|
1651
|
-
const errEl = document.getElementById(errElId);
|
|
1652
|
-
if (errEl) errEl.setAttribute("hidden", "");
|
|
1653
|
-
}
|
|
1654
|
-
}
|
|
1655
1721
|
const redirect = apiBtn.getAttribute("data-api-redirect");
|
|
1656
1722
|
if (redirect) window.location.href = redirect;
|
|
1657
1723
|
if (apiBtn.hasAttribute("data-api-reload")) window.location.reload();
|
|
1658
1724
|
} catch (err) {
|
|
1659
1725
|
console.error(`[Dolphin] API ${evtName} Error:`, err);
|
|
1660
|
-
const errorToast = apiBtn.getAttribute("data-api-error-toast");
|
|
1661
|
-
if (errorToast) this._showToast(errorToast, "error");
|
|
1662
|
-
const errorElId = apiBtn.getAttribute("data-rt-api-error");
|
|
1663
|
-
if (errorElId) {
|
|
1664
|
-
const duration = parseInt(apiBtn.getAttribute("data-rt-alert-duration") || "0", 10);
|
|
1665
|
-
this._showAlert(errorElId, duration);
|
|
1666
|
-
const sucElId = apiBtn.getAttribute("data-rt-api-success");
|
|
1667
|
-
if (sucElId) {
|
|
1668
|
-
const sucEl = document.getElementById(sucElId);
|
|
1669
|
-
if (sucEl) sucEl.setAttribute("hidden", "");
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
1726
|
}
|
|
1673
1727
|
}
|
|
1674
1728
|
const storeActionBtn = e.target.closest(`[data-store-${evtName}]`);
|
|
@@ -1865,24 +1919,33 @@ function attachDOMBinding(clientProto) {
|
|
|
1865
1919
|
const processNode = (node) => {
|
|
1866
1920
|
if (node.hasAttribute("data-rt-text")) {
|
|
1867
1921
|
const key = node.getAttribute("data-rt-text");
|
|
1868
|
-
if (key
|
|
1922
|
+
if (key) {
|
|
1923
|
+
const val = evaluateExpression(key, processedPayload);
|
|
1924
|
+
if (val !== void 0 && val !== null) node.textContent = val;
|
|
1925
|
+
}
|
|
1869
1926
|
}
|
|
1870
1927
|
if (node.hasAttribute("data-rt-html")) {
|
|
1871
1928
|
const key = node.getAttribute("data-rt-html");
|
|
1872
|
-
if (key
|
|
1873
|
-
|
|
1929
|
+
if (key) {
|
|
1930
|
+
const val = evaluateExpression(key, processedPayload);
|
|
1931
|
+
if (val !== void 0 && val !== null) {
|
|
1932
|
+
node.innerHTML = sanitizeHTML(val);
|
|
1933
|
+
}
|
|
1874
1934
|
}
|
|
1875
1935
|
}
|
|
1876
1936
|
if (node.hasAttribute("data-rt-attr")) {
|
|
1877
1937
|
const attrStr = node.getAttribute("data-rt-attr");
|
|
1878
1938
|
if (attrStr) {
|
|
1879
|
-
attrStr
|
|
1880
|
-
const
|
|
1881
|
-
if (
|
|
1882
|
-
const attrName =
|
|
1883
|
-
const key =
|
|
1884
|
-
if (attrName && key
|
|
1885
|
-
|
|
1939
|
+
splitByUnquotedChar(attrStr, ",").forEach((b) => {
|
|
1940
|
+
const pair = splitFirstUnquotedColon(b);
|
|
1941
|
+
if (pair) {
|
|
1942
|
+
const attrName = pair[0].trim();
|
|
1943
|
+
const key = pair[1].trim();
|
|
1944
|
+
if (attrName && key) {
|
|
1945
|
+
const val = evaluateExpression(key, processedPayload);
|
|
1946
|
+
if (val !== void 0 && val !== null) {
|
|
1947
|
+
node.setAttribute(attrName, val);
|
|
1948
|
+
}
|
|
1886
1949
|
}
|
|
1887
1950
|
}
|
|
1888
1951
|
});
|
|
@@ -1891,13 +1954,13 @@ function attachDOMBinding(clientProto) {
|
|
|
1891
1954
|
if (node.hasAttribute("data-rt-class")) {
|
|
1892
1955
|
const classStr = node.getAttribute("data-rt-class");
|
|
1893
1956
|
if (classStr) {
|
|
1894
|
-
classStr
|
|
1895
|
-
const
|
|
1896
|
-
if (
|
|
1897
|
-
const className =
|
|
1898
|
-
const key =
|
|
1957
|
+
splitByUnquotedChar(classStr, ",").forEach((b) => {
|
|
1958
|
+
const pair = splitFirstUnquotedColon(b);
|
|
1959
|
+
if (pair) {
|
|
1960
|
+
const className = pair[0].trim();
|
|
1961
|
+
const key = pair[1].trim();
|
|
1899
1962
|
const classNames = className.split(/\s+/).filter(Boolean);
|
|
1900
|
-
if (processedPayload
|
|
1963
|
+
if (evaluateExpression(key, processedPayload)) {
|
|
1901
1964
|
classNames.forEach((c) => node.classList.add(c));
|
|
1902
1965
|
} else {
|
|
1903
1966
|
classNames.forEach((c) => node.classList.remove(c));
|
|
@@ -1909,7 +1972,7 @@ function attachDOMBinding(clientProto) {
|
|
|
1909
1972
|
if (node.hasAttribute("data-rt-if")) {
|
|
1910
1973
|
const key = node.getAttribute("data-rt-if");
|
|
1911
1974
|
if (key) {
|
|
1912
|
-
if (processedPayload
|
|
1975
|
+
if (evaluateExpression(key, processedPayload)) {
|
|
1913
1976
|
node.style.display = "";
|
|
1914
1977
|
} else {
|
|
1915
1978
|
node.style.display = "none";
|
|
@@ -1919,7 +1982,7 @@ function attachDOMBinding(clientProto) {
|
|
|
1919
1982
|
if (node.hasAttribute("data-rt-hide")) {
|
|
1920
1983
|
const key = node.getAttribute("data-rt-hide");
|
|
1921
1984
|
if (key) {
|
|
1922
|
-
if (processedPayload
|
|
1985
|
+
if (evaluateExpression(key, processedPayload)) {
|
|
1923
1986
|
node.style.display = "none";
|
|
1924
1987
|
} else {
|
|
1925
1988
|
node.style.display = "";
|
|
@@ -1969,21 +2032,35 @@ function attachDOMBinding(clientProto) {
|
|
|
1969
2032
|
return;
|
|
1970
2033
|
}
|
|
1971
2034
|
resolvingSet.add(src);
|
|
1972
|
-
|
|
2035
|
+
const hashIndex = src.indexOf("#");
|
|
2036
|
+
const url = hashIndex !== -1 ? src.substring(0, hashIndex) : src;
|
|
2037
|
+
const selector = hashIndex !== -1 ? src.substring(hashIndex) : null;
|
|
2038
|
+
let promise = componentPromiseCache.get(url);
|
|
1973
2039
|
if (!promise) {
|
|
1974
|
-
promise = fetch(
|
|
2040
|
+
promise = fetch(url).then((res) => {
|
|
1975
2041
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1976
2042
|
return res.text();
|
|
1977
2043
|
});
|
|
1978
|
-
promise.catch(() => componentPromiseCache.delete(
|
|
1979
|
-
componentPromiseCache.set(
|
|
2044
|
+
promise.catch(() => componentPromiseCache.delete(url));
|
|
2045
|
+
componentPromiseCache.set(url, promise);
|
|
1980
2046
|
}
|
|
1981
2047
|
let content = "";
|
|
1982
2048
|
try {
|
|
1983
2049
|
content = await promise;
|
|
2050
|
+
if (selector && typeof DOMParser !== "undefined") {
|
|
2051
|
+
const parser = new DOMParser();
|
|
2052
|
+
const doc = parser.parseFromString(content, "text/html");
|
|
2053
|
+
const targetEl = doc.querySelector(selector);
|
|
2054
|
+
if (targetEl) {
|
|
2055
|
+
content = targetEl.outerHTML;
|
|
2056
|
+
} else {
|
|
2057
|
+
console.warn(`[Dolphin Component Warning]: Selector "${selector}" not found in imported file "${url}".`);
|
|
2058
|
+
content = `<span style="color:orange;font-weight:bold;">Selector ${selector} not found in ${url}</span>`;
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
1984
2061
|
} catch (err) {
|
|
1985
|
-
console.error(`[Dolphin Component Error]: Failed to fetch component "${
|
|
1986
|
-
content = `<span style="color:red;font-weight:bold;">Failed to import ${
|
|
2062
|
+
console.error(`[Dolphin Component Error]: Failed to fetch component "${url}":`, err);
|
|
2063
|
+
content = `<span style="color:red;font-weight:bold;">Failed to import ${url}</span>`;
|
|
1987
2064
|
}
|
|
1988
2065
|
el.innerHTML = sanitizeHTML(content);
|
|
1989
2066
|
el.removeAttribute("data-import");
|
|
@@ -2751,6 +2828,32 @@ function attachPwa(clientProto) {
|
|
|
2751
2828
|
}
|
|
2752
2829
|
|
|
2753
2830
|
// src/testing.ts
|
|
2831
|
+
function createMockFn() {
|
|
2832
|
+
if (typeof jest !== "undefined" && typeof jest.fn === "function") {
|
|
2833
|
+
return jest.fn();
|
|
2834
|
+
}
|
|
2835
|
+
const fn = (...args) => {
|
|
2836
|
+
fn.mock.calls.push(args);
|
|
2837
|
+
if (fn._implementation) {
|
|
2838
|
+
return fn._implementation(...args);
|
|
2839
|
+
}
|
|
2840
|
+
return fn._returnValue;
|
|
2841
|
+
};
|
|
2842
|
+
fn.mock = {
|
|
2843
|
+
calls: []
|
|
2844
|
+
};
|
|
2845
|
+
fn._returnValue = void 0;
|
|
2846
|
+
fn._implementation = null;
|
|
2847
|
+
fn.mockReturnValue = (val) => {
|
|
2848
|
+
fn._returnValue = val;
|
|
2849
|
+
return fn;
|
|
2850
|
+
};
|
|
2851
|
+
fn.mockImplementation = (impl) => {
|
|
2852
|
+
fn._implementation = impl;
|
|
2853
|
+
return fn;
|
|
2854
|
+
};
|
|
2855
|
+
return fn;
|
|
2856
|
+
}
|
|
2754
2857
|
var DolphinTestUtils = class {
|
|
2755
2858
|
static render(html) {
|
|
2756
2859
|
if (typeof document === "undefined") {
|
|
@@ -2777,11 +2880,11 @@ var DolphinTestUtils = class {
|
|
|
2777
2880
|
send: (data) => {
|
|
2778
2881
|
sentMessages.push(data);
|
|
2779
2882
|
},
|
|
2780
|
-
close:
|
|
2781
|
-
onopen:
|
|
2782
|
-
onmessage:
|
|
2783
|
-
onclose:
|
|
2784
|
-
onerror:
|
|
2883
|
+
close: createMockFn(),
|
|
2884
|
+
onopen: createMockFn(),
|
|
2885
|
+
onmessage: createMockFn(),
|
|
2886
|
+
onclose: createMockFn(),
|
|
2887
|
+
onerror: createMockFn(),
|
|
2785
2888
|
sentMessages
|
|
2786
2889
|
};
|
|
2787
2890
|
global.WebSocket = class {
|
|
@@ -2816,8 +2919,8 @@ var DolphinTestUtils = class {
|
|
|
2816
2919
|
static simulateClick(el) {
|
|
2817
2920
|
const clickEvt = {
|
|
2818
2921
|
target: el,
|
|
2819
|
-
preventDefault:
|
|
2820
|
-
stopPropagation:
|
|
2922
|
+
preventDefault: createMockFn(),
|
|
2923
|
+
stopPropagation: createMockFn()
|
|
2821
2924
|
};
|
|
2822
2925
|
const clickListeners = global.document._listeners?.["click"] || [];
|
|
2823
2926
|
clickListeners.forEach((listener) => listener(clickEvt));
|
|
@@ -2826,8 +2929,8 @@ var DolphinTestUtils = class {
|
|
|
2826
2929
|
el.value = value;
|
|
2827
2930
|
const changeEvt = {
|
|
2828
2931
|
target: el,
|
|
2829
|
-
preventDefault:
|
|
2830
|
-
stopPropagation:
|
|
2932
|
+
preventDefault: createMockFn(),
|
|
2933
|
+
stopPropagation: createMockFn()
|
|
2831
2934
|
};
|
|
2832
2935
|
const changeListeners = global.document._listeners?.["change"] || [];
|
|
2833
2936
|
changeListeners.forEach((listener) => listener(changeEvt));
|
package/dist/testing.d.ts
CHANGED
|
@@ -7,11 +7,11 @@ export declare class DolphinTestUtils {
|
|
|
7
7
|
static mockWebSocket(): {
|
|
8
8
|
readyState: number;
|
|
9
9
|
send: (data: string) => void;
|
|
10
|
-
close:
|
|
11
|
-
onopen:
|
|
12
|
-
onmessage:
|
|
13
|
-
onclose:
|
|
14
|
-
onerror:
|
|
10
|
+
close: any;
|
|
11
|
+
onopen: any;
|
|
12
|
+
onmessage: any;
|
|
13
|
+
onclose: any;
|
|
14
|
+
onerror: any;
|
|
15
15
|
sentMessages: string[];
|
|
16
16
|
};
|
|
17
17
|
static simulateClick(el: any): void;
|