dolphin-client 1.0.9 → 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
@@ -16,7 +16,7 @@ var APIHandler = class {
16
16
  target.patch = (pathOrBody, bodyOrOptions, options) => typeof pathOrBody === "string" ? this.request("PATCH", pathOrBody, bodyOrOptions, options) : this.request("PATCH", joined, pathOrBody, bodyOrOptions);
17
17
  target.del = (pathOrOptions, options) => typeof pathOrOptions === "string" ? this.request("DELETE", pathOrOptions, null, options) : this.request("DELETE", joined, null, pathOrOptions);
18
18
  target.request = (method, subPath, body, options) => {
19
- const finalPath = subPath ? `${joined}/${subPath.startsWith("/") ? subPath.slice(1) : subPath}` : joined;
19
+ const finalPath = subPath ? joined ? `${joined}/${subPath.startsWith("/") ? subPath.slice(1) : subPath}` : subPath : joined;
20
20
  return this.request(method, finalPath, body, options);
21
21
  };
22
22
  target.requestDirect = (method, path, body, options) => {
@@ -200,8 +200,9 @@ var APIHandler = class {
200
200
  const _isRetry = options._isRetry === true;
201
201
  let finalMethod = method.toUpperCase();
202
202
  let finalBody = body;
203
+ const hasBody = !["GET", "HEAD"].includes(finalMethod);
203
204
  const headers = {
204
- "Content-Type": "application/json",
205
+ ...hasBody ? { "Content-Type": "application/json" } : {},
205
206
  ...options.headers || {}
206
207
  };
207
208
  if (["PUT", "PATCH", "DELETE"].includes(finalMethod)) {
@@ -971,6 +972,82 @@ function attachDOMBinding(clientProto) {
971
972
  function escapeRegExp(str) {
972
973
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
973
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
+ }
974
1051
  function resolveTemplate(el) {
975
1052
  const template = el.getAttribute("data-rt-template");
976
1053
  if (!template) return null;
@@ -987,9 +1064,26 @@ function attachDOMBinding(clientProto) {
987
1064
  if (!templateStr.includes("{#if") && !templateStr.includes("{#each")) {
988
1065
  let result = templateStr;
989
1066
  for (let key in context) {
990
- const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
991
- result = result.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
992
- }
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
+ });
993
1087
  return result;
994
1088
  }
995
1089
  try {
@@ -1109,7 +1203,9 @@ function attachDOMBinding(clientProto) {
1109
1203
  if (typeof document === "undefined") return html;
1110
1204
  try {
1111
1205
  const parser = new DOMParser();
1112
- 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");
1113
1209
  const body = doc.body;
1114
1210
  const sanitizeNode = (el) => {
1115
1211
  const tag = el.tagName.toLowerCase();
@@ -1223,7 +1319,7 @@ function attachDOMBinding(clientProto) {
1223
1319
  });
1224
1320
  }
1225
1321
  }
1226
- clientProto.setStoreState = function(storeName, key, val) {
1322
+ clientProto.setStoreState = function(storeName, key, val, originEl) {
1227
1323
  this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
1228
1324
  if (!this.uiStores.has(storeName)) {
1229
1325
  this.uiStores.set(storeName, {});
@@ -1236,6 +1332,7 @@ function attachDOMBinding(clientProto) {
1236
1332
  if (typeof document !== "undefined") {
1237
1333
  const readElements = document.querySelectorAll(`[data-store-read="${storeName}.${key}"]`);
1238
1334
  readElements.forEach((el) => {
1335
+ if (el === originEl) return;
1239
1336
  if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
1240
1337
  if (el.type === "checkbox") {
1241
1338
  el.checked = !!val;
@@ -1311,20 +1408,49 @@ function attachDOMBinding(clientProto) {
1311
1408
  if (key) return ctx[key];
1312
1409
  return ctx;
1313
1410
  }
1314
- current = current.parentElement;
1411
+ current = current.parentElement || current.parentNode;
1315
1412
  }
1316
1413
  return null;
1317
1414
  };
1318
1415
  clientProto._executeStoreAction = function(expression, element) {
1319
1416
  this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
1417
+ const parentCtx = element && typeof this.getClosestContext === "function" ? this.getClosestContext(element) : null;
1320
1418
  const context = new Proxy({}, {
1321
1419
  has: (target, prop) => {
1322
1420
  return true;
1323
1421
  },
1324
1422
  get: (target, prop) => {
1325
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
+ }
1326
1450
  return new Proxy({}, {
1327
1451
  get: (subTarget, subProp) => {
1452
+ if (subProp === "__storeName__") return prop;
1453
+ if (subProp === "__isStoreProxy__") return true;
1328
1454
  if (typeof subProp === "string") {
1329
1455
  return this.getStoreState(prop, subProp);
1330
1456
  }
@@ -1366,7 +1492,7 @@ function attachDOMBinding(clientProto) {
1366
1492
  const storeName = parts[0];
1367
1493
  const key = parts[1];
1368
1494
  const val = e.target.type === "checkbox" ? e.target.checked : e.target.value;
1369
- this.setStoreState(storeName, key, val);
1495
+ this.setStoreState(storeName, key, val, e.target);
1370
1496
  }
1371
1497
  }
1372
1498
  const rules = e.target.getAttribute("data-rt-validate");
@@ -1726,24 +1852,33 @@ function attachDOMBinding(clientProto) {
1726
1852
  const processNode = (node) => {
1727
1853
  if (node.hasAttribute("data-rt-text")) {
1728
1854
  const key = node.getAttribute("data-rt-text");
1729
- 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
+ }
1730
1859
  }
1731
1860
  if (node.hasAttribute("data-rt-html")) {
1732
1861
  const key = node.getAttribute("data-rt-html");
1733
- if (key && processedPayload[key] !== void 0 && processedPayload[key] !== null) {
1734
- 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
+ }
1735
1867
  }
1736
1868
  }
1737
1869
  if (node.hasAttribute("data-rt-attr")) {
1738
1870
  const attrStr = node.getAttribute("data-rt-attr");
1739
1871
  if (attrStr) {
1740
- attrStr.split(",").forEach((b) => {
1741
- const parts = b.split(":");
1742
- if (parts.length === 2) {
1743
- const attrName = parts[0].trim();
1744
- const key = parts[1].trim();
1745
- if (attrName && key && processedPayload[key] !== void 0 && processedPayload[key] !== null) {
1746
- 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
+ }
1747
1882
  }
1748
1883
  }
1749
1884
  });
@@ -1752,13 +1887,13 @@ function attachDOMBinding(clientProto) {
1752
1887
  if (node.hasAttribute("data-rt-class")) {
1753
1888
  const classStr = node.getAttribute("data-rt-class");
1754
1889
  if (classStr) {
1755
- classStr.split(",").forEach((b) => {
1756
- const parts = b.split(":");
1757
- if (parts.length === 2) {
1758
- const className = parts[0].trim();
1759
- 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();
1760
1895
  const classNames = className.split(/\s+/).filter(Boolean);
1761
- if (processedPayload[key]) {
1896
+ if (evaluateExpression(key, processedPayload)) {
1762
1897
  classNames.forEach((c) => node.classList.add(c));
1763
1898
  } else {
1764
1899
  classNames.forEach((c) => node.classList.remove(c));
@@ -1770,7 +1905,7 @@ function attachDOMBinding(clientProto) {
1770
1905
  if (node.hasAttribute("data-rt-if")) {
1771
1906
  const key = node.getAttribute("data-rt-if");
1772
1907
  if (key) {
1773
- if (processedPayload[key]) {
1908
+ if (evaluateExpression(key, processedPayload)) {
1774
1909
  node.style.display = "";
1775
1910
  } else {
1776
1911
  node.style.display = "none";
@@ -1780,7 +1915,7 @@ function attachDOMBinding(clientProto) {
1780
1915
  if (node.hasAttribute("data-rt-hide")) {
1781
1916
  const key = node.getAttribute("data-rt-hide");
1782
1917
  if (key) {
1783
- if (processedPayload[key]) {
1918
+ if (evaluateExpression(key, processedPayload)) {
1784
1919
  node.style.display = "none";
1785
1920
  } else {
1786
1921
  node.style.display = "";
@@ -1830,21 +1965,35 @@ function attachDOMBinding(clientProto) {
1830
1965
  return;
1831
1966
  }
1832
1967
  resolvingSet.add(src);
1833
- 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);
1834
1972
  if (!promise) {
1835
- promise = fetch(src).then((res) => {
1973
+ promise = fetch(url).then((res) => {
1836
1974
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
1837
1975
  return res.text();
1838
1976
  });
1839
- promise.catch(() => componentPromiseCache.delete(src));
1840
- componentPromiseCache.set(src, promise);
1977
+ promise.catch(() => componentPromiseCache.delete(url));
1978
+ componentPromiseCache.set(url, promise);
1841
1979
  }
1842
1980
  let content = "";
1843
1981
  try {
1844
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
+ }
1845
1994
  } catch (err) {
1846
- console.error(`[Dolphin Component Error]: Failed to fetch component "${src}":`, err);
1847
- 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>`;
1848
1997
  }
1849
1998
  el.innerHTML = sanitizeHTML(content);
1850
1999
  el.removeAttribute("data-import");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dolphin-client",
3
- "version": "1.0.9",
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",