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/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(/[.*+?^${}()|[\]\\]/g, "\\$&");
992
- result = result.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
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 doc = parser.parseFromString(html, "text/html");
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
- let resolvedApiTarget = apiTarget;
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 && processedPayload[key] !== void 0 && processedPayload[key] !== null) node.textContent = processedPayload[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 && processedPayload[key] !== void 0 && processedPayload[key] !== null) {
1873
- node.innerHTML = sanitizeHTML(processedPayload[key]);
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.split(",").forEach((b) => {
1880
- const parts = b.split(":");
1881
- if (parts.length === 2) {
1882
- const attrName = parts[0].trim();
1883
- const key = parts[1].trim();
1884
- if (attrName && key && processedPayload[key] !== void 0 && processedPayload[key] !== null) {
1885
- node.setAttribute(attrName, processedPayload[key]);
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.split(",").forEach((b) => {
1895
- const parts = b.split(":");
1896
- if (parts.length === 2) {
1897
- const className = parts[0].trim();
1898
- const key = parts[1].trim();
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[key]) {
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[key]) {
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[key]) {
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
- let promise = componentPromiseCache.get(src);
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(src).then((res) => {
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(src));
1979
- componentPromiseCache.set(src, promise);
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 "${src}":`, err);
1986
- content = `<span style="color:red;font-weight:bold;">Failed to import ${src}</span>`;
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: jest.fn(),
2781
- onopen: jest.fn(),
2782
- onmessage: jest.fn(),
2783
- onclose: jest.fn(),
2784
- onerror: jest.fn(),
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: jest.fn(),
2820
- stopPropagation: jest.fn()
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: jest.fn(),
2830
- stopPropagation: jest.fn()
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: jest.Mock<any, any, any>;
11
- onopen: jest.Mock<any, any, any>;
12
- onmessage: jest.Mock<any, any, any>;
13
- onclose: jest.Mock<any, any, any>;
14
- onerror: jest.Mock<any, any, any>;
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;