dolphin-client 1.0.3 → 1.0.5

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.
@@ -107,7 +107,7 @@ var DolphinModule = (() => {
107
107
  */
108
108
  async requestDirect(method, path, body = null, options = {}) {
109
109
  const _isRetry = options._isRetry === true;
110
- const url = `${this.client.httpUrl}${path.startsWith("/") ? path : "/" + path}`;
110
+ const url = path.startsWith("http://") || path.startsWith("https://") ? path : `${this.client.httpUrl}${path.startsWith("/") ? path : "/" + path}`;
111
111
  if (this.client.options.debug) {
112
112
  console.log(`%c\u{1F680} [Dolphin API Request]:`, "color: #3b82f6; font-weight: bold;", method.toUpperCase(), path, body || "");
113
113
  }
@@ -894,8 +894,28 @@ var DolphinModule = (() => {
894
894
  }
895
895
  }
896
896
  `;
897
+ let safeContext = context;
898
+ if (typeof Proxy !== "undefined" && context !== null && typeof context === "object") {
899
+ safeContext = new Proxy(context, {
900
+ has(target, key) {
901
+ if (typeof key === "symbol") return false;
902
+ return true;
903
+ },
904
+ get(target, key) {
905
+ if (key === Symbol.unscopables) return void 0;
906
+ if (key in target) return target[key];
907
+ if (typeof globalThis !== "undefined" && key in globalThis) {
908
+ return globalThis[key];
909
+ }
910
+ if (typeof window !== "undefined" && key in window) {
911
+ return window[key];
912
+ }
913
+ return void 0;
914
+ }
915
+ });
916
+ }
897
917
  const fn = new Function("context", fnBody);
898
- return fn(context);
918
+ return fn(safeContext);
899
919
  } catch (e) {
900
920
  console.error("[Dolphin Template Compiler Error]:", e);
901
921
  let fallback = templateStr;
@@ -1047,6 +1067,9 @@ var DolphinModule = (() => {
1047
1067
  });
1048
1068
  }
1049
1069
  this.publish(`store/${storeName}`, store);
1070
+ if (typeof this._updateDOM === "function") {
1071
+ this._updateDOM(`store/${storeName}`, store);
1072
+ }
1050
1073
  };
1051
1074
  clientProto.getStoreState = function(storeName, key) {
1052
1075
  this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
@@ -1344,20 +1367,28 @@ var DolphinModule = (() => {
1344
1367
  if (!path) continue;
1345
1368
  try {
1346
1369
  const result = await this.api.get(path);
1370
+ const apiStore = el.getAttribute("data-api-store");
1371
+ if (apiStore) {
1372
+ const parts = apiStore.split(".");
1373
+ if (parts.length === 2) {
1374
+ this.setStoreState(parts[0], parts[1], result);
1375
+ }
1376
+ }
1347
1377
  const rtBind = el.getAttribute("data-rt-bind");
1348
- if (rtBind) {
1378
+ if (rtBind && !apiStore) {
1349
1379
  this._updateDOM(rtBind, result);
1350
- } else {
1380
+ } else if (!apiStore) {
1351
1381
  const template = resolveTemplate(el);
1352
1382
  if (template && typeof result === "object" && result !== null) {
1353
- if (Array.isArray(result)) {
1383
+ const processedResult = this._applyDeclarativeDirectives(el, result);
1384
+ if (Array.isArray(processedResult)) {
1354
1385
  let combinedHTML = "";
1355
- for (const item of result) {
1386
+ for (const item of processedResult) {
1356
1387
  combinedHTML += renderTemplate(template, item);
1357
1388
  }
1358
1389
  scheduleDOMUpdate(el, combinedHTML);
1359
1390
  } else {
1360
- scheduleDOMUpdate(el, renderTemplate(template, result));
1391
+ scheduleDOMUpdate(el, renderTemplate(template, processedResult));
1361
1392
  }
1362
1393
  } else {
1363
1394
  if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
@@ -1372,21 +1403,139 @@ var DolphinModule = (() => {
1372
1403
  }
1373
1404
  }
1374
1405
  };
1406
+ clientProto._applyDeclarativeDirectives = function(el, payload) {
1407
+ let processedPayload = payload;
1408
+ if (typeof payload === "object" && payload !== null) {
1409
+ const applyFilterSearchSort = (arr) => {
1410
+ let result = [...arr];
1411
+ const filterAttr = el.getAttribute("data-rt-filter");
1412
+ if (filterAttr) {
1413
+ const parts = filterAttr.split("==");
1414
+ if (parts.length === 2) {
1415
+ const itemProp = parts[0].trim();
1416
+ const storeExpr = parts[1].trim();
1417
+ let filterVal = void 0;
1418
+ const storeParts = storeExpr.split(".");
1419
+ if (storeParts.length === 2) {
1420
+ filterVal = this.getStoreState(storeParts[0], storeParts[1]);
1421
+ } else {
1422
+ filterVal = payload[storeExpr] !== void 0 ? payload[storeExpr] : this.getStoreState("app", storeExpr);
1423
+ }
1424
+ if (filterVal !== void 0 && filterVal !== null && filterVal !== "") {
1425
+ result = result.filter((item) => item[itemProp] === filterVal);
1426
+ }
1427
+ }
1428
+ }
1429
+ const searchAttr = el.getAttribute("data-rt-search");
1430
+ if (searchAttr) {
1431
+ const parts = searchAttr.split("==");
1432
+ if (parts.length === 2) {
1433
+ const itemProp = parts[0].trim();
1434
+ const storeExpr = parts[1].trim();
1435
+ let searchVal = void 0;
1436
+ const storeParts = storeExpr.split(".");
1437
+ if (storeParts.length === 2) {
1438
+ searchVal = this.getStoreState(storeParts[0], storeParts[1]);
1439
+ } else {
1440
+ searchVal = payload[storeExpr] !== void 0 ? payload[storeExpr] : this.getStoreState("app", storeExpr);
1441
+ }
1442
+ if (searchVal !== void 0 && searchVal !== null && searchVal !== "") {
1443
+ const query = String(searchVal).toLowerCase();
1444
+ result = result.filter((item) => {
1445
+ const val = item[itemProp];
1446
+ return val !== void 0 && val !== null && String(val).toLowerCase().includes(query);
1447
+ });
1448
+ }
1449
+ }
1450
+ }
1451
+ const sortAttr = el.getAttribute("data-rt-sort");
1452
+ if (sortAttr) {
1453
+ let sortByVal = void 0;
1454
+ const storeParts = sortAttr.split(".");
1455
+ if (storeParts.length === 2) {
1456
+ sortByVal = this.getStoreState(storeParts[0], storeParts[1]);
1457
+ } else {
1458
+ sortByVal = payload[sortAttr] !== void 0 ? payload[sortAttr] : this.getStoreState("app", sortAttr);
1459
+ }
1460
+ if (sortByVal && sortByVal !== "") {
1461
+ if (sortByVal === "popular") {
1462
+ result.sort((a, b) => {
1463
+ const rateA = a.rating?.rate || a.rate || 0;
1464
+ const rateB = b.rating?.rate || b.rate || 0;
1465
+ return rateB - rateA;
1466
+ });
1467
+ } else {
1468
+ let field = "";
1469
+ let direction = "asc";
1470
+ if (sortByVal.endsWith("-low") || sortByVal.endsWith("-asc")) {
1471
+ field = sortByVal.replace("-low", "").replace("-asc", "");
1472
+ direction = "asc";
1473
+ } else if (sortByVal.endsWith("-high") || sortByVal.endsWith("-desc")) {
1474
+ field = sortByVal.replace("-high", "").replace("-desc", "");
1475
+ direction = "desc";
1476
+ }
1477
+ if (field) {
1478
+ result.sort((a, b) => {
1479
+ const resolveVal = (obj, path) => {
1480
+ return path.split(".").reduce((acc, part) => acc && acc[part], obj);
1481
+ };
1482
+ let valA = resolveVal(a, field);
1483
+ let valB = resolveVal(b, field);
1484
+ if (valA === void 0) valA = a[field];
1485
+ if (valB === void 0) valB = b[field];
1486
+ if (typeof valA === "string" && typeof valB === "string") {
1487
+ return direction === "asc" ? valA.localeCompare(valB) : valB.localeCompare(valA);
1488
+ }
1489
+ const numA = Number(valA);
1490
+ const numB = Number(valB);
1491
+ if (!isNaN(numA) && !isNaN(numB)) {
1492
+ return direction === "asc" ? numA - numB : numB - numA;
1493
+ }
1494
+ return 0;
1495
+ });
1496
+ }
1497
+ }
1498
+ }
1499
+ }
1500
+ return result;
1501
+ };
1502
+ if (Array.isArray(payload)) {
1503
+ processedPayload = applyFilterSearchSort(payload);
1504
+ } else {
1505
+ let foundArrayKey = "";
1506
+ for (const key in payload) {
1507
+ if (Array.isArray(payload[key])) {
1508
+ foundArrayKey = key;
1509
+ break;
1510
+ }
1511
+ }
1512
+ if (foundArrayKey) {
1513
+ const processedArray = applyFilterSearchSort(payload[foundArrayKey]);
1514
+ processedPayload = {
1515
+ ...payload,
1516
+ [foundArrayKey]: processedArray
1517
+ };
1518
+ }
1519
+ }
1520
+ }
1521
+ return processedPayload;
1522
+ };
1375
1523
  clientProto._updateDOM = function(topic, payload) {
1376
1524
  if (typeof document === "undefined") return;
1377
1525
  const elements = document.querySelectorAll(`[data-rt-bind="${topic}"]`);
1378
1526
  elements.forEach((el) => {
1379
- if (el.getAttribute("data-rt-type") === "context" && typeof payload === "object" && payload !== null) {
1380
- el._rtContext = payload;
1527
+ const processedPayload = this._applyDeclarativeDirectives(el, payload);
1528
+ if (el.getAttribute("data-rt-type") === "context" && typeof processedPayload === "object" && processedPayload !== null) {
1529
+ el._rtContext = processedPayload;
1381
1530
  const processNode = (node) => {
1382
1531
  if (node.hasAttribute("data-rt-text")) {
1383
1532
  const key = node.getAttribute("data-rt-text");
1384
- if (key && payload[key] !== void 0 && payload[key] !== null) node.textContent = payload[key];
1533
+ if (key && processedPayload[key] !== void 0 && processedPayload[key] !== null) node.textContent = processedPayload[key];
1385
1534
  }
1386
1535
  if (node.hasAttribute("data-rt-html")) {
1387
1536
  const key = node.getAttribute("data-rt-html");
1388
- if (key && payload[key] !== void 0 && payload[key] !== null) {
1389
- node.innerHTML = sanitizeHTML(payload[key]);
1537
+ if (key && processedPayload[key] !== void 0 && processedPayload[key] !== null) {
1538
+ node.innerHTML = sanitizeHTML(processedPayload[key]);
1390
1539
  }
1391
1540
  }
1392
1541
  if (node.hasAttribute("data-rt-attr")) {
@@ -1397,8 +1546,8 @@ var DolphinModule = (() => {
1397
1546
  if (parts.length === 2) {
1398
1547
  const attrName = parts[0].trim();
1399
1548
  const key = parts[1].trim();
1400
- if (attrName && key && payload[key] !== void 0 && payload[key] !== null) {
1401
- node.setAttribute(attrName, payload[key]);
1549
+ if (attrName && key && processedPayload[key] !== void 0 && processedPayload[key] !== null) {
1550
+ node.setAttribute(attrName, processedPayload[key]);
1402
1551
  }
1403
1552
  }
1404
1553
  });
@@ -1412,10 +1561,11 @@ var DolphinModule = (() => {
1412
1561
  if (parts.length === 2) {
1413
1562
  const className = parts[0].trim();
1414
1563
  const key = parts[1].trim();
1415
- if (payload[key]) {
1416
- node.classList.add(className);
1564
+ const classNames = className.split(/\s+/).filter(Boolean);
1565
+ if (processedPayload[key]) {
1566
+ classNames.forEach((c) => node.classList.add(c));
1417
1567
  } else {
1418
- node.classList.remove(className);
1568
+ classNames.forEach((c) => node.classList.remove(c));
1419
1569
  }
1420
1570
  }
1421
1571
  });
@@ -1424,7 +1574,7 @@ var DolphinModule = (() => {
1424
1574
  if (node.hasAttribute("data-rt-if")) {
1425
1575
  const key = node.getAttribute("data-rt-if");
1426
1576
  if (key) {
1427
- if (payload[key]) {
1577
+ if (processedPayload[key]) {
1428
1578
  node.style.display = "";
1429
1579
  } else {
1430
1580
  node.style.display = "none";
@@ -1434,7 +1584,7 @@ var DolphinModule = (() => {
1434
1584
  if (node.hasAttribute("data-rt-hide")) {
1435
1585
  const key = node.getAttribute("data-rt-hide");
1436
1586
  if (key) {
1437
- if (payload[key]) {
1587
+ if (processedPayload[key]) {
1438
1588
  node.style.display = "none";
1439
1589
  } else {
1440
1590
  node.style.display = "";
@@ -1447,22 +1597,22 @@ var DolphinModule = (() => {
1447
1597
  return;
1448
1598
  }
1449
1599
  const template = resolveTemplate(el);
1450
- if (template && typeof payload === "object" && payload !== null) {
1451
- if (Array.isArray(payload)) {
1600
+ if (template && typeof processedPayload === "object" && processedPayload !== null) {
1601
+ if (Array.isArray(processedPayload)) {
1452
1602
  let combinedHTML = "";
1453
- for (const item of payload) {
1603
+ for (const item of processedPayload) {
1454
1604
  combinedHTML += renderTemplate(template, item);
1455
1605
  }
1456
1606
  scheduleDOMUpdate(el, combinedHTML);
1457
1607
  } else {
1458
- scheduleDOMUpdate(el, renderTemplate(template, payload));
1608
+ scheduleDOMUpdate(el, renderTemplate(template, processedPayload));
1459
1609
  }
1460
1610
  return;
1461
1611
  }
1462
1612
  if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
1463
- el.value = typeof payload === "object" ? payload.value !== void 0 ? payload.value : "" : payload;
1613
+ el.value = typeof processedPayload === "object" ? processedPayload.value !== void 0 ? processedPayload.value : "" : processedPayload;
1464
1614
  } else {
1465
- el.innerHTML = typeof payload === "object" ? payload.html || payload.text || JSON.stringify(payload) : payload;
1615
+ el.innerHTML = typeof processedPayload === "object" ? processedPayload.html || processedPayload.text || JSON.stringify(processedPayload) : processedPayload;
1466
1616
  }
1467
1617
  });
1468
1618
  };
@@ -1,20 +1,20 @@
1
- var DolphinModule=(()=>{var $=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var V=Object.prototype.hasOwnProperty;var J=(p,t)=>{for(var e in t)$(p,e,{get:t[e],enumerable:!0})},Q=(p,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of K(t))!V.call(p,r)&&r!==e&&$(p,r,{get:()=>t[r],enumerable:!(n=z(t,r))||n.enumerable});return p};var X=p=>Q($({},"__esModule",{value:!0}),p);var Z={};J(Z,{DolphinClient:()=>v});var x=class{client;constructor(t){return this.client=t,this._createProxy([])}_createProxy(t){let e=t.join("/"),n=i=>this.request("GET",e,null,i);n.get=(i,a)=>typeof i=="string"?this.request("GET",i,null,a):this.request("GET",e,null,i),n.post=(i,a,l)=>typeof i=="string"?this.request("POST",i,a,l):this.request("POST",e,i,a),n.put=(i,a,l)=>typeof i=="string"?this.request("PUT",i,a,l):this.request("PUT",e,i,a),n.patch=(i,a,l)=>typeof i=="string"?this.request("PATCH",i,a,l):this.request("PATCH",e,i,a),n.del=(i,a)=>typeof i=="string"?this.request("DELETE",i,null,a):this.request("DELETE",e,null,i),n.request=(i,a,l,b)=>{let T=a?`${e}/${a.startsWith("/")?a.slice(1):a}`:e;return this.request(i,T,l,b)},n.requestDirect=(i,a,l,b)=>this.requestDirect(i,a,l,b);let r=["get","post","put","patch","del","request","requestDirect"];return new Proxy(n,{get:(i,a)=>typeof a=="string"&&!r.includes(a)?this._createProxy([...t,a]):i[a]})}async request(t,e,n=null,r={}){if(this.client.offline){let i=this.client.offline.isOnline,a=`${t.toUpperCase()}:${e}`;if(t.toUpperCase()==="GET")if(i)try{let l=await this.requestDirect(t,e,n,r);return await this.client.offline.setCache(a,l),l}catch(l){let b=await this.client.offline.getCache(a);if(b!=null)return b;throw l}else{let l=await this.client.offline.getCache(a);if(l!=null)return l;throw{status:503,data:{error:"Offline, no cache available"}}}else return i?this.requestDirect(t,e,n,r):(await this.client.offline.queueMutation(t,e,n),this.client._dispatch("offline:queued",{method:t,path:e,body:n}),{success:!0,offline:!0,message:"Mutation queued offline"})}return this.requestDirect(t,e,n,r)}async requestDirect(t,e,n=null,r={}){let i=r._isRetry===!0,a=`${this.client.httpUrl}${e.startsWith("/")?e:"/"+e}`;this.client.options.debug&&console.log("%c\u{1F680} [Dolphin API Request]:","color: #3b82f6; font-weight: bold;",t.toUpperCase(),e,n||"");let l=new AbortController,b=setTimeout(()=>l.abort(),this.client.options.timeout),T={"Content-Type":"application/json",...r.headers||{}};this.client.accessToken&&(T.Authorization=`Bearer ${this.client.accessToken}`);let h={...r};delete h._isRetry;try{let o=await fetch(a,{method:t,headers:T,signal:l.signal,...n?{body:JSON.stringify(n)}:{},...h});if(clearTimeout(b),o.status===401&&!i&&this.client.options.autoRefreshToken&&await this.client.auth._silentRefresh())return this.request(t,e,n,{...r,_isRetry:!0});let s=(o.headers.get("content-type")||"").includes("application/json")?await o.json():await o.text();if(!o.ok)throw{status:o.status,data:s};if(this.client.options.debug&&console.log("%c\u2705 [Dolphin API Success]:","color: #10b981; font-weight: bold;",t.toUpperCase(),e,s),s&&typeof s=="object"&&s.accessToken&&(this.client.setToken(s.accessToken),s.user&&(this.client.auth.user=s.user)),this.client.options.autoBroadcast&&["POST","PUT","PATCH","DELETE"].includes(t.toUpperCase())){let c=e.startsWith("/")?e.substring(1):e;this.client.publish(c,{method:t.toUpperCase(),payload:n,result:s})}return s}catch(o){throw clearTimeout(b),this.client.options.debug&&console.error("%c\u274C [Dolphin API Error]:","color: #ef4444; font-weight: bold;",t.toUpperCase(),e,o),o.name==="AbortError"?{status:408,data:{error:"Request timed out"}}:o}}};var C=class{client;user;_refreshing;constructor(t){this.client=t,this.user=null,this._refreshing=!1}async login(t,e){let n=await this.client.api.post("/api/auth/login",{email:t,password:e});return n.accessToken&&(this.client.setToken(n.accessToken),this.user=n.user||null),n}async register(t){return this.client.api.post("/api/auth/register",t)}async me(){let t=await this.client.api.get("/api/auth/me");return t.success&&(this.user=t.data),t}async logout(){try{await this.client.api.post("/api/auth/logout")}catch{}this.client.setToken(null),this.user=null}async refresh(){return this._silentRefresh()}async _silentRefresh(){if(this._refreshing)return!1;this._refreshing=!0;try{let t=await this.client.api.post("/api/auth/refresh",null,{_isRetry:!0});return t.accessToken?(this.client.setToken(t.accessToken),!0):!1}catch{return this.client.setToken(null),!1}finally{this._refreshing=!1}}async verify2FA(t,e){let n={code:t,email:e||this.user?.email},r=await this.client.api.post("/api/auth/2fa/verify",n);return r.accessToken&&(this.client.setToken(r.accessToken),r.user&&(this.user=r.user)),r}async enable2FA(){return this.client.api.post("/api/auth/2fa/enable")}async disable2FA(t){return this.client.api.post("/api/auth/2fa/disable",{code:t})}async forgotPassword(t){return this.client.api.post("/api/auth/forgot-password",{email:t})}async resetPassword(t,e){return this.client.api.post("/api/auth/reset-password",{token:t,newPassword:e})}};var M=class{client;data;listeners;subscribed;constructor(t){return this.client=t,this.data=new Map,this.listeners=new Set,this.subscribed=new Set,new Proxy(this,{get:(e,n)=>{if(n in e)return e[n];if(typeof n=="string")return this._getCollection(n)}})}_getCollection(t){if(!this.data.has(t)){let e={_rawItems:[],items:[],loading:!0,error:null,success:!1,_filter:null,_sort:null,where:n=>(e._filter=n,this._applyTransform(e),e),orderBy:(n,r="asc")=>(e._sort={key:n,direction:r},this._applyTransform(e),e),reset:()=>(e._filter=null,e._sort=null,this._applyTransform(e),e)};this.data.set(t,e),this._fetchAndSync(t)}return this.data.get(t)}async _fetchAndSync(t){let e=this.data.get(t);try{let n=await this.client.api.get(`/${t.toLowerCase()}`);e._rawItems=Array.isArray(n)?n:n.data||[],e.loading=!1,e.success=!0,e.error=null,this._applyTransform(e),this.subscribed.has(t)||(this.client.subscribe(`db:sync/${t.toLowerCase()}`,r=>{this._handleRemoteUpdate(t,r)}),this.subscribed.add(t))}catch(n){e.loading=!1,e.success=!1,e.error=n.data?.error||n.message||"Fetch failed",this._notify()}}_applyTransform(t){let e=[...t._rawItems];if(t._filter&&(e=e.filter(t._filter)),t._sort){let{key:n,direction:r}=t._sort;e.sort((i,a)=>i[n]===a[n]?0:(i[n]>a[n]?1:-1)*(r==="asc"?1:-1))}t.items=e,this._notify()}_handleRemoteUpdate(t,e){let n=this.data.get(t);if(!n)return;let{type:r,data:i}=e,a=n._rawItems;r==="create"?a=[...a,i]:r==="update"?a=a.map(l=>l.id===i.id||l._id===i._id?{...l,...i}:l):r==="delete"&&(a=a.filter(l=>!(i.id!=null&&l.id===i.id||i._id!=null&&l._id===i._id))),n._rawItems=a,this._applyTransform(n)}subscribe(t){return this.listeners.add(t),()=>this.listeners.delete(t)}getSnapshot(t){return this.data.get(t)||{items:[],loading:!1,error:null,success:!1}}_notify(){this.listeners.forEach(t=>t())}};var v=class{host;httpUrl;deviceId;options;socket;storage;accessToken;api;auth;store;handlers;signalHandlers;fileHandlers;_offlineQueue;reconnectAttempts;_attachedListeners;constructor(t="",e="",n={}){!t&&typeof window<"u"&&(t=window.location.host);let r="http:";t.startsWith("https://")?r="https:":t.startsWith("http://")?r="http:":typeof window<"u"&&(r=window.location.protocol),this.host=(t||"localhost").replace(/\/$/,"").replace(/^https?:\/\//,""),this.httpUrl=`${r}//${this.host}`,this.deviceId=e||"web_"+Math.random().toString(36).substr(2,8),this.options={timeout:15e3,chunkSize:65536,maxReconnect:5,autoRefreshToken:!0,debug:!1,...n},this.socket=null,this.storage=typeof localStorage<"u"?localStorage:{getItem:()=>null,setItem:()=>{},removeItem:()=>{}},this.accessToken=this.storage.getItem("dolphin_token"),this.api=new x(this),this.auth=new C(this),this.store=new M(this),this.handlers=new Map,this.signalHandlers=new Set,this.fileHandlers=new Set,this._offlineQueue=[],this.reconnectAttempts=0,this._attachedListeners=[],typeof window<"u"&&typeof this._initDOMBinding=="function"&&this._initDOMBinding(),typeof this._initOffline=="function"&&this._initOffline(),typeof this._initA11y=="function"&&this._initA11y(),typeof this._initI18n=="function"&&this._initI18n(),typeof this._initDragDrop=="function"&&this._initDragDrop(),typeof this._initCollab=="function"&&this._initCollab()}setToken(t){this.accessToken=t,t?this.storage.setItem("dolphin_token",t):this.storage.removeItem("dolphin_token")}async connect(){return new Promise((t,e)=>{let r=`${this.httpUrl.startsWith("https")?"wss:":"ws:"}//${this.host}/realtime?deviceId=${this.deviceId}`;console.log(`[Dolphin] Connecting to ${r}...`),this.socket=new WebSocket(r),this.socket.onopen=()=>{console.log(`[Dolphin] Connected as "${this.deviceId}" \u{1F42C}`),this.reconnectAttempts=0,this._flushOfflineQueue(),t()},this.socket.onmessage=i=>this._handleMessage(i.data),this.socket.onclose=()=>{console.warn("[Dolphin] Connection closed"),this._maybeReconnect()},this.socket.onerror=i=>{console.error("[Dolphin] WebSocket error:",i),e(i)}})}disconnect(){this.socket&&(this.socket.onclose=null,this.socket.close(),this.socket=null),this.cleanupDomListeners()}_handleMessage(t){try{let e=JSON.parse(t);this.options.debug&&console.log("%c\u{1F4E5} [Dolphin WS Incoming]:","color: #eab308; font-weight: bold;",e),e.type&&e.from&&(e.to===this.deviceId||e.to==="all")&&(e.msgId&&e.type!=="ACK"&&this._sendAck(e.from,e.msgId),this.signalHandlers.forEach(n=>n(e))),e.type==="FILE_AVAILABLE"&&this.fileHandlers.forEach(n=>n(e)),e.type==="FILE_CHUNK"&&(this.saveFileProgress(e.fileId,e.chunkIndex),this._dispatch("file:chunk",e),this._dispatch(`file:chunk/${e.fileId}`,e)),e.type==="FILE_UPLOAD_ACK"&&this._dispatch(`file:upload:ack/${e.fileId}`,e),e.type==="PULL_RESPONSE"&&(this._dispatch("pull:response",e.payload,e.topic),this._dispatch(`pull:response/${e.topic}`,e.payload,e.topic)),e.topic&&e.payload!==void 0&&this.handlers.forEach((n,r)=>{this._matchTopic(r,e.topic)&&n.forEach(i=>i(e.payload,e.topic))})}catch{this._dispatch("raw",t)}}_dispatch(t,e,n){let r=this.handlers.get(t);r&&r.forEach(i=>i(e,n||t))}_sendRaw(t){this.options.debug&&console.log("%c\u{1F4E4} [Dolphin WS Outgoing]:","color: #8b5cf6; font-weight: bold;",t);let e=typeof t=="string"?t:JSON.stringify(t);this.socket&&this.socket.readyState===WebSocket.OPEN?this.socket.send(e):this._offlineQueue.length<100&&this._offlineQueue.push(e)}_flushOfflineQueue(){for(;this._offlineQueue.length>0;){let t=this._offlineQueue.shift();this.socket&&this.socket.readyState===WebSocket.OPEN&&this.socket.send(t)}}_sendAck(t,e){this._sendRaw({type:"ACK",from:this.deviceId,to:t,data:{ackId:e},timestamp:Date.now()})}_matchTopic(t,e){if(t===e||t==="#")return!0;let n=t.split("/"),r=e.split("/");if(n.length!==r.length&&!t.includes("#"))return!1;for(let i=0;i<n.length;i++){if(n[i]==="#")return!0;if(n[i]!=="+"&&n[i]!==r[i])return!1}return n.length===r.length}_maybeReconnect(){if(this.reconnectAttempts<this.options.maxReconnect){this.reconnectAttempts++;let t=Math.pow(2,this.reconnectAttempts)*1e3;console.log(`[Dolphin] Reconnecting in ${t/1e3}s (attempt ${this.reconnectAttempts})...`),setTimeout(()=>this.connect().catch(()=>{}),t)}else console.error("[Dolphin] Max reconnect attempts reached.")}subscribe(t,e){this.handlers.has(t)||(this.handlers.set(t,new Set),this._sendRaw({type:"sub",topic:t})),this.handlers.get(t).add(e)}unsubscribe(t,e){if(this.handlers.has(t)){let n=this.handlers.get(t);n.delete(e),n.size===0&&(this.handlers.delete(t),this._sendRaw({type:"unsub",topic:t}))}}publish(t,e){this._sendRaw({topic:t,payload:e})}pubPush(t,e){this._sendRaw({type:"pub",topic:t,payload:e})}subPull(t,e=10){this._sendRaw({type:"PULL_REQUEST",topic:t,count:e})}async pubFile(t,e,n="",r){let i;e instanceof Blob?i=await e.arrayBuffer():e instanceof ArrayBuffer?i=e:i=e.buffer||e;let a=new Uint8Array(i),l=this.options.chunkSize,b=Math.ceil(a.length/l);this._sendRaw({type:"FILE_UPLOAD_START",fileId:t,name:n,size:a.length,totalChunks:b,chunkSize:l});for(let T=0;T<b;T++){let h=a.slice(T*l,(T+1)*l),o=this._uint8ToBase64(h);this._sendRaw({type:"FILE_UPLOAD_CHUNK",fileId:t,chunkIndex:T,totalChunks:b,data:o}),r&&r(Math.round((T+1)/b*100)),T%10===0&&await new Promise(u=>setTimeout(u,0))}this._sendRaw({type:"FILE_UPLOAD_DONE",fileId:t})}_uint8ToBase64(t){let e="";for(let n=0;n<t.length;n++)e+=String.fromCharCode(t[n]);return typeof btoa<"u"?btoa(e):Buffer.from(e,"binary").toString("base64")}subFile(t,e=0){this._sendRaw({type:"FILE_REQUEST",fileId:t,startChunk:e})}resumeFile(t){let e=parseInt(this.storage.getItem(`dolphin_file_${t}`)||"-1");this.subFile(t,e+1)}saveFileProgress(t,e){this.storage.setItem(`dolphin_file_${t}`,e.toString())}onSignal(t){this.signalHandlers.add(t)}offSignal(t){this.signalHandlers.delete(t)}onFileAvailable(t){this.fileHandlers.add(t)}offFileAvailable(t){this.fileHandlers.delete(t)}addDomListener(t,e,n){t&&(t.addEventListener(e,n),this._attachedListeners=this._attachedListeners||[],this._attachedListeners.push({target:t,event:e,cb:n}))}cleanupDomListeners(){this._attachedListeners&&(this._attachedListeners.forEach(({target:t,event:e,cb:n})=>{try{t.removeEventListener(e,n)}catch{}}),this._attachedListeners=[])}};function R(p){function t(h){return h.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function e(h){let o=h.getAttribute("data-rt-template");if(!o)return null;if(typeof document<"u"&&!o.includes("<"))try{let u=document.querySelector(o);if(u)return u.innerHTML}catch{}return o}function n(h,o){if(!h.includes("{#if")&&!h.includes("{#each")){let u=h;for(let s in o){let c=s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");u=u.replace(new RegExp(`\\{\\{${c}\\}\\}`,"g"),o[s]!==void 0&&o[s]!==null?o[s]:"")}return u}try{let u=g=>g.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t"),s=`let out = "";
2
- `,c=0,m=/(\{\{([\s\S]*?)\}\}|\{#if\s+([\s\S]*?)\}|\{:else\s+if\s+([\s\S]*?)\}|\{:else\}|\{\/if\}|\{#each\s+([\s\S]*?)\s+as\s+([a-zA-Z_$][a-zA-Z0-9_$]*)(?:\s*,\s*([a-zA-Z_$][a-zA-Z0-9_$]*))?\}|\{\/each\}|\{([^{}]+?)\})/g,f=[],d;for(;(d=m.exec(h))!==null;){let g=h.slice(c,d.index);g&&(s+=`out += "${u(g)}";
3
- `);let w=d[0];if(w.startsWith("{{")){let A=d[2];s+=`out += (${A} !== undefined && ${A} !== null ? ${A} : "");
4
- `}else if(w.startsWith("{#if")){let A=d[3];s+=`if (${A}) {
5
- `}else if(w.startsWith("{:else if")){let A=d[4];s+=`} else if (${A}) {
6
- `}else if(w.startsWith("{:else}"))s+=`} else {
7
- `;else if(w.startsWith("{/if}"))s+=`}
8
- `;else if(w.startsWith("{#each")){let A=d[5],D=d[6],S=d[7];f.push({indexVar:S}),s+=`if (typeof ${A} !== "undefined" && ${A} !== null && Array.isArray(${A})) {
9
- `,S&&(s+=` let ${S} = 0;
10
- `),s+=` for (let ${D} of ${A}) {
11
- `}else if(w.startsWith("{/each}")){let A=f.pop();A&&A.indexVar&&(s+=` ${A.indexVar}++;
1
+ var DolphinModule=(()=>{var P=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var Z=Object.getOwnPropertyNames;var G=Object.prototype.hasOwnProperty;var tt=(m,t)=>{for(var e in t)P(m,e,{get:t[e],enumerable:!0})},et=(m,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of Z(t))!G.call(m,r)&&r!==e&&P(m,r,{get:()=>t[r],enumerable:!(n=Y(t,r))||n.enumerable});return m};var nt=m=>et(P({},"__esModule",{value:!0}),m);var it={};tt(it,{DolphinClient:()=>k});var x=class{client;constructor(t){return this.client=t,this._createProxy([])}_createProxy(t){let e=t.join("/"),n=i=>this.request("GET",e,null,i);n.get=(i,o)=>typeof i=="string"?this.request("GET",i,null,o):this.request("GET",e,null,i),n.post=(i,o,u)=>typeof i=="string"?this.request("POST",i,o,u):this.request("POST",e,i,o),n.put=(i,o,u)=>typeof i=="string"?this.request("PUT",i,o,u):this.request("PUT",e,i,o),n.patch=(i,o,u)=>typeof i=="string"?this.request("PATCH",i,o,u):this.request("PATCH",e,i,o),n.del=(i,o)=>typeof i=="string"?this.request("DELETE",i,null,o):this.request("DELETE",e,null,i),n.request=(i,o,u,S)=>{let D=o?`${e}/${o.startsWith("/")?o.slice(1):o}`:e;return this.request(i,D,u,S)},n.requestDirect=(i,o,u,S)=>this.requestDirect(i,o,u,S);let r=["get","post","put","patch","del","request","requestDirect"];return new Proxy(n,{get:(i,o)=>typeof o=="string"&&!r.includes(o)?this._createProxy([...t,o]):i[o]})}async request(t,e,n=null,r={}){if(this.client.offline){let i=this.client.offline.isOnline,o=`${t.toUpperCase()}:${e}`;if(t.toUpperCase()==="GET")if(i)try{let u=await this.requestDirect(t,e,n,r);return await this.client.offline.setCache(o,u),u}catch(u){let S=await this.client.offline.getCache(o);if(S!=null)return S;throw u}else{let u=await this.client.offline.getCache(o);if(u!=null)return u;throw{status:503,data:{error:"Offline, no cache available"}}}else return i?this.requestDirect(t,e,n,r):(await this.client.offline.queueMutation(t,e,n),this.client._dispatch("offline:queued",{method:t,path:e,body:n}),{success:!0,offline:!0,message:"Mutation queued offline"})}return this.requestDirect(t,e,n,r)}async requestDirect(t,e,n=null,r={}){let i=r._isRetry===!0,o=e.startsWith("http://")||e.startsWith("https://")?e:`${this.client.httpUrl}${e.startsWith("/")?e:"/"+e}`;this.client.options.debug&&console.log("%c\u{1F680} [Dolphin API Request]:","color: #3b82f6; font-weight: bold;",t.toUpperCase(),e,n||"");let u=new AbortController,S=setTimeout(()=>u.abort(),this.client.options.timeout),D={"Content-Type":"application/json",...r.headers||{}};this.client.accessToken&&(D.Authorization=`Bearer ${this.client.accessToken}`);let g={...r};delete g._isRetry;try{let c=await fetch(o,{method:t,headers:D,signal:u.signal,...n?{body:JSON.stringify(n)}:{},...g});if(clearTimeout(S),c.status===401&&!i&&this.client.options.autoRefreshToken&&await this.client.auth._silentRefresh())return this.request(t,e,n,{...r,_isRetry:!0});let s=(c.headers.get("content-type")||"").includes("application/json")?await c.json():await c.text();if(!c.ok)throw{status:c.status,data:s};if(this.client.options.debug&&console.log("%c\u2705 [Dolphin API Success]:","color: #10b981; font-weight: bold;",t.toUpperCase(),e,s),s&&typeof s=="object"&&s.accessToken&&(this.client.setToken(s.accessToken),s.user&&(this.client.auth.user=s.user)),this.client.options.autoBroadcast&&["POST","PUT","PATCH","DELETE"].includes(t.toUpperCase())){let a=e.startsWith("/")?e.substring(1):e;this.client.publish(a,{method:t.toUpperCase(),payload:n,result:s})}return s}catch(c){throw clearTimeout(S),this.client.options.debug&&console.error("%c\u274C [Dolphin API Error]:","color: #ef4444; font-weight: bold;",t.toUpperCase(),e,c),c.name==="AbortError"?{status:408,data:{error:"Request timed out"}}:c}}};var C=class{client;user;_refreshing;constructor(t){this.client=t,this.user=null,this._refreshing=!1}async login(t,e){let n=await this.client.api.post("/api/auth/login",{email:t,password:e});return n.accessToken&&(this.client.setToken(n.accessToken),this.user=n.user||null),n}async register(t){return this.client.api.post("/api/auth/register",t)}async me(){let t=await this.client.api.get("/api/auth/me");return t.success&&(this.user=t.data),t}async logout(){try{await this.client.api.post("/api/auth/logout")}catch{}this.client.setToken(null),this.user=null}async refresh(){return this._silentRefresh()}async _silentRefresh(){if(this._refreshing)return!1;this._refreshing=!0;try{let t=await this.client.api.post("/api/auth/refresh",null,{_isRetry:!0});return t.accessToken?(this.client.setToken(t.accessToken),!0):!1}catch{return this.client.setToken(null),!1}finally{this._refreshing=!1}}async verify2FA(t,e){let n={code:t,email:e||this.user?.email},r=await this.client.api.post("/api/auth/2fa/verify",n);return r.accessToken&&(this.client.setToken(r.accessToken),r.user&&(this.user=r.user)),r}async enable2FA(){return this.client.api.post("/api/auth/2fa/enable")}async disable2FA(t){return this.client.api.post("/api/auth/2fa/disable",{code:t})}async forgotPassword(t){return this.client.api.post("/api/auth/forgot-password",{email:t})}async resetPassword(t,e){return this.client.api.post("/api/auth/reset-password",{token:t,newPassword:e})}};var M=class{client;data;listeners;subscribed;constructor(t){return this.client=t,this.data=new Map,this.listeners=new Set,this.subscribed=new Set,new Proxy(this,{get:(e,n)=>{if(n in e)return e[n];if(typeof n=="string")return this._getCollection(n)}})}_getCollection(t){if(!this.data.has(t)){let e={_rawItems:[],items:[],loading:!0,error:null,success:!1,_filter:null,_sort:null,where:n=>(e._filter=n,this._applyTransform(e),e),orderBy:(n,r="asc")=>(e._sort={key:n,direction:r},this._applyTransform(e),e),reset:()=>(e._filter=null,e._sort=null,this._applyTransform(e),e)};this.data.set(t,e),this._fetchAndSync(t)}return this.data.get(t)}async _fetchAndSync(t){let e=this.data.get(t);try{let n=await this.client.api.get(`/${t.toLowerCase()}`);e._rawItems=Array.isArray(n)?n:n.data||[],e.loading=!1,e.success=!0,e.error=null,this._applyTransform(e),this.subscribed.has(t)||(this.client.subscribe(`db:sync/${t.toLowerCase()}`,r=>{this._handleRemoteUpdate(t,r)}),this.subscribed.add(t))}catch(n){e.loading=!1,e.success=!1,e.error=n.data?.error||n.message||"Fetch failed",this._notify()}}_applyTransform(t){let e=[...t._rawItems];if(t._filter&&(e=e.filter(t._filter)),t._sort){let{key:n,direction:r}=t._sort;e.sort((i,o)=>i[n]===o[n]?0:(i[n]>o[n]?1:-1)*(r==="asc"?1:-1))}t.items=e,this._notify()}_handleRemoteUpdate(t,e){let n=this.data.get(t);if(!n)return;let{type:r,data:i}=e,o=n._rawItems;r==="create"?o=[...o,i]:r==="update"?o=o.map(u=>u.id===i.id||u._id===i._id?{...u,...i}:u):r==="delete"&&(o=o.filter(u=>!(i.id!=null&&u.id===i.id||i._id!=null&&u._id===i._id))),n._rawItems=o,this._applyTransform(n)}subscribe(t){return this.listeners.add(t),()=>this.listeners.delete(t)}getSnapshot(t){return this.data.get(t)||{items:[],loading:!1,error:null,success:!1}}_notify(){this.listeners.forEach(t=>t())}};var k=class{host;httpUrl;deviceId;options;socket;storage;accessToken;api;auth;store;handlers;signalHandlers;fileHandlers;_offlineQueue;reconnectAttempts;_attachedListeners;constructor(t="",e="",n={}){!t&&typeof window<"u"&&(t=window.location.host);let r="http:";t.startsWith("https://")?r="https:":t.startsWith("http://")?r="http:":typeof window<"u"&&(r=window.location.protocol),this.host=(t||"localhost").replace(/\/$/,"").replace(/^https?:\/\//,""),this.httpUrl=`${r}//${this.host}`,this.deviceId=e||"web_"+Math.random().toString(36).substr(2,8),this.options={timeout:15e3,chunkSize:65536,maxReconnect:5,autoRefreshToken:!0,debug:!1,...n},this.socket=null,this.storage=typeof localStorage<"u"?localStorage:{getItem:()=>null,setItem:()=>{},removeItem:()=>{}},this.accessToken=this.storage.getItem("dolphin_token"),this.api=new x(this),this.auth=new C(this),this.store=new M(this),this.handlers=new Map,this.signalHandlers=new Set,this.fileHandlers=new Set,this._offlineQueue=[],this.reconnectAttempts=0,this._attachedListeners=[],typeof window<"u"&&typeof this._initDOMBinding=="function"&&this._initDOMBinding(),typeof this._initOffline=="function"&&this._initOffline(),typeof this._initA11y=="function"&&this._initA11y(),typeof this._initI18n=="function"&&this._initI18n(),typeof this._initDragDrop=="function"&&this._initDragDrop(),typeof this._initCollab=="function"&&this._initCollab()}setToken(t){this.accessToken=t,t?this.storage.setItem("dolphin_token",t):this.storage.removeItem("dolphin_token")}async connect(){return new Promise((t,e)=>{let r=`${this.httpUrl.startsWith("https")?"wss:":"ws:"}//${this.host}/realtime?deviceId=${this.deviceId}`;console.log(`[Dolphin] Connecting to ${r}...`),this.socket=new WebSocket(r),this.socket.onopen=()=>{console.log(`[Dolphin] Connected as "${this.deviceId}" \u{1F42C}`),this.reconnectAttempts=0,this._flushOfflineQueue(),t()},this.socket.onmessage=i=>this._handleMessage(i.data),this.socket.onclose=()=>{console.warn("[Dolphin] Connection closed"),this._maybeReconnect()},this.socket.onerror=i=>{console.error("[Dolphin] WebSocket error:",i),e(i)}})}disconnect(){this.socket&&(this.socket.onclose=null,this.socket.close(),this.socket=null),this.cleanupDomListeners()}_handleMessage(t){try{let e=JSON.parse(t);this.options.debug&&console.log("%c\u{1F4E5} [Dolphin WS Incoming]:","color: #eab308; font-weight: bold;",e),e.type&&e.from&&(e.to===this.deviceId||e.to==="all")&&(e.msgId&&e.type!=="ACK"&&this._sendAck(e.from,e.msgId),this.signalHandlers.forEach(n=>n(e))),e.type==="FILE_AVAILABLE"&&this.fileHandlers.forEach(n=>n(e)),e.type==="FILE_CHUNK"&&(this.saveFileProgress(e.fileId,e.chunkIndex),this._dispatch("file:chunk",e),this._dispatch(`file:chunk/${e.fileId}`,e)),e.type==="FILE_UPLOAD_ACK"&&this._dispatch(`file:upload:ack/${e.fileId}`,e),e.type==="PULL_RESPONSE"&&(this._dispatch("pull:response",e.payload,e.topic),this._dispatch(`pull:response/${e.topic}`,e.payload,e.topic)),e.topic&&e.payload!==void 0&&this.handlers.forEach((n,r)=>{this._matchTopic(r,e.topic)&&n.forEach(i=>i(e.payload,e.topic))})}catch{this._dispatch("raw",t)}}_dispatch(t,e,n){let r=this.handlers.get(t);r&&r.forEach(i=>i(e,n||t))}_sendRaw(t){this.options.debug&&console.log("%c\u{1F4E4} [Dolphin WS Outgoing]:","color: #8b5cf6; font-weight: bold;",t);let e=typeof t=="string"?t:JSON.stringify(t);this.socket&&this.socket.readyState===WebSocket.OPEN?this.socket.send(e):this._offlineQueue.length<100&&this._offlineQueue.push(e)}_flushOfflineQueue(){for(;this._offlineQueue.length>0;){let t=this._offlineQueue.shift();this.socket&&this.socket.readyState===WebSocket.OPEN&&this.socket.send(t)}}_sendAck(t,e){this._sendRaw({type:"ACK",from:this.deviceId,to:t,data:{ackId:e},timestamp:Date.now()})}_matchTopic(t,e){if(t===e||t==="#")return!0;let n=t.split("/"),r=e.split("/");if(n.length!==r.length&&!t.includes("#"))return!1;for(let i=0;i<n.length;i++){if(n[i]==="#")return!0;if(n[i]!=="+"&&n[i]!==r[i])return!1}return n.length===r.length}_maybeReconnect(){if(this.reconnectAttempts<this.options.maxReconnect){this.reconnectAttempts++;let t=Math.pow(2,this.reconnectAttempts)*1e3;console.log(`[Dolphin] Reconnecting in ${t/1e3}s (attempt ${this.reconnectAttempts})...`),setTimeout(()=>this.connect().catch(()=>{}),t)}else console.error("[Dolphin] Max reconnect attempts reached.")}subscribe(t,e){this.handlers.has(t)||(this.handlers.set(t,new Set),this._sendRaw({type:"sub",topic:t})),this.handlers.get(t).add(e)}unsubscribe(t,e){if(this.handlers.has(t)){let n=this.handlers.get(t);n.delete(e),n.size===0&&(this.handlers.delete(t),this._sendRaw({type:"unsub",topic:t}))}}publish(t,e){this._sendRaw({topic:t,payload:e})}pubPush(t,e){this._sendRaw({type:"pub",topic:t,payload:e})}subPull(t,e=10){this._sendRaw({type:"PULL_REQUEST",topic:t,count:e})}async pubFile(t,e,n="",r){let i;e instanceof Blob?i=await e.arrayBuffer():e instanceof ArrayBuffer?i=e:i=e.buffer||e;let o=new Uint8Array(i),u=this.options.chunkSize,S=Math.ceil(o.length/u);this._sendRaw({type:"FILE_UPLOAD_START",fileId:t,name:n,size:o.length,totalChunks:S,chunkSize:u});for(let D=0;D<S;D++){let g=o.slice(D*u,(D+1)*u),c=this._uint8ToBase64(g);this._sendRaw({type:"FILE_UPLOAD_CHUNK",fileId:t,chunkIndex:D,totalChunks:S,data:c}),r&&r(Math.round((D+1)/S*100)),D%10===0&&await new Promise(l=>setTimeout(l,0))}this._sendRaw({type:"FILE_UPLOAD_DONE",fileId:t})}_uint8ToBase64(t){let e="";for(let n=0;n<t.length;n++)e+=String.fromCharCode(t[n]);return typeof btoa<"u"?btoa(e):Buffer.from(e,"binary").toString("base64")}subFile(t,e=0){this._sendRaw({type:"FILE_REQUEST",fileId:t,startChunk:e})}resumeFile(t){let e=parseInt(this.storage.getItem(`dolphin_file_${t}`)||"-1");this.subFile(t,e+1)}saveFileProgress(t,e){this.storage.setItem(`dolphin_file_${t}`,e.toString())}onSignal(t){this.signalHandlers.add(t)}offSignal(t){this.signalHandlers.delete(t)}onFileAvailable(t){this.fileHandlers.add(t)}offFileAvailable(t){this.fileHandlers.delete(t)}addDomListener(t,e,n){t&&(t.addEventListener(e,n),this._attachedListeners=this._attachedListeners||[],this._attachedListeners.push({target:t,event:e,cb:n}))}cleanupDomListeners(){this._attachedListeners&&(this._attachedListeners.forEach(({target:t,event:e,cb:n})=>{try{t.removeEventListener(e,n)}catch{}}),this._attachedListeners=[])}};function O(m){function t(g){return g.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function e(g){let c=g.getAttribute("data-rt-template");if(!c)return null;if(typeof document<"u"&&!c.includes("<"))try{let l=document.querySelector(c);if(l)return l.innerHTML}catch{}return c}function n(g,c){if(!g.includes("{#if")&&!g.includes("{#each")){let l=g;for(let s in c){let a=s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");l=l.replace(new RegExp(`\\{\\{${a}\\}\\}`,"g"),c[s]!==void 0&&c[s]!==null?c[s]:"")}return l}try{let l=p=>p.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t"),s=`let out = "";
2
+ `,a=0,y=/(\{\{([\s\S]*?)\}\}|\{#if\s+([\s\S]*?)\}|\{:else\s+if\s+([\s\S]*?)\}|\{:else\}|\{\/if\}|\{#each\s+([\s\S]*?)\s+as\s+([a-zA-Z_$][a-zA-Z0-9_$]*)(?:\s*,\s*([a-zA-Z_$][a-zA-Z0-9_$]*))?\}|\{\/each\}|\{([^{}]+?)\})/g,b=[],d;for(;(d=y.exec(g))!==null;){let p=g.slice(a,d.index);p&&(s+=`out += "${l(p)}";
3
+ `);let A=d[0];if(A.startsWith("{{")){let _=d[2];s+=`out += (${_} !== undefined && ${_} !== null ? ${_} : "");
4
+ `}else if(A.startsWith("{#if")){let _=d[3];s+=`if (${_}) {
5
+ `}else if(A.startsWith("{:else if")){let _=d[4];s+=`} else if (${_}) {
6
+ `}else if(A.startsWith("{:else}"))s+=`} else {
7
+ `;else if(A.startsWith("{/if}"))s+=`}
8
+ `;else if(A.startsWith("{#each")){let _=d[5],T=d[6],v=d[7];b.push({indexVar:v}),s+=`if (typeof ${_} !== "undefined" && ${_} !== null && Array.isArray(${_})) {
9
+ `,v&&(s+=` let ${v} = 0;
10
+ `),s+=` for (let ${T} of ${_}) {
11
+ `}else if(A.startsWith("{/each}")){let _=b.pop();_&&_.indexVar&&(s+=` ${_.indexVar}++;
12
12
  `),s+=` }
13
13
  }
14
- `}else if(w.startsWith("{")){let A=d[8];A&&(s+=`out += (${A} !== undefined && ${A} !== null ? ${A} : "");
15
- `)}c=m.lastIndex}let y=h.slice(c);y&&(s+=`out += "${u(y)}";
14
+ `}else if(A.startsWith("{")){let _=d[8];_&&(s+=`out += (${_} !== undefined && ${_} !== null ? ${_} : "");
15
+ `)}a=y.lastIndex}let f=g.slice(a);f&&(s+=`out += "${l(f)}";
16
16
  `),s+=`return out;
17
- `;let _=`
17
+ `;let w=`
18
18
  with (context) {
19
19
  try {
20
20
  ${s}
@@ -23,4 +23,4 @@ var DolphinModule=(()=>{var $=Object.defineProperty;var z=Object.getOwnPropertyD
23
23
  return '';
24
24
  }
25
25
  }
26
- `;return new Function("context",_)(o)}catch(u){console.error("[Dolphin Template Compiler Error]:",u);let s=h;for(let c in o){let m=c.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");s=s.replace(new RegExp(`\\{\\{${m}\\}\\}`,"g"),o[c]!==void 0&&o[c]!==null?o[c]:"")}return s}}function r(h){if(typeof document>"u")return h;try{let s=new DOMParser().parseFromString(h,"text/html").body,c=m=>{let f=m.tagName.toLowerCase();if(["script","iframe","object","embed","link","style","meta","applet","svg"].includes(f)){m.parentNode?.removeChild(m);return}let d=m.attributes;for(let y=d.length-1;y>=0;y--){let _=d[y].name.toLowerCase(),E=d[y].value.toLowerCase();(_.startsWith("on")||["src","href","data"].includes(_)&&(E.includes("javascript:")||E.includes("data:text/html")))&&m.removeAttribute(d[y].name)}Array.from(m.children).forEach(c)};return Array.from(s.children).forEach(c),s.innerHTML}catch{return h}}function i(h,o){if(h.nodeType!==o.nodeType){h.parentNode?.replaceChild(o.cloneNode(!0),h);return}if(h.nodeType===Node.TEXT_NODE){h.textContent!==o.textContent&&(h.textContent=o.textContent);return}if(h.nodeType===Node.ELEMENT_NODE){let u=h,s=o;if(u.tagName!==s.tagName){u.parentNode?.replaceChild(s.cloneNode(!0),u);return}let c=u.attributes,m=s.attributes;for(let g=c.length-1;g>=0;g--){let w=c[g].name;s.hasAttribute(w)||u.removeAttribute(w)}for(let g=0;g<m.length;g++){let w=m[g].name,A=m[g].value;u.getAttribute(w)!==A&&u.setAttribute(w,A)}u.tagName==="INPUT"||u.tagName==="TEXTAREA"?(u.value!==s.value&&(u.value=s.value),u.checked!==s.checked&&(u.checked=s.checked)):u.tagName==="SELECT"&&u.value!==s.value&&(u.value=s.value);let f=Array.from(u.childNodes),d=Array.from(s.childNodes),y=f.length,_=d.length,E=Math.max(y,_);for(let g=0;g<E;g++)g>=y?u.appendChild(d[g].cloneNode(!0)):g>=_?u.removeChild(f[g]):i(f[g],d[g])}}function a(h,o){if(typeof document>"u")return;let u=document.createElement(h.tagName);u.innerHTML=o;let s=Array.from(h.childNodes),c=Array.from(u.childNodes),m=s.length,f=c.length,d=Math.max(m,f);for(let y=0;y<d;y++)y>=m?h.appendChild(c[y].cloneNode(!0)):y>=f?h.removeChild(s[y]):i(s[y],c[y])}let l=new Map,b=!1;function T(h,o){l.set(h,o),b||(b=!0,(typeof requestAnimationFrame<"u"?requestAnimationFrame:s=>setTimeout(s,0))(()=>{l.forEach((s,c)=>{a(c,s)}),l.clear(),b=!1}))}p.setStoreState=function(h,o,u){this.uiStores=this.uiStores||new Map,this.uiStores.has(h)||this.uiStores.set(h,{});let s=this.uiStores.get(h);s[o]=u,this.options.debug&&console.log("%c\u{1F4BE} [Dolphin Store Update]:","color: #ec4899; font-weight: bold;",`${h}.${o}`,"=",u),typeof document<"u"&&document.querySelectorAll(`[data-store-read="${h}.${o}"]`).forEach(m=>{m.tagName==="INPUT"||m.tagName==="TEXTAREA"?m.type==="checkbox"?m.checked=!!u:m.value=u??"":m.textContent=u??""}),this.publish(`store/${h}`,s)},p.getStoreState=function(h,o){this.uiStores=this.uiStores||new Map;let u=this.uiStores.get(h);return u?u[o]:void 0},p._scanStoreBinds=function(){if(typeof document>"u")return;document.querySelectorAll("[data-store-write]").forEach(u=>{let s=u.getAttribute("data-store-write");if(s){let c=s.split(".");if(c.length===2){let m=c[0],f=c[1],d=u.type==="checkbox"?u.checked:u.value;this.uiStores=this.uiStores||new Map,this.uiStores.has(m)||this.uiStores.set(m,{});let y=this.uiStores.get(m);y[f]===void 0&&(y[f]=d)}}}),document.querySelectorAll("[data-store-read]").forEach(u=>{let s=u.getAttribute("data-store-read");if(s){let c=s.split(".");if(c.length===2){let m=c[0],f=c[1],d=this.getStoreState(m,f);d!=null&&(u.tagName==="INPUT"||u.tagName==="TEXTAREA"?u.type==="checkbox"?u.checked=!!d:u.value=d:u.textContent=d)}}})},p.getClosestContext=function(h,o){let u=h;for(;u;){if(u._rtContext){let s=u._rtContext;return o?s[o]:s}u=u.parentElement}return null},p._executeStoreAction=function(h,o){this.uiStores=this.uiStores||new Map;let u=new Proxy({},{has:(s,c)=>!0,get:(s,c)=>{if(typeof c=="string")return new Proxy({},{get:(m,f)=>{if(typeof f=="string")return this.getStoreState(c,f)},set:(m,f,d)=>typeof f=="string"?(this.setStoreState(c,f,d),!0):!1})}});try{new Function("ctx",`with(ctx) { ${h} }`)(u)}catch(s){console.error("%c[Dolphin Store Action Error]:","color: #ef4444; font-weight: bold;",s),o&&console.error("%cFailed Element:","color: #f97316; font-weight: bold;",o),console.error("%cFailed Expression:","color: #3b82f6; font-style: italic;",h)}},p._initDOMBinding=function(){if(this._domInitialized)return;this._domInitialized=!0;let h=["input","change","keyup","paste","blur"],o=new Map;h.forEach(s=>{this.addDomListener(document,s,c=>{if(!c.target||!c.target.getAttribute)return;let m=c.target.getAttribute("data-store-write");if(m){let _=m.split(".");if(_.length===2){let E=_[0],g=_[1],w=c.target.type==="checkbox"?c.target.checked:c.target.value;this.setStoreState(E,g,w)}}let f=c.target.getAttribute("data-rt-validate"),d=c.target.name;if(f&&d&&typeof this.validateField=="function"){let _=c.target.closest("form"),E=_?Object.fromEntries(new FormData(_).entries()):{},g=this.validateField(c.target.value,f,E);g?(c.target.classList.add("invalid"),this.publish(`errors/${d}`,g)):(c.target.classList.remove("invalid"),this.publish(`errors/${d}`,""))}let y=c.target.getAttribute("data-rt-push");if(y){let _=c.target.getAttribute("data-rt-debounce"),E=_?parseInt(_,10):0,g=()=>{let w={name:c.target.name,value:c.target.value};this.pubPush(y,w)};if(E>0){o.has(c.target)&&clearTimeout(o.get(c.target));let w=setTimeout(g,E);o.set(c.target,w)}else g()}})}),this.addDomListener(document,"submit",async s=>{if(!s.target||!s.target.getAttribute)return;let c=s.target.getAttribute("data-rt-submit"),m=s.target.getAttribute("data-api-submit");if(c||m){let f=s.target.querySelectorAll("[data-rt-validate]"),d=!0;if(f.length>0&&typeof this.validateField=="function"){let g=Object.fromEntries(new FormData(s.target).entries());f.forEach(w=>{let A=w.getAttribute("data-rt-validate"),D=w.name;if(A&&D){let S=this.validateField(w.value,A,g);S?(d=!1,w.classList.add("invalid"),this.publish(`errors/${D}`,S)):(w.classList.remove("invalid"),this.publish(`errors/${D}`,""))}})}if(!d){s.preventDefault(),s.stopPropagation();return}s.preventDefault();let y=this.getClosestContext(s.target)||{},_=new FormData(s.target),E=Object.fromEntries(_.entries());if(c){let g=c;for(let w in y){let A=t(w);g=g.replace(new RegExp(`\\{\\{${A}\\}\\}`,"g"),y[w]!==void 0&&y[w]!==null?y[w]:"")}this.publish(g,E)}else if(m){let g=m;for(let S in y){let k=t(S);g=g.replace(new RegExp(`\\{\\{${k}\\}\\}`,"g"),y[S]!==void 0&&y[S]!==null?y[S]:"")}let w=g.trim().split(" "),A=w.length>1?w[0].toUpperCase():"POST",D=w.length>1?w[1]:w[0];try{let S=await this.api.request(A,D,E),k=s.target.getAttribute("data-api-result");k&&this._updateDOM(k,S);let L=s.target.getAttribute("data-api-redirect");L&&(window.location.href=L),s.target.hasAttribute("data-api-reload")&&window.location.reload()}catch(S){console.error("[Dolphin] API Submit Error:",S)}}}}),["click","change","submit","input","keydown","keyup","dblclick","focus","blur","mouseenter","mouseleave"].forEach(s=>{this.addDomListener(document,s,async c=>{if(!c.target||!c.target.closest)return;let m=c.target.closest(`[data-rt-${s}]`),f=c.target.closest(`[data-api-${s}]`);if(m){s==="submit"&&c.preventDefault();let y=m.getAttribute(`data-rt-${s}`),_=m.getAttribute("data-rt-payload"),E=this.getClosestContext(m)||{},g={};if(_){let w=_;for(let A in E){let D=t(A);w=w.replace(new RegExp(`\\{\\{${D}\\}\\}`,"g"),E[A]!==void 0&&E[A]!==null?E[A]:"")}try{g=JSON.parse(w)}catch{g={}}}this.publish(y,g)}if(f){s==="submit"&&c.preventDefault();let y=f.getAttribute(`data-api-${s}`),_=f.getAttribute("data-api-payload"),E=this.getClosestContext(f)||{},g=y.trim().split(" "),w=g.length>1?g[0].toUpperCase():"POST",A=g.length>1?g[1]:g[0],D=null;if(_){let S=_;for(let k in E){let L=t(k);S=S.replace(new RegExp(`\\{\\{${L}\\}\\}`,"g"),E[k]!==void 0&&E[k]!==null?E[k]:"")}try{D=JSON.parse(S)}catch{D=null}}try{let S=await this.api.request(w,A,D),k=f.getAttribute("data-api-result");k&&this._updateDOM(k,S);let L=f.getAttribute("data-api-redirect");L&&(window.location.href=L),f.hasAttribute("data-api-reload")&&window.location.reload()}catch(S){console.error(`[Dolphin] API ${s} Error:`,S)}}let d=c.target.closest(`[data-store-${s}]`);if(d){s==="submit"&&c.preventDefault();let y=d.getAttribute(`data-store-${s}`);y&&this._executeStoreAction(y,d)}})}),this.subscribe("#",(s,c)=>{this._updateDOM(c,s)}),this._scanAndFetchAPIBinds(),this._scanStoreBinds()},p._scanAndFetchAPIBinds=async function(){if(typeof document>"u")return;let h=document.querySelectorAll("[data-api-get]");for(let o of Array.from(h)){let u=o.getAttribute("data-api-get");if(u)try{let s=await this.api.get(u),c=o.getAttribute("data-rt-bind");if(c)this._updateDOM(c,s);else{let m=e(o);if(m&&typeof s=="object"&&s!==null)if(Array.isArray(s)){let f="";for(let d of s)f+=n(m,d);T(o,f)}else T(o,n(m,s));else o.tagName==="INPUT"||o.tagName==="TEXTAREA"?o.value=typeof s=="object"?s.value!==void 0?s.value:"":s:o.innerHTML=typeof s=="object"?s.html||s.text||JSON.stringify(s):s}}catch(s){console.error("[Dolphin] API Get Error:",s)}}},p._updateDOM=function(h,o){if(typeof document>"u")return;document.querySelectorAll(`[data-rt-bind="${h}"]`).forEach(s=>{if(s.getAttribute("data-rt-type")==="context"&&typeof o=="object"&&o!==null){s._rtContext=o;let m=f=>{if(f.hasAttribute("data-rt-text")){let d=f.getAttribute("data-rt-text");d&&o[d]!==void 0&&o[d]!==null&&(f.textContent=o[d])}if(f.hasAttribute("data-rt-html")){let d=f.getAttribute("data-rt-html");d&&o[d]!==void 0&&o[d]!==null&&(f.innerHTML=r(o[d]))}if(f.hasAttribute("data-rt-attr")){let d=f.getAttribute("data-rt-attr");d&&d.split(",").forEach(y=>{let _=y.split(":");if(_.length===2){let E=_[0].trim(),g=_[1].trim();E&&g&&o[g]!==void 0&&o[g]!==null&&f.setAttribute(E,o[g])}})}if(f.hasAttribute("data-rt-class")){let d=f.getAttribute("data-rt-class");d&&d.split(",").forEach(y=>{let _=y.split(":");if(_.length===2){let E=_[0].trim(),g=_[1].trim();o[g]?f.classList.add(E):f.classList.remove(E)}})}if(f.hasAttribute("data-rt-if")){let d=f.getAttribute("data-rt-if");d&&(o[d]?f.style.display="":f.style.display="none")}if(f.hasAttribute("data-rt-hide")){let d=f.getAttribute("data-rt-hide");d&&(o[d]?f.style.display="none":f.style.display="")}};m(s),s.querySelectorAll("[data-rt-text], [data-rt-html], [data-rt-attr], [data-rt-class], [data-rt-if], [data-rt-hide]").forEach(m);return}let c=e(s);if(c&&typeof o=="object"&&o!==null){if(Array.isArray(o)){let m="";for(let f of o)m+=n(c,f);T(s,m)}else T(s,n(c,o));return}s.tagName==="INPUT"||s.tagName==="TEXTAREA"?s.value=typeof o=="object"?o.value!==void 0?o.value:"":o:s.innerHTML=typeof o=="object"?o.html||o.text||JSON.stringify(o):o})}}var I=class{client;db;isOnline;memoryCache=new Map;memoryMutations=[];constructor(t){this.client=t,this.isOnline=typeof window<"u"&&typeof navigator<"u"?navigator.onLine:!0,this.initDB(),this.setupNetworkListeners()}initDB(){if(!(typeof indexedDB>"u"))try{let t=indexedDB.open("dolphin_offline",1);t.onupgradeneeded=e=>{let n=e.target.result;n.objectStoreNames.contains("cache")||n.createObjectStore("cache"),n.objectStoreNames.contains("mutations")||n.createObjectStore("mutations",{keyPath:"id",autoIncrement:!0})},t.onsuccess=e=>{this.db=e.target.result,this.isOnline&&this.syncMutations()}}catch(t){console.warn("[Dolphin Offline] Failed to initialize IndexedDB:",t)}}setupNetworkListeners(){typeof window>"u"||(this.client.addDomListener(window,"online",()=>{this.isOnline=!0,this.client._dispatch("network:status",{online:!0}),this.syncMutations()}),this.client.addDomListener(window,"offline",()=>{this.isOnline=!1,this.client._dispatch("network:status",{online:!1})}))}async getCache(t){return this.db?new Promise(e=>{try{let i=this.db.transaction("cache","readonly").objectStore("cache").get(t);i.onsuccess=()=>e(i.result?i.result.data:null),i.onerror=()=>e(null)}catch{e(null)}}):this.memoryCache.get(t)}async setCache(t,e){if(!this.db){this.memoryCache.set(t,e);return}return new Promise(n=>{try{let r=this.db.transaction("cache","readwrite");r.objectStore("cache").put({data:e,timestamp:Date.now()},t),r.oncomplete=()=>n()}catch{n()}})}async queueMutation(t,e,n){let r={method:t,path:e,payload:n,timestamp:Date.now()};if(!this.db){this.memoryMutations.push(r);return}return new Promise(i=>{try{let a=this.db.transaction("mutations","readwrite");a.objectStore("mutations").add(r),a.oncomplete=()=>i()}catch{i()}})}async getMutations(){return this.db?new Promise(t=>{try{let r=this.db.transaction("mutations","readonly").objectStore("mutations").getAll();r.onsuccess=()=>t(r.result||[]),r.onerror=()=>t([])}catch{t([])}}):[...this.memoryMutations]}async removeMutation(t){if(!this.db){this.memoryMutations=this.memoryMutations.filter(e=>e.id!==t);return}return new Promise(e=>{try{let n=this.db.transaction("mutations","readwrite");n.objectStore("mutations").delete(t),n.oncomplete=()=>e()}catch{e()}})}async syncMutations(){let t=await this.getMutations();if(t.length!==0){console.log(`[Dolphin Offline] Syncing ${t.length} queued mutations...`);for(let e of t)try{await this.client.api.requestDirect(e.method,e.path,e.payload),e.id!==void 0?await this.removeMutation(e.id):this.memoryMutations.shift()}catch(n){if(console.error(`[Dolphin Offline] Sync failed for mutation ${e.method} ${e.path}:`,n),n&&n.status&&n.status>=400&&n.status<500)console.warn("[Dolphin Offline] Discarding invalid mutation."),e.id!==void 0?await this.removeMutation(e.id):this.memoryMutations.shift();else break}}}};function q(p){p._initOffline=function(){this.offline=new I(this)}}function Y(p,t,e){let n=t.split(",");for(let r of n){let i=r.trim().split(":"),a=i[0],l=i[1];if(a==="required"){if(!p||p.trim()==="")return"This field is required"}else if(a==="email"){if(p&&p.trim()!==""&&!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(p))return"Please enter a valid email address"}else if(a==="min"){let b=parseInt(l,10);if(!p||p.length<b)return`Must be at least ${b} characters`}else if(a==="match"&&e&&p!==e[l])return`Must match ${l}`}return null}function O(p){p.validateField=Y}function N(p){p.animateElement=function(t,e,n=300){if(typeof t.animate!="function"){t.classList.add(e),setTimeout(()=>t.classList.remove(e),n);return}e==="fade-in"?t.animate([{opacity:0,transform:"translateY(10px)"},{opacity:1,transform:"translateY(0)"}],{duration:n,easing:"ease-out"}):e==="fade-out"&&t.animate([{opacity:1,transform:"translateY(0)"},{opacity:0,transform:"translateY(10px)"}],{duration:n,easing:"ease-in"})},p.staggerListItems=function(t,e,n=50){if(typeof document>"u")return;t.querySelectorAll(e).forEach((i,a)=>{i.style.animationDelay=`${a*n}ms`,i.classList.add("staggered-item")})}}function U(p){p._initA11y=function(){typeof document>"u"||(this.addDomListener(document,"keydown",t=>{if(t.key!=="Tab")return;document.querySelectorAll("[data-rt-a11y-focus-trap]").forEach(n=>{if(n.style.display==="none"||n.hasAttribute("aria-hidden")&&n.getAttribute("aria-hidden")==="true")return;let i=Array.from(n.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex="0"], [contenteditable]'));if(i.length===0)return;let a=i[0],l=i[i.length-1];t.shiftKey?document.activeElement===a&&(l.focus(),t.preventDefault()):document.activeElement===l&&(a.focus(),t.preventDefault())})}),this.addDomListener(document,"keydown",t=>{if(!["ArrowUp","ArrowDown","Enter"].includes(t.key))return;document.querySelectorAll("[data-rt-keynav]").forEach(n=>{let r=Array.from(n.children);if(r.length===0)return;let i=r.findIndex(a=>a.classList.contains("active")||document.activeElement===a);t.key==="ArrowDown"?(i=(i+1)%r.length,r[i].focus(),r.forEach((a,l)=>{l===i?a.classList.add("active"):a.classList.remove("active")}),t.preventDefault()):t.key==="ArrowUp"?(i=(i-1+r.length)%r.length,r[i].focus(),r.forEach((a,l)=>{l===i?a.classList.add("active"):a.classList.remove("active")}),t.preventDefault()):t.key==="Enter"&&i!==-1&&(r[i].click(),t.preventDefault())})}))},p.autoAriaModal=function(t,e){e?(t.setAttribute("role","dialog"),t.setAttribute("aria-modal","true"),t.setAttribute("aria-hidden","false"),t.focus()):t.setAttribute("aria-hidden","true")}}function F(p){p._initI18n=function(){if(this.i18n=this.i18n||{locale:"en",dicts:{}},typeof document>"u")return;if(document.querySelectorAll("[data-i18n-dict]").forEach(e=>{let n=e.getAttribute("data-i18n-dict");if(n)try{let r=JSON.parse(e.textContent||"{}");this.i18n.dicts[n]={...this.i18n.dicts[n]||{},...r}}catch(r){console.warn("[Dolphin i18n] Failed to parse dictionary for locale:",n,r)}}),!this.i18n.locale&&typeof navigator<"u"){let e=navigator.language.split("-")[0];this.i18n.dicts[e]&&(this.i18n.locale=e)}this.addDomListener(document,"click",e=>{let n=e.target.closest("[data-i18n-switch]");if(n){let r=n.getAttribute("data-i18n-switch");r&&this.setLocale(r)}}),this.translateDOM()},p.setLocale=function(t){this.i18n=this.i18n||{locale:"en",dicts:{}},this.i18n.locale=t,this.translateDOM(),this.publish("i18n/locale",t)},p.translateDOM=function(){if(typeof document>"u")return;this.i18n=this.i18n||{locale:"en",dicts:{}};let t=this.i18n.locale||"en",e=this.i18n.dicts[t]||{};document.querySelectorAll("[data-i18n-key]").forEach(r=>{let i=r.getAttribute("data-i18n-key");if(!i)return;let a=i.split(".").reduce((b,T)=>b?b[T]:null,e);a==null&&(a=i);let l=r.getAttribute("data-i18n-params");if(l)try{let b=JSON.parse(l),T=h=>h.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");for(let h in b){let o=T(h);a=a.replace(new RegExp(`\\{\\{${o}\\}\\}`,"g"),b[h])}}catch{}r.tagName==="INPUT"||r.tagName==="TEXTAREA"?r.placeholder=a:r.textContent=a})}}function H(p){p._initDragDrop=function(){typeof document>"u"||(this.addDomListener(document,"dragstart",t=>{let e=t.target.closest("[data-drag]");if(!e)return;let n=e.getAttribute("data-drag");n&&(t.dataTransfer.setData("text/plain",n),t.dataTransfer.effectAllowed="move",e.classList.add("dragging"))}),this.addDomListener(document,"dragend",t=>{let e=t.target.closest("[data-drag]");e&&e.classList.remove("dragging")}),this.addDomListener(document,"dragover",t=>{let e=t.target.closest("[data-drop]");e&&(t.preventDefault(),e.classList.add("drag-over"))}),this.addDomListener(document,"dragleave",t=>{let e=t.target.closest("[data-drop]");e&&e.classList.remove("drag-over")}),this.addDomListener(document,"drop",t=>{let e=t.target.closest("[data-drop]");if(!e)return;t.preventDefault(),e.classList.remove("drag-over");let n=e.getAttribute("data-drop"),r=t.dataTransfer.getData("text/plain");if(n&&r)try{let i=JSON.parse(r);this.publish(n,i)}catch{this.publish(n,{value:r})}}),this.addDomListener(document,"dragover",t=>{let e=t.target.closest("[data-sortable]");if(!e)return;t.preventDefault();let n=e.querySelector(".dragging");if(!n)return;let i=Array.from(e.querySelectorAll("[data-drag]:not(.dragging)")).find(a=>{let l=a.getBoundingClientRect();return t.clientY-l.top-l.height/2<0});i?e.insertBefore(n,i):e.appendChild(n)}),this.addDomListener(document,"drop",t=>{let e=t.target.closest("[data-sortable]");if(!e)return;let n=e.getAttribute("data-sortable");if(!n)return;let i=Array.from(e.querySelectorAll("[data-drag]")).map((a,l)=>{let b=a.getAttribute("data-drag");try{return{index:l,payload:JSON.parse(b||"{}")}}catch{return{index:l,payload:b}}});this.publish(n,i)}))}}function W(p){p._initCollab=function(){typeof document>"u"||(this.addDomListener(document,"mousemove",t=>{document.querySelectorAll("[data-rt-cursor-share]").forEach(n=>{let r=n.getAttribute("data-rt-cursor-share");if(!r)return;let i=n.getBoundingClientRect(),a=(t.clientX-i.left)/i.width,l=(t.clientY-i.top)/i.height,b=Date.now();(!n._lastSent||b-n._lastSent>50)&&(n._lastSent=b,this.pubPush(`collab/${r}/cursor/${this.deviceId}`,{deviceId:this.deviceId,x:a,y:l}))})}),this.addDomListener(document,"input",t=>{let e=t.target.getAttribute("data-rt-typing");if(!e)return;let n=e,r=i=>{this.pubPush(`collab/${n}/typing/${this.deviceId}`,{deviceId:this.deviceId,typing:i})};t.target._isTyping||(t.target._isTyping=!0,r(!0)),t.target._typingTimer&&clearTimeout(t.target._typingTimer),t.target._typingTimer=setTimeout(()=>{t.target._isTyping=!1,r(!1)},2e3)}),this.addDomListener(document,"input",t=>{let e=t.target.getAttribute("data-rt-crdt");if(!e)return;let n=e,r=t.target.value,i=Date.now();this.publish(`collab/${n}/crdt`,{deviceId:this.deviceId,value:r,timestamp:i,cursorPos:t.target.selectionStart})}),this.subscribe("collab/+/cursor/+",(t,e)=>{let n=e.split("/"),r=n[1],i=n[3];if(i===this.deviceId)return;let a=document.querySelector(`[data-rt-cursor-share="${r}"]`);if(!a)return;let l=a.querySelector(`.rt-cursor-${i}`);l||(l=document.createElement("div"),l.className=`rt-cursor rt-cursor-${i}`,l.style.position="absolute",l.style.width="10px",l.style.height="10px",l.style.borderRadius="50%",l.style.backgroundColor="#"+Math.floor(Math.random()*16777215).toString(16),l.style.pointerEvents="none",a.appendChild(l));let b=a.getBoundingClientRect();l.style.left=t.x*b.width+"px",l.style.top=t.y*b.height+"px"}),this.subscribe("collab/+/crdt",(t,e)=>{if(t.deviceId===this.deviceId)return;let r=e.split("/")[1];document.querySelectorAll(`[data-rt-crdt="${r}"]`).forEach(a=>{if(!a._lastUpdate||t.timestamp>a._lastUpdate){a._lastUpdate=t.timestamp;let l=a.selectionStart;a.value=t.value,document.activeElement===a&&a.setSelectionRange(l,l)}})}))}}function j(p){p.registerServiceWorker=async function(t="/sw.js"){if(typeof navigator>"u"||!("serviceWorker"in navigator))return console.warn("[Dolphin PWA] Service Workers are not supported in this browser."),null;try{let e=await navigator.serviceWorker.register(t);return console.log("[Dolphin PWA] Service Worker registered successfully with scope:",e.scope),e}catch(e){return console.error("[Dolphin PWA] Service Worker registration failed:",e),null}},p.subscribePushNotifications=async function(t){if(typeof window>"u"||!("serviceWorker"in navigator)||!("PushManager"in window))return console.warn("[Dolphin PWA] Push notifications are not supported in this browser."),null;try{let e=await navigator.serviceWorker.ready,n=await e.pushManager.getSubscription();if(!n){let r="=".repeat((4-t.length%4)%4),i=(t+r).replace(/\-/g,"+").replace(/_/g,"/"),a=window.atob(i),l=new Uint8Array(a.length);for(let b=0;b<a.length;++b)l[b]=a.charCodeAt(b);n=await e.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:l})}return console.log("[Dolphin PWA] Subscribed to push notifications:",n),n}catch(e){return console.error("[Dolphin PWA] Push notification subscription failed:",e),null}}}var P=class{static render(t){if(typeof document>"u")throw new Error("DolphinTestUtils.render requires a DOM document environment to execute.");let e=document.createElement("div");return e.innerHTML=t,document.body.appendChild(e),{container:e,find:n=>e.querySelector(n),fireEvent:(n,r)=>{let i=document.createEvent("Event");i.initEvent(r,!0,!0),n.dispatchEvent(i)}}}static mockWebSocket(){let t=[],e={readyState:1,send:n=>{t.push(n)},close:jest.fn(),onopen:jest.fn(),onmessage:jest.fn(),onclose:jest.fn(),onerror:jest.fn(),sentMessages:t};return global.WebSocket=class{static OPEN=1;readyState=e.readyState;send=e.send;close=e.close;set onopen(n){e.onopen=n}get onopen(){return e.onopen}set onmessage(n){e.onmessage=n}get onmessage(){return e.onmessage}set onclose(n){e.onclose=n}get getonclose(){return e.onclose}constructor(){setTimeout(()=>e.onopen&&e.onopen(),0)}},e}static simulateClick(t){let e={target:t,preventDefault:jest.fn(),stopPropagation:jest.fn()};(global.document._listeners?.click||[]).forEach(r=>r(e))}static simulateChange(t,e){t.value=e;let n={target:t,preventDefault:jest.fn(),stopPropagation:jest.fn()};(global.document._listeners?.change||[]).forEach(i=>i(n))}};function B(p){p.testing=P}R(v.prototype);q(v.prototype);O(v.prototype);N(v.prototype);U(v.prototype);F(v.prototype);H(v.prototype);W(v.prototype);j(v.prototype);B(v.prototype);typeof window<"u"&&(window.DolphinClient=v,document.addEventListener("DOMContentLoaded",()=>{if(!window.dolphin){let p=document.querySelector('script[src*="dolphin-client"]'),t=p?p.getAttribute("data-debug")==="true":!1,e=new v(void 0,void 0,{debug:t});window.dolphin=e,t&&(console.log("%c\u{1F42C} [Dolphin Client] Auto-initialized local reactive engine!","color: #06b6d4; font-weight: bold; font-size: 14px;"),console.log('%c\u{1F449} Tip: You can access the client instance via "window.dolphin" in console.',"color: #94a3b8; font-style: italic;")),document.querySelector('[data-store-write="app.username"]')&&e.setStoreState("app","username","\u0928\u092E\u0938\u094D\u0924\u0947 \u0938\u093E\u0925\u0940!")}}));return X(Z);})();
26
+ `,E=c;return typeof Proxy<"u"&&c!==null&&typeof c=="object"&&(E=new Proxy(c,{has(p,A){return typeof A!="symbol"},get(p,A){if(A!==Symbol.unscopables){if(A in p)return p[A];if(typeof globalThis<"u"&&A in globalThis)return globalThis[A];if(typeof window<"u"&&A in window)return window[A]}}})),new Function("context",w)(E)}catch(l){console.error("[Dolphin Template Compiler Error]:",l);let s=g;for(let a in c){let y=a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");s=s.replace(new RegExp(`\\{\\{${y}\\}\\}`,"g"),c[a]!==void 0&&c[a]!==null?c[a]:"")}return s}}function r(g){if(typeof document>"u")return g;try{let s=new DOMParser().parseFromString(g,"text/html").body,a=y=>{let b=y.tagName.toLowerCase();if(["script","iframe","object","embed","link","style","meta","applet","svg"].includes(b)){y.parentNode?.removeChild(y);return}let d=y.attributes;for(let f=d.length-1;f>=0;f--){let w=d[f].name.toLowerCase(),E=d[f].value.toLowerCase();(w.startsWith("on")||["src","href","data"].includes(w)&&(E.includes("javascript:")||E.includes("data:text/html")))&&y.removeAttribute(d[f].name)}Array.from(y.children).forEach(a)};return Array.from(s.children).forEach(a),s.innerHTML}catch{return g}}function i(g,c){if(g.nodeType!==c.nodeType){g.parentNode?.replaceChild(c.cloneNode(!0),g);return}if(g.nodeType===Node.TEXT_NODE){g.textContent!==c.textContent&&(g.textContent=c.textContent);return}if(g.nodeType===Node.ELEMENT_NODE){let l=g,s=c;if(l.tagName!==s.tagName){l.parentNode?.replaceChild(s.cloneNode(!0),l);return}let a=l.attributes,y=s.attributes;for(let h=a.length-1;h>=0;h--){let p=a[h].name;s.hasAttribute(p)||l.removeAttribute(p)}for(let h=0;h<y.length;h++){let p=y[h].name,A=y[h].value;l.getAttribute(p)!==A&&l.setAttribute(p,A)}l.tagName==="INPUT"||l.tagName==="TEXTAREA"?(l.value!==s.value&&(l.value=s.value),l.checked!==s.checked&&(l.checked=s.checked)):l.tagName==="SELECT"&&l.value!==s.value&&(l.value=s.value);let b=Array.from(l.childNodes),d=Array.from(s.childNodes),f=b.length,w=d.length,E=Math.max(f,w);for(let h=0;h<E;h++)h>=f?l.appendChild(d[h].cloneNode(!0)):h>=w?l.removeChild(b[h]):i(b[h],d[h])}}function o(g,c){if(typeof document>"u")return;let l=document.createElement(g.tagName);l.innerHTML=c;let s=Array.from(g.childNodes),a=Array.from(l.childNodes),y=s.length,b=a.length,d=Math.max(y,b);for(let f=0;f<d;f++)f>=y?g.appendChild(a[f].cloneNode(!0)):f>=b?g.removeChild(s[f]):i(s[f],a[f])}let u=new Map,S=!1;function D(g,c){u.set(g,c),S||(S=!0,(typeof requestAnimationFrame<"u"?requestAnimationFrame:s=>setTimeout(s,0))(()=>{u.forEach((s,a)=>{o(a,s)}),u.clear(),S=!1}))}m.setStoreState=function(g,c,l){this.uiStores=this.uiStores||new Map,this.uiStores.has(g)||this.uiStores.set(g,{});let s=this.uiStores.get(g);s[c]=l,this.options.debug&&console.log("%c\u{1F4BE} [Dolphin Store Update]:","color: #ec4899; font-weight: bold;",`${g}.${c}`,"=",l),typeof document<"u"&&document.querySelectorAll(`[data-store-read="${g}.${c}"]`).forEach(y=>{y.tagName==="INPUT"||y.tagName==="TEXTAREA"?y.type==="checkbox"?y.checked=!!l:y.value=l??"":y.textContent=l??""}),this.publish(`store/${g}`,s),typeof this._updateDOM=="function"&&this._updateDOM(`store/${g}`,s)},m.getStoreState=function(g,c){this.uiStores=this.uiStores||new Map;let l=this.uiStores.get(g);return l?l[c]:void 0},m._scanStoreBinds=function(){if(typeof document>"u")return;document.querySelectorAll("[data-store-write]").forEach(l=>{let s=l.getAttribute("data-store-write");if(s){let a=s.split(".");if(a.length===2){let y=a[0],b=a[1],d=l.type==="checkbox"?l.checked:l.value;this.uiStores=this.uiStores||new Map,this.uiStores.has(y)||this.uiStores.set(y,{});let f=this.uiStores.get(y);f[b]===void 0&&(f[b]=d)}}}),document.querySelectorAll("[data-store-read]").forEach(l=>{let s=l.getAttribute("data-store-read");if(s){let a=s.split(".");if(a.length===2){let y=a[0],b=a[1],d=this.getStoreState(y,b);d!=null&&(l.tagName==="INPUT"||l.tagName==="TEXTAREA"?l.type==="checkbox"?l.checked=!!d:l.value=d:l.textContent=d)}}})},m.getClosestContext=function(g,c){let l=g;for(;l;){if(l._rtContext){let s=l._rtContext;return c?s[c]:s}l=l.parentElement}return null},m._executeStoreAction=function(g,c){this.uiStores=this.uiStores||new Map;let l=new Proxy({},{has:(s,a)=>!0,get:(s,a)=>{if(typeof a=="string")return new Proxy({},{get:(y,b)=>{if(typeof b=="string")return this.getStoreState(a,b)},set:(y,b,d)=>typeof b=="string"?(this.setStoreState(a,b,d),!0):!1})}});try{new Function("ctx",`with(ctx) { ${g} }`)(l)}catch(s){console.error("%c[Dolphin Store Action Error]:","color: #ef4444; font-weight: bold;",s),c&&console.error("%cFailed Element:","color: #f97316; font-weight: bold;",c),console.error("%cFailed Expression:","color: #3b82f6; font-style: italic;",g)}},m._initDOMBinding=function(){if(this._domInitialized)return;this._domInitialized=!0;let g=["input","change","keyup","paste","blur"],c=new Map;g.forEach(s=>{this.addDomListener(document,s,a=>{if(!a.target||!a.target.getAttribute)return;let y=a.target.getAttribute("data-store-write");if(y){let w=y.split(".");if(w.length===2){let E=w[0],h=w[1],p=a.target.type==="checkbox"?a.target.checked:a.target.value;this.setStoreState(E,h,p)}}let b=a.target.getAttribute("data-rt-validate"),d=a.target.name;if(b&&d&&typeof this.validateField=="function"){let w=a.target.closest("form"),E=w?Object.fromEntries(new FormData(w).entries()):{},h=this.validateField(a.target.value,b,E);h?(a.target.classList.add("invalid"),this.publish(`errors/${d}`,h)):(a.target.classList.remove("invalid"),this.publish(`errors/${d}`,""))}let f=a.target.getAttribute("data-rt-push");if(f){let w=a.target.getAttribute("data-rt-debounce"),E=w?parseInt(w,10):0,h=()=>{let p={name:a.target.name,value:a.target.value};this.pubPush(f,p)};if(E>0){c.has(a.target)&&clearTimeout(c.get(a.target));let p=setTimeout(h,E);c.set(a.target,p)}else h()}})}),this.addDomListener(document,"submit",async s=>{if(!s.target||!s.target.getAttribute)return;let a=s.target.getAttribute("data-rt-submit"),y=s.target.getAttribute("data-api-submit");if(a||y){let b=s.target.querySelectorAll("[data-rt-validate]"),d=!0;if(b.length>0&&typeof this.validateField=="function"){let h=Object.fromEntries(new FormData(s.target).entries());b.forEach(p=>{let A=p.getAttribute("data-rt-validate"),_=p.name;if(A&&_){let T=this.validateField(p.value,A,h);T?(d=!1,p.classList.add("invalid"),this.publish(`errors/${_}`,T)):(p.classList.remove("invalid"),this.publish(`errors/${_}`,""))}})}if(!d){s.preventDefault(),s.stopPropagation();return}s.preventDefault();let f=this.getClosestContext(s.target)||{},w=new FormData(s.target),E=Object.fromEntries(w.entries());if(a){let h=a;for(let p in f){let A=t(p);h=h.replace(new RegExp(`\\{\\{${A}\\}\\}`,"g"),f[p]!==void 0&&f[p]!==null?f[p]:"")}this.publish(h,E)}else if(y){let h=y;for(let T in f){let v=t(T);h=h.replace(new RegExp(`\\{\\{${v}\\}\\}`,"g"),f[T]!==void 0&&f[T]!==null?f[T]:"")}let p=h.trim().split(" "),A=p.length>1?p[0].toUpperCase():"POST",_=p.length>1?p[1]:p[0];try{let T=await this.api.request(A,_,E),v=s.target.getAttribute("data-api-result");v&&this._updateDOM(v,T);let L=s.target.getAttribute("data-api-redirect");L&&(window.location.href=L),s.target.hasAttribute("data-api-reload")&&window.location.reload()}catch(T){console.error("[Dolphin] API Submit Error:",T)}}}}),["click","change","submit","input","keydown","keyup","dblclick","focus","blur","mouseenter","mouseleave"].forEach(s=>{this.addDomListener(document,s,async a=>{if(!a.target||!a.target.closest)return;let y=a.target.closest(`[data-rt-${s}]`),b=a.target.closest(`[data-api-${s}]`);if(y){s==="submit"&&a.preventDefault();let f=y.getAttribute(`data-rt-${s}`),w=y.getAttribute("data-rt-payload"),E=this.getClosestContext(y)||{},h={};if(w){let p=w;for(let A in E){let _=t(A);p=p.replace(new RegExp(`\\{\\{${_}\\}\\}`,"g"),E[A]!==void 0&&E[A]!==null?E[A]:"")}try{h=JSON.parse(p)}catch{h={}}}this.publish(f,h)}if(b){s==="submit"&&a.preventDefault();let f=b.getAttribute(`data-api-${s}`),w=b.getAttribute("data-api-payload"),E=this.getClosestContext(b)||{},h=f.trim().split(" "),p=h.length>1?h[0].toUpperCase():"POST",A=h.length>1?h[1]:h[0],_=null;if(w){let T=w;for(let v in E){let L=t(v);T=T.replace(new RegExp(`\\{\\{${L}\\}\\}`,"g"),E[v]!==void 0&&E[v]!==null?E[v]:"")}try{_=JSON.parse(T)}catch{_=null}}try{let T=await this.api.request(p,A,_),v=b.getAttribute("data-api-result");v&&this._updateDOM(v,T);let L=b.getAttribute("data-api-redirect");L&&(window.location.href=L),b.hasAttribute("data-api-reload")&&window.location.reload()}catch(T){console.error(`[Dolphin] API ${s} Error:`,T)}}let d=a.target.closest(`[data-store-${s}]`);if(d){s==="submit"&&a.preventDefault();let f=d.getAttribute(`data-store-${s}`);f&&this._executeStoreAction(f,d)}})}),this.subscribe("#",(s,a)=>{this._updateDOM(a,s)}),this._scanAndFetchAPIBinds(),this._scanStoreBinds()},m._scanAndFetchAPIBinds=async function(){if(typeof document>"u")return;let g=document.querySelectorAll("[data-api-get]");for(let c of Array.from(g)){let l=c.getAttribute("data-api-get");if(l)try{let s=await this.api.get(l),a=c.getAttribute("data-api-store");if(a){let b=a.split(".");b.length===2&&this.setStoreState(b[0],b[1],s)}let y=c.getAttribute("data-rt-bind");if(y&&!a)this._updateDOM(y,s);else if(!a){let b=e(c);if(b&&typeof s=="object"&&s!==null){let d=this._applyDeclarativeDirectives(c,s);if(Array.isArray(d)){let f="";for(let w of d)f+=n(b,w);D(c,f)}else D(c,n(b,d))}else c.tagName==="INPUT"||c.tagName==="TEXTAREA"?c.value=typeof s=="object"?s.value!==void 0?s.value:"":s:c.innerHTML=typeof s=="object"?s.html||s.text||JSON.stringify(s):s}}catch(s){console.error("[Dolphin] API Get Error:",s)}}},m._applyDeclarativeDirectives=function(g,c){let l=c;if(typeof c=="object"&&c!==null){let s=a=>{let y=[...a],b=g.getAttribute("data-rt-filter");if(b){let w=b.split("==");if(w.length===2){let E=w[0].trim(),h=w[1].trim(),p,A=h.split(".");A.length===2?p=this.getStoreState(A[0],A[1]):p=c[h]!==void 0?c[h]:this.getStoreState("app",h),p!=null&&p!==""&&(y=y.filter(_=>_[E]===p))}}let d=g.getAttribute("data-rt-search");if(d){let w=d.split("==");if(w.length===2){let E=w[0].trim(),h=w[1].trim(),p,A=h.split(".");if(A.length===2?p=this.getStoreState(A[0],A[1]):p=c[h]!==void 0?c[h]:this.getStoreState("app",h),p!=null&&p!==""){let _=String(p).toLowerCase();y=y.filter(T=>{let v=T[E];return v!=null&&String(v).toLowerCase().includes(_)})}}}let f=g.getAttribute("data-rt-sort");if(f){let w,E=f.split(".");if(E.length===2?w=this.getStoreState(E[0],E[1]):w=c[f]!==void 0?c[f]:this.getStoreState("app",f),w&&w!=="")if(w==="popular")y.sort((h,p)=>{let A=h.rating?.rate||h.rate||0;return(p.rating?.rate||p.rate||0)-A});else{let h="",p="asc";w.endsWith("-low")||w.endsWith("-asc")?(h=w.replace("-low","").replace("-asc",""),p="asc"):(w.endsWith("-high")||w.endsWith("-desc"))&&(h=w.replace("-high","").replace("-desc",""),p="desc"),h&&y.sort((A,_)=>{let T=(J,Q)=>Q.split(".").reduce((N,X)=>N&&N[X],J),v=T(A,h),L=T(_,h);if(v===void 0&&(v=A[h]),L===void 0&&(L=_[h]),typeof v=="string"&&typeof L=="string")return p==="asc"?v.localeCompare(L):L.localeCompare(v);let $=Number(v),I=Number(L);return!isNaN($)&&!isNaN(I)?p==="asc"?$-I:I-$:0})}}return y};if(Array.isArray(c))l=s(c);else{let a="";for(let y in c)if(Array.isArray(c[y])){a=y;break}if(a){let y=s(c[a]);l={...c,[a]:y}}}}return l},m._updateDOM=function(g,c){if(typeof document>"u")return;document.querySelectorAll(`[data-rt-bind="${g}"]`).forEach(s=>{let a=this._applyDeclarativeDirectives(s,c);if(s.getAttribute("data-rt-type")==="context"&&typeof a=="object"&&a!==null){s._rtContext=a;let b=d=>{if(d.hasAttribute("data-rt-text")){let f=d.getAttribute("data-rt-text");f&&a[f]!==void 0&&a[f]!==null&&(d.textContent=a[f])}if(d.hasAttribute("data-rt-html")){let f=d.getAttribute("data-rt-html");f&&a[f]!==void 0&&a[f]!==null&&(d.innerHTML=r(a[f]))}if(d.hasAttribute("data-rt-attr")){let f=d.getAttribute("data-rt-attr");f&&f.split(",").forEach(w=>{let E=w.split(":");if(E.length===2){let h=E[0].trim(),p=E[1].trim();h&&p&&a[p]!==void 0&&a[p]!==null&&d.setAttribute(h,a[p])}})}if(d.hasAttribute("data-rt-class")){let f=d.getAttribute("data-rt-class");f&&f.split(",").forEach(w=>{let E=w.split(":");if(E.length===2){let h=E[0].trim(),p=E[1].trim(),A=h.split(/\s+/).filter(Boolean);a[p]?A.forEach(_=>d.classList.add(_)):A.forEach(_=>d.classList.remove(_))}})}if(d.hasAttribute("data-rt-if")){let f=d.getAttribute("data-rt-if");f&&(a[f]?d.style.display="":d.style.display="none")}if(d.hasAttribute("data-rt-hide")){let f=d.getAttribute("data-rt-hide");f&&(a[f]?d.style.display="none":d.style.display="")}};b(s),s.querySelectorAll("[data-rt-text], [data-rt-html], [data-rt-attr], [data-rt-class], [data-rt-if], [data-rt-hide]").forEach(b);return}let y=e(s);if(y&&typeof a=="object"&&a!==null){if(Array.isArray(a)){let b="";for(let d of a)b+=n(y,d);D(s,b)}else D(s,n(y,a));return}s.tagName==="INPUT"||s.tagName==="TEXTAREA"?s.value=typeof a=="object"?a.value!==void 0?a.value:"":a:s.innerHTML=typeof a=="object"?a.html||a.text||JSON.stringify(a):a})}}var R=class{client;db;isOnline;memoryCache=new Map;memoryMutations=[];constructor(t){this.client=t,this.isOnline=typeof window<"u"&&typeof navigator<"u"?navigator.onLine:!0,this.initDB(),this.setupNetworkListeners()}initDB(){if(!(typeof indexedDB>"u"))try{let t=indexedDB.open("dolphin_offline",1);t.onupgradeneeded=e=>{let n=e.target.result;n.objectStoreNames.contains("cache")||n.createObjectStore("cache"),n.objectStoreNames.contains("mutations")||n.createObjectStore("mutations",{keyPath:"id",autoIncrement:!0})},t.onsuccess=e=>{this.db=e.target.result,this.isOnline&&this.syncMutations()}}catch(t){console.warn("[Dolphin Offline] Failed to initialize IndexedDB:",t)}}setupNetworkListeners(){typeof window>"u"||(this.client.addDomListener(window,"online",()=>{this.isOnline=!0,this.client._dispatch("network:status",{online:!0}),this.syncMutations()}),this.client.addDomListener(window,"offline",()=>{this.isOnline=!1,this.client._dispatch("network:status",{online:!1})}))}async getCache(t){return this.db?new Promise(e=>{try{let i=this.db.transaction("cache","readonly").objectStore("cache").get(t);i.onsuccess=()=>e(i.result?i.result.data:null),i.onerror=()=>e(null)}catch{e(null)}}):this.memoryCache.get(t)}async setCache(t,e){if(!this.db){this.memoryCache.set(t,e);return}return new Promise(n=>{try{let r=this.db.transaction("cache","readwrite");r.objectStore("cache").put({data:e,timestamp:Date.now()},t),r.oncomplete=()=>n()}catch{n()}})}async queueMutation(t,e,n){let r={method:t,path:e,payload:n,timestamp:Date.now()};if(!this.db){this.memoryMutations.push(r);return}return new Promise(i=>{try{let o=this.db.transaction("mutations","readwrite");o.objectStore("mutations").add(r),o.oncomplete=()=>i()}catch{i()}})}async getMutations(){return this.db?new Promise(t=>{try{let r=this.db.transaction("mutations","readonly").objectStore("mutations").getAll();r.onsuccess=()=>t(r.result||[]),r.onerror=()=>t([])}catch{t([])}}):[...this.memoryMutations]}async removeMutation(t){if(!this.db){this.memoryMutations=this.memoryMutations.filter(e=>e.id!==t);return}return new Promise(e=>{try{let n=this.db.transaction("mutations","readwrite");n.objectStore("mutations").delete(t),n.oncomplete=()=>e()}catch{e()}})}async syncMutations(){let t=await this.getMutations();if(t.length!==0){console.log(`[Dolphin Offline] Syncing ${t.length} queued mutations...`);for(let e of t)try{await this.client.api.requestDirect(e.method,e.path,e.payload),e.id!==void 0?await this.removeMutation(e.id):this.memoryMutations.shift()}catch(n){if(console.error(`[Dolphin Offline] Sync failed for mutation ${e.method} ${e.path}:`,n),n&&n.status&&n.status>=400&&n.status<500)console.warn("[Dolphin Offline] Discarding invalid mutation."),e.id!==void 0?await this.removeMutation(e.id):this.memoryMutations.shift();else break}}}};function U(m){m._initOffline=function(){this.offline=new R(this)}}function st(m,t,e){let n=t.split(",");for(let r of n){let i=r.trim().split(":"),o=i[0],u=i[1];if(o==="required"){if(!m||m.trim()==="")return"This field is required"}else if(o==="email"){if(m&&m.trim()!==""&&!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(m))return"Please enter a valid email address"}else if(o==="min"){let S=parseInt(u,10);if(!m||m.length<S)return`Must be at least ${S} characters`}else if(o==="match"&&e&&m!==e[u])return`Must match ${u}`}return null}function W(m){m.validateField=st}function F(m){m.animateElement=function(t,e,n=300){if(typeof t.animate!="function"){t.classList.add(e),setTimeout(()=>t.classList.remove(e),n);return}e==="fade-in"?t.animate([{opacity:0,transform:"translateY(10px)"},{opacity:1,transform:"translateY(0)"}],{duration:n,easing:"ease-out"}):e==="fade-out"&&t.animate([{opacity:1,transform:"translateY(0)"},{opacity:0,transform:"translateY(10px)"}],{duration:n,easing:"ease-in"})},m.staggerListItems=function(t,e,n=50){if(typeof document>"u")return;t.querySelectorAll(e).forEach((i,o)=>{i.style.animationDelay=`${o*n}ms`,i.classList.add("staggered-item")})}}function H(m){m._initA11y=function(){typeof document>"u"||(this.addDomListener(document,"keydown",t=>{if(t.key!=="Tab")return;document.querySelectorAll("[data-rt-a11y-focus-trap]").forEach(n=>{if(n.style.display==="none"||n.hasAttribute("aria-hidden")&&n.getAttribute("aria-hidden")==="true")return;let i=Array.from(n.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex="0"], [contenteditable]'));if(i.length===0)return;let o=i[0],u=i[i.length-1];t.shiftKey?document.activeElement===o&&(u.focus(),t.preventDefault()):document.activeElement===u&&(o.focus(),t.preventDefault())})}),this.addDomListener(document,"keydown",t=>{if(!["ArrowUp","ArrowDown","Enter"].includes(t.key))return;document.querySelectorAll("[data-rt-keynav]").forEach(n=>{let r=Array.from(n.children);if(r.length===0)return;let i=r.findIndex(o=>o.classList.contains("active")||document.activeElement===o);t.key==="ArrowDown"?(i=(i+1)%r.length,r[i].focus(),r.forEach((o,u)=>{u===i?o.classList.add("active"):o.classList.remove("active")}),t.preventDefault()):t.key==="ArrowUp"?(i=(i-1+r.length)%r.length,r[i].focus(),r.forEach((o,u)=>{u===i?o.classList.add("active"):o.classList.remove("active")}),t.preventDefault()):t.key==="Enter"&&i!==-1&&(r[i].click(),t.preventDefault())})}))},m.autoAriaModal=function(t,e){e?(t.setAttribute("role","dialog"),t.setAttribute("aria-modal","true"),t.setAttribute("aria-hidden","false"),t.focus()):t.setAttribute("aria-hidden","true")}}function j(m){m._initI18n=function(){if(this.i18n=this.i18n||{locale:"en",dicts:{}},typeof document>"u")return;if(document.querySelectorAll("[data-i18n-dict]").forEach(e=>{let n=e.getAttribute("data-i18n-dict");if(n)try{let r=JSON.parse(e.textContent||"{}");this.i18n.dicts[n]={...this.i18n.dicts[n]||{},...r}}catch(r){console.warn("[Dolphin i18n] Failed to parse dictionary for locale:",n,r)}}),!this.i18n.locale&&typeof navigator<"u"){let e=navigator.language.split("-")[0];this.i18n.dicts[e]&&(this.i18n.locale=e)}this.addDomListener(document,"click",e=>{let n=e.target.closest("[data-i18n-switch]");if(n){let r=n.getAttribute("data-i18n-switch");r&&this.setLocale(r)}}),this.translateDOM()},m.setLocale=function(t){this.i18n=this.i18n||{locale:"en",dicts:{}},this.i18n.locale=t,this.translateDOM(),this.publish("i18n/locale",t)},m.translateDOM=function(){if(typeof document>"u")return;this.i18n=this.i18n||{locale:"en",dicts:{}};let t=this.i18n.locale||"en",e=this.i18n.dicts[t]||{};document.querySelectorAll("[data-i18n-key]").forEach(r=>{let i=r.getAttribute("data-i18n-key");if(!i)return;let o=i.split(".").reduce((S,D)=>S?S[D]:null,e);o==null&&(o=i);let u=r.getAttribute("data-i18n-params");if(u)try{let S=JSON.parse(u),D=g=>g.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");for(let g in S){let c=D(g);o=o.replace(new RegExp(`\\{\\{${c}\\}\\}`,"g"),S[g])}}catch{}r.tagName==="INPUT"||r.tagName==="TEXTAREA"?r.placeholder=o:r.textContent=o})}}function B(m){m._initDragDrop=function(){typeof document>"u"||(this.addDomListener(document,"dragstart",t=>{let e=t.target.closest("[data-drag]");if(!e)return;let n=e.getAttribute("data-drag");n&&(t.dataTransfer.setData("text/plain",n),t.dataTransfer.effectAllowed="move",e.classList.add("dragging"))}),this.addDomListener(document,"dragend",t=>{let e=t.target.closest("[data-drag]");e&&e.classList.remove("dragging")}),this.addDomListener(document,"dragover",t=>{let e=t.target.closest("[data-drop]");e&&(t.preventDefault(),e.classList.add("drag-over"))}),this.addDomListener(document,"dragleave",t=>{let e=t.target.closest("[data-drop]");e&&e.classList.remove("drag-over")}),this.addDomListener(document,"drop",t=>{let e=t.target.closest("[data-drop]");if(!e)return;t.preventDefault(),e.classList.remove("drag-over");let n=e.getAttribute("data-drop"),r=t.dataTransfer.getData("text/plain");if(n&&r)try{let i=JSON.parse(r);this.publish(n,i)}catch{this.publish(n,{value:r})}}),this.addDomListener(document,"dragover",t=>{let e=t.target.closest("[data-sortable]");if(!e)return;t.preventDefault();let n=e.querySelector(".dragging");if(!n)return;let i=Array.from(e.querySelectorAll("[data-drag]:not(.dragging)")).find(o=>{let u=o.getBoundingClientRect();return t.clientY-u.top-u.height/2<0});i?e.insertBefore(n,i):e.appendChild(n)}),this.addDomListener(document,"drop",t=>{let e=t.target.closest("[data-sortable]");if(!e)return;let n=e.getAttribute("data-sortable");if(!n)return;let i=Array.from(e.querySelectorAll("[data-drag]")).map((o,u)=>{let S=o.getAttribute("data-drag");try{return{index:u,payload:JSON.parse(S||"{}")}}catch{return{index:u,payload:S}}});this.publish(n,i)}))}}function V(m){m._initCollab=function(){typeof document>"u"||(this.addDomListener(document,"mousemove",t=>{document.querySelectorAll("[data-rt-cursor-share]").forEach(n=>{let r=n.getAttribute("data-rt-cursor-share");if(!r)return;let i=n.getBoundingClientRect(),o=(t.clientX-i.left)/i.width,u=(t.clientY-i.top)/i.height,S=Date.now();(!n._lastSent||S-n._lastSent>50)&&(n._lastSent=S,this.pubPush(`collab/${r}/cursor/${this.deviceId}`,{deviceId:this.deviceId,x:o,y:u}))})}),this.addDomListener(document,"input",t=>{let e=t.target.getAttribute("data-rt-typing");if(!e)return;let n=e,r=i=>{this.pubPush(`collab/${n}/typing/${this.deviceId}`,{deviceId:this.deviceId,typing:i})};t.target._isTyping||(t.target._isTyping=!0,r(!0)),t.target._typingTimer&&clearTimeout(t.target._typingTimer),t.target._typingTimer=setTimeout(()=>{t.target._isTyping=!1,r(!1)},2e3)}),this.addDomListener(document,"input",t=>{let e=t.target.getAttribute("data-rt-crdt");if(!e)return;let n=e,r=t.target.value,i=Date.now();this.publish(`collab/${n}/crdt`,{deviceId:this.deviceId,value:r,timestamp:i,cursorPos:t.target.selectionStart})}),this.subscribe("collab/+/cursor/+",(t,e)=>{let n=e.split("/"),r=n[1],i=n[3];if(i===this.deviceId)return;let o=document.querySelector(`[data-rt-cursor-share="${r}"]`);if(!o)return;let u=o.querySelector(`.rt-cursor-${i}`);u||(u=document.createElement("div"),u.className=`rt-cursor rt-cursor-${i}`,u.style.position="absolute",u.style.width="10px",u.style.height="10px",u.style.borderRadius="50%",u.style.backgroundColor="#"+Math.floor(Math.random()*16777215).toString(16),u.style.pointerEvents="none",o.appendChild(u));let S=o.getBoundingClientRect();u.style.left=t.x*S.width+"px",u.style.top=t.y*S.height+"px"}),this.subscribe("collab/+/crdt",(t,e)=>{if(t.deviceId===this.deviceId)return;let r=e.split("/")[1];document.querySelectorAll(`[data-rt-crdt="${r}"]`).forEach(o=>{if(!o._lastUpdate||t.timestamp>o._lastUpdate){o._lastUpdate=t.timestamp;let u=o.selectionStart;o.value=t.value,document.activeElement===o&&o.setSelectionRange(u,u)}})}))}}function z(m){m.registerServiceWorker=async function(t="/sw.js"){if(typeof navigator>"u"||!("serviceWorker"in navigator))return console.warn("[Dolphin PWA] Service Workers are not supported in this browser."),null;try{let e=await navigator.serviceWorker.register(t);return console.log("[Dolphin PWA] Service Worker registered successfully with scope:",e.scope),e}catch(e){return console.error("[Dolphin PWA] Service Worker registration failed:",e),null}},m.subscribePushNotifications=async function(t){if(typeof window>"u"||!("serviceWorker"in navigator)||!("PushManager"in window))return console.warn("[Dolphin PWA] Push notifications are not supported in this browser."),null;try{let e=await navigator.serviceWorker.ready,n=await e.pushManager.getSubscription();if(!n){let r="=".repeat((4-t.length%4)%4),i=(t+r).replace(/\-/g,"+").replace(/_/g,"/"),o=window.atob(i),u=new Uint8Array(o.length);for(let S=0;S<o.length;++S)u[S]=o.charCodeAt(S);n=await e.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:u})}return console.log("[Dolphin PWA] Subscribed to push notifications:",n),n}catch(e){return console.error("[Dolphin PWA] Push notification subscription failed:",e),null}}}var q=class{static render(t){if(typeof document>"u")throw new Error("DolphinTestUtils.render requires a DOM document environment to execute.");let e=document.createElement("div");return e.innerHTML=t,document.body.appendChild(e),{container:e,find:n=>e.querySelector(n),fireEvent:(n,r)=>{let i=document.createEvent("Event");i.initEvent(r,!0,!0),n.dispatchEvent(i)}}}static mockWebSocket(){let t=[],e={readyState:1,send:n=>{t.push(n)},close:jest.fn(),onopen:jest.fn(),onmessage:jest.fn(),onclose:jest.fn(),onerror:jest.fn(),sentMessages:t};return global.WebSocket=class{static OPEN=1;readyState=e.readyState;send=e.send;close=e.close;set onopen(n){e.onopen=n}get onopen(){return e.onopen}set onmessage(n){e.onmessage=n}get onmessage(){return e.onmessage}set onclose(n){e.onclose=n}get getonclose(){return e.onclose}constructor(){setTimeout(()=>e.onopen&&e.onopen(),0)}},e}static simulateClick(t){let e={target:t,preventDefault:jest.fn(),stopPropagation:jest.fn()};(global.document._listeners?.click||[]).forEach(r=>r(e))}static simulateChange(t,e){t.value=e;let n={target:t,preventDefault:jest.fn(),stopPropagation:jest.fn()};(global.document._listeners?.change||[]).forEach(i=>i(n))}};function K(m){m.testing=q}O(k.prototype);U(k.prototype);W(k.prototype);F(k.prototype);H(k.prototype);j(k.prototype);B(k.prototype);V(k.prototype);z(k.prototype);K(k.prototype);typeof window<"u"&&(window.DolphinClient=k,document.addEventListener("DOMContentLoaded",()=>{if(!window.dolphin){let m=document.querySelector('script[src*="dolphin-client"]'),t=m?m.getAttribute("data-debug")==="true":!1,e=new k(void 0,void 0,{debug:t});window.dolphin=e,t&&(console.log("%c\u{1F42C} [Dolphin Client] Auto-initialized local reactive engine!","color: #06b6d4; font-weight: bold; font-size: 14px;"),console.log('%c\u{1F449} Tip: You can access the client instance via "window.dolphin" in console.',"color: #94a3b8; font-style: italic;")),document.querySelector('[data-store-write="app.username"]')&&e.setStoreState("app","username","\u0928\u092E\u0938\u094D\u0924\u0947 \u0938\u093E\u0925\u0940!")}}));return nt(it);})();
package/dist/index.cjs CHANGED
@@ -107,7 +107,7 @@ var APIHandler = class {
107
107
  */
108
108
  async requestDirect(method, path, body = null, options = {}) {
109
109
  const _isRetry = options._isRetry === true;
110
- const url = `${this.client.httpUrl}${path.startsWith("/") ? path : "/" + path}`;
110
+ const url = path.startsWith("http://") || path.startsWith("https://") ? path : `${this.client.httpUrl}${path.startsWith("/") ? path : "/" + path}`;
111
111
  if (this.client.options.debug) {
112
112
  console.log(`%c\u{1F680} [Dolphin API Request]:`, "color: #3b82f6; font-weight: bold;", method.toUpperCase(), path, body || "");
113
113
  }
@@ -894,8 +894,28 @@ function attachDOMBinding(clientProto) {
894
894
  }
895
895
  }
896
896
  `;
897
+ let safeContext = context;
898
+ if (typeof Proxy !== "undefined" && context !== null && typeof context === "object") {
899
+ safeContext = new Proxy(context, {
900
+ has(target, key) {
901
+ if (typeof key === "symbol") return false;
902
+ return true;
903
+ },
904
+ get(target, key) {
905
+ if (key === Symbol.unscopables) return void 0;
906
+ if (key in target) return target[key];
907
+ if (typeof globalThis !== "undefined" && key in globalThis) {
908
+ return globalThis[key];
909
+ }
910
+ if (typeof window !== "undefined" && key in window) {
911
+ return window[key];
912
+ }
913
+ return void 0;
914
+ }
915
+ });
916
+ }
897
917
  const fn = new Function("context", fnBody);
898
- return fn(context);
918
+ return fn(safeContext);
899
919
  } catch (e) {
900
920
  console.error("[Dolphin Template Compiler Error]:", e);
901
921
  let fallback = templateStr;
@@ -1047,6 +1067,9 @@ function attachDOMBinding(clientProto) {
1047
1067
  });
1048
1068
  }
1049
1069
  this.publish(`store/${storeName}`, store);
1070
+ if (typeof this._updateDOM === "function") {
1071
+ this._updateDOM(`store/${storeName}`, store);
1072
+ }
1050
1073
  };
1051
1074
  clientProto.getStoreState = function(storeName, key) {
1052
1075
  this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
@@ -1344,20 +1367,28 @@ function attachDOMBinding(clientProto) {
1344
1367
  if (!path) continue;
1345
1368
  try {
1346
1369
  const result = await this.api.get(path);
1370
+ const apiStore = el.getAttribute("data-api-store");
1371
+ if (apiStore) {
1372
+ const parts = apiStore.split(".");
1373
+ if (parts.length === 2) {
1374
+ this.setStoreState(parts[0], parts[1], result);
1375
+ }
1376
+ }
1347
1377
  const rtBind = el.getAttribute("data-rt-bind");
1348
- if (rtBind) {
1378
+ if (rtBind && !apiStore) {
1349
1379
  this._updateDOM(rtBind, result);
1350
- } else {
1380
+ } else if (!apiStore) {
1351
1381
  const template = resolveTemplate(el);
1352
1382
  if (template && typeof result === "object" && result !== null) {
1353
- if (Array.isArray(result)) {
1383
+ const processedResult = this._applyDeclarativeDirectives(el, result);
1384
+ if (Array.isArray(processedResult)) {
1354
1385
  let combinedHTML = "";
1355
- for (const item of result) {
1386
+ for (const item of processedResult) {
1356
1387
  combinedHTML += renderTemplate(template, item);
1357
1388
  }
1358
1389
  scheduleDOMUpdate(el, combinedHTML);
1359
1390
  } else {
1360
- scheduleDOMUpdate(el, renderTemplate(template, result));
1391
+ scheduleDOMUpdate(el, renderTemplate(template, processedResult));
1361
1392
  }
1362
1393
  } else {
1363
1394
  if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
@@ -1372,21 +1403,139 @@ function attachDOMBinding(clientProto) {
1372
1403
  }
1373
1404
  }
1374
1405
  };
1406
+ clientProto._applyDeclarativeDirectives = function(el, payload) {
1407
+ let processedPayload = payload;
1408
+ if (typeof payload === "object" && payload !== null) {
1409
+ const applyFilterSearchSort = (arr) => {
1410
+ let result = [...arr];
1411
+ const filterAttr = el.getAttribute("data-rt-filter");
1412
+ if (filterAttr) {
1413
+ const parts = filterAttr.split("==");
1414
+ if (parts.length === 2) {
1415
+ const itemProp = parts[0].trim();
1416
+ const storeExpr = parts[1].trim();
1417
+ let filterVal = void 0;
1418
+ const storeParts = storeExpr.split(".");
1419
+ if (storeParts.length === 2) {
1420
+ filterVal = this.getStoreState(storeParts[0], storeParts[1]);
1421
+ } else {
1422
+ filterVal = payload[storeExpr] !== void 0 ? payload[storeExpr] : this.getStoreState("app", storeExpr);
1423
+ }
1424
+ if (filterVal !== void 0 && filterVal !== null && filterVal !== "") {
1425
+ result = result.filter((item) => item[itemProp] === filterVal);
1426
+ }
1427
+ }
1428
+ }
1429
+ const searchAttr = el.getAttribute("data-rt-search");
1430
+ if (searchAttr) {
1431
+ const parts = searchAttr.split("==");
1432
+ if (parts.length === 2) {
1433
+ const itemProp = parts[0].trim();
1434
+ const storeExpr = parts[1].trim();
1435
+ let searchVal = void 0;
1436
+ const storeParts = storeExpr.split(".");
1437
+ if (storeParts.length === 2) {
1438
+ searchVal = this.getStoreState(storeParts[0], storeParts[1]);
1439
+ } else {
1440
+ searchVal = payload[storeExpr] !== void 0 ? payload[storeExpr] : this.getStoreState("app", storeExpr);
1441
+ }
1442
+ if (searchVal !== void 0 && searchVal !== null && searchVal !== "") {
1443
+ const query = String(searchVal).toLowerCase();
1444
+ result = result.filter((item) => {
1445
+ const val = item[itemProp];
1446
+ return val !== void 0 && val !== null && String(val).toLowerCase().includes(query);
1447
+ });
1448
+ }
1449
+ }
1450
+ }
1451
+ const sortAttr = el.getAttribute("data-rt-sort");
1452
+ if (sortAttr) {
1453
+ let sortByVal = void 0;
1454
+ const storeParts = sortAttr.split(".");
1455
+ if (storeParts.length === 2) {
1456
+ sortByVal = this.getStoreState(storeParts[0], storeParts[1]);
1457
+ } else {
1458
+ sortByVal = payload[sortAttr] !== void 0 ? payload[sortAttr] : this.getStoreState("app", sortAttr);
1459
+ }
1460
+ if (sortByVal && sortByVal !== "") {
1461
+ if (sortByVal === "popular") {
1462
+ result.sort((a, b) => {
1463
+ const rateA = a.rating?.rate || a.rate || 0;
1464
+ const rateB = b.rating?.rate || b.rate || 0;
1465
+ return rateB - rateA;
1466
+ });
1467
+ } else {
1468
+ let field = "";
1469
+ let direction = "asc";
1470
+ if (sortByVal.endsWith("-low") || sortByVal.endsWith("-asc")) {
1471
+ field = sortByVal.replace("-low", "").replace("-asc", "");
1472
+ direction = "asc";
1473
+ } else if (sortByVal.endsWith("-high") || sortByVal.endsWith("-desc")) {
1474
+ field = sortByVal.replace("-high", "").replace("-desc", "");
1475
+ direction = "desc";
1476
+ }
1477
+ if (field) {
1478
+ result.sort((a, b) => {
1479
+ const resolveVal = (obj, path) => {
1480
+ return path.split(".").reduce((acc, part) => acc && acc[part], obj);
1481
+ };
1482
+ let valA = resolveVal(a, field);
1483
+ let valB = resolveVal(b, field);
1484
+ if (valA === void 0) valA = a[field];
1485
+ if (valB === void 0) valB = b[field];
1486
+ if (typeof valA === "string" && typeof valB === "string") {
1487
+ return direction === "asc" ? valA.localeCompare(valB) : valB.localeCompare(valA);
1488
+ }
1489
+ const numA = Number(valA);
1490
+ const numB = Number(valB);
1491
+ if (!isNaN(numA) && !isNaN(numB)) {
1492
+ return direction === "asc" ? numA - numB : numB - numA;
1493
+ }
1494
+ return 0;
1495
+ });
1496
+ }
1497
+ }
1498
+ }
1499
+ }
1500
+ return result;
1501
+ };
1502
+ if (Array.isArray(payload)) {
1503
+ processedPayload = applyFilterSearchSort(payload);
1504
+ } else {
1505
+ let foundArrayKey = "";
1506
+ for (const key in payload) {
1507
+ if (Array.isArray(payload[key])) {
1508
+ foundArrayKey = key;
1509
+ break;
1510
+ }
1511
+ }
1512
+ if (foundArrayKey) {
1513
+ const processedArray = applyFilterSearchSort(payload[foundArrayKey]);
1514
+ processedPayload = {
1515
+ ...payload,
1516
+ [foundArrayKey]: processedArray
1517
+ };
1518
+ }
1519
+ }
1520
+ }
1521
+ return processedPayload;
1522
+ };
1375
1523
  clientProto._updateDOM = function(topic, payload) {
1376
1524
  if (typeof document === "undefined") return;
1377
1525
  const elements = document.querySelectorAll(`[data-rt-bind="${topic}"]`);
1378
1526
  elements.forEach((el) => {
1379
- if (el.getAttribute("data-rt-type") === "context" && typeof payload === "object" && payload !== null) {
1380
- el._rtContext = payload;
1527
+ const processedPayload = this._applyDeclarativeDirectives(el, payload);
1528
+ if (el.getAttribute("data-rt-type") === "context" && typeof processedPayload === "object" && processedPayload !== null) {
1529
+ el._rtContext = processedPayload;
1381
1530
  const processNode = (node) => {
1382
1531
  if (node.hasAttribute("data-rt-text")) {
1383
1532
  const key = node.getAttribute("data-rt-text");
1384
- if (key && payload[key] !== void 0 && payload[key] !== null) node.textContent = payload[key];
1533
+ if (key && processedPayload[key] !== void 0 && processedPayload[key] !== null) node.textContent = processedPayload[key];
1385
1534
  }
1386
1535
  if (node.hasAttribute("data-rt-html")) {
1387
1536
  const key = node.getAttribute("data-rt-html");
1388
- if (key && payload[key] !== void 0 && payload[key] !== null) {
1389
- node.innerHTML = sanitizeHTML(payload[key]);
1537
+ if (key && processedPayload[key] !== void 0 && processedPayload[key] !== null) {
1538
+ node.innerHTML = sanitizeHTML(processedPayload[key]);
1390
1539
  }
1391
1540
  }
1392
1541
  if (node.hasAttribute("data-rt-attr")) {
@@ -1397,8 +1546,8 @@ function attachDOMBinding(clientProto) {
1397
1546
  if (parts.length === 2) {
1398
1547
  const attrName = parts[0].trim();
1399
1548
  const key = parts[1].trim();
1400
- if (attrName && key && payload[key] !== void 0 && payload[key] !== null) {
1401
- node.setAttribute(attrName, payload[key]);
1549
+ if (attrName && key && processedPayload[key] !== void 0 && processedPayload[key] !== null) {
1550
+ node.setAttribute(attrName, processedPayload[key]);
1402
1551
  }
1403
1552
  }
1404
1553
  });
@@ -1412,10 +1561,11 @@ function attachDOMBinding(clientProto) {
1412
1561
  if (parts.length === 2) {
1413
1562
  const className = parts[0].trim();
1414
1563
  const key = parts[1].trim();
1415
- if (payload[key]) {
1416
- node.classList.add(className);
1564
+ const classNames = className.split(/\s+/).filter(Boolean);
1565
+ if (processedPayload[key]) {
1566
+ classNames.forEach((c) => node.classList.add(c));
1417
1567
  } else {
1418
- node.classList.remove(className);
1568
+ classNames.forEach((c) => node.classList.remove(c));
1419
1569
  }
1420
1570
  }
1421
1571
  });
@@ -1424,7 +1574,7 @@ function attachDOMBinding(clientProto) {
1424
1574
  if (node.hasAttribute("data-rt-if")) {
1425
1575
  const key = node.getAttribute("data-rt-if");
1426
1576
  if (key) {
1427
- if (payload[key]) {
1577
+ if (processedPayload[key]) {
1428
1578
  node.style.display = "";
1429
1579
  } else {
1430
1580
  node.style.display = "none";
@@ -1434,7 +1584,7 @@ function attachDOMBinding(clientProto) {
1434
1584
  if (node.hasAttribute("data-rt-hide")) {
1435
1585
  const key = node.getAttribute("data-rt-hide");
1436
1586
  if (key) {
1437
- if (payload[key]) {
1587
+ if (processedPayload[key]) {
1438
1588
  node.style.display = "none";
1439
1589
  } else {
1440
1590
  node.style.display = "";
@@ -1447,22 +1597,22 @@ function attachDOMBinding(clientProto) {
1447
1597
  return;
1448
1598
  }
1449
1599
  const template = resolveTemplate(el);
1450
- if (template && typeof payload === "object" && payload !== null) {
1451
- if (Array.isArray(payload)) {
1600
+ if (template && typeof processedPayload === "object" && processedPayload !== null) {
1601
+ if (Array.isArray(processedPayload)) {
1452
1602
  let combinedHTML = "";
1453
- for (const item of payload) {
1603
+ for (const item of processedPayload) {
1454
1604
  combinedHTML += renderTemplate(template, item);
1455
1605
  }
1456
1606
  scheduleDOMUpdate(el, combinedHTML);
1457
1607
  } else {
1458
- scheduleDOMUpdate(el, renderTemplate(template, payload));
1608
+ scheduleDOMUpdate(el, renderTemplate(template, processedPayload));
1459
1609
  }
1460
1610
  return;
1461
1611
  }
1462
1612
  if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
1463
- el.value = typeof payload === "object" ? payload.value !== void 0 ? payload.value : "" : payload;
1613
+ el.value = typeof processedPayload === "object" ? processedPayload.value !== void 0 ? processedPayload.value : "" : processedPayload;
1464
1614
  } else {
1465
- el.innerHTML = typeof payload === "object" ? payload.html || payload.text || JSON.stringify(payload) : payload;
1615
+ el.innerHTML = typeof processedPayload === "object" ? processedPayload.html || processedPayload.text || JSON.stringify(processedPayload) : processedPayload;
1466
1616
  }
1467
1617
  });
1468
1618
  };
package/dist/index.js CHANGED
@@ -82,7 +82,7 @@ var APIHandler = class {
82
82
  */
83
83
  async requestDirect(method, path, body = null, options = {}) {
84
84
  const _isRetry = options._isRetry === true;
85
- const url = `${this.client.httpUrl}${path.startsWith("/") ? path : "/" + path}`;
85
+ const url = path.startsWith("http://") || path.startsWith("https://") ? path : `${this.client.httpUrl}${path.startsWith("/") ? path : "/" + path}`;
86
86
  if (this.client.options.debug) {
87
87
  console.log(`%c\u{1F680} [Dolphin API Request]:`, "color: #3b82f6; font-weight: bold;", method.toUpperCase(), path, body || "");
88
88
  }
@@ -869,8 +869,28 @@ function attachDOMBinding(clientProto) {
869
869
  }
870
870
  }
871
871
  `;
872
+ let safeContext = context;
873
+ if (typeof Proxy !== "undefined" && context !== null && typeof context === "object") {
874
+ safeContext = new Proxy(context, {
875
+ has(target, key) {
876
+ if (typeof key === "symbol") return false;
877
+ return true;
878
+ },
879
+ get(target, key) {
880
+ if (key === Symbol.unscopables) return void 0;
881
+ if (key in target) return target[key];
882
+ if (typeof globalThis !== "undefined" && key in globalThis) {
883
+ return globalThis[key];
884
+ }
885
+ if (typeof window !== "undefined" && key in window) {
886
+ return window[key];
887
+ }
888
+ return void 0;
889
+ }
890
+ });
891
+ }
872
892
  const fn = new Function("context", fnBody);
873
- return fn(context);
893
+ return fn(safeContext);
874
894
  } catch (e) {
875
895
  console.error("[Dolphin Template Compiler Error]:", e);
876
896
  let fallback = templateStr;
@@ -1022,6 +1042,9 @@ function attachDOMBinding(clientProto) {
1022
1042
  });
1023
1043
  }
1024
1044
  this.publish(`store/${storeName}`, store);
1045
+ if (typeof this._updateDOM === "function") {
1046
+ this._updateDOM(`store/${storeName}`, store);
1047
+ }
1025
1048
  };
1026
1049
  clientProto.getStoreState = function(storeName, key) {
1027
1050
  this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
@@ -1319,20 +1342,28 @@ function attachDOMBinding(clientProto) {
1319
1342
  if (!path) continue;
1320
1343
  try {
1321
1344
  const result = await this.api.get(path);
1345
+ const apiStore = el.getAttribute("data-api-store");
1346
+ if (apiStore) {
1347
+ const parts = apiStore.split(".");
1348
+ if (parts.length === 2) {
1349
+ this.setStoreState(parts[0], parts[1], result);
1350
+ }
1351
+ }
1322
1352
  const rtBind = el.getAttribute("data-rt-bind");
1323
- if (rtBind) {
1353
+ if (rtBind && !apiStore) {
1324
1354
  this._updateDOM(rtBind, result);
1325
- } else {
1355
+ } else if (!apiStore) {
1326
1356
  const template = resolveTemplate(el);
1327
1357
  if (template && typeof result === "object" && result !== null) {
1328
- if (Array.isArray(result)) {
1358
+ const processedResult = this._applyDeclarativeDirectives(el, result);
1359
+ if (Array.isArray(processedResult)) {
1329
1360
  let combinedHTML = "";
1330
- for (const item of result) {
1361
+ for (const item of processedResult) {
1331
1362
  combinedHTML += renderTemplate(template, item);
1332
1363
  }
1333
1364
  scheduleDOMUpdate(el, combinedHTML);
1334
1365
  } else {
1335
- scheduleDOMUpdate(el, renderTemplate(template, result));
1366
+ scheduleDOMUpdate(el, renderTemplate(template, processedResult));
1336
1367
  }
1337
1368
  } else {
1338
1369
  if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
@@ -1347,21 +1378,139 @@ function attachDOMBinding(clientProto) {
1347
1378
  }
1348
1379
  }
1349
1380
  };
1381
+ clientProto._applyDeclarativeDirectives = function(el, payload) {
1382
+ let processedPayload = payload;
1383
+ if (typeof payload === "object" && payload !== null) {
1384
+ const applyFilterSearchSort = (arr) => {
1385
+ let result = [...arr];
1386
+ const filterAttr = el.getAttribute("data-rt-filter");
1387
+ if (filterAttr) {
1388
+ const parts = filterAttr.split("==");
1389
+ if (parts.length === 2) {
1390
+ const itemProp = parts[0].trim();
1391
+ const storeExpr = parts[1].trim();
1392
+ let filterVal = void 0;
1393
+ const storeParts = storeExpr.split(".");
1394
+ if (storeParts.length === 2) {
1395
+ filterVal = this.getStoreState(storeParts[0], storeParts[1]);
1396
+ } else {
1397
+ filterVal = payload[storeExpr] !== void 0 ? payload[storeExpr] : this.getStoreState("app", storeExpr);
1398
+ }
1399
+ if (filterVal !== void 0 && filterVal !== null && filterVal !== "") {
1400
+ result = result.filter((item) => item[itemProp] === filterVal);
1401
+ }
1402
+ }
1403
+ }
1404
+ const searchAttr = el.getAttribute("data-rt-search");
1405
+ if (searchAttr) {
1406
+ const parts = searchAttr.split("==");
1407
+ if (parts.length === 2) {
1408
+ const itemProp = parts[0].trim();
1409
+ const storeExpr = parts[1].trim();
1410
+ let searchVal = void 0;
1411
+ const storeParts = storeExpr.split(".");
1412
+ if (storeParts.length === 2) {
1413
+ searchVal = this.getStoreState(storeParts[0], storeParts[1]);
1414
+ } else {
1415
+ searchVal = payload[storeExpr] !== void 0 ? payload[storeExpr] : this.getStoreState("app", storeExpr);
1416
+ }
1417
+ if (searchVal !== void 0 && searchVal !== null && searchVal !== "") {
1418
+ const query = String(searchVal).toLowerCase();
1419
+ result = result.filter((item) => {
1420
+ const val = item[itemProp];
1421
+ return val !== void 0 && val !== null && String(val).toLowerCase().includes(query);
1422
+ });
1423
+ }
1424
+ }
1425
+ }
1426
+ const sortAttr = el.getAttribute("data-rt-sort");
1427
+ if (sortAttr) {
1428
+ let sortByVal = void 0;
1429
+ const storeParts = sortAttr.split(".");
1430
+ if (storeParts.length === 2) {
1431
+ sortByVal = this.getStoreState(storeParts[0], storeParts[1]);
1432
+ } else {
1433
+ sortByVal = payload[sortAttr] !== void 0 ? payload[sortAttr] : this.getStoreState("app", sortAttr);
1434
+ }
1435
+ if (sortByVal && sortByVal !== "") {
1436
+ if (sortByVal === "popular") {
1437
+ result.sort((a, b) => {
1438
+ const rateA = a.rating?.rate || a.rate || 0;
1439
+ const rateB = b.rating?.rate || b.rate || 0;
1440
+ return rateB - rateA;
1441
+ });
1442
+ } else {
1443
+ let field = "";
1444
+ let direction = "asc";
1445
+ if (sortByVal.endsWith("-low") || sortByVal.endsWith("-asc")) {
1446
+ field = sortByVal.replace("-low", "").replace("-asc", "");
1447
+ direction = "asc";
1448
+ } else if (sortByVal.endsWith("-high") || sortByVal.endsWith("-desc")) {
1449
+ field = sortByVal.replace("-high", "").replace("-desc", "");
1450
+ direction = "desc";
1451
+ }
1452
+ if (field) {
1453
+ result.sort((a, b) => {
1454
+ const resolveVal = (obj, path) => {
1455
+ return path.split(".").reduce((acc, part) => acc && acc[part], obj);
1456
+ };
1457
+ let valA = resolveVal(a, field);
1458
+ let valB = resolveVal(b, field);
1459
+ if (valA === void 0) valA = a[field];
1460
+ if (valB === void 0) valB = b[field];
1461
+ if (typeof valA === "string" && typeof valB === "string") {
1462
+ return direction === "asc" ? valA.localeCompare(valB) : valB.localeCompare(valA);
1463
+ }
1464
+ const numA = Number(valA);
1465
+ const numB = Number(valB);
1466
+ if (!isNaN(numA) && !isNaN(numB)) {
1467
+ return direction === "asc" ? numA - numB : numB - numA;
1468
+ }
1469
+ return 0;
1470
+ });
1471
+ }
1472
+ }
1473
+ }
1474
+ }
1475
+ return result;
1476
+ };
1477
+ if (Array.isArray(payload)) {
1478
+ processedPayload = applyFilterSearchSort(payload);
1479
+ } else {
1480
+ let foundArrayKey = "";
1481
+ for (const key in payload) {
1482
+ if (Array.isArray(payload[key])) {
1483
+ foundArrayKey = key;
1484
+ break;
1485
+ }
1486
+ }
1487
+ if (foundArrayKey) {
1488
+ const processedArray = applyFilterSearchSort(payload[foundArrayKey]);
1489
+ processedPayload = {
1490
+ ...payload,
1491
+ [foundArrayKey]: processedArray
1492
+ };
1493
+ }
1494
+ }
1495
+ }
1496
+ return processedPayload;
1497
+ };
1350
1498
  clientProto._updateDOM = function(topic, payload) {
1351
1499
  if (typeof document === "undefined") return;
1352
1500
  const elements = document.querySelectorAll(`[data-rt-bind="${topic}"]`);
1353
1501
  elements.forEach((el) => {
1354
- if (el.getAttribute("data-rt-type") === "context" && typeof payload === "object" && payload !== null) {
1355
- el._rtContext = payload;
1502
+ const processedPayload = this._applyDeclarativeDirectives(el, payload);
1503
+ if (el.getAttribute("data-rt-type") === "context" && typeof processedPayload === "object" && processedPayload !== null) {
1504
+ el._rtContext = processedPayload;
1356
1505
  const processNode = (node) => {
1357
1506
  if (node.hasAttribute("data-rt-text")) {
1358
1507
  const key = node.getAttribute("data-rt-text");
1359
- if (key && payload[key] !== void 0 && payload[key] !== null) node.textContent = payload[key];
1508
+ if (key && processedPayload[key] !== void 0 && processedPayload[key] !== null) node.textContent = processedPayload[key];
1360
1509
  }
1361
1510
  if (node.hasAttribute("data-rt-html")) {
1362
1511
  const key = node.getAttribute("data-rt-html");
1363
- if (key && payload[key] !== void 0 && payload[key] !== null) {
1364
- node.innerHTML = sanitizeHTML(payload[key]);
1512
+ if (key && processedPayload[key] !== void 0 && processedPayload[key] !== null) {
1513
+ node.innerHTML = sanitizeHTML(processedPayload[key]);
1365
1514
  }
1366
1515
  }
1367
1516
  if (node.hasAttribute("data-rt-attr")) {
@@ -1372,8 +1521,8 @@ function attachDOMBinding(clientProto) {
1372
1521
  if (parts.length === 2) {
1373
1522
  const attrName = parts[0].trim();
1374
1523
  const key = parts[1].trim();
1375
- if (attrName && key && payload[key] !== void 0 && payload[key] !== null) {
1376
- node.setAttribute(attrName, payload[key]);
1524
+ if (attrName && key && processedPayload[key] !== void 0 && processedPayload[key] !== null) {
1525
+ node.setAttribute(attrName, processedPayload[key]);
1377
1526
  }
1378
1527
  }
1379
1528
  });
@@ -1387,10 +1536,11 @@ function attachDOMBinding(clientProto) {
1387
1536
  if (parts.length === 2) {
1388
1537
  const className = parts[0].trim();
1389
1538
  const key = parts[1].trim();
1390
- if (payload[key]) {
1391
- node.classList.add(className);
1539
+ const classNames = className.split(/\s+/).filter(Boolean);
1540
+ if (processedPayload[key]) {
1541
+ classNames.forEach((c) => node.classList.add(c));
1392
1542
  } else {
1393
- node.classList.remove(className);
1543
+ classNames.forEach((c) => node.classList.remove(c));
1394
1544
  }
1395
1545
  }
1396
1546
  });
@@ -1399,7 +1549,7 @@ function attachDOMBinding(clientProto) {
1399
1549
  if (node.hasAttribute("data-rt-if")) {
1400
1550
  const key = node.getAttribute("data-rt-if");
1401
1551
  if (key) {
1402
- if (payload[key]) {
1552
+ if (processedPayload[key]) {
1403
1553
  node.style.display = "";
1404
1554
  } else {
1405
1555
  node.style.display = "none";
@@ -1409,7 +1559,7 @@ function attachDOMBinding(clientProto) {
1409
1559
  if (node.hasAttribute("data-rt-hide")) {
1410
1560
  const key = node.getAttribute("data-rt-hide");
1411
1561
  if (key) {
1412
- if (payload[key]) {
1562
+ if (processedPayload[key]) {
1413
1563
  node.style.display = "none";
1414
1564
  } else {
1415
1565
  node.style.display = "";
@@ -1422,22 +1572,22 @@ function attachDOMBinding(clientProto) {
1422
1572
  return;
1423
1573
  }
1424
1574
  const template = resolveTemplate(el);
1425
- if (template && typeof payload === "object" && payload !== null) {
1426
- if (Array.isArray(payload)) {
1575
+ if (template && typeof processedPayload === "object" && processedPayload !== null) {
1576
+ if (Array.isArray(processedPayload)) {
1427
1577
  let combinedHTML = "";
1428
- for (const item of payload) {
1578
+ for (const item of processedPayload) {
1429
1579
  combinedHTML += renderTemplate(template, item);
1430
1580
  }
1431
1581
  scheduleDOMUpdate(el, combinedHTML);
1432
1582
  } else {
1433
- scheduleDOMUpdate(el, renderTemplate(template, payload));
1583
+ scheduleDOMUpdate(el, renderTemplate(template, processedPayload));
1434
1584
  }
1435
1585
  return;
1436
1586
  }
1437
1587
  if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
1438
- el.value = typeof payload === "object" ? payload.value !== void 0 ? payload.value : "" : payload;
1588
+ el.value = typeof processedPayload === "object" ? processedPayload.value !== void 0 ? processedPayload.value : "" : processedPayload;
1439
1589
  } else {
1440
- el.innerHTML = typeof payload === "object" ? payload.html || payload.text || JSON.stringify(payload) : payload;
1590
+ el.innerHTML = typeof processedPayload === "object" ? processedPayload.html || processedPayload.text || JSON.stringify(processedPayload) : processedPayload;
1441
1591
  }
1442
1592
  });
1443
1593
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dolphin-client",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
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",