dolphin-client 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/dolphin-client.js +37 -6
- package/dist/dolphin-client.min.js +14 -14
- package/dist/index.cjs +37 -6
- package/dist/index.js +37 -6
- package/fulltutorial.md +734 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ By breathing life back into standard HTML, we have resurrected the simplicity of
|
|
|
23
23
|
## Documentation
|
|
24
24
|
|
|
25
25
|
> [!TIP]
|
|
26
|
-
> 📖 Read our comprehensive **[Full Developer Tutorial & Integration Guide](
|
|
26
|
+
> 📖 Read our comprehensive **[Full Developer Tutorial & Integration Guide](https://github.com/Phuyalshankar/dolphin-client/blob/main/fulltutorial.md)** for detailed guides, Next.js setups, WebRTC intercoms, global stores, drag-and-drop sortable lists, and real-world examples!
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
@@ -70,7 +70,7 @@ Tired of the command line and `node_modules` clutter? We've got you covered!
|
|
|
70
70
|
> [!TIP]
|
|
71
71
|
> **If the ZIP file download is unavailable:**
|
|
72
72
|
> You can clone this repository directly or copy the `dist/dolphin-client.js` (or `dist/dolphin-client.min.js`) file from your clone.
|
|
73
|
-
> For the premium styling layer, create a `dolphin-css.css` file and add the custom effects (like `.fx-glass` and `.fx-neon`) described in the **[Full Developer Tutorial](
|
|
73
|
+
> For the premium styling layer, create a `dolphin-css.css` file and add the custom effects (like `.fx-glass` and `.fx-neon`) described in the **[Full Developer Tutorial](https://github.com/Phuyalshankar/dolphin-client/blob/main/fulltutorial.md)**.
|
|
74
74
|
|
|
75
75
|
Extract the zip directly inside your project folder to get a clean local directory structure with pre-bundled assets:
|
|
76
76
|
```
|
package/dist/dolphin-client.js
CHANGED
|
@@ -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(
|
|
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,10 +1367,17 @@ 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
1383
|
if (Array.isArray(result)) {
|
|
@@ -1412,10 +1442,11 @@ var DolphinModule = (() => {
|
|
|
1412
1442
|
if (parts.length === 2) {
|
|
1413
1443
|
const className = parts[0].trim();
|
|
1414
1444
|
const key = parts[1].trim();
|
|
1445
|
+
const classNames = className.split(/\s+/).filter(Boolean);
|
|
1415
1446
|
if (payload[key]) {
|
|
1416
|
-
node.classList.add(
|
|
1447
|
+
classNames.forEach((c) => node.classList.add(c));
|
|
1417
1448
|
} else {
|
|
1418
|
-
node.classList.remove(
|
|
1449
|
+
classNames.forEach((c) => node.classList.remove(c));
|
|
1419
1450
|
}
|
|
1420
1451
|
}
|
|
1421
1452
|
});
|
|
@@ -1,18 +1,18 @@
|
|
|
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,
|
|
3
|
-
`);let
|
|
4
|
-
`}else if(
|
|
5
|
-
`}else if(
|
|
6
|
-
`}else if(
|
|
7
|
-
`;else if(
|
|
8
|
-
`;else if(
|
|
9
|
-
`,
|
|
10
|
-
`),s+=` for (let ${
|
|
11
|
-
`}else if(
|
|
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:()=>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,l)=>typeof i=="string"?this.request("POST",i,o,l):this.request("POST",e,i,o),n.put=(i,o,l)=>typeof i=="string"?this.request("PUT",i,o,l):this.request("PUT",e,i,o),n.patch=(i,o,l)=>typeof i=="string"?this.request("PATCH",i,o,l):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,l,w)=>{let T=o?`${e}/${o.startsWith("/")?o.slice(1):o}`:e;return this.request(i,T,l,w)},n.requestDirect=(i,o,l,w)=>this.requestDirect(i,o,l,w);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 l=await this.requestDirect(t,e,n,r);return await this.client.offline.setCache(o,l),l}catch(l){let w=await this.client.offline.getCache(o);if(w!=null)return w;throw l}else{let l=await this.client.offline.getCache(o);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,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 l=new AbortController,w=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 a=await fetch(o,{method:t,headers:T,signal:l.signal,...n?{body:JSON.stringify(n)}:{},...h});if(clearTimeout(w),a.status===401&&!i&&this.client.options.autoRefreshToken&&await this.client.auth._silentRefresh())return this.request(t,e,n,{...r,_isRetry:!0});let s=(a.headers.get("content-type")||"").includes("application/json")?await a.json():await a.text();if(!a.ok)throw{status:a.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(a){throw clearTimeout(w),this.client.options.debug&&console.error("%c\u274C [Dolphin API Error]:","color: #ef4444; font-weight: bold;",t.toUpperCase(),e,a),a.name==="AbortError"?{status:408,data:{error:"Request timed out"}}:a}}};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(l=>l.id===i.id||l._id===i._id?{...l,...i}:l):r==="delete"&&(o=o.filter(l=>!(i.id!=null&&l.id===i.id||i._id!=null&&l._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),l=this.options.chunkSize,w=Math.ceil(o.length/l);this._sendRaw({type:"FILE_UPLOAD_START",fileId:t,name:n,size:o.length,totalChunks:w,chunkSize:l});for(let T=0;T<w;T++){let h=o.slice(T*l,(T+1)*l),a=this._uint8ToBase64(h);this._sendRaw({type:"FILE_UPLOAD_CHUNK",fileId:t,chunkIndex:T,totalChunks:w,data:a}),r&&r(Math.round((T+1)/w*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 a=h.getAttribute("data-rt-template");if(!a)return null;if(typeof document<"u"&&!a.includes("<"))try{let u=document.querySelector(a);if(u)return u.innerHTML}catch{}return a}function n(h,a){if(!h.includes("{#if")&&!h.includes("{#each")){let u=h;for(let s in a){let c=s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");u=u.replace(new RegExp(`\\{\\{${c}\\}\\}`,"g"),a[s]!==void 0&&a[s]!==null?a[s]:"")}return u}try{let u=b=>b.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t"),s=`let out = "";
|
|
2
|
+
`,c=0,g=/(\{\{([\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,d=[],f;for(;(f=g.exec(h))!==null;){let b=h.slice(c,f.index);b&&(s+=`out += "${u(b)}";
|
|
3
|
+
`);let A=f[0];if(A.startsWith("{{")){let E=f[2];s+=`out += (${E} !== undefined && ${E} !== null ? ${E} : "");
|
|
4
|
+
`}else if(A.startsWith("{#if")){let E=f[3];s+=`if (${E}) {
|
|
5
|
+
`}else if(A.startsWith("{:else if")){let E=f[4];s+=`} else if (${E}) {
|
|
6
|
+
`}else if(A.startsWith("{:else}"))s+=`} else {
|
|
7
|
+
`;else if(A.startsWith("{/if}"))s+=`}
|
|
8
|
+
`;else if(A.startsWith("{#each")){let E=f[5],v=f[6],D=f[7];d.push({indexVar:D}),s+=`if (typeof ${E} !== "undefined" && ${E} !== null && Array.isArray(${E})) {
|
|
9
|
+
`,D&&(s+=` let ${D} = 0;
|
|
10
|
+
`),s+=` for (let ${v} of ${E}) {
|
|
11
|
+
`}else if(A.startsWith("{/each}")){let E=d.pop();E&&E.indexVar&&(s+=` ${E.indexVar}++;
|
|
12
12
|
`),s+=` }
|
|
13
13
|
}
|
|
14
|
-
`}else if(
|
|
15
|
-
`)}c=
|
|
14
|
+
`}else if(A.startsWith("{")){let E=f[8];E&&(s+=`out += (${E} !== undefined && ${E} !== null ? ${E} : "");
|
|
15
|
+
`)}c=g.lastIndex}let m=h.slice(c);m&&(s+=`out += "${u(m)}";
|
|
16
16
|
`),s+=`return out;
|
|
17
17
|
`;let _=`
|
|
18
18
|
with (context) {
|
|
@@ -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
|
+
`,S=a;return typeof Proxy<"u"&&a!==null&&typeof a=="object"&&(S=new Proxy(a,{has(b,A){return typeof A!="symbol"},get(b,A){if(A!==Symbol.unscopables){if(A in b)return b[A];if(typeof globalThis<"u"&&A in globalThis)return globalThis[A];if(typeof window<"u"&&A in window)return window[A]}}})),new Function("context",_)(S)}catch(u){console.error("[Dolphin Template Compiler Error]:",u);let s=h;for(let c in a){let g=c.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");s=s.replace(new RegExp(`\\{\\{${g}\\}\\}`,"g"),a[c]!==void 0&&a[c]!==null?a[c]:"")}return s}}function r(h){if(typeof document>"u")return h;try{let s=new DOMParser().parseFromString(h,"text/html").body,c=g=>{let d=g.tagName.toLowerCase();if(["script","iframe","object","embed","link","style","meta","applet","svg"].includes(d)){g.parentNode?.removeChild(g);return}let f=g.attributes;for(let m=f.length-1;m>=0;m--){let _=f[m].name.toLowerCase(),S=f[m].value.toLowerCase();(_.startsWith("on")||["src","href","data"].includes(_)&&(S.includes("javascript:")||S.includes("data:text/html")))&&g.removeAttribute(f[m].name)}Array.from(g.children).forEach(c)};return Array.from(s.children).forEach(c),s.innerHTML}catch{return h}}function i(h,a){if(h.nodeType!==a.nodeType){h.parentNode?.replaceChild(a.cloneNode(!0),h);return}if(h.nodeType===Node.TEXT_NODE){h.textContent!==a.textContent&&(h.textContent=a.textContent);return}if(h.nodeType===Node.ELEMENT_NODE){let u=h,s=a;if(u.tagName!==s.tagName){u.parentNode?.replaceChild(s.cloneNode(!0),u);return}let c=u.attributes,g=s.attributes;for(let y=c.length-1;y>=0;y--){let b=c[y].name;s.hasAttribute(b)||u.removeAttribute(b)}for(let y=0;y<g.length;y++){let b=g[y].name,A=g[y].value;u.getAttribute(b)!==A&&u.setAttribute(b,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 d=Array.from(u.childNodes),f=Array.from(s.childNodes),m=d.length,_=f.length,S=Math.max(m,_);for(let y=0;y<S;y++)y>=m?u.appendChild(f[y].cloneNode(!0)):y>=_?u.removeChild(d[y]):i(d[y],f[y])}}function o(h,a){if(typeof document>"u")return;let u=document.createElement(h.tagName);u.innerHTML=a;let s=Array.from(h.childNodes),c=Array.from(u.childNodes),g=s.length,d=c.length,f=Math.max(g,d);for(let m=0;m<f;m++)m>=g?h.appendChild(c[m].cloneNode(!0)):m>=d?h.removeChild(s[m]):i(s[m],c[m])}let l=new Map,w=!1;function T(h,a){l.set(h,a),w||(w=!0,(typeof requestAnimationFrame<"u"?requestAnimationFrame:s=>setTimeout(s,0))(()=>{l.forEach((s,c)=>{o(c,s)}),l.clear(),w=!1}))}p.setStoreState=function(h,a,u){this.uiStores=this.uiStores||new Map,this.uiStores.has(h)||this.uiStores.set(h,{});let s=this.uiStores.get(h);s[a]=u,this.options.debug&&console.log("%c\u{1F4BE} [Dolphin Store Update]:","color: #ec4899; font-weight: bold;",`${h}.${a}`,"=",u),typeof document<"u"&&document.querySelectorAll(`[data-store-read="${h}.${a}"]`).forEach(g=>{g.tagName==="INPUT"||g.tagName==="TEXTAREA"?g.type==="checkbox"?g.checked=!!u:g.value=u??"":g.textContent=u??""}),this.publish(`store/${h}`,s),typeof this._updateDOM=="function"&&this._updateDOM(`store/${h}`,s)},p.getStoreState=function(h,a){this.uiStores=this.uiStores||new Map;let u=this.uiStores.get(h);return u?u[a]: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 g=c[0],d=c[1],f=u.type==="checkbox"?u.checked:u.value;this.uiStores=this.uiStores||new Map,this.uiStores.has(g)||this.uiStores.set(g,{});let m=this.uiStores.get(g);m[d]===void 0&&(m[d]=f)}}}),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 g=c[0],d=c[1],f=this.getStoreState(g,d);f!=null&&(u.tagName==="INPUT"||u.tagName==="TEXTAREA"?u.type==="checkbox"?u.checked=!!f:u.value=f:u.textContent=f)}}})},p.getClosestContext=function(h,a){let u=h;for(;u;){if(u._rtContext){let s=u._rtContext;return a?s[a]:s}u=u.parentElement}return null},p._executeStoreAction=function(h,a){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:(g,d)=>{if(typeof d=="string")return this.getStoreState(c,d)},set:(g,d,f)=>typeof d=="string"?(this.setStoreState(c,d,f),!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),a&&console.error("%cFailed Element:","color: #f97316; font-weight: bold;",a),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"],a=new Map;h.forEach(s=>{this.addDomListener(document,s,c=>{if(!c.target||!c.target.getAttribute)return;let g=c.target.getAttribute("data-store-write");if(g){let _=g.split(".");if(_.length===2){let S=_[0],y=_[1],b=c.target.type==="checkbox"?c.target.checked:c.target.value;this.setStoreState(S,y,b)}}let d=c.target.getAttribute("data-rt-validate"),f=c.target.name;if(d&&f&&typeof this.validateField=="function"){let _=c.target.closest("form"),S=_?Object.fromEntries(new FormData(_).entries()):{},y=this.validateField(c.target.value,d,S);y?(c.target.classList.add("invalid"),this.publish(`errors/${f}`,y)):(c.target.classList.remove("invalid"),this.publish(`errors/${f}`,""))}let m=c.target.getAttribute("data-rt-push");if(m){let _=c.target.getAttribute("data-rt-debounce"),S=_?parseInt(_,10):0,y=()=>{let b={name:c.target.name,value:c.target.value};this.pubPush(m,b)};if(S>0){a.has(c.target)&&clearTimeout(a.get(c.target));let b=setTimeout(y,S);a.set(c.target,b)}else y()}})}),this.addDomListener(document,"submit",async s=>{if(!s.target||!s.target.getAttribute)return;let c=s.target.getAttribute("data-rt-submit"),g=s.target.getAttribute("data-api-submit");if(c||g){let d=s.target.querySelectorAll("[data-rt-validate]"),f=!0;if(d.length>0&&typeof this.validateField=="function"){let y=Object.fromEntries(new FormData(s.target).entries());d.forEach(b=>{let A=b.getAttribute("data-rt-validate"),E=b.name;if(A&&E){let v=this.validateField(b.value,A,y);v?(f=!1,b.classList.add("invalid"),this.publish(`errors/${E}`,v)):(b.classList.remove("invalid"),this.publish(`errors/${E}`,""))}})}if(!f){s.preventDefault(),s.stopPropagation();return}s.preventDefault();let m=this.getClosestContext(s.target)||{},_=new FormData(s.target),S=Object.fromEntries(_.entries());if(c){let y=c;for(let b in m){let A=t(b);y=y.replace(new RegExp(`\\{\\{${A}\\}\\}`,"g"),m[b]!==void 0&&m[b]!==null?m[b]:"")}this.publish(y,S)}else if(g){let y=g;for(let v in m){let D=t(v);y=y.replace(new RegExp(`\\{\\{${D}\\}\\}`,"g"),m[v]!==void 0&&m[v]!==null?m[v]:"")}let b=y.trim().split(" "),A=b.length>1?b[0].toUpperCase():"POST",E=b.length>1?b[1]:b[0];try{let v=await this.api.request(A,E,S),D=s.target.getAttribute("data-api-result");D&&this._updateDOM(D,v);let L=s.target.getAttribute("data-api-redirect");L&&(window.location.href=L),s.target.hasAttribute("data-api-reload")&&window.location.reload()}catch(v){console.error("[Dolphin] API Submit Error:",v)}}}}),["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 g=c.target.closest(`[data-rt-${s}]`),d=c.target.closest(`[data-api-${s}]`);if(g){s==="submit"&&c.preventDefault();let m=g.getAttribute(`data-rt-${s}`),_=g.getAttribute("data-rt-payload"),S=this.getClosestContext(g)||{},y={};if(_){let b=_;for(let A in S){let E=t(A);b=b.replace(new RegExp(`\\{\\{${E}\\}\\}`,"g"),S[A]!==void 0&&S[A]!==null?S[A]:"")}try{y=JSON.parse(b)}catch{y={}}}this.publish(m,y)}if(d){s==="submit"&&c.preventDefault();let m=d.getAttribute(`data-api-${s}`),_=d.getAttribute("data-api-payload"),S=this.getClosestContext(d)||{},y=m.trim().split(" "),b=y.length>1?y[0].toUpperCase():"POST",A=y.length>1?y[1]:y[0],E=null;if(_){let v=_;for(let D in S){let L=t(D);v=v.replace(new RegExp(`\\{\\{${L}\\}\\}`,"g"),S[D]!==void 0&&S[D]!==null?S[D]:"")}try{E=JSON.parse(v)}catch{E=null}}try{let v=await this.api.request(b,A,E),D=d.getAttribute("data-api-result");D&&this._updateDOM(D,v);let L=d.getAttribute("data-api-redirect");L&&(window.location.href=L),d.hasAttribute("data-api-reload")&&window.location.reload()}catch(v){console.error(`[Dolphin] API ${s} Error:`,v)}}let f=c.target.closest(`[data-store-${s}]`);if(f){s==="submit"&&c.preventDefault();let m=f.getAttribute(`data-store-${s}`);m&&this._executeStoreAction(m,f)}})}),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 a of Array.from(h)){let u=a.getAttribute("data-api-get");if(u)try{let s=await this.api.get(u),c=a.getAttribute("data-api-store");if(c){let d=c.split(".");d.length===2&&this.setStoreState(d[0],d[1],s)}let g=a.getAttribute("data-rt-bind");if(g&&!c)this._updateDOM(g,s);else if(!c){let d=e(a);if(d&&typeof s=="object"&&s!==null)if(Array.isArray(s)){let f="";for(let m of s)f+=n(d,m);T(a,f)}else T(a,n(d,s));else a.tagName==="INPUT"||a.tagName==="TEXTAREA"?a.value=typeof s=="object"?s.value!==void 0?s.value:"":s:a.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,a){if(typeof document>"u")return;document.querySelectorAll(`[data-rt-bind="${h}"]`).forEach(s=>{if(s.getAttribute("data-rt-type")==="context"&&typeof a=="object"&&a!==null){s._rtContext=a;let g=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(m=>{let _=m.split(":");if(_.length===2){let S=_[0].trim(),y=_[1].trim();S&&y&&a[y]!==void 0&&a[y]!==null&&d.setAttribute(S,a[y])}})}if(d.hasAttribute("data-rt-class")){let f=d.getAttribute("data-rt-class");f&&f.split(",").forEach(m=>{let _=m.split(":");if(_.length===2){let S=_[0].trim(),y=_[1].trim(),b=S.split(/\s+/).filter(Boolean);a[y]?b.forEach(A=>d.classList.add(A)):b.forEach(A=>d.classList.remove(A))}})}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="")}};g(s),s.querySelectorAll("[data-rt-text], [data-rt-html], [data-rt-attr], [data-rt-class], [data-rt-if], [data-rt-hide]").forEach(g);return}let c=e(s);if(c&&typeof a=="object"&&a!==null){if(Array.isArray(a)){let g="";for(let d of a)g+=n(c,d);T(s,g)}else T(s,n(c,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 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 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 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(":"),o=i[0],l=i[1];if(o==="required"){if(!p||p.trim()==="")return"This field is required"}else if(o==="email"){if(p&&p.trim()!==""&&!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(p))return"Please enter a valid email address"}else if(o==="min"){let w=parseInt(l,10);if(!p||p.length<w)return`Must be at least ${w} characters`}else if(o==="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,o)=>{i.style.animationDelay=`${o*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 o=i[0],l=i[i.length-1];t.shiftKey?document.activeElement===o&&(l.focus(),t.preventDefault()):document.activeElement===l&&(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,l)=>{l===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,l)=>{l===i?o.classList.add("active"):o.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 o=i.split(".").reduce((w,T)=>w?w[T]:null,e);o==null&&(o=i);let l=r.getAttribute("data-i18n-params");if(l)try{let w=JSON.parse(l),T=h=>h.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");for(let h in w){let a=T(h);o=o.replace(new RegExp(`\\{\\{${a}\\}\\}`,"g"),w[h])}}catch{}r.tagName==="INPUT"||r.tagName==="TEXTAREA"?r.placeholder=o:r.textContent=o})}}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(o=>{let l=o.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((o,l)=>{let w=o.getAttribute("data-drag");try{return{index:l,payload:JSON.parse(w||"{}")}}catch{return{index:l,payload:w}}});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(),o=(t.clientX-i.left)/i.width,l=(t.clientY-i.top)/i.height,w=Date.now();(!n._lastSent||w-n._lastSent>50)&&(n._lastSent=w,this.pubPush(`collab/${r}/cursor/${this.deviceId}`,{deviceId:this.deviceId,x:o,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 o=document.querySelector(`[data-rt-cursor-share="${r}"]`);if(!o)return;let l=o.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",o.appendChild(l));let w=o.getBoundingClientRect();l.style.left=t.x*w.width+"px",l.style.top=t.y*w.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 l=o.selectionStart;o.value=t.value,document.activeElement===o&&o.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,"/"),o=window.atob(i),l=new Uint8Array(o.length);for(let w=0;w<o.length;++w)l[w]=o.charCodeAt(w);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(k.prototype);q(k.prototype);O(k.prototype);N(k.prototype);U(k.prototype);F(k.prototype);H(k.prototype);W(k.prototype);j(k.prototype);B(k.prototype);typeof window<"u"&&(window.DolphinClient=k,document.addEventListener("DOMContentLoaded",()=>{if(!window.dolphin){let p=document.querySelector('script[src*="dolphin-client"]'),t=p?p.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 X(Z);})();
|
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(
|
|
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,10 +1367,17 @@ 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
1383
|
if (Array.isArray(result)) {
|
|
@@ -1412,10 +1442,11 @@ function attachDOMBinding(clientProto) {
|
|
|
1412
1442
|
if (parts.length === 2) {
|
|
1413
1443
|
const className = parts[0].trim();
|
|
1414
1444
|
const key = parts[1].trim();
|
|
1445
|
+
const classNames = className.split(/\s+/).filter(Boolean);
|
|
1415
1446
|
if (payload[key]) {
|
|
1416
|
-
node.classList.add(
|
|
1447
|
+
classNames.forEach((c) => node.classList.add(c));
|
|
1417
1448
|
} else {
|
|
1418
|
-
node.classList.remove(
|
|
1449
|
+
classNames.forEach((c) => node.classList.remove(c));
|
|
1419
1450
|
}
|
|
1420
1451
|
}
|
|
1421
1452
|
});
|
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(
|
|
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,10 +1342,17 @@ 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
1358
|
if (Array.isArray(result)) {
|
|
@@ -1387,10 +1417,11 @@ function attachDOMBinding(clientProto) {
|
|
|
1387
1417
|
if (parts.length === 2) {
|
|
1388
1418
|
const className = parts[0].trim();
|
|
1389
1419
|
const key = parts[1].trim();
|
|
1420
|
+
const classNames = className.split(/\s+/).filter(Boolean);
|
|
1390
1421
|
if (payload[key]) {
|
|
1391
|
-
node.classList.add(
|
|
1422
|
+
classNames.forEach((c) => node.classList.add(c));
|
|
1392
1423
|
} else {
|
|
1393
|
-
node.classList.remove(
|
|
1424
|
+
classNames.forEach((c) => node.classList.remove(c));
|
|
1394
1425
|
}
|
|
1395
1426
|
}
|
|
1396
1427
|
});
|
package/fulltutorial.md
ADDED
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
# Dolphin Client 🐬 - Full Developer Tutorial & Integration Guide
|
|
2
|
+
|
|
3
|
+
Welcome to the comprehensive guide for **Dolphin Client**, the ultimate hookless, framework-agnostic real-time reactive DOM-binding library.
|
|
4
|
+
|
|
5
|
+
This tutorial will teach you how to build modern, lightweight, real-time web applications (including WebSockets, REST APIs, and WebRTC Intercom Calling) using **pure HTML, CSS, and Dolphin Client—with ZERO lines of React/Vue/Angular state management code!**
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## १. Introduction & Hookless Architecture (परिचय र वास्तुकला)
|
|
10
|
+
|
|
11
|
+
In traditional frontend development (React, Next.js, Vue), syncing real-time data with the UI requires heavy state management hooks (`useState`, `useEffect`, `useSyncExternalStore`), virtual DOM diffing, and complex component lifecycles.
|
|
12
|
+
|
|
13
|
+
**Dolphin Client** completely eliminates this framework fatigue through **Direct DOM Reactivity**:
|
|
14
|
+
- **HTML-First Declarative Bindings**: Bind real-time data topics to HTML elements using standard `data-rt-*` and `data-api-*` attributes.
|
|
15
|
+
- **Micro-Templates**: Write dynamic HTML lists directly inside `data-rt-template` attributes.
|
|
16
|
+
- **DOM-Context Drilling (`getClosestContext`)**: Instead of passing props down manually (prop drilling), child elements dynamically crawl *up* the DOM tree to extract parent data.
|
|
17
|
+
- **Framework Agnostic**: Works perfectly in Next.js Server Components, static HTML files, WordPress, jQuery, or any legacy web portal.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## २. Installation (इन्स्टलेसन)
|
|
22
|
+
|
|
23
|
+
Install the standalone package via NPM:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install dolphin-client
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Script Tag Integration (Browser-Native IIFE)
|
|
30
|
+
For static websites or plain HTML files, copy `node_modules/dolphin-client/dist/dolphin-client.js` and load it via a script tag:
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<script src="dist/dolphin-client.js"></script>
|
|
34
|
+
<script>
|
|
35
|
+
// Access through the global DolphinModule namespace
|
|
36
|
+
const dolphin = new DolphinModule.DolphinClient('http://localhost:3000', 'ROOM_101');
|
|
37
|
+
dolphin.connect();
|
|
38
|
+
</script>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## ३. Core HTML Directives Guide (HTML डाइरेक्टिभ्स निर्देशिका)
|
|
44
|
+
|
|
45
|
+
Dolphin Client automatically scans your HTML document and binds interactive listeners and real-time streams based on custom attributes.
|
|
46
|
+
|
|
47
|
+
### ३.१. Values & Event Pushes (`data-rt-push`)
|
|
48
|
+
Pushes the inputs of form elements to a WebSocket topic. It automatically registers unified listeners on `['input', 'change', 'keyup', 'paste', 'blur']` to ensure robust value sync across all interactions (typing, select dropdown changes, copying/pasting).
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<!-- Automatically publishes input values in real-time to the chat topic -->
|
|
52
|
+
<input name="chat" data-rt-push="chat/messages/ROOM_101" placeholder="Type a message..." />
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### ३.२. Unified Interaction Bindings (`data-rt-[event]` and `data-api-[event]`)
|
|
56
|
+
Allows triggering realtime publishes or HTTP API requests on **any** standard browser event (`click`, `change`, `keydown`, `keyup`, `dblclick`, `focus`, `blur`, `mouseenter`, `mouseleave`).
|
|
57
|
+
|
|
58
|
+
```html
|
|
59
|
+
<!-- Publishes payload on double click -->
|
|
60
|
+
<div data-rt-dblclick="sensor/trigger" data-rt-payload='{"status": "alert"}'>Double Click Me</div>
|
|
61
|
+
|
|
62
|
+
<!-- Triggers an API request on select dropdown change -->
|
|
63
|
+
<select data-api-change="POST /api/settings" data-api-payload='{"theme": "dark"}' data-api-result="settings/status">
|
|
64
|
+
<option value="dark">Dark Theme</option>
|
|
65
|
+
<option value="light">Light Theme</option>
|
|
66
|
+
</select>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### ३.३. Svelte-Style Block Conditionals & Loops (कन्डिसनल र लुप ब्लकहरू)
|
|
70
|
+
When data arrives via HTTP API or WebSockets, Dolphin compiles your templates on-the-fly. In addition to standard double mustache (`{{key}}`) replacements, Dolphin Client v2.0 features a fully integrated **Svelte-Style Template Compiler** natively running in the browser:
|
|
71
|
+
|
|
72
|
+
- **`{#if expression}` / `{:else if expression}` / `{:else}` / `{/if}`**: Compile Svelte-style conditional flows. You can nest `{#if}` statements to arbitrary levels!
|
|
73
|
+
- **`{#each expression as item}`** or **`{#each expression as item, index}`**: Loop over arrays dynamically. The optional `index` variable starts at `0` and increments automatically.
|
|
74
|
+
- **Single Curly `{expression}` and Double Curly `{{expression}}`**: Output values safely. Dolphin resolves variable paths, nested object properties (like `user.name`), optional chaining (`?.`), and logical operators.
|
|
75
|
+
- **Dynamic Attribute Interpolation**: Interpolates expressions inside standard HTML attributes dynamically (e.g., `src="{user.avatar}"` or `data-api-click="POST /api/reply/{notification.id}"`).
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
### ३.३.१. Real-World Example 1: Multi-Level Nested Conditionals (User Profile Card)
|
|
80
|
+
This example showcases a full dynamic user profile card. It handles states like:
|
|
81
|
+
1. **User not logged in**: Displays login input fields.
|
|
82
|
+
2. **User logged in but unverified**: Displays a warning card with a verification link.
|
|
83
|
+
3. **User verified but not premium**: Displays their details with an upgrade call-to-action button.
|
|
84
|
+
4. **User verified and premium**: Highlights their status with a premium badge.
|
|
85
|
+
|
|
86
|
+
```html
|
|
87
|
+
<div data-api-get="/api/user/profile"
|
|
88
|
+
data-rt-template='
|
|
89
|
+
<div class="profile-container">
|
|
90
|
+
{#if user}
|
|
91
|
+
{#if user.verified}
|
|
92
|
+
<div class="profile-card fx-aurora">
|
|
93
|
+
<img src="{user.avatar}" class="avatar" alt="{user.name}" />
|
|
94
|
+
<h2>{user.name}</h2>
|
|
95
|
+
<p>{user.email}</p>
|
|
96
|
+
|
|
97
|
+
{#if user.premium}
|
|
98
|
+
<div class="premium-badge fx-neon">⭐ Premium Member</div>
|
|
99
|
+
<button class="filled">Access Premium Content</button>
|
|
100
|
+
{:else}
|
|
101
|
+
<div class="upgrade-badge fx-glass">Upgrade to Premium</div>
|
|
102
|
+
<button class="outline">View Plans →</button>
|
|
103
|
+
{/if}
|
|
104
|
+
</div>
|
|
105
|
+
{:else}
|
|
106
|
+
<div class="verify-card fx-glass">
|
|
107
|
+
<h3>Verify Your Account</h3>
|
|
108
|
+
<p>Check your email for verification link</p>
|
|
109
|
+
<button data-api-click="POST /api/resend-verification">
|
|
110
|
+
Resend Email
|
|
111
|
+
</button>
|
|
112
|
+
</div>
|
|
113
|
+
{/if}
|
|
114
|
+
{:else}
|
|
115
|
+
<div class="login-card fx-glass">
|
|
116
|
+
<h3>Login to Continue</h3>
|
|
117
|
+
<input placeholder="Email" />
|
|
118
|
+
<input type="password" placeholder="Password" />
|
|
119
|
+
<button class="filled primary">Login</button>
|
|
120
|
+
<a href="/register">Create Account</a>
|
|
121
|
+
</div>
|
|
122
|
+
{/if}
|
|
123
|
+
</div>
|
|
124
|
+
'></div>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
### ३.३.२. Real-World Example 2: List Loops with Loop Indices (Notifications Feed)
|
|
130
|
+
This example displays a dynamic list of real-time notifications. It automatically loops over the list, renders specific markup based on notification types (`message`, `alert`, `update`, etc.), prints the sequential loop index (`#0`, `#1`), and binds dynamic HTTP API action triggers to button clicks:
|
|
131
|
+
|
|
132
|
+
```html
|
|
133
|
+
<div data-rt-bind="notifications"
|
|
134
|
+
data-rt-template='
|
|
135
|
+
<div class="notifications-container">
|
|
136
|
+
{#if notifications.length > 0}
|
|
137
|
+
<div class="notifications-list">
|
|
138
|
+
<h3>You have {notifications.length} notifications</h3>
|
|
139
|
+
|
|
140
|
+
{#each notifications as notification, index}
|
|
141
|
+
<div class="notification-item fx-glass">
|
|
142
|
+
{#if notification.type === "message"}
|
|
143
|
+
<div class="message-notification">
|
|
144
|
+
<span class="icon">💬</span>
|
|
145
|
+
<div class="content">
|
|
146
|
+
<strong>#{index} from {notification.from}</strong>
|
|
147
|
+
<p>{notification.message}</p>
|
|
148
|
+
|
|
149
|
+
{#if notification.replyNeeded}
|
|
150
|
+
<button data-api-click="POST /api/reply/{notification.id}">
|
|
151
|
+
Reply
|
|
152
|
+
</button>
|
|
153
|
+
{:else}
|
|
154
|
+
<span class="read-status">✓ Read</span>
|
|
155
|
+
{/if}
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
{:else if notification.type === "alert"}
|
|
160
|
+
<div class="alert-notification fx-neon">
|
|
161
|
+
<span class="icon">⚠️</span>
|
|
162
|
+
<div class="content">
|
|
163
|
+
<strong>#{index} Alert!</strong>
|
|
164
|
+
<p>{notification.message}</p>
|
|
165
|
+
|
|
166
|
+
{#if notification.action}
|
|
167
|
+
<button data-api-click="POST /api/alert/{notification.id}/action">
|
|
168
|
+
{notification.action}
|
|
169
|
+
</button>
|
|
170
|
+
{/if}
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
{:else if notification.type === "update"}
|
|
175
|
+
<div class="update-notification">
|
|
176
|
+
<span class="icon">🔄</span>
|
|
177
|
+
<div class="content">
|
|
178
|
+
<strong>Update Available (v{notification.version})</strong>
|
|
179
|
+
<p>{notification.message}</p>
|
|
180
|
+
|
|
181
|
+
{#if notification.urgency === "major"}
|
|
182
|
+
<button class="urgent" data-api-click="POST /api/update">
|
|
183
|
+
Update Now
|
|
184
|
+
</button>
|
|
185
|
+
{:else}
|
|
186
|
+
<button data-api-click="POST /api/update/dismiss">
|
|
187
|
+
Dismiss
|
|
188
|
+
</button>
|
|
189
|
+
{/if}
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
{:else}
|
|
194
|
+
<div class="default-notification">
|
|
195
|
+
<span class="icon">📢</span>
|
|
196
|
+
<div class="content">
|
|
197
|
+
<p>{notification.message}</p>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
{/if}
|
|
201
|
+
</div>
|
|
202
|
+
{/each}
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
{:else}
|
|
206
|
+
<div class="no-notifications fx-glass">
|
|
207
|
+
<div class="empty-icon">🔔</div>
|
|
208
|
+
<h3>No new notifications</h3>
|
|
209
|
+
<p>You are all caught up!</p>
|
|
210
|
+
</div>
|
|
211
|
+
{/if}
|
|
212
|
+
</div>
|
|
213
|
+
'></div>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
### ३.३.३. HTML Directives & Sub-bindings (डाइरेक्टिभ्स र सब-बाइन्डिङ)
|
|
219
|
+
|
|
220
|
+
### ३.४. Browser-Native Template Tags (No Backticks!)
|
|
221
|
+
To completely avoid multi-line strings, backticks (\`...\`), and quote-escaping issues in your HTML attributes, you can point `data-rt-template` directly to a standard browser-native **`<template>` tag selector**:
|
|
222
|
+
|
|
223
|
+
1. **On your binding container**: Point `data-rt-template` to the CSS ID selector of your template element (e.g. `#device-card`).
|
|
224
|
+
2. **In your HTML body**: Declare a standard `<template>` tag. It will not render on page load, but Dolphin Client will automatically query it and instantiate it dynamically!
|
|
225
|
+
|
|
226
|
+
```html
|
|
227
|
+
<!-- 1. Binding Container points to the template selector (Zero Backticks!) -->
|
|
228
|
+
<div data-api-get="/api/devices" data-rt-bind="devices/online" data-rt-template="#device-card"></div>
|
|
229
|
+
|
|
230
|
+
<!-- 2. Browser-Native Template Tag with perfect syntax-highlighting -->
|
|
231
|
+
<template id="device-card">
|
|
232
|
+
<div data-rt-type="context" class="card">
|
|
233
|
+
<span class="status-dot" data-rt-class="bg-emerald-400:isOnline,bg-red-400:isOffline"></span>
|
|
234
|
+
<h3 data-rt-text="id"></h3>
|
|
235
|
+
<img data-rt-attr="src:avatarUrl,alt:id" class="avatar" />
|
|
236
|
+
<button onclick="dialPeer('{{id}}')">Call</button>
|
|
237
|
+
</div>
|
|
238
|
+
</template>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Benefits:
|
|
242
|
+
- **Clean Code**: No complex escaping or template literal backticks in HTML attributes.
|
|
243
|
+
- **IDE Support**: Full HTML syntax highlighting and autocomplete inside standard `<template>` tags in modern text editors (VS Code, Cursor).
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## ४. Parent-Child Context Propagation (`getClosestContext`)
|
|
248
|
+
|
|
249
|
+
When rendering lists dynamically inside templates, child components (like a click button) often need access to their parent's specific item context (like the item `id` or `price`).
|
|
250
|
+
|
|
251
|
+
In Dolphin, you declare **`data-rt-type="context"`** on the parent container. The rendering engine stores the data object directly on the DOM element (`element._rtContext = payload`).
|
|
252
|
+
|
|
253
|
+
When an event triggers on a child element, Dolphin's **`getClosestContext(childElement)`** traverses up the DOM tree (`current = current.parentElement`) to fetch the nearest parent context and dynamically replaces mustaches (`{{variable}}`):
|
|
254
|
+
|
|
255
|
+
```html
|
|
256
|
+
<div data-rt-bind="store/books" data-rt-template='
|
|
257
|
+
<!-- Parent Context Node -->
|
|
258
|
+
<div data-rt-type="context" class="book-row">
|
|
259
|
+
<h3>{{title}}</h3>
|
|
260
|
+
<!-- Child trigger climbs up to parent context to resolve {{price}} -->
|
|
261
|
+
<button data-api-click="POST /api/cart/add" data-api-payload='{"item": "{{title}}", "price": {{price}}}'>
|
|
262
|
+
Buy for ${{price}}
|
|
263
|
+
</button>
|
|
264
|
+
</div>
|
|
265
|
+
'></div>
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## ५. REST + Realtime Hybrid Loading (Offline-First)
|
|
271
|
+
|
|
272
|
+
A major breakthrough in Dolphin Client is **Hybrid REST + Realtime Binding**. It solves the problem where a web page appears empty or broken before the WebSocket (`rt`) connects.
|
|
273
|
+
|
|
274
|
+
If an element has **both** `data-api-get` and `data-rt-bind`:
|
|
275
|
+
1. **Initial HTTP Fetch (REST Mode)**: On page load, `data-api-get` fetches initial data from the HTTP API. Dolphin immediately routes the HTTP JSON response through the template renderer (`_updateDOM`), rendering fully compiled HTML instantly.
|
|
276
|
+
2. **WebSocket Takeover (Realtime Mode)**: As soon as the WebSocket connects, any real-time update published to the topic will seamlessly overwrite and update the HTML in real-time.
|
|
277
|
+
|
|
278
|
+
```html
|
|
279
|
+
<!-- Loads instantly from REST API, updates instantly via WebSockets! -->
|
|
280
|
+
<div data-api-get="/api/devices" data-rt-bind="devices/online" data-rt-template="..."></div>
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## ६. Next.js Integration Guide for 100% Pure SEO
|
|
286
|
+
|
|
287
|
+
Next.js Server Components are rendered on the server into pure HTML, which is crucial for full SEO. However, React usually requires `"use client"` for dynamic reactivity.
|
|
288
|
+
|
|
289
|
+
Using **Dolphin Client**, you can build a highly dynamic Next.js application with **ZERO `"use client"` directives**, achieving **100% full SEO score!**
|
|
290
|
+
|
|
291
|
+
### Step 1: Create a Next.js Server Component (NO `"use client"`)
|
|
292
|
+
Create `app/intercom/page.js`. Fetch the initial data on the server and render the markup with `data-rt-template`:
|
|
293
|
+
|
|
294
|
+
```jsx
|
|
295
|
+
// app/intercom/page.js
|
|
296
|
+
import Script from 'next/script';
|
|
297
|
+
|
|
298
|
+
async function fetchInitialDevices() {
|
|
299
|
+
const res = await fetch('http://localhost:3000/api/devices', { cache: 'no-store' });
|
|
300
|
+
const data = await res.json();
|
|
301
|
+
return data.devices || [];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export default async function IntercomPage() {
|
|
305
|
+
const initialDevices = await fetchInitialDevices();
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
<div className="container">
|
|
309
|
+
<h1>Dolphin Intercom Console</h1>
|
|
310
|
+
|
|
311
|
+
{/* 1. Renders statically on the server so search engines see it instantly for SEO! */}
|
|
312
|
+
<div id="online-directory" data-rt-bind="devices/online" data-rt-template='
|
|
313
|
+
<div data-rt-type="context" class="card">
|
|
314
|
+
<span class="status-dot"></span>
|
|
315
|
+
<span class="id-label">{{id}}</span>
|
|
316
|
+
<button onclick="window.dialPeer('{{id}}')" class="btn">Call</button>
|
|
317
|
+
</div>
|
|
318
|
+
'>
|
|
319
|
+
{/* Server-Side Pre-rendering for search crawlers */}
|
|
320
|
+
{initialDevices.map(device => (
|
|
321
|
+
<div key={device.id} data-rt-type="context" className="card">
|
|
322
|
+
<span className="status-dot"></span>
|
|
323
|
+
<span className="id-label">{device.id}</span>
|
|
324
|
+
<button className="btn">Call</button>
|
|
325
|
+
</div>
|
|
326
|
+
))}
|
|
327
|
+
</div>
|
|
328
|
+
|
|
329
|
+
{/* 2. Load Dolphin Client statically in the browser */}
|
|
330
|
+
<Script src="/node_modules/dolphin-client/dist/dolphin-client.js" strategy="afterInteractive" />
|
|
331
|
+
<Script src="/init-intercom.js" strategy="afterInteractive" />
|
|
332
|
+
</div>
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Step 2: Initialize Intercom client-side (`public/init-intercom.js`)
|
|
338
|
+
Create the initialization script in your public folder to configure WebSockets and WebRTC when the browser loads:
|
|
339
|
+
|
|
340
|
+
```javascript
|
|
341
|
+
// public/init-intercom.js
|
|
342
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
343
|
+
if (typeof DolphinModule === 'undefined') return;
|
|
344
|
+
|
|
345
|
+
const serverIP = window.location.hostname;
|
|
346
|
+
const deviceId = 'ROOM_101'; // Compute dynamically if needed
|
|
347
|
+
|
|
348
|
+
// Initialize client (auto-scans DOM and registers bindings!)
|
|
349
|
+
const dolphin = new DolphinModule.DolphinClient(`http://${serverIP}:3000`, deviceId);
|
|
350
|
+
window.dolphin = dolphin;
|
|
351
|
+
|
|
352
|
+
dolphin.connect().then(() => {
|
|
353
|
+
console.log("WebSocket connected. Handshaking signaling...");
|
|
354
|
+
|
|
355
|
+
// Subscribe to online device status presence
|
|
356
|
+
dolphin.subscribe('devices/status', (payload) => {
|
|
357
|
+
if (payload && payload.devices) {
|
|
358
|
+
dolphin._updateDOM('devices/online', payload.devices);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## ७. WebRTC Intercom Signaling APIs (वेबआरटिसी कलिङ एपीआई)
|
|
368
|
+
|
|
369
|
+
Dolphin Client has built-in signaling mechanisms for high-performance WebRTC peer connection pipelines.
|
|
370
|
+
|
|
371
|
+
### ७.१. Setting Up Peer Connection & Handshake
|
|
372
|
+
Here is the clean JS setup to dial and establish an audio/video call using standard signaling:
|
|
373
|
+
|
|
374
|
+
```javascript
|
|
375
|
+
let localStream = null;
|
|
376
|
+
let peerConnection = null;
|
|
377
|
+
|
|
378
|
+
async function dialPeer(peerId) {
|
|
379
|
+
// 1. Capture local audio/video with robust fallback (handles missing webcams/mics)
|
|
380
|
+
try {
|
|
381
|
+
localStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
|
|
382
|
+
} catch {
|
|
383
|
+
try {
|
|
384
|
+
localStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false }); // Audio only
|
|
385
|
+
} catch {
|
|
386
|
+
console.warn("No hardware capture devices available");
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// 2. Initialize Peer Connection
|
|
391
|
+
peerConnection = new RTCPeerConnection({
|
|
392
|
+
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// 3. Add local tracks
|
|
396
|
+
if (localStream) {
|
|
397
|
+
localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream));
|
|
398
|
+
} else {
|
|
399
|
+
// Add receive-only transceivers if local hardware is missing
|
|
400
|
+
peerConnection.addTransceiver('audio', { direction: 'recvonly' });
|
|
401
|
+
peerConnection.addTransceiver('video', { direction: 'recvonly' });
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// 4. Handle ICE Candidates
|
|
405
|
+
peerConnection.onicecandidate = (event) => {
|
|
406
|
+
if (event.candidate) {
|
|
407
|
+
sendSignal(peerId, 'ICE_CANDIDATE', { candidate: event.candidate });
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
// 5. Play Remote Audio/Video Track with Autoplay bypass
|
|
412
|
+
peerConnection.ontrack = (event) => {
|
|
413
|
+
const remoteVideo = document.getElementById('remote-video');
|
|
414
|
+
if (remoteVideo) {
|
|
415
|
+
remoteVideo.srcObject = event.streams[0];
|
|
416
|
+
remoteVideo.play().catch(e => console.error("Autoplay blocked:", e));
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
// 6. Create SDP Offer & Publish Signaling
|
|
421
|
+
const offer = await peerConnection.createOffer();
|
|
422
|
+
await peerConnection.setLocalDescription(offer);
|
|
423
|
+
sendSignal(peerId, 'INVITE', { sdp: offer.sdp });
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function sendSignal(to, type, data) {
|
|
429
|
+
window.dolphin.publish(`phone/signaling/${to}`, {
|
|
430
|
+
from: window.dolphin.deviceId,
|
|
431
|
+
to,
|
|
432
|
+
type,
|
|
433
|
+
data,
|
|
434
|
+
timestamp: Date.now()
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## ८. Virtual DOM Reconciliation & RAF Batched Updates (भर्चुअल DOM र ब्याच अपडेट्स)
|
|
442
|
+
|
|
443
|
+
To ensure maximum performance and maintain UI state (like input focus, text selection, and active input cursors) during high-frequency real-time updates, Dolphin Client v2.0 features an automatic, zero-dependency **Virtual DOM Diffing engine** (`diffDOM` & `patchDOM`):
|
|
444
|
+
|
|
445
|
+
- **Automatic Selective Patching**: When new data arrives, Dolphin compiles the template in memory, diffs it against the live DOM, and selectively updates only the modified text, attributes, or nodes instead of wiping the entire container.
|
|
446
|
+
- **60fps Batched Updates (`scheduleDOMUpdate`)**: High-frequency writes are automatically debounced and batched into browser `requestAnimationFrame` render cycles, completely preventing layout thrashing and stutter.
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
## ९. Offline-First Cache & Persistent Mutation Queue (अफ्लाइन-फर्स्ट र सूचिकृत म्यूटेशन)
|
|
451
|
+
|
|
452
|
+
Dolphin Client comes with a native offline-first persistence engine driven by **IndexedDB API**:
|
|
453
|
+
|
|
454
|
+
- **GET Requests Caching**: Successful HTTP GET requests (`data-api-get`) are cached automatically in IndexedDB. If the network goes down, Dolphin serves the last cached value instantly.
|
|
455
|
+
- **Offline Writes Queue**: Form submissions, API clicks, and writes (`POST`, `PUT`, `DELETE`) executed while offline are queued securely in IndexedDB and resolved with a positive mock response.
|
|
456
|
+
- **Automatic Sync Engine**: Once the client reconnects, the sync engine automatically flushes the queue sequentially, triggering conflict callbacks if necessary.
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
## १०. Global Reactive Stores & Declarative Form Validation (ग्लोबल स्टोर र स्वचालित फारम प्रमाणीकरण)
|
|
461
|
+
|
|
462
|
+
Dolphin Client v2.0 provides an elegant, hookless state management and form validation system:
|
|
463
|
+
|
|
464
|
+
### १०.१. State Directives (`data-store`)
|
|
465
|
+
Manage client-side global reactive stores without writing custom scripts:
|
|
466
|
+
|
|
467
|
+
```html
|
|
468
|
+
<!-- Initialize/bind an input to automatically write to store "user" at key "name" -->
|
|
469
|
+
<input data-store-write="user.name" placeholder="Enter your name" />
|
|
470
|
+
|
|
471
|
+
<!-- Bind an element to display the name from the store in real-time -->
|
|
472
|
+
<span data-store-read="user.name"></span>
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### १०.१.२. Declarative Store Actions (`data-store-[event]`) - बिना जाभास्क्रिप्ट गणित र सर्तहरू (No-JS Calculations & Conditions)
|
|
476
|
+
Dolphin Client v2.0 ले सिधै HTML बाटै डेटा स्टोर अपडेट गर्ने र जस्तोसुकै जटिल गणितीय हिसाब, लजिकल अपरेटर, र सर्तहरू (conditional statements) चलाउने फिचर प्रदान गर्दछ। यसको लागि तपाईँले कुनै जाभास्क्रिप्ट फङ्सन वा `<script>` ट्याग लेख्नै पर्दैन।
|
|
477
|
+
|
|
478
|
+
तपाईँले `data-store-click`, `data-store-change` जस्ता एट्रिब्युटहरू प्रयोग गरेर सिधै HTML भित्रै जाभास्क्रिप्टका गणितीय सूत्रहरू लेख्न सक्नुहुन्छ:
|
|
479
|
+
|
|
480
|
+
#### क) गणितीय हिसाबहरू (Mathematical Calculations)
|
|
481
|
+
```html
|
|
482
|
+
<!-- दुईवटा भ्यालु स्वतः गुणन गरेर 'app.total' मा राख्न -->
|
|
483
|
+
<button data-store-click="app.total = app.price * app.quantity">कुल हिसाब निकाल्नुहोस्</button>
|
|
484
|
+
|
|
485
|
+
<!-- १ देखि १०० सम्मको रेन्डम नम्बर स्वतः जेनेरेट गर्न -->
|
|
486
|
+
<button data-store-click="app.randomNum = Math.floor(Math.random() * 100) + 1">Random Number</button>
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
#### ख) सर्त र निर्णयहरू (Conditions & Ternary Operators)
|
|
490
|
+
Ternary operator (`condition ? true : false`) वा बहु-लाइन `if/else` सर्तहरू सिधै HTML बाटै चल्छन्:
|
|
491
|
+
```html
|
|
492
|
+
<!-- यदि स्कोर ५० वा सोभन्दा बढी भए 'Passed' नत्र 'Failed' सेट गर्न -->
|
|
493
|
+
<button data-store-click="app.result = (app.score >= 50) ? 'Passed' : 'Failed'">नतिजा हेर्नुहोस्</button>
|
|
494
|
+
|
|
495
|
+
<!-- यदि काउन्ट १० भन्दा सानो भए बढाउने, नत्र ० मा फर्काउने -->
|
|
496
|
+
<button data-store-click="if (app.count < 10) { app.count++ } else { app.count = 0 }">Incrementor</button>
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
#### ग) अन-अफ टगल गर्ने (Boolean / Toggle Logic)
|
|
500
|
+
डार्क मोड अन-अफ गर्न वा कुनै स्टेटलाई स्वतः उल्टो (toggle) बनाउन:
|
|
501
|
+
```html
|
|
502
|
+
<!-- darkMode को भ्यालु true भए false र false भए true बनाउन -->
|
|
503
|
+
<button data-store-click="app.darkMode = !app.darkMode">डार्क मोड अन/अफ</button>
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### १०.२. Declarative Validations (`data-validate`)
|
|
507
|
+
Apply robust validation rules to inputs and forms instantly. Tagged invalid inputs automatically receive `.invalid` classes and publish error text:
|
|
508
|
+
|
|
509
|
+
- **`required`**: Checks for non-empty input.
|
|
510
|
+
- **`email`**: Validates standard email address formats.
|
|
511
|
+
- **`min:N`**: Enforces a minimum length of N characters.
|
|
512
|
+
- **`match:inputName`**: Compares value (e.g. for Password Confirmations).
|
|
513
|
+
|
|
514
|
+
```html
|
|
515
|
+
<form id="signup-form">
|
|
516
|
+
<!-- Validates in real-time on keypress/blur and outputs error to topic errors/email -->
|
|
517
|
+
<input name="email" data-validate="required,email" placeholder="Your Email" />
|
|
518
|
+
|
|
519
|
+
<input name="password" type="password" data-validate="required,min:8" placeholder="Password" />
|
|
520
|
+
<input name="confirm" type="password" data-validate="match:password" placeholder="Confirm Password" />
|
|
521
|
+
|
|
522
|
+
<button type="submit">Sign Up</button>
|
|
523
|
+
</form>
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
---
|
|
527
|
+
|
|
528
|
+
## ११. Animations, Accessibility (a11y), and Internationalization (i18n)
|
|
529
|
+
|
|
530
|
+
Dolphin Client incorporates built-in UI polish tools to make apps fluid, accessible, and global:
|
|
531
|
+
|
|
532
|
+
### ११.१. Staggered Animations
|
|
533
|
+
Trigger animations on lists automatically using the Web Animations API:
|
|
534
|
+
|
|
535
|
+
```javascript
|
|
536
|
+
// Add fluid staggers to list containers easily
|
|
537
|
+
dolphin.staggerListItems(document.getElementById('list-container'), '.item-class', 50);
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### ११.२. Automated Accessibility (a11y)
|
|
541
|
+
Easily implement accessible widgets and focus traps:
|
|
542
|
+
|
|
543
|
+
- **Modal Trap (`data-rt-a11y-focus-trap`)**: Confines `Tab` keyboard focus inside the visible modal container.
|
|
544
|
+
- **Keyboard List Navigation (`data-rt-keynav`)**: Enables users to traverse lists using `ArrowUp` / `ArrowDown` and click using `Enter`.
|
|
545
|
+
- **`autoAriaModal(el, isOpen)`**: Dynamically sets `role="dialog"`, `aria-modal="true"`, and `aria-hidden` tags.
|
|
546
|
+
|
|
547
|
+
```html
|
|
548
|
+
<!-- Modal box equipped with focus trap and keyboard accessibility -->
|
|
549
|
+
<div id="settings-modal" data-rt-a11y-focus-trap data-rt-keynav tabindex="-1">
|
|
550
|
+
<button class="close-btn">Close</button>
|
|
551
|
+
<ul class="options-list">
|
|
552
|
+
<li tabindex="0" class="active">Dark Mode</li>
|
|
553
|
+
<li tabindex="0">Light Mode</li>
|
|
554
|
+
</ul>
|
|
555
|
+
</div>
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
### ११.३. Dynamic Translation Engine (i18n)
|
|
559
|
+
Provide multi-language dictionary translations seamlessly. Dotted nested keys (e.g. `auth.login`) and JSON interpolation parameter injection are fully supported:
|
|
560
|
+
|
|
561
|
+
```html
|
|
562
|
+
<!-- Embed dictionary data directly in HTML -->
|
|
563
|
+
<script type="application/json" data-i18n-dict="en">
|
|
564
|
+
{
|
|
565
|
+
"welcome": "Hello {{name}}! Welcome back.",
|
|
566
|
+
"auth": { "login": "Log In" }
|
|
567
|
+
}
|
|
568
|
+
</script>
|
|
569
|
+
|
|
570
|
+
<script type="application/json" data-i18n-dict="ne">
|
|
571
|
+
{
|
|
572
|
+
"welcome": "नमस्ते {{name}}! स्वागत छ।",
|
|
573
|
+
"auth": { "login": "लग-इन" }
|
|
574
|
+
}
|
|
575
|
+
</script>
|
|
576
|
+
|
|
577
|
+
<!-- Render translations automatically -->
|
|
578
|
+
<h1 data-i18n-key="welcome" data-i18n-params='{"name": "Ram"}'></h1>
|
|
579
|
+
<button data-i18n-key="auth.login"></button>
|
|
580
|
+
|
|
581
|
+
<!-- Switch languages dynamically on click -->
|
|
582
|
+
<button data-i18n-switch="ne">नेपाली</button>
|
|
583
|
+
<button data-i18n-switch="en">English</button>
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
## १२. Drag-and-Drop, Sortable Lists, and Real-time Collaboration (CRDT)
|
|
589
|
+
|
|
590
|
+
Build multi-player collaborative interfaces declarative:
|
|
591
|
+
|
|
592
|
+
### १२.१. Declarative Drag & Drop
|
|
593
|
+
Move items between sections and sync updates across devices instantly:
|
|
594
|
+
|
|
595
|
+
```html
|
|
596
|
+
<!-- Draggable Card publishes payload to WS -->
|
|
597
|
+
<div data-drag='{"id": 105, "title": "Buy milk"}' draggable="true">Card Item</div>
|
|
598
|
+
|
|
599
|
+
<!-- Drop Zone accepts draggable data and publishes to "tasks/moved" topic -->
|
|
600
|
+
<div data-drop="tasks/moved">Drop Here</div>
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
### १२.२. Sortable Lists
|
|
604
|
+
Drag to reorder lists in real-time. Dolphin automatically calculates elements midpoints during `dragover`, reorganizes children, and publishes the new index orders:
|
|
605
|
+
|
|
606
|
+
```html
|
|
607
|
+
<!-- Sortable container publishes new order map to "tasks/order" -->
|
|
608
|
+
<ul data-sortable="tasks/order">
|
|
609
|
+
<li data-drag='{"id": 1}' draggable="true">Task A</li>
|
|
610
|
+
<li data-drag='{"id": 2}' draggable="true">Task B</li>
|
|
611
|
+
<li data-drag='{"id": 3}' draggable="true">Task C</li>
|
|
612
|
+
</ul>
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
### १२.३. Collaborative Cursor Sharing & CRDT Document Sync
|
|
616
|
+
Broaden your app into a cooperative space:
|
|
617
|
+
|
|
618
|
+
- **Shared Mouse Cursors (`data-rt-cursor-share`)**: Automatically tracks mouse movements relative to a container, publishes cursor ratio coordinates, and draws colored cursor circles for remote users.
|
|
619
|
+
- **Typing Indicators (`data-rt-typing`)**: Broadcasts typing statuses in real-time.
|
|
620
|
+
- **Vector Clock CRDT Sync (`data-rt-crdt`)**: Resolves concurrent writing conflicts on text inputs using timestamps while preserving caret focus selections.
|
|
621
|
+
|
|
622
|
+
```html
|
|
623
|
+
<!-- Real-time Collaborative Board with cursors and typing indicator -->
|
|
624
|
+
<div data-rt-cursor-share="room_1" class="board-canvas">
|
|
625
|
+
<input data-rt-typing="room_1" placeholder="Typing indicator active..." />
|
|
626
|
+
|
|
627
|
+
<!-- Collaborative CRDT synced textarea -->
|
|
628
|
+
<textarea data-rt-crdt="shared_doc" placeholder="Type here concurrently..."></textarea>
|
|
629
|
+
</div>
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
---
|
|
633
|
+
|
|
634
|
+
## १३. Standalone Testing Utilities (DolphinTestUtils)
|
|
635
|
+
|
|
636
|
+
For continuous integration (CI) and automated tests, Dolphin Client v2.0 exposes a clean standalone testing bundle inside [src/testing.ts](./src/testing.ts):
|
|
637
|
+
|
|
638
|
+
- **`DolphinTestUtils.mockWebSocket()`**: Injects a high-fidelity WebSocket mock constructor that captures outbound payloads into `sentMessages`, mimics connection state transitions, and manages message broadcasting.
|
|
639
|
+
- **Event Simulators**: Programmatically trigger `click` and `change` inputs to test DOM directives in Node.js/Jest environments with zero headless browser requirements.
|
|
640
|
+
|
|
641
|
+
```javascript
|
|
642
|
+
const { DolphinTestUtils } = require('dolphin-client/dist/testing');
|
|
643
|
+
|
|
644
|
+
// Mock standard WebSocket environment cleanly
|
|
645
|
+
const mockWS = DolphinTestUtils.mockWebSocket();
|
|
646
|
+
|
|
647
|
+
// Trigger connection opening
|
|
648
|
+
mockWS.onopen();
|
|
649
|
+
|
|
650
|
+
// Validate published messages
|
|
651
|
+
dolphin.publish('test/topic', { ok: true });
|
|
652
|
+
expect(mockWS.sentMessages).toContain(JSON.stringify({ topic: 'test/topic', payload: { ok: true } }));
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
|
|
657
|
+
## १४. Styling with DolphinCSS - Premium Aesthetics (DolphinCSS सँग स्टाइलिङ)
|
|
658
|
+
|
|
659
|
+
To make your hookless real-time applications look breathtakingly premium and modern, you can seamlessly combine **Dolphin Client v2.0** with **DolphinCSS**, our advanced visual-first CSS utility framework.
|
|
660
|
+
|
|
661
|
+
DolphinCSS replaces the need to write dozens of Tailwind utility classes by exposing pre-built global components, neon glows, and gorgeous modern effects out-of-the-box.
|
|
662
|
+
|
|
663
|
+
### १४.१. Integration Setup
|
|
664
|
+
Import the DolphinCSS stylesheet directly inside your browser index or main entrypoint:
|
|
665
|
+
|
|
666
|
+
```html
|
|
667
|
+
<!-- Import DolphinCSS stylesheet for world-class styling -->
|
|
668
|
+
<link rel="stylesheet" href="path/to/dolphincss/dolphin-css.css">
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
### १४.२. Premium Visual Effects (`fx-*`)
|
|
672
|
+
Combine these high-end visual decorators on any card, dialog, input, or container:
|
|
673
|
+
- **`fx-glass`**: Classic frosted glassmorphism with dynamic backdrop blur.
|
|
674
|
+
- **`fx-crystal`**: Ultra-clear crystalline border with inner shadows.
|
|
675
|
+
- **`fx-neon`**: Cyberpunk glowing borders and drop shadows.
|
|
676
|
+
- **`fx-aurora`**: Frosted northern-lights color depth gradient.
|
|
677
|
+
- **`fx-float`**: Smooth 3D floating animation on hover.
|
|
678
|
+
|
|
679
|
+
### १४.३. Building a Reactive, Glowing DolphinCSS Card Component
|
|
680
|
+
Here is a complete, real-world example of how to build a stunning, frosted-glass profile card that updates reactively in real-time via WebSockets:
|
|
681
|
+
|
|
682
|
+
```html
|
|
683
|
+
<!-- Loads profile instantly from REST, updates instantly via WebSocket topic "user/profile" -->
|
|
684
|
+
<div
|
|
685
|
+
data-api-get="/api/profile"
|
|
686
|
+
data-rt-bind="user/profile"
|
|
687
|
+
data-rt-template='
|
|
688
|
+
<div data-rt-type="context" class="fx-glass card p-8 border border-white/20 rounded-2xl max-w-sm hover-jelly transition-all duration-300 relative overflow-hidden">
|
|
689
|
+
<!-- Glow background decoration -->
|
|
690
|
+
<div class="absolute top-0 right-0 w-32 h-32 bg-primary-500/20 rounded-full blur-2xl"></div>
|
|
691
|
+
|
|
692
|
+
<!-- Profile Header -->
|
|
693
|
+
<div class="flex-left gap-4 mb-4 relative z-10">
|
|
694
|
+
<div class="w-16 h-16 rounded-full border-2 border-primary-400 overflow-hidden shadow-lg p-0.5">
|
|
695
|
+
<img data-rt-attr="src:avatarUrl,alt:name" class="w-full h-full rounded-full object-cover" />
|
|
696
|
+
</div>
|
|
697
|
+
<div>
|
|
698
|
+
<h3 data-rt-text="name" class="text-xl font-bold text-white m-0"></h3>
|
|
699
|
+
<p data-rt-text="role" class="text-primary-300 text-sm font-medium"></p>
|
|
700
|
+
</div>
|
|
701
|
+
</div>
|
|
702
|
+
|
|
703
|
+
<!-- Bio Description -->
|
|
704
|
+
<p data-rt-text="bio" class="text-white/70 text-sm leading-relaxed mb-6 relative z-10"></p>
|
|
705
|
+
|
|
706
|
+
<!-- Actions Section -->
|
|
707
|
+
<div class="flex-between relative z-10">
|
|
708
|
+
<div class="flex gap-2">
|
|
709
|
+
<span class="px-3 py-1 rounded-full bg-white/10 text-xs font-medium text-white/90">Dolphin</span>
|
|
710
|
+
<span class="px-3 py-1 rounded-full bg-white/10 text-xs font-medium text-white/90">React-Less</span>
|
|
711
|
+
</div>
|
|
712
|
+
<!-- Pulsing glowing green success action button -->
|
|
713
|
+
<button class="circle filled success-500 text-white p-2 glow glow-pulse hover:scale-110 transition-all flex-center">
|
|
714
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
715
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
716
|
+
</svg>
|
|
717
|
+
</button>
|
|
718
|
+
</div>
|
|
719
|
+
</div>
|
|
720
|
+
'
|
|
721
|
+
></div>
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
By marrying the **reactive strength of Dolphin Client** with the **premium design aesthetics of DolphinCSS**, you can build state-of-the-art, lightning-fast interfaces that look premium and responsive.
|
|
725
|
+
|
|
726
|
+
---
|
|
727
|
+
|
|
728
|
+
## १५. Production Best Practices (उत्कृष्ट अभ्यासहरू)
|
|
729
|
+
|
|
730
|
+
- **Strict Environment Security**: During native app packaging, always map standard Android/iOS run-time device permissions. Native apps do not suffer from browser HTTP context locks!
|
|
731
|
+
- **Auto Reconnect Policy**: Dolphin Client has an exponentional backoff reconnect strategy built-in. Set `maxReconnect` options to customize connection retry budgets.
|
|
732
|
+
- **Graceful Hardware Fallbacks**: Always test your WebRTC systems with receive-only transceivers (`recvonly`) so desktop terminals with no microphoness/cameras can still receive and play signals properly.
|
|
733
|
+
|
|
734
|
+
Enjoy building hookless, lightning-fast, and premium real-time applications with **Dolphin Client**! 🐬
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dolphin-client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
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",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"files": [
|
|
17
17
|
"dist",
|
|
18
18
|
"README.md",
|
|
19
|
-
"LICENSE"
|
|
19
|
+
"LICENSE",
|
|
20
|
+
"fulltutorial.md"
|
|
20
21
|
],
|
|
21
22
|
"scripts": {
|
|
22
23
|
"build": "npm run build:iife && npm run build:min && npm run build:esm && npm run build:cjs && tsc",
|