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.cjs
CHANGED
|
@@ -997,6 +997,82 @@ function attachDOMBinding(clientProto) {
|
|
|
997
997
|
function escapeRegExp(str) {
|
|
998
998
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
999
999
|
}
|
|
1000
|
+
function evaluateExpression(expr, ctx) {
|
|
1001
|
+
if (!ctx || typeof ctx !== "object") return void 0;
|
|
1002
|
+
try {
|
|
1003
|
+
const safeCtx = new Proxy(ctx, {
|
|
1004
|
+
has(target, prop) {
|
|
1005
|
+
return true;
|
|
1006
|
+
},
|
|
1007
|
+
get(target, prop) {
|
|
1008
|
+
if (typeof prop === "string") {
|
|
1009
|
+
if (prop in target) return target[prop];
|
|
1010
|
+
if (typeof globalThis !== "undefined" && prop in globalThis) return globalThis[prop];
|
|
1011
|
+
if (typeof window !== "undefined" && prop in window) return window[prop];
|
|
1012
|
+
}
|
|
1013
|
+
return void 0;
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
const fn = new Function("ctx", `with(ctx) { return (${expr}); }`);
|
|
1017
|
+
return fn(safeCtx);
|
|
1018
|
+
} catch {
|
|
1019
|
+
return ctx[expr];
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
function splitByUnquotedChar(str, char) {
|
|
1023
|
+
const parts = [];
|
|
1024
|
+
let current = "";
|
|
1025
|
+
let inSingleQuote = false;
|
|
1026
|
+
let inDoubleQuote = false;
|
|
1027
|
+
let inBacktick = false;
|
|
1028
|
+
let depth = 0;
|
|
1029
|
+
for (let i = 0; i < str.length; i++) {
|
|
1030
|
+
const c = str[i];
|
|
1031
|
+
if (c === "'" && !inDoubleQuote && !inBacktick) {
|
|
1032
|
+
inSingleQuote = !inSingleQuote;
|
|
1033
|
+
} else if (c === '"' && !inSingleQuote && !inBacktick) {
|
|
1034
|
+
inDoubleQuote = !inDoubleQuote;
|
|
1035
|
+
} else if (c === "`" && !inSingleQuote && !inDoubleQuote) {
|
|
1036
|
+
inBacktick = !inBacktick;
|
|
1037
|
+
} else if (c === "(" || c === "[" || c === "{") {
|
|
1038
|
+
if (!inSingleQuote && !inDoubleQuote && !inBacktick) depth++;
|
|
1039
|
+
} else if (c === ")" || c === "]" || c === "}") {
|
|
1040
|
+
if (!inSingleQuote && !inDoubleQuote && !inBacktick) depth--;
|
|
1041
|
+
}
|
|
1042
|
+
if (c === char && !inSingleQuote && !inDoubleQuote && !inBacktick && depth === 0) {
|
|
1043
|
+
parts.push(current);
|
|
1044
|
+
current = "";
|
|
1045
|
+
} else {
|
|
1046
|
+
current += c;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
parts.push(current);
|
|
1050
|
+
return parts;
|
|
1051
|
+
}
|
|
1052
|
+
function splitFirstUnquotedColon(str) {
|
|
1053
|
+
let inSingleQuote = false;
|
|
1054
|
+
let inDoubleQuote = false;
|
|
1055
|
+
let inBacktick = false;
|
|
1056
|
+
let depth = 0;
|
|
1057
|
+
for (let i = 0; i < str.length; i++) {
|
|
1058
|
+
const c = str[i];
|
|
1059
|
+
if (c === "'" && !inDoubleQuote && !inBacktick) {
|
|
1060
|
+
inSingleQuote = !inSingleQuote;
|
|
1061
|
+
} else if (c === '"' && !inSingleQuote && !inBacktick) {
|
|
1062
|
+
inDoubleQuote = !inDoubleQuote;
|
|
1063
|
+
} else if (c === "`" && !inSingleQuote && !inDoubleQuote) {
|
|
1064
|
+
inBacktick = !inBacktick;
|
|
1065
|
+
} else if (c === "(" || c === "[" || c === "{") {
|
|
1066
|
+
if (!inSingleQuote && !inDoubleQuote && !inBacktick) depth++;
|
|
1067
|
+
} else if (c === ")" || c === "]" || c === "}") {
|
|
1068
|
+
if (!inSingleQuote && !inDoubleQuote && !inBacktick) depth--;
|
|
1069
|
+
}
|
|
1070
|
+
if (c === ":" && !inSingleQuote && !inDoubleQuote && !inBacktick && depth === 0) {
|
|
1071
|
+
return [str.slice(0, i), str.slice(i + 1)];
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
return null;
|
|
1075
|
+
}
|
|
1000
1076
|
function resolveTemplate(el) {
|
|
1001
1077
|
const template = el.getAttribute("data-rt-template");
|
|
1002
1078
|
if (!template) return null;
|
|
@@ -1013,9 +1089,26 @@ function attachDOMBinding(clientProto) {
|
|
|
1013
1089
|
if (!templateStr.includes("{#if") && !templateStr.includes("{#each")) {
|
|
1014
1090
|
let result = templateStr;
|
|
1015
1091
|
for (let key in context) {
|
|
1016
|
-
const escapedKey = key.replace(/[
|
|
1017
|
-
result = result.replace(new RegExp(
|
|
1018
|
-
}
|
|
1092
|
+
const escapedKey = key.replace(/[.*+?^$${}()|[\]\\]/g, "\\$&");
|
|
1093
|
+
result = result.replace(new RegExp("\\{\\{" + escapedKey + "\\}}", "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
|
|
1094
|
+
}
|
|
1095
|
+
result = result.replace(/\{\{([\s\S]*?)\}\}/g, (match, expr) => {
|
|
1096
|
+
const trimmed = expr.trim();
|
|
1097
|
+
if (!trimmed) return "";
|
|
1098
|
+
if (/^[a-zA-Z_$][a-zA-Z0-9_$]*(?:\??\.[a-zA-Z_$][a-zA-Z0-9_$]*)+$/.test(trimmed)) {
|
|
1099
|
+
const parts = trimmed.split(/\??\./);
|
|
1100
|
+
let val = context;
|
|
1101
|
+
for (const part of parts) {
|
|
1102
|
+
if (val === void 0 || val === null) {
|
|
1103
|
+
val = void 0;
|
|
1104
|
+
break;
|
|
1105
|
+
}
|
|
1106
|
+
val = val[part];
|
|
1107
|
+
}
|
|
1108
|
+
return val !== void 0 && val !== null ? val : "";
|
|
1109
|
+
}
|
|
1110
|
+
return match;
|
|
1111
|
+
});
|
|
1019
1112
|
return result;
|
|
1020
1113
|
}
|
|
1021
1114
|
try {
|
|
@@ -1135,7 +1228,9 @@ function attachDOMBinding(clientProto) {
|
|
|
1135
1228
|
if (typeof document === "undefined") return html;
|
|
1136
1229
|
try {
|
|
1137
1230
|
const parser = new DOMParser();
|
|
1138
|
-
const
|
|
1231
|
+
const hasBodyOrHtml = /<\s*(?:body|html)\b/i.test(html);
|
|
1232
|
+
const parseString = hasBodyOrHtml ? html : `<body>${html}</body>`;
|
|
1233
|
+
const doc = parser.parseFromString(parseString, "text/html");
|
|
1139
1234
|
const body = doc.body;
|
|
1140
1235
|
const sanitizeNode = (el) => {
|
|
1141
1236
|
const tag = el.tagName.toLowerCase();
|
|
@@ -1249,7 +1344,7 @@ function attachDOMBinding(clientProto) {
|
|
|
1249
1344
|
});
|
|
1250
1345
|
}
|
|
1251
1346
|
}
|
|
1252
|
-
clientProto.setStoreState = function(storeName, key, val) {
|
|
1347
|
+
clientProto.setStoreState = function(storeName, key, val, originEl) {
|
|
1253
1348
|
this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
|
|
1254
1349
|
if (!this.uiStores.has(storeName)) {
|
|
1255
1350
|
this.uiStores.set(storeName, {});
|
|
@@ -1262,6 +1357,7 @@ function attachDOMBinding(clientProto) {
|
|
|
1262
1357
|
if (typeof document !== "undefined") {
|
|
1263
1358
|
const readElements = document.querySelectorAll(`[data-store-read="${storeName}.${key}"]`);
|
|
1264
1359
|
readElements.forEach((el) => {
|
|
1360
|
+
if (el === originEl) return;
|
|
1265
1361
|
if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
|
|
1266
1362
|
if (el.type === "checkbox") {
|
|
1267
1363
|
el.checked = !!val;
|
|
@@ -1285,6 +1381,73 @@ function attachDOMBinding(clientProto) {
|
|
|
1285
1381
|
};
|
|
1286
1382
|
clientProto._scanStoreBinds = function() {
|
|
1287
1383
|
if (typeof document === "undefined") return;
|
|
1384
|
+
const storeElements = document.querySelectorAll("dolphin-store");
|
|
1385
|
+
storeElements.forEach((el) => {
|
|
1386
|
+
if (typeof el.getAttribute !== "function") return;
|
|
1387
|
+
const storeName = el.getAttribute("name") || el.getAttribute("data-store");
|
|
1388
|
+
if (!storeName) return;
|
|
1389
|
+
const hasChildren = el.children && el.children.length > 0;
|
|
1390
|
+
if (hasChildren) {
|
|
1391
|
+
if (typeof el.setAttribute === "function") {
|
|
1392
|
+
el.setAttribute("data-rt-bind", `store/${storeName}`);
|
|
1393
|
+
el.setAttribute("data-rt-type", "context");
|
|
1394
|
+
}
|
|
1395
|
+
} else {
|
|
1396
|
+
if (el.style) {
|
|
1397
|
+
el.style.display = "none";
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
if (!hasChildren) {
|
|
1401
|
+
const content = el.textContent ? el.textContent.trim() : "";
|
|
1402
|
+
if (content && content.startsWith("{")) {
|
|
1403
|
+
try {
|
|
1404
|
+
const parsed = JSON.parse(content);
|
|
1405
|
+
if (parsed && typeof parsed === "object") {
|
|
1406
|
+
Object.keys(parsed).forEach((key) => {
|
|
1407
|
+
this.setStoreState(storeName, key, parsed[key]);
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
} catch (err) {
|
|
1411
|
+
console.error(`[Dolphin Store Init Error] Failed to parse JSON inside <dolphin-store name="${storeName}">:`, err);
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
const templateSelector = el.getAttribute("template");
|
|
1416
|
+
if (el.attributes) {
|
|
1417
|
+
const excludeAttrs = ["name", "data-store", "style", "data-rt-bind", "data-rt-type", "template"];
|
|
1418
|
+
Array.from(el.attributes).forEach((attr) => {
|
|
1419
|
+
if (!excludeAttrs.includes(attr.name)) {
|
|
1420
|
+
let val = attr.value;
|
|
1421
|
+
if (val === "true") val = true;
|
|
1422
|
+
else if (val === "false") val = false;
|
|
1423
|
+
else if (val === "null") val = null;
|
|
1424
|
+
else if (!isNaN(Number(val)) && val.trim() !== "") val = Number(val);
|
|
1425
|
+
this.setStoreState(storeName, attr.name, val);
|
|
1426
|
+
}
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
if (templateSelector && !hasChildren && el.parentNode && typeof document !== "undefined") {
|
|
1430
|
+
const markerId = `_ds_${storeName}_${templateSelector.replace(/[^a-z0-9]/gi, "_")}`;
|
|
1431
|
+
let wrapper = document.querySelector(`[data-ds-wired="${markerId}"]`);
|
|
1432
|
+
if (!wrapper) {
|
|
1433
|
+
wrapper = document.createElement("div");
|
|
1434
|
+
wrapper.setAttribute("data-rt-bind", `store/${storeName}`);
|
|
1435
|
+
wrapper.setAttribute("data-rt-template", templateSelector);
|
|
1436
|
+
wrapper.setAttribute("data-ds-wired", markerId);
|
|
1437
|
+
el.parentNode.insertBefore(wrapper, el.nextSibling);
|
|
1438
|
+
}
|
|
1439
|
+
if (typeof this._updateDOM === "function") {
|
|
1440
|
+
this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
|
|
1441
|
+
const currentStore = this.uiStores.get(storeName) || {};
|
|
1442
|
+
this._updateDOM(`store/${storeName}`, currentStore);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
if (hasChildren && typeof this._updateDOM === "function") {
|
|
1446
|
+
this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
|
|
1447
|
+
const currentStore = this.uiStores.get(storeName) || {};
|
|
1448
|
+
this._updateDOM(`store/${storeName}`, currentStore);
|
|
1449
|
+
}
|
|
1450
|
+
});
|
|
1288
1451
|
const writeEls = document.querySelectorAll("[data-store-write]");
|
|
1289
1452
|
writeEls.forEach((el) => {
|
|
1290
1453
|
const writeBind = el.getAttribute("data-store-write");
|
|
@@ -1337,20 +1500,49 @@ function attachDOMBinding(clientProto) {
|
|
|
1337
1500
|
if (key) return ctx[key];
|
|
1338
1501
|
return ctx;
|
|
1339
1502
|
}
|
|
1340
|
-
current = current.parentElement;
|
|
1503
|
+
current = current.parentElement || current.parentNode;
|
|
1341
1504
|
}
|
|
1342
1505
|
return null;
|
|
1343
1506
|
};
|
|
1344
1507
|
clientProto._executeStoreAction = function(expression, element) {
|
|
1345
1508
|
this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
|
|
1509
|
+
const parentCtx = element && typeof this.getClosestContext === "function" ? this.getClosestContext(element) : null;
|
|
1346
1510
|
const context = new Proxy({}, {
|
|
1347
1511
|
has: (target, prop) => {
|
|
1348
1512
|
return true;
|
|
1349
1513
|
},
|
|
1350
1514
|
get: (target, prop) => {
|
|
1351
1515
|
if (typeof prop === "string") {
|
|
1516
|
+
if (prop === "log") {
|
|
1517
|
+
return (arg) => {
|
|
1518
|
+
if (arg === void 0) {
|
|
1519
|
+
const allStores = {};
|
|
1520
|
+
this.uiStores.forEach((val, key) => {
|
|
1521
|
+
allStores[key] = { ...val };
|
|
1522
|
+
});
|
|
1523
|
+
console.log(`%c\u{1F4CA} [Dolphin All UI Stores]:`, "color: #06b6d4; font-weight: bold;", allStores);
|
|
1524
|
+
} else if (arg && typeof arg === "object" && arg.__isStoreProxy__) {
|
|
1525
|
+
const storeName = arg.__storeName__;
|
|
1526
|
+
const store = this.uiStores.get(storeName);
|
|
1527
|
+
console.log(`%c\u{1F4CA} [Dolphin Store: ${storeName}]:`, "color: #06b6d4; font-weight: bold;", store ? { ...store } : {});
|
|
1528
|
+
} else {
|
|
1529
|
+
console.log(`%c\u{1F4CA} [Dolphin Log]:`, "color: #06b6d4; font-weight: bold;", arg);
|
|
1530
|
+
}
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
if (parentCtx && parentCtx[prop] !== void 0) {
|
|
1534
|
+
return parentCtx[prop];
|
|
1535
|
+
}
|
|
1536
|
+
if (typeof globalThis !== "undefined" && prop in globalThis) {
|
|
1537
|
+
return globalThis[prop];
|
|
1538
|
+
}
|
|
1539
|
+
if (typeof window !== "undefined" && prop in window) {
|
|
1540
|
+
return window[prop];
|
|
1541
|
+
}
|
|
1352
1542
|
return new Proxy({}, {
|
|
1353
1543
|
get: (subTarget, subProp) => {
|
|
1544
|
+
if (subProp === "__storeName__") return prop;
|
|
1545
|
+
if (subProp === "__isStoreProxy__") return true;
|
|
1354
1546
|
if (typeof subProp === "string") {
|
|
1355
1547
|
return this.getStoreState(prop, subProp);
|
|
1356
1548
|
}
|
|
@@ -1377,86 +1569,6 @@ function attachDOMBinding(clientProto) {
|
|
|
1377
1569
|
console.error("%cFailed Expression:", "color: #3b82f6; font-style: italic;", expression);
|
|
1378
1570
|
}
|
|
1379
1571
|
};
|
|
1380
|
-
clientProto._showAlert = function(id, duration) {
|
|
1381
|
-
if (typeof document === "undefined") return;
|
|
1382
|
-
const el = document.getElementById(id) || document.querySelector(`[id="${id}"]`);
|
|
1383
|
-
if (!el) return;
|
|
1384
|
-
el.removeAttribute("hidden");
|
|
1385
|
-
el.style.display = "";
|
|
1386
|
-
if (duration && duration > 0) {
|
|
1387
|
-
setTimeout(() => {
|
|
1388
|
-
el.setAttribute("hidden", "");
|
|
1389
|
-
}, duration);
|
|
1390
|
-
}
|
|
1391
|
-
};
|
|
1392
|
-
clientProto._showToast = function(message, type = "success") {
|
|
1393
|
-
if (typeof document === "undefined") return;
|
|
1394
|
-
const colors = {
|
|
1395
|
-
success: { bg: "rgba(16,185,129,0.15)", border: "#10b981", icon: "\u2705" },
|
|
1396
|
-
error: { bg: "rgba(239,68,68,0.15)", border: "#ef4444", icon: "\u274C" },
|
|
1397
|
-
info: { bg: "rgba(59,130,246,0.15)", border: "#3b82f6", icon: "\u2139\uFE0F" }
|
|
1398
|
-
};
|
|
1399
|
-
const c = colors[type] || colors.success;
|
|
1400
|
-
const toast = document.createElement("div");
|
|
1401
|
-
toast.setAttribute("data-dolphin-toast", "");
|
|
1402
|
-
const existing = document.querySelectorAll("[data-dolphin-toast]");
|
|
1403
|
-
const offset = existing.length * 72;
|
|
1404
|
-
toast.style.cssText = [
|
|
1405
|
-
"position:fixed",
|
|
1406
|
-
`bottom:${24 + offset}px`,
|
|
1407
|
-
"right:24px",
|
|
1408
|
-
"z-index:2147483647",
|
|
1409
|
-
`background:${c.bg}`,
|
|
1410
|
-
`border:1px solid ${c.border}`,
|
|
1411
|
-
"color:#fff",
|
|
1412
|
-
"padding:14px 20px",
|
|
1413
|
-
"border-radius:14px",
|
|
1414
|
-
"font-size:14px",
|
|
1415
|
-
"font-weight:600",
|
|
1416
|
-
"font-family:system-ui,sans-serif",
|
|
1417
|
-
"box-shadow:0 8px 32px rgba(0,0,0,0.4)",
|
|
1418
|
-
"display:flex",
|
|
1419
|
-
"align-items:center",
|
|
1420
|
-
"gap:10px",
|
|
1421
|
-
"max-width:380px",
|
|
1422
|
-
"word-break:break-word",
|
|
1423
|
-
"transform:translateY(80px)",
|
|
1424
|
-
"opacity:0",
|
|
1425
|
-
"transition:transform 0.35s cubic-bezier(0.34,1.56,0.64,1),opacity 0.3s ease",
|
|
1426
|
-
"pointer-events:auto",
|
|
1427
|
-
"backdrop-filter:blur(12px)"
|
|
1428
|
-
].join(";");
|
|
1429
|
-
toast.innerHTML = `<span style="font-size:18px">${c.icon}</span><span>${message}</span>`;
|
|
1430
|
-
document.body.appendChild(toast);
|
|
1431
|
-
const restack = () => {
|
|
1432
|
-
const remaining = document.querySelectorAll("[data-dolphin-toast]");
|
|
1433
|
-
remaining.forEach((el, i) => {
|
|
1434
|
-
el.style.bottom = `${24 + i * 72}px`;
|
|
1435
|
-
});
|
|
1436
|
-
};
|
|
1437
|
-
const raf = typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : (cb) => setTimeout(cb, 0);
|
|
1438
|
-
raf(() => raf(() => {
|
|
1439
|
-
toast.style.transform = "translateY(0)";
|
|
1440
|
-
toast.style.opacity = "1";
|
|
1441
|
-
}));
|
|
1442
|
-
const removeToast = () => {
|
|
1443
|
-
clearTimeout(toast._removeTimer);
|
|
1444
|
-
if (toast.parentNode) {
|
|
1445
|
-
toast.parentNode.removeChild(toast);
|
|
1446
|
-
restack();
|
|
1447
|
-
}
|
|
1448
|
-
};
|
|
1449
|
-
const hideTimer = setTimeout(() => {
|
|
1450
|
-
toast.style.transform = "translateY(80px)";
|
|
1451
|
-
toast.style.opacity = "0";
|
|
1452
|
-
toast._removeTimer = setTimeout(removeToast, 400);
|
|
1453
|
-
}, 3500);
|
|
1454
|
-
toast._hideTimer = hideTimer;
|
|
1455
|
-
toast.addEventListener("click", () => {
|
|
1456
|
-
clearTimeout(toast._hideTimer);
|
|
1457
|
-
removeToast();
|
|
1458
|
-
}, { once: true });
|
|
1459
|
-
};
|
|
1460
1572
|
clientProto._initDOMBinding = function() {
|
|
1461
1573
|
if (this._domInitialized) return;
|
|
1462
1574
|
this._domInitialized = true;
|
|
@@ -1472,7 +1584,7 @@ function attachDOMBinding(clientProto) {
|
|
|
1472
1584
|
const storeName = parts[0];
|
|
1473
1585
|
const key = parts[1];
|
|
1474
1586
|
const val = e.target.type === "checkbox" ? e.target.checked : e.target.value;
|
|
1475
|
-
this.setStoreState(storeName, key, val);
|
|
1587
|
+
this.setStoreState(storeName, key, val, e.target);
|
|
1476
1588
|
}
|
|
1477
1589
|
}
|
|
1478
1590
|
const rules = e.target.getAttribute("data-rt-validate");
|
|
@@ -1555,11 +1667,6 @@ function attachDOMBinding(clientProto) {
|
|
|
1555
1667
|
resolvedTopic = resolvedTopic.replace(new RegExp(`\\{\\{${escapedK}\\}\\}`, "g"), parentCtx[k] !== void 0 && parentCtx[k] !== null ? parentCtx[k] : "");
|
|
1556
1668
|
}
|
|
1557
1669
|
this.publish(resolvedTopic, data);
|
|
1558
|
-
const rtSuccessId = e.target.getAttribute("data-rt-api-success");
|
|
1559
|
-
if (rtSuccessId) {
|
|
1560
|
-
const rtSuccessEl = document.getElementById(rtSuccessId);
|
|
1561
|
-
if (rtSuccessEl) this._showToast(rtSuccessEl.textContent || rtSuccessEl.innerText || "", "success");
|
|
1562
|
-
}
|
|
1563
1670
|
} else if (apiTarget) {
|
|
1564
1671
|
let resolvedTarget = apiTarget;
|
|
1565
1672
|
for (const k in parentCtx) {
|
|
@@ -1576,40 +1683,16 @@ function attachDOMBinding(clientProto) {
|
|
|
1576
1683
|
const result = await this.api.request(method, path, data);
|
|
1577
1684
|
const resultBind = e.target.getAttribute("data-api-result");
|
|
1578
1685
|
if (resultBind) this._updateDOM(resultBind, result);
|
|
1579
|
-
const successToast = e.target.getAttribute("data-api-toast");
|
|
1580
|
-
if (successToast) this._showToast(successToast, "success");
|
|
1581
|
-
const successElId = e.target.getAttribute("data-rt-api-success");
|
|
1582
|
-
if (successElId) {
|
|
1583
|
-
const duration = parseInt(e.target.getAttribute("data-rt-alert-duration") || "0", 10);
|
|
1584
|
-
this._showAlert(successElId, duration);
|
|
1585
|
-
const errElId = e.target.getAttribute("data-rt-api-error");
|
|
1586
|
-
if (errElId) {
|
|
1587
|
-
const errEl = document.getElementById(errElId);
|
|
1588
|
-
if (errEl) errEl.setAttribute("hidden", "");
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
1686
|
const redirect = e.target.getAttribute("data-api-redirect");
|
|
1592
1687
|
if (redirect) window.location.href = redirect;
|
|
1593
1688
|
if (e.target.hasAttribute("data-api-reload")) window.location.reload();
|
|
1594
1689
|
} catch (err) {
|
|
1595
1690
|
console.error("[Dolphin] API Submit Error:", err);
|
|
1596
|
-
const errorToast = e.target.getAttribute("data-api-error-toast");
|
|
1597
|
-
if (errorToast) this._showToast(errorToast, "error");
|
|
1598
|
-
const errorElId = e.target.getAttribute("data-rt-api-error");
|
|
1599
|
-
if (errorElId) {
|
|
1600
|
-
const duration = parseInt(e.target.getAttribute("data-rt-alert-duration") || "0", 10);
|
|
1601
|
-
this._showAlert(errorElId, duration);
|
|
1602
|
-
const sucElId = e.target.getAttribute("data-rt-api-success");
|
|
1603
|
-
if (sucElId) {
|
|
1604
|
-
const sucEl = document.getElementById(sucElId);
|
|
1605
|
-
if (sucEl) sucEl.setAttribute("hidden", "");
|
|
1606
|
-
}
|
|
1607
|
-
}
|
|
1608
1691
|
}
|
|
1609
1692
|
}
|
|
1610
1693
|
}
|
|
1611
1694
|
});
|
|
1612
|
-
const INTERACTION_EVENTS = ["click", "change", "input", "keydown", "keyup", "dblclick", "focus", "blur", "mouseenter", "mouseleave"];
|
|
1695
|
+
const INTERACTION_EVENTS = ["click", "change", "submit", "input", "keydown", "keyup", "dblclick", "focus", "blur", "mouseenter", "mouseleave"];
|
|
1613
1696
|
INTERACTION_EVENTS.forEach((evtName) => {
|
|
1614
1697
|
this.addDomListener(document, evtName, async (e) => {
|
|
1615
1698
|
if (!e.target || !e.target.closest) return;
|
|
@@ -1640,12 +1723,7 @@ function attachDOMBinding(clientProto) {
|
|
|
1640
1723
|
const apiTarget = apiBtn.getAttribute(`data-api-${evtName}`);
|
|
1641
1724
|
const actionData = apiBtn.getAttribute("data-api-payload");
|
|
1642
1725
|
const parentCtx = this.getClosestContext(apiBtn) || {};
|
|
1643
|
-
|
|
1644
|
-
for (const k in parentCtx) {
|
|
1645
|
-
const escapedK = escapeRegExp(k);
|
|
1646
|
-
resolvedApiTarget = resolvedApiTarget.replace(new RegExp(`\\{\\{${escapedK}\\}\\}`, "g"), parentCtx[k] !== void 0 && parentCtx[k] !== null ? parentCtx[k] : "");
|
|
1647
|
-
}
|
|
1648
|
-
const parts = resolvedApiTarget.trim().split(" ");
|
|
1726
|
+
const parts = apiTarget.trim().split(" ");
|
|
1649
1727
|
const method = parts.length > 1 ? parts[0].toUpperCase() : "POST";
|
|
1650
1728
|
const path = parts.length > 1 ? parts[1] : parts[0];
|
|
1651
1729
|
let payload = null;
|
|
@@ -1665,35 +1743,11 @@ function attachDOMBinding(clientProto) {
|
|
|
1665
1743
|
const result = await this.api.request(method, path, payload);
|
|
1666
1744
|
const resultBind = apiBtn.getAttribute("data-api-result");
|
|
1667
1745
|
if (resultBind) this._updateDOM(resultBind, result);
|
|
1668
|
-
const successToast = apiBtn.getAttribute("data-api-toast");
|
|
1669
|
-
if (successToast) this._showToast(successToast, "success");
|
|
1670
|
-
const successElId = apiBtn.getAttribute("data-rt-api-success");
|
|
1671
|
-
if (successElId) {
|
|
1672
|
-
const duration = parseInt(apiBtn.getAttribute("data-rt-alert-duration") || "0", 10);
|
|
1673
|
-
this._showAlert(successElId, duration);
|
|
1674
|
-
const errElId = apiBtn.getAttribute("data-rt-api-error");
|
|
1675
|
-
if (errElId) {
|
|
1676
|
-
const errEl = document.getElementById(errElId);
|
|
1677
|
-
if (errEl) errEl.setAttribute("hidden", "");
|
|
1678
|
-
}
|
|
1679
|
-
}
|
|
1680
1746
|
const redirect = apiBtn.getAttribute("data-api-redirect");
|
|
1681
1747
|
if (redirect) window.location.href = redirect;
|
|
1682
1748
|
if (apiBtn.hasAttribute("data-api-reload")) window.location.reload();
|
|
1683
1749
|
} catch (err) {
|
|
1684
1750
|
console.error(`[Dolphin] API ${evtName} Error:`, err);
|
|
1685
|
-
const errorToast = apiBtn.getAttribute("data-api-error-toast");
|
|
1686
|
-
if (errorToast) this._showToast(errorToast, "error");
|
|
1687
|
-
const errorElId = apiBtn.getAttribute("data-rt-api-error");
|
|
1688
|
-
if (errorElId) {
|
|
1689
|
-
const duration = parseInt(apiBtn.getAttribute("data-rt-alert-duration") || "0", 10);
|
|
1690
|
-
this._showAlert(errorElId, duration);
|
|
1691
|
-
const sucElId = apiBtn.getAttribute("data-rt-api-success");
|
|
1692
|
-
if (sucElId) {
|
|
1693
|
-
const sucEl = document.getElementById(sucElId);
|
|
1694
|
-
if (sucEl) sucEl.setAttribute("hidden", "");
|
|
1695
|
-
}
|
|
1696
|
-
}
|
|
1697
1751
|
}
|
|
1698
1752
|
}
|
|
1699
1753
|
const storeActionBtn = e.target.closest(`[data-store-${evtName}]`);
|
|
@@ -1890,24 +1944,33 @@ function attachDOMBinding(clientProto) {
|
|
|
1890
1944
|
const processNode = (node) => {
|
|
1891
1945
|
if (node.hasAttribute("data-rt-text")) {
|
|
1892
1946
|
const key = node.getAttribute("data-rt-text");
|
|
1893
|
-
if (key
|
|
1947
|
+
if (key) {
|
|
1948
|
+
const val = evaluateExpression(key, processedPayload);
|
|
1949
|
+
if (val !== void 0 && val !== null) node.textContent = val;
|
|
1950
|
+
}
|
|
1894
1951
|
}
|
|
1895
1952
|
if (node.hasAttribute("data-rt-html")) {
|
|
1896
1953
|
const key = node.getAttribute("data-rt-html");
|
|
1897
|
-
if (key
|
|
1898
|
-
|
|
1954
|
+
if (key) {
|
|
1955
|
+
const val = evaluateExpression(key, processedPayload);
|
|
1956
|
+
if (val !== void 0 && val !== null) {
|
|
1957
|
+
node.innerHTML = sanitizeHTML(val);
|
|
1958
|
+
}
|
|
1899
1959
|
}
|
|
1900
1960
|
}
|
|
1901
1961
|
if (node.hasAttribute("data-rt-attr")) {
|
|
1902
1962
|
const attrStr = node.getAttribute("data-rt-attr");
|
|
1903
1963
|
if (attrStr) {
|
|
1904
|
-
attrStr
|
|
1905
|
-
const
|
|
1906
|
-
if (
|
|
1907
|
-
const attrName =
|
|
1908
|
-
const key =
|
|
1909
|
-
if (attrName && key
|
|
1910
|
-
|
|
1964
|
+
splitByUnquotedChar(attrStr, ",").forEach((b) => {
|
|
1965
|
+
const pair = splitFirstUnquotedColon(b);
|
|
1966
|
+
if (pair) {
|
|
1967
|
+
const attrName = pair[0].trim();
|
|
1968
|
+
const key = pair[1].trim();
|
|
1969
|
+
if (attrName && key) {
|
|
1970
|
+
const val = evaluateExpression(key, processedPayload);
|
|
1971
|
+
if (val !== void 0 && val !== null) {
|
|
1972
|
+
node.setAttribute(attrName, val);
|
|
1973
|
+
}
|
|
1911
1974
|
}
|
|
1912
1975
|
}
|
|
1913
1976
|
});
|
|
@@ -1916,13 +1979,13 @@ function attachDOMBinding(clientProto) {
|
|
|
1916
1979
|
if (node.hasAttribute("data-rt-class")) {
|
|
1917
1980
|
const classStr = node.getAttribute("data-rt-class");
|
|
1918
1981
|
if (classStr) {
|
|
1919
|
-
classStr
|
|
1920
|
-
const
|
|
1921
|
-
if (
|
|
1922
|
-
const className =
|
|
1923
|
-
const key =
|
|
1982
|
+
splitByUnquotedChar(classStr, ",").forEach((b) => {
|
|
1983
|
+
const pair = splitFirstUnquotedColon(b);
|
|
1984
|
+
if (pair) {
|
|
1985
|
+
const className = pair[0].trim();
|
|
1986
|
+
const key = pair[1].trim();
|
|
1924
1987
|
const classNames = className.split(/\s+/).filter(Boolean);
|
|
1925
|
-
if (processedPayload
|
|
1988
|
+
if (evaluateExpression(key, processedPayload)) {
|
|
1926
1989
|
classNames.forEach((c) => node.classList.add(c));
|
|
1927
1990
|
} else {
|
|
1928
1991
|
classNames.forEach((c) => node.classList.remove(c));
|
|
@@ -1934,7 +1997,7 @@ function attachDOMBinding(clientProto) {
|
|
|
1934
1997
|
if (node.hasAttribute("data-rt-if")) {
|
|
1935
1998
|
const key = node.getAttribute("data-rt-if");
|
|
1936
1999
|
if (key) {
|
|
1937
|
-
if (processedPayload
|
|
2000
|
+
if (evaluateExpression(key, processedPayload)) {
|
|
1938
2001
|
node.style.display = "";
|
|
1939
2002
|
} else {
|
|
1940
2003
|
node.style.display = "none";
|
|
@@ -1944,7 +2007,7 @@ function attachDOMBinding(clientProto) {
|
|
|
1944
2007
|
if (node.hasAttribute("data-rt-hide")) {
|
|
1945
2008
|
const key = node.getAttribute("data-rt-hide");
|
|
1946
2009
|
if (key) {
|
|
1947
|
-
if (processedPayload
|
|
2010
|
+
if (evaluateExpression(key, processedPayload)) {
|
|
1948
2011
|
node.style.display = "none";
|
|
1949
2012
|
} else {
|
|
1950
2013
|
node.style.display = "";
|
|
@@ -1994,21 +2057,35 @@ function attachDOMBinding(clientProto) {
|
|
|
1994
2057
|
return;
|
|
1995
2058
|
}
|
|
1996
2059
|
resolvingSet.add(src);
|
|
1997
|
-
|
|
2060
|
+
const hashIndex = src.indexOf("#");
|
|
2061
|
+
const url = hashIndex !== -1 ? src.substring(0, hashIndex) : src;
|
|
2062
|
+
const selector = hashIndex !== -1 ? src.substring(hashIndex) : null;
|
|
2063
|
+
let promise = componentPromiseCache.get(url);
|
|
1998
2064
|
if (!promise) {
|
|
1999
|
-
promise = fetch(
|
|
2065
|
+
promise = fetch(url).then((res) => {
|
|
2000
2066
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
2001
2067
|
return res.text();
|
|
2002
2068
|
});
|
|
2003
|
-
promise.catch(() => componentPromiseCache.delete(
|
|
2004
|
-
componentPromiseCache.set(
|
|
2069
|
+
promise.catch(() => componentPromiseCache.delete(url));
|
|
2070
|
+
componentPromiseCache.set(url, promise);
|
|
2005
2071
|
}
|
|
2006
2072
|
let content = "";
|
|
2007
2073
|
try {
|
|
2008
2074
|
content = await promise;
|
|
2075
|
+
if (selector && typeof DOMParser !== "undefined") {
|
|
2076
|
+
const parser = new DOMParser();
|
|
2077
|
+
const doc = parser.parseFromString(content, "text/html");
|
|
2078
|
+
const targetEl = doc.querySelector(selector);
|
|
2079
|
+
if (targetEl) {
|
|
2080
|
+
content = targetEl.outerHTML;
|
|
2081
|
+
} else {
|
|
2082
|
+
console.warn(`[Dolphin Component Warning]: Selector "${selector}" not found in imported file "${url}".`);
|
|
2083
|
+
content = `<span style="color:orange;font-weight:bold;">Selector ${selector} not found in ${url}</span>`;
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2009
2086
|
} catch (err) {
|
|
2010
|
-
console.error(`[Dolphin Component Error]: Failed to fetch component "${
|
|
2011
|
-
content = `<span style="color:red;font-weight:bold;">Failed to import ${
|
|
2087
|
+
console.error(`[Dolphin Component Error]: Failed to fetch component "${url}":`, err);
|
|
2088
|
+
content = `<span style="color:red;font-weight:bold;">Failed to import ${url}</span>`;
|
|
2012
2089
|
}
|
|
2013
2090
|
el.innerHTML = sanitizeHTML(content);
|
|
2014
2091
|
el.removeAttribute("data-import");
|
|
@@ -2776,6 +2853,32 @@ function attachPwa(clientProto) {
|
|
|
2776
2853
|
}
|
|
2777
2854
|
|
|
2778
2855
|
// src/testing.ts
|
|
2856
|
+
function createMockFn() {
|
|
2857
|
+
if (typeof jest !== "undefined" && typeof jest.fn === "function") {
|
|
2858
|
+
return jest.fn();
|
|
2859
|
+
}
|
|
2860
|
+
const fn = (...args) => {
|
|
2861
|
+
fn.mock.calls.push(args);
|
|
2862
|
+
if (fn._implementation) {
|
|
2863
|
+
return fn._implementation(...args);
|
|
2864
|
+
}
|
|
2865
|
+
return fn._returnValue;
|
|
2866
|
+
};
|
|
2867
|
+
fn.mock = {
|
|
2868
|
+
calls: []
|
|
2869
|
+
};
|
|
2870
|
+
fn._returnValue = void 0;
|
|
2871
|
+
fn._implementation = null;
|
|
2872
|
+
fn.mockReturnValue = (val) => {
|
|
2873
|
+
fn._returnValue = val;
|
|
2874
|
+
return fn;
|
|
2875
|
+
};
|
|
2876
|
+
fn.mockImplementation = (impl) => {
|
|
2877
|
+
fn._implementation = impl;
|
|
2878
|
+
return fn;
|
|
2879
|
+
};
|
|
2880
|
+
return fn;
|
|
2881
|
+
}
|
|
2779
2882
|
var DolphinTestUtils = class {
|
|
2780
2883
|
static render(html) {
|
|
2781
2884
|
if (typeof document === "undefined") {
|
|
@@ -2802,11 +2905,11 @@ var DolphinTestUtils = class {
|
|
|
2802
2905
|
send: (data) => {
|
|
2803
2906
|
sentMessages.push(data);
|
|
2804
2907
|
},
|
|
2805
|
-
close:
|
|
2806
|
-
onopen:
|
|
2807
|
-
onmessage:
|
|
2808
|
-
onclose:
|
|
2809
|
-
onerror:
|
|
2908
|
+
close: createMockFn(),
|
|
2909
|
+
onopen: createMockFn(),
|
|
2910
|
+
onmessage: createMockFn(),
|
|
2911
|
+
onclose: createMockFn(),
|
|
2912
|
+
onerror: createMockFn(),
|
|
2810
2913
|
sentMessages
|
|
2811
2914
|
};
|
|
2812
2915
|
global.WebSocket = class {
|
|
@@ -2841,8 +2944,8 @@ var DolphinTestUtils = class {
|
|
|
2841
2944
|
static simulateClick(el) {
|
|
2842
2945
|
const clickEvt = {
|
|
2843
2946
|
target: el,
|
|
2844
|
-
preventDefault:
|
|
2845
|
-
stopPropagation:
|
|
2947
|
+
preventDefault: createMockFn(),
|
|
2948
|
+
stopPropagation: createMockFn()
|
|
2846
2949
|
};
|
|
2847
2950
|
const clickListeners = global.document._listeners?.["click"] || [];
|
|
2848
2951
|
clickListeners.forEach((listener) => listener(clickEvt));
|
|
@@ -2851,8 +2954,8 @@ var DolphinTestUtils = class {
|
|
|
2851
2954
|
el.value = value;
|
|
2852
2955
|
const changeEvt = {
|
|
2853
2956
|
target: el,
|
|
2854
|
-
preventDefault:
|
|
2855
|
-
stopPropagation:
|
|
2957
|
+
preventDefault: createMockFn(),
|
|
2958
|
+
stopPropagation: createMockFn()
|
|
2856
2959
|
};
|
|
2857
2960
|
const changeListeners = global.document._listeners?.["change"] || [];
|
|
2858
2961
|
changeListeners.forEach((listener) => listener(changeEvt));
|