dolphin-client 1.1.1 → 1.1.2

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;
@@ -1312,20 +1408,49 @@ function attachDOMBinding(clientProto) {
1312
1408
  if (key) return ctx[key];
1313
1409
  return ctx;
1314
1410
  }
1315
- current = current.parentElement;
1411
+ current = current.parentElement || current.parentNode;
1316
1412
  }
1317
1413
  return null;
1318
1414
  };
1319
1415
  clientProto._executeStoreAction = function(expression, element) {
1320
1416
  this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
1417
+ const parentCtx = element && typeof this.getClosestContext === "function" ? this.getClosestContext(element) : null;
1321
1418
  const context = new Proxy({}, {
1322
1419
  has: (target, prop) => {
1323
1420
  return true;
1324
1421
  },
1325
1422
  get: (target, prop) => {
1326
1423
  if (typeof prop === "string") {
1424
+ if (prop === "log") {
1425
+ return (arg) => {
1426
+ if (arg === void 0) {
1427
+ const allStores = {};
1428
+ this.uiStores.forEach((val, key) => {
1429
+ allStores[key] = { ...val };
1430
+ });
1431
+ console.log(`%c\u{1F4CA} [Dolphin All UI Stores]:`, "color: #06b6d4; font-weight: bold;", allStores);
1432
+ } else if (arg && typeof arg === "object" && arg.__isStoreProxy__) {
1433
+ const storeName = arg.__storeName__;
1434
+ const store = this.uiStores.get(storeName);
1435
+ console.log(`%c\u{1F4CA} [Dolphin Store: ${storeName}]:`, "color: #06b6d4; font-weight: bold;", store ? { ...store } : {});
1436
+ } else {
1437
+ console.log(`%c\u{1F4CA} [Dolphin Log]:`, "color: #06b6d4; font-weight: bold;", arg);
1438
+ }
1439
+ };
1440
+ }
1441
+ if (parentCtx && parentCtx[prop] !== void 0) {
1442
+ return parentCtx[prop];
1443
+ }
1444
+ if (typeof globalThis !== "undefined" && prop in globalThis) {
1445
+ return globalThis[prop];
1446
+ }
1447
+ if (typeof window !== "undefined" && prop in window) {
1448
+ return window[prop];
1449
+ }
1327
1450
  return new Proxy({}, {
1328
1451
  get: (subTarget, subProp) => {
1452
+ if (subProp === "__storeName__") return prop;
1453
+ if (subProp === "__isStoreProxy__") return true;
1329
1454
  if (typeof subProp === "string") {
1330
1455
  return this.getStoreState(prop, subProp);
1331
1456
  }
@@ -1352,86 +1477,6 @@ function attachDOMBinding(clientProto) {
1352
1477
  console.error("%cFailed Expression:", "color: #3b82f6; font-style: italic;", expression);
1353
1478
  }
1354
1479
  };
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
1480
  clientProto._initDOMBinding = function() {
1436
1481
  if (this._domInitialized) return;
1437
1482
  this._domInitialized = true;
@@ -1447,7 +1492,7 @@ function attachDOMBinding(clientProto) {
1447
1492
  const storeName = parts[0];
1448
1493
  const key = parts[1];
1449
1494
  const val = e.target.type === "checkbox" ? e.target.checked : e.target.value;
1450
- this.setStoreState(storeName, key, val);
1495
+ this.setStoreState(storeName, key, val, e.target);
1451
1496
  }
1452
1497
  }
1453
1498
  const rules = e.target.getAttribute("data-rt-validate");
@@ -1530,11 +1575,6 @@ function attachDOMBinding(clientProto) {
1530
1575
  resolvedTopic = resolvedTopic.replace(new RegExp(`\\{\\{${escapedK}\\}\\}`, "g"), parentCtx[k] !== void 0 && parentCtx[k] !== null ? parentCtx[k] : "");
1531
1576
  }
1532
1577
  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
1578
  } else if (apiTarget) {
1539
1579
  let resolvedTarget = apiTarget;
1540
1580
  for (const k in parentCtx) {
@@ -1551,40 +1591,16 @@ function attachDOMBinding(clientProto) {
1551
1591
  const result = await this.api.request(method, path, data);
1552
1592
  const resultBind = e.target.getAttribute("data-api-result");
1553
1593
  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
1594
  const redirect = e.target.getAttribute("data-api-redirect");
1567
1595
  if (redirect) window.location.href = redirect;
1568
1596
  if (e.target.hasAttribute("data-api-reload")) window.location.reload();
1569
1597
  } catch (err) {
1570
1598
  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
1599
  }
1584
1600
  }
1585
1601
  }
1586
1602
  });
1587
- const INTERACTION_EVENTS = ["click", "change", "input", "keydown", "keyup", "dblclick", "focus", "blur", "mouseenter", "mouseleave"];
1603
+ const INTERACTION_EVENTS = ["click", "change", "submit", "input", "keydown", "keyup", "dblclick", "focus", "blur", "mouseenter", "mouseleave"];
1588
1604
  INTERACTION_EVENTS.forEach((evtName) => {
1589
1605
  this.addDomListener(document, evtName, async (e) => {
1590
1606
  if (!e.target || !e.target.closest) return;
@@ -1615,12 +1631,7 @@ function attachDOMBinding(clientProto) {
1615
1631
  const apiTarget = apiBtn.getAttribute(`data-api-${evtName}`);
1616
1632
  const actionData = apiBtn.getAttribute("data-api-payload");
1617
1633
  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(" ");
1634
+ const parts = apiTarget.trim().split(" ");
1624
1635
  const method = parts.length > 1 ? parts[0].toUpperCase() : "POST";
1625
1636
  const path = parts.length > 1 ? parts[1] : parts[0];
1626
1637
  let payload = null;
@@ -1640,35 +1651,11 @@ function attachDOMBinding(clientProto) {
1640
1651
  const result = await this.api.request(method, path, payload);
1641
1652
  const resultBind = apiBtn.getAttribute("data-api-result");
1642
1653
  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
1654
  const redirect = apiBtn.getAttribute("data-api-redirect");
1656
1655
  if (redirect) window.location.href = redirect;
1657
1656
  if (apiBtn.hasAttribute("data-api-reload")) window.location.reload();
1658
1657
  } catch (err) {
1659
1658
  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
1659
  }
1673
1660
  }
1674
1661
  const storeActionBtn = e.target.closest(`[data-store-${evtName}]`);
@@ -1865,24 +1852,33 @@ function attachDOMBinding(clientProto) {
1865
1852
  const processNode = (node) => {
1866
1853
  if (node.hasAttribute("data-rt-text")) {
1867
1854
  const key = node.getAttribute("data-rt-text");
1868
- if (key && processedPayload[key] !== void 0 && processedPayload[key] !== null) node.textContent = processedPayload[key];
1855
+ if (key) {
1856
+ const val = evaluateExpression(key, processedPayload);
1857
+ if (val !== void 0 && val !== null) node.textContent = val;
1858
+ }
1869
1859
  }
1870
1860
  if (node.hasAttribute("data-rt-html")) {
1871
1861
  const key = node.getAttribute("data-rt-html");
1872
- if (key && processedPayload[key] !== void 0 && processedPayload[key] !== null) {
1873
- node.innerHTML = sanitizeHTML(processedPayload[key]);
1862
+ if (key) {
1863
+ const val = evaluateExpression(key, processedPayload);
1864
+ if (val !== void 0 && val !== null) {
1865
+ node.innerHTML = sanitizeHTML(val);
1866
+ }
1874
1867
  }
1875
1868
  }
1876
1869
  if (node.hasAttribute("data-rt-attr")) {
1877
1870
  const attrStr = node.getAttribute("data-rt-attr");
1878
1871
  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]);
1872
+ splitByUnquotedChar(attrStr, ",").forEach((b) => {
1873
+ const pair = splitFirstUnquotedColon(b);
1874
+ if (pair) {
1875
+ const attrName = pair[0].trim();
1876
+ const key = pair[1].trim();
1877
+ if (attrName && key) {
1878
+ const val = evaluateExpression(key, processedPayload);
1879
+ if (val !== void 0 && val !== null) {
1880
+ node.setAttribute(attrName, val);
1881
+ }
1886
1882
  }
1887
1883
  }
1888
1884
  });
@@ -1891,13 +1887,13 @@ function attachDOMBinding(clientProto) {
1891
1887
  if (node.hasAttribute("data-rt-class")) {
1892
1888
  const classStr = node.getAttribute("data-rt-class");
1893
1889
  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();
1890
+ splitByUnquotedChar(classStr, ",").forEach((b) => {
1891
+ const pair = splitFirstUnquotedColon(b);
1892
+ if (pair) {
1893
+ const className = pair[0].trim();
1894
+ const key = pair[1].trim();
1899
1895
  const classNames = className.split(/\s+/).filter(Boolean);
1900
- if (processedPayload[key]) {
1896
+ if (evaluateExpression(key, processedPayload)) {
1901
1897
  classNames.forEach((c) => node.classList.add(c));
1902
1898
  } else {
1903
1899
  classNames.forEach((c) => node.classList.remove(c));
@@ -1909,7 +1905,7 @@ function attachDOMBinding(clientProto) {
1909
1905
  if (node.hasAttribute("data-rt-if")) {
1910
1906
  const key = node.getAttribute("data-rt-if");
1911
1907
  if (key) {
1912
- if (processedPayload[key]) {
1908
+ if (evaluateExpression(key, processedPayload)) {
1913
1909
  node.style.display = "";
1914
1910
  } else {
1915
1911
  node.style.display = "none";
@@ -1919,7 +1915,7 @@ function attachDOMBinding(clientProto) {
1919
1915
  if (node.hasAttribute("data-rt-hide")) {
1920
1916
  const key = node.getAttribute("data-rt-hide");
1921
1917
  if (key) {
1922
- if (processedPayload[key]) {
1918
+ if (evaluateExpression(key, processedPayload)) {
1923
1919
  node.style.display = "none";
1924
1920
  } else {
1925
1921
  node.style.display = "";
@@ -1969,21 +1965,35 @@ function attachDOMBinding(clientProto) {
1969
1965
  return;
1970
1966
  }
1971
1967
  resolvingSet.add(src);
1972
- let promise = componentPromiseCache.get(src);
1968
+ const hashIndex = src.indexOf("#");
1969
+ const url = hashIndex !== -1 ? src.substring(0, hashIndex) : src;
1970
+ const selector = hashIndex !== -1 ? src.substring(hashIndex) : null;
1971
+ let promise = componentPromiseCache.get(url);
1973
1972
  if (!promise) {
1974
- promise = fetch(src).then((res) => {
1973
+ promise = fetch(url).then((res) => {
1975
1974
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
1976
1975
  return res.text();
1977
1976
  });
1978
- promise.catch(() => componentPromiseCache.delete(src));
1979
- componentPromiseCache.set(src, promise);
1977
+ promise.catch(() => componentPromiseCache.delete(url));
1978
+ componentPromiseCache.set(url, promise);
1980
1979
  }
1981
1980
  let content = "";
1982
1981
  try {
1983
1982
  content = await promise;
1983
+ if (selector && typeof DOMParser !== "undefined") {
1984
+ const parser = new DOMParser();
1985
+ const doc = parser.parseFromString(content, "text/html");
1986
+ const targetEl = doc.querySelector(selector);
1987
+ if (targetEl) {
1988
+ content = targetEl.outerHTML;
1989
+ } else {
1990
+ console.warn(`[Dolphin Component Warning]: Selector "${selector}" not found in imported file "${url}".`);
1991
+ content = `<span style="color:orange;font-weight:bold;">Selector ${selector} not found in ${url}</span>`;
1992
+ }
1993
+ }
1984
1994
  } 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>`;
1995
+ console.error(`[Dolphin Component Error]: Failed to fetch component "${url}":`, err);
1996
+ content = `<span style="color:red;font-weight:bold;">Failed to import ${url}</span>`;
1987
1997
  }
1988
1998
  el.innerHTML = sanitizeHTML(content);
1989
1999
  el.removeAttribute("data-import");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dolphin-client",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "HTML is back! Hookless, framework-agnostic real-time reactive DOM-binding client for Dolphin Server with WebSockets, WebRTC signaling, and offline REST API fallbacks.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",