dolphin-client 1.0.0 β†’ 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,14 +20,26 @@ By breathing life back into standard HTML, we have resurrected the simplicity of
20
20
 
21
21
  ---
22
22
 
23
+ ## Documentation
24
+
25
+ > [!TIP]
26
+ > πŸ“– Read our comprehensive **[Full Developer Tutorial & Integration Guide](./fulltutorial.md)** for detailed guides, Next.js setups, WebRTC intercoms, global stores, drag-and-drop sortable lists, and real-world examples!
27
+
28
+ ---
29
+
23
30
  ## Features
24
31
 
25
32
  - **Hookless Reactivity**: Bind topics to DOM elements via HTML data attributesβ€”no React, Vue, or Angular state management required.
33
+ - **Svelte-Style Templates Compiler**: Native browser compiler supporting Svelte-style loop blocks (`{#each ... as ...}`), multi-level nested conditionals (`{#if} / {:else if} / {:else}`), loop indices (`index`), optional chaining, and dynamic attribute interpolation.
26
34
  - **Unified Event Binding**: Loop-based browser event handling for values (`data-rt-push`) and actions (`data-rt-[event]` / `data-api-[event]`).
27
35
  - **Context API/Prop drilling in Pure DOM**: Crawls up the DOM tree (`getClosestContext`) to fetch parent contexts and inject parameters.
28
36
  - **REST API + Realtime Hybrid Support**: Evaluates templates (`data-rt-template`) on initial HTTP fetches (`data-api-get`) and transitions seamlessly to real-time WebSockets on connection.
29
37
  - **WebRTC Intercom Signaling**: Built-in methods to handle peer connections, track negotiation, ICE candidates, and signaling.
30
- - **Ultralight weight**: Zero external dependencies, pure browser-native runtime APIs.
38
+ - **Ultralight weight**: Zero external dependencies, pure browser-native runtime APIs (~39KB compressed bundle!).
39
+
40
+ ---
41
+
42
+ ## Installation & Setup
31
43
 
32
44
  ### Method 1: NPM (For Modern Bundlers)
33
45
  ```bash
@@ -37,19 +49,38 @@ npm install dolphin-client
37
49
  ### Method 2: Direct Local Download (For No-Install / Plain HTML)
38
50
  Tired of the command line and `node_modules` clutter? We've got you covered!
39
51
 
40
- [![Download dolphin-client.js](https://img.shields.io/badge/Download-dolphin--client.js-F7DF1E?style=for-the-badge&logo=javascript&logoColor=black)](https://unpkg.com/dolphin-client/dist/dolphin-client.js)    [![Download dolphin-bundle.zip](https://img.shields.io/badge/Download-dolphin--bundle.zip-4CAF50?style=for-the-badge&logo=archive&logoColor=white)](https://github.com/Phuyalshankar/dolphin-server-modules/releases/latest/download/dolphin-bundle.zip)
41
-
42
- *(Right-click on **dolphin-client.js** and select "Save Link As..." to save it directly to your project)*
43
-
52
+ [![Download dolphin-client.js](https://img.shields.io/badge/Download-dolphin--client.js-F7DF1E?style=for-the-badge&logo=javascript&logoColor=black)](https://unpkg.com/dolphin-client/dist/dolphin-client.js)    [![Download dolphin-client.min.js (Minified)](https://img.shields.io/badge/Download-dolphin--client.min.js-06B6D4?style=for-the-badge&logo=javascript&logoColor=white)](https://unpkg.com/dolphin-client/dist/dolphin-client.min.js)    [![Download dolphin-bundle.zip](https://img.shields.io/badge/Download-dolphin--bundle.zip-4CAF50?style=for-the-badge&logo=archive&logoColor=white)](https://github.com/Phuyalshankar/dolphin-server-modules/releases/latest/download/dolphin-bundle.zip)
53
+
54
+ > [!IMPORTANT]
55
+ > **How to Download the JS Files:**
56
+ > Clicking the JS download buttons will display the raw JavaScript code in your browser.
57
+ > To download it as a file:
58
+ > 1. **Right-click** on the yellow/blue button and select **"Save Link As..."** (or "Save Target As...").
59
+ > 2. Or, if you clicked it, press **`Ctrl + S`** (or `Cmd + S` on Mac) on the code page to save it.
60
+ > 3. Alternatively, run these commands in your terminal to download directly:
61
+ > * **Standard (Development):**
62
+ > ```bash
63
+ > curl -o dolphin-client.js https://unpkg.com/dolphin-client/dist/dolphin-client.js
64
+ > ```
65
+ > * **Minified (Production):**
66
+ > ```bash
67
+ > curl -o dolphin-client.min.js https://unpkg.com/dolphin-client/dist/dolphin-client.min.js
68
+ > ```
69
+
70
+ > [!TIP]
71
+ > **If the ZIP file download is unavailable:**
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](./fulltutorial.md)**.
44
74
 
45
75
  Extract the zip directly inside your project folder to get a clean local directory structure with pre-bundled assets:
46
76
  ```
47
77
  my-project/
48
78
  β”œβ”€β”€ css/
49
- β”‚ └── dolphin-css.css (DolphinCSS Premium Visuals Layer)
79
+ β”‚ └── dolphin-css.css (DolphinCSS Premium Visuals Layer)
50
80
  β”œβ”€β”€ js/
51
- β”‚ └── dolphin-client.js (Dolphin Reactivity Engine)
52
- └── index.html (A ready-to-run skeleton template!)
81
+ β”‚ β”œβ”€β”€ dolphin-client.js (Standard Reactivity Engine)
82
+ β”‚ └── dolphin-client.min.js (Minified Production Reactivity Engine)
83
+ └── index.html (A ready-to-run skeleton template!)
53
84
  ```
54
85
  Inside your HTML, simply link them locally:
55
86
  ```html
@@ -57,44 +88,105 @@ Inside your HTML, simply link them locally:
57
88
  <script src="js/dolphin-client.js"></script>
58
89
  ```
59
90
 
60
- ## Basic Usage
91
+ ---
92
+
93
+ ## Interactive Examples Guide
61
94
 
62
- ### 1. In Modern Bundlers (Next.js, Vite, React)
95
+ Below are clean, ready-to-use HTML examples for the core features of Dolphin Client. Because Dolphin supports **Zero-Configuration Auto-Initialization**, these will run instantly!
96
+
97
+ ### 1. Real-Time (RT) Pub/Sub (Real-time Chat)
98
+ Publish inputs on typing, and display incoming messages in real-time under a WebSocket topic:
99
+
100
+ ```html
101
+ <!-- Automatically publishes typed values to topic 'chat/messages' -->
102
+ <input name="chatMessage" data-rt-push="chat/messages" placeholder="Type a message..." />
103
+
104
+ <!-- Displays and appends all incoming messages under 'chat/messages' -->
105
+ <div data-rt-bind="chat/messages" data-rt-template='
106
+ <div class="message-card">
107
+ <span class="user-label">@user:</span>
108
+ <p>{{chatMessage}}</p>
109
+ </div>
110
+ '></div>
111
+ ```
63
112
 
64
- ```javascript
65
- import { DolphinClient } from 'dolphin-client';
113
+ ### 2. REST API Integration (Reactive List Loader)
114
+ Fetch data instantly via REST API on page load and compile templates dynamically:
66
115
 
67
- const dolphin = new DolphinClient('http://localhost:3000', 'ROOM_101');
68
- await dolphin.connect();
116
+ ```html
117
+ <!-- Fetches from GET /api/devices, compiles items inside #device-card-template -->
118
+ <div data-api-get="/api/devices" data-rt-template="#device-card-template">
119
+ <!-- Loading spinner placeholder: automatically removed when data arrives! -->
120
+ <div class="spinner">Loading devices...</div>
121
+ </div>
122
+
123
+ <!-- Browser-Native Template Tag (No quote-escaping or backticks needed!) -->
124
+ <template id="device-card-template">
125
+ <div class="device-card">
126
+ <h3>{{name}}</h3>
127
+ <span class="badge">{{status}}</span>
128
+ </div>
129
+ </template>
69
130
  ```
70
131
 
71
- ### 2. In Plain HTML (Static / Script Tag)
132
+ ### 3. Declarative Form Validation
133
+ Apply strong validation rules to inputs and display errors directly in the UI with absolutely **zero JavaScript**:
72
134
 
73
135
  ```html
74
- <script src="node_modules/dolphin-client/dist/dolphin-client.js"></script>
75
- <script>
76
- const dolphin = new DolphinModule.DolphinClient('http://localhost:3000', 'ROOM_101');
77
- dolphin.connect();
78
- </script>
136
+ <form id="login-form" data-api-submit="POST /api/login">
137
+ <div class="form-group">
138
+ <input name="email" data-validate="required,email" placeholder="Your Email" />
139
+ <!-- Automatically reads and displays validation error directly in the UI -->
140
+ <span class="error-msg" data-rt-bind="errors/email"></span>
141
+ </div>
142
+
143
+ <div class="form-group">
144
+ <input name="password" type="password" data-validate="required,min:8" placeholder="Password" />
145
+ <span class="error-msg" data-rt-bind="errors/password"></span>
146
+ </div>
147
+
148
+ <button type="submit">Log In</button>
149
+ </form>
79
150
  ```
80
151
 
81
- ## HTML Directives
152
+ ### 4. Global State & Declarative Store Actions (No-JS Actions!)
153
+ Manage local stores and run complex calculations, conditions, and toggles **directly in HTML attributes**:
82
154
 
83
- ### Pushing value changes
84
155
  ```html
85
- <input name="chat" data-rt-push="chat/messages/ROOM_101" placeholder="Type..." />
156
+ <!-- 1. Auto-sync inputs directly into store key 'app.username' -->
157
+ <input data-store-write="app.username" placeholder="Type name..." />
158
+ <h3 data-store-read="app.username"></h3>
159
+
160
+ <!-- 2. Pure HTML Mathematical Counter -->
161
+ <div class="counter-box">
162
+ <div class="counter-value" data-store-read="app.count">0</div>
163
+ <!-- Updates state directly in HTML click. Dolphin updates the UI dynamically! -->
164
+ <button data-store-click="app.count = (app.count || 0) + 1">+</button>
165
+ <button data-store-click="app.count = (app.count || 0) - 1">-</button>
166
+ </div>
167
+
168
+ <!-- 3. Complex Calculations (e.g. Area & Billing) -->
169
+ <button data-store-click="
170
+ app.area = 3.14159 * (app.radius * app.radius);
171
+ app.circumference = 2 * 3.14159 * app.radius
172
+ ">
173
+ Calculate Circle
174
+ </button>
175
+
176
+ <!-- 4. Logic Toggles (e.g. Dark Mode Toggle) -->
177
+ <button data-store-click="app.darkMode = !app.darkMode">Toggle Dark Mode</button>
86
178
  ```
87
179
 
88
- ### Directives & Templates
180
+ ### 5. Silent Zero-Configuration Auto-Initialization
181
+ When loaded via a standard `<script>` tag in browser environments, Dolphin Client automatically boots up a default client instance as `window.dolphin` on `DOMContentLoaded`.
182
+
183
+ For debugging, pass `data-debug="true"` on your script tag to turn on gorgeous, color-coded logging in your developer console for all API calls, WebSocket events, and Store updates:
89
184
  ```html
90
- <div data-api-get="/api/devices" data-rt-bind="devices/online" data-rt-template='
91
- <div data-rt-type="context">
92
- <h3>{{id}}</h3>
93
- <button onclick="dialPeer(&apos;{{id}}&apos;)">Dial</button>
94
- </div>
95
- '></div>
185
+ <script src="js/dolphin-client.js" data-debug="true"></script>
96
186
  ```
97
187
 
188
+ ---
189
+
98
190
  ## License
99
191
 
100
192
  ISC License
@@ -108,6 +108,9 @@ var DolphinModule = (() => {
108
108
  async requestDirect(method, path, body = null, options = {}) {
109
109
  const _isRetry = options._isRetry === true;
110
110
  const url = `${this.client.httpUrl}${path.startsWith("/") ? path : "/" + path}`;
111
+ if (this.client.options.debug) {
112
+ console.log(`%c\u{1F680} [Dolphin API Request]:`, "color: #3b82f6; font-weight: bold;", method.toUpperCase(), path, body || "");
113
+ }
111
114
  const controller = new AbortController();
112
115
  const timeoutId = setTimeout(
113
116
  () => controller.abort(),
@@ -140,6 +143,9 @@ var DolphinModule = (() => {
140
143
  const contentType = response.headers.get("content-type") || "";
141
144
  const data = contentType.includes("application/json") ? await response.json() : await response.text();
142
145
  if (!response.ok) throw { status: response.status, data };
146
+ if (this.client.options.debug) {
147
+ console.log(`%c\u2705 [Dolphin API Success]:`, "color: #10b981; font-weight: bold;", method.toUpperCase(), path, data);
148
+ }
143
149
  if (data && typeof data === "object") {
144
150
  if (data.accessToken) {
145
151
  this.client.setToken(data.accessToken);
@@ -153,6 +159,9 @@ var DolphinModule = (() => {
153
159
  return data;
154
160
  } catch (err) {
155
161
  clearTimeout(timeoutId);
162
+ if (this.client.options.debug) {
163
+ console.error(`%c\u274C [Dolphin API Error]:`, "color: #ef4444; font-weight: bold;", method.toUpperCase(), path, err);
164
+ }
156
165
  if (err.name === "AbortError") {
157
166
  throw { status: 408, data: { error: "Request timed out" } };
158
167
  }
@@ -437,6 +446,7 @@ var DolphinModule = (() => {
437
446
  // 64 KB
438
447
  maxReconnect: 5,
439
448
  autoRefreshToken: true,
449
+ debug: false,
440
450
  ...options
441
451
  };
442
452
  this.socket = null;
@@ -519,6 +529,9 @@ var DolphinModule = (() => {
519
529
  _handleMessage(data) {
520
530
  try {
521
531
  const msg = JSON.parse(data);
532
+ if (this.options.debug) {
533
+ console.log("%c\u{1F4E5} [Dolphin WS Incoming]:", "color: #eab308; font-weight: bold;", msg);
534
+ }
522
535
  if (msg.type && msg.from && (msg.to === this.deviceId || msg.to === "all")) {
523
536
  if (msg.msgId && msg.type !== "ACK") this._sendAck(msg.from, msg.msgId);
524
537
  this.signalHandlers.forEach((h) => h(msg));
@@ -556,6 +569,9 @@ var DolphinModule = (() => {
556
569
  }
557
570
  /** @private */
558
571
  _sendRaw(msg) {
572
+ if (this.options.debug) {
573
+ console.log("%c\u{1F4E4} [Dolphin WS Outgoing]:", "color: #8b5cf6; font-weight: bold;", msg);
574
+ }
559
575
  const str = typeof msg === "string" ? msg : JSON.stringify(msg);
560
576
  if (this.socket && this.socket.readyState === WebSocket.OPEN) {
561
577
  this.socket.send(str);
@@ -788,6 +804,108 @@ var DolphinModule = (() => {
788
804
  }
789
805
  return template;
790
806
  }
807
+ function renderTemplate(templateStr, context) {
808
+ if (!templateStr.includes("{#if") && !templateStr.includes("{#each")) {
809
+ let result = templateStr;
810
+ for (let key in context) {
811
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
812
+ result = result.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
813
+ }
814
+ return result;
815
+ }
816
+ try {
817
+ const escapeString = (str) => {
818
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
819
+ };
820
+ let compiled = 'let out = "";\n';
821
+ let lastIndex = 0;
822
+ const regex = /(\{\{([\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;
823
+ const eachStack = [];
824
+ let match;
825
+ while ((match = regex.exec(templateStr)) !== null) {
826
+ const plainText = templateStr.slice(lastIndex, match.index);
827
+ if (plainText) {
828
+ compiled += `out += "${escapeString(plainText)}";
829
+ `;
830
+ }
831
+ const token = match[0];
832
+ if (token.startsWith("{{")) {
833
+ const expr = match[2];
834
+ compiled += `out += (${expr} !== undefined && ${expr} !== null ? ${expr} : "");
835
+ `;
836
+ } else if (token.startsWith("{#if")) {
837
+ const expr = match[3];
838
+ compiled += `if (${expr}) {
839
+ `;
840
+ } else if (token.startsWith("{:else if")) {
841
+ const expr = match[4];
842
+ compiled += `} else if (${expr}) {
843
+ `;
844
+ } else if (token.startsWith("{:else}")) {
845
+ compiled += `} else {
846
+ `;
847
+ } else if (token.startsWith("{/if}")) {
848
+ compiled += `}
849
+ `;
850
+ } else if (token.startsWith("{#each")) {
851
+ const expr = match[5];
852
+ const itemVar = match[6];
853
+ const indexVar = match[7];
854
+ eachStack.push({ indexVar });
855
+ compiled += `if (typeof ${expr} !== "undefined" && ${expr} !== null && Array.isArray(${expr})) {
856
+ `;
857
+ if (indexVar) {
858
+ compiled += ` let ${indexVar} = 0;
859
+ `;
860
+ }
861
+ compiled += ` for (let ${itemVar} of ${expr}) {
862
+ `;
863
+ } else if (token.startsWith("{/each}")) {
864
+ const info = eachStack.pop();
865
+ if (info && info.indexVar) {
866
+ compiled += ` ${info.indexVar}++;
867
+ `;
868
+ }
869
+ compiled += ` }
870
+ }
871
+ `;
872
+ } else if (token.startsWith("{")) {
873
+ const expr = match[8];
874
+ if (expr) {
875
+ compiled += `out += (${expr} !== undefined && ${expr} !== null ? ${expr} : "");
876
+ `;
877
+ }
878
+ }
879
+ lastIndex = regex.lastIndex;
880
+ }
881
+ const remaining = templateStr.slice(lastIndex);
882
+ if (remaining) {
883
+ compiled += `out += "${escapeString(remaining)}";
884
+ `;
885
+ }
886
+ compiled += "return out;\n";
887
+ const fnBody = `
888
+ with (context) {
889
+ try {
890
+ ${compiled}
891
+ } catch (innerErr) {
892
+ console.warn('[Dolphin Template Eval Warning]:', innerErr);
893
+ return '';
894
+ }
895
+ }
896
+ `;
897
+ const fn = new Function("context", fnBody);
898
+ return fn(context);
899
+ } catch (e) {
900
+ console.error("[Dolphin Template Compiler Error]:", e);
901
+ let fallback = templateStr;
902
+ for (let key in context) {
903
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
904
+ fallback = fallback.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
905
+ }
906
+ return fallback;
907
+ }
908
+ }
791
909
  function sanitizeHTML(html) {
792
910
  if (typeof document === "undefined") return html;
793
911
  try {
@@ -911,6 +1029,9 @@ var DolphinModule = (() => {
911
1029
  }
912
1030
  const store = this.uiStores.get(storeName);
913
1031
  store[key] = val;
1032
+ if (this.options.debug) {
1033
+ console.log(`%c\u{1F4BE} [Dolphin Store Update]:`, "color: #ec4899; font-weight: bold;", `${storeName}.${key}`, "=", val);
1034
+ }
914
1035
  if (typeof document !== "undefined") {
915
1036
  const readElements = document.querySelectorAll(`[data-store-read="${storeName}.${key}"]`);
916
1037
  readElements.forEach((el) => {
@@ -990,6 +1111,42 @@ var DolphinModule = (() => {
990
1111
  }
991
1112
  return null;
992
1113
  };
1114
+ clientProto._executeStoreAction = function(expression, element) {
1115
+ this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
1116
+ const context = new Proxy({}, {
1117
+ has: (target, prop) => {
1118
+ return true;
1119
+ },
1120
+ get: (target, prop) => {
1121
+ if (typeof prop === "string") {
1122
+ return new Proxy({}, {
1123
+ get: (subTarget, subProp) => {
1124
+ if (typeof subProp === "string") {
1125
+ return this.getStoreState(prop, subProp);
1126
+ }
1127
+ },
1128
+ set: (subTarget, subProp, val) => {
1129
+ if (typeof subProp === "string") {
1130
+ this.setStoreState(prop, subProp, val);
1131
+ return true;
1132
+ }
1133
+ return false;
1134
+ }
1135
+ });
1136
+ }
1137
+ }
1138
+ });
1139
+ try {
1140
+ const fn = new Function("ctx", `with(ctx) { ${expression} }`);
1141
+ fn(context);
1142
+ } catch (err) {
1143
+ console.error("%c[Dolphin Store Action Error]:", "color: #ef4444; font-weight: bold;", err);
1144
+ if (element) {
1145
+ console.error("%cFailed Element:", "color: #f97316; font-weight: bold;", element);
1146
+ }
1147
+ console.error("%cFailed Expression:", "color: #3b82f6; font-style: italic;", expression);
1148
+ }
1149
+ };
993
1150
  clientProto._initDOMBinding = function() {
994
1151
  if (this._domInitialized) return;
995
1152
  this._domInitialized = true;
@@ -1163,6 +1320,14 @@ var DolphinModule = (() => {
1163
1320
  console.error(`[Dolphin] API ${evtName} Error:`, err);
1164
1321
  }
1165
1322
  }
1323
+ const storeActionBtn = e.target.closest(`[data-store-${evtName}]`);
1324
+ if (storeActionBtn) {
1325
+ if (evtName === "submit") e.preventDefault();
1326
+ const expr = storeActionBtn.getAttribute(`data-store-${evtName}`);
1327
+ if (expr) {
1328
+ this._executeStoreAction(expr, storeActionBtn);
1329
+ }
1330
+ }
1166
1331
  });
1167
1332
  });
1168
1333
  this.subscribe("#", (payload, topic) => {
@@ -1188,21 +1353,11 @@ var DolphinModule = (() => {
1188
1353
  if (Array.isArray(result)) {
1189
1354
  let combinedHTML = "";
1190
1355
  for (const item of result) {
1191
- let finalItemHTML = template;
1192
- for (let key in item) {
1193
- const escapedKey = escapeRegExp(key);
1194
- finalItemHTML = finalItemHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), item[key] !== void 0 && item[key] !== null ? item[key] : "");
1195
- }
1196
- combinedHTML += finalItemHTML;
1356
+ combinedHTML += renderTemplate(template, item);
1197
1357
  }
1198
1358
  scheduleDOMUpdate(el, combinedHTML);
1199
1359
  } else {
1200
- let finalHTML = template;
1201
- for (let key in result) {
1202
- const escapedKey = escapeRegExp(key);
1203
- finalHTML = finalHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), result[key] !== void 0 && result[key] !== null ? result[key] : "");
1204
- }
1205
- scheduleDOMUpdate(el, finalHTML);
1360
+ scheduleDOMUpdate(el, renderTemplate(template, result));
1206
1361
  }
1207
1362
  } else {
1208
1363
  if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
@@ -1296,21 +1451,11 @@ var DolphinModule = (() => {
1296
1451
  if (Array.isArray(payload)) {
1297
1452
  let combinedHTML = "";
1298
1453
  for (const item of payload) {
1299
- let finalItemHTML = template;
1300
- for (let key in item) {
1301
- const escapedKey = escapeRegExp(key);
1302
- finalItemHTML = finalItemHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), item[key] !== void 0 && item[key] !== null ? item[key] : "");
1303
- }
1304
- combinedHTML += finalItemHTML;
1454
+ combinedHTML += renderTemplate(template, item);
1305
1455
  }
1306
1456
  scheduleDOMUpdate(el, combinedHTML);
1307
1457
  } else {
1308
- let finalHTML = template;
1309
- for (let key in payload) {
1310
- const escapedKey = escapeRegExp(key);
1311
- finalHTML = finalHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), payload[key] !== void 0 && payload[key] !== null ? payload[key] : "");
1312
- }
1313
- scheduleDOMUpdate(el, finalHTML);
1458
+ scheduleDOMUpdate(el, renderTemplate(template, payload));
1314
1459
  }
1315
1460
  return;
1316
1461
  }
@@ -2037,6 +2182,21 @@ var DolphinModule = (() => {
2037
2182
  attachTesting(DolphinClient.prototype);
2038
2183
  if (typeof window !== "undefined") {
2039
2184
  window.DolphinClient = DolphinClient;
2185
+ document.addEventListener("DOMContentLoaded", () => {
2186
+ if (!window.dolphin) {
2187
+ const scriptEl = document.querySelector('script[src*="dolphin-client"]');
2188
+ const debugMode = scriptEl ? scriptEl.getAttribute("data-debug") === "true" : false;
2189
+ const dolphin = new DolphinClient(void 0, void 0, { debug: debugMode });
2190
+ window.dolphin = dolphin;
2191
+ if (debugMode) {
2192
+ console.log("%c\u{1F42C} [Dolphin Client] Auto-initialized local reactive engine!", "color: #06b6d4; font-weight: bold; font-size: 14px;");
2193
+ console.log('%c\u{1F449} Tip: You can access the client instance via "window.dolphin" in console.', "color: #94a3b8; font-style: italic;");
2194
+ }
2195
+ if (document.querySelector('[data-store-write="app.username"]')) {
2196
+ dolphin.setStoreState("app", "username", "\u0928\u092E\u0938\u094D\u0924\u0947 \u0938\u093E\u0925\u0940!");
2197
+ }
2198
+ }
2199
+ });
2040
2200
  }
2041
2201
  return __toCommonJS(index_exports);
2042
2202
  })();
@@ -0,0 +1,26 @@
1
+ var DolphinModule=(()=>{var $=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var V=Object.prototype.hasOwnProperty;var J=(p,t)=>{for(var e in t)$(p,e,{get:t[e],enumerable:!0})},Q=(p,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of K(t))!V.call(p,r)&&r!==e&&$(p,r,{get:()=>t[r],enumerable:!(n=z(t,r))||n.enumerable});return p};var X=p=>Q($({},"__esModule",{value:!0}),p);var Z={};J(Z,{DolphinClient:()=>v});var x=class{client;constructor(t){return this.client=t,this._createProxy([])}_createProxy(t){let e=t.join("/"),n=i=>this.request("GET",e,null,i);n.get=(i,a)=>typeof i=="string"?this.request("GET",i,null,a):this.request("GET",e,null,i),n.post=(i,a,l)=>typeof i=="string"?this.request("POST",i,a,l):this.request("POST",e,i,a),n.put=(i,a,l)=>typeof i=="string"?this.request("PUT",i,a,l):this.request("PUT",e,i,a),n.patch=(i,a,l)=>typeof i=="string"?this.request("PATCH",i,a,l):this.request("PATCH",e,i,a),n.del=(i,a)=>typeof i=="string"?this.request("DELETE",i,null,a):this.request("DELETE",e,null,i),n.request=(i,a,l,b)=>{let T=a?`${e}/${a.startsWith("/")?a.slice(1):a}`:e;return this.request(i,T,l,b)},n.requestDirect=(i,a,l,b)=>this.requestDirect(i,a,l,b);let r=["get","post","put","patch","del","request","requestDirect"];return new Proxy(n,{get:(i,a)=>typeof a=="string"&&!r.includes(a)?this._createProxy([...t,a]):i[a]})}async request(t,e,n=null,r={}){if(this.client.offline){let i=this.client.offline.isOnline,a=`${t.toUpperCase()}:${e}`;if(t.toUpperCase()==="GET")if(i)try{let l=await this.requestDirect(t,e,n,r);return await this.client.offline.setCache(a,l),l}catch(l){let b=await this.client.offline.getCache(a);if(b!=null)return b;throw l}else{let l=await this.client.offline.getCache(a);if(l!=null)return l;throw{status:503,data:{error:"Offline, no cache available"}}}else return i?this.requestDirect(t,e,n,r):(await this.client.offline.queueMutation(t,e,n),this.client._dispatch("offline:queued",{method:t,path:e,body:n}),{success:!0,offline:!0,message:"Mutation queued offline"})}return this.requestDirect(t,e,n,r)}async requestDirect(t,e,n=null,r={}){let i=r._isRetry===!0,a=`${this.client.httpUrl}${e.startsWith("/")?e:"/"+e}`;this.client.options.debug&&console.log("%c\u{1F680} [Dolphin API Request]:","color: #3b82f6; font-weight: bold;",t.toUpperCase(),e,n||"");let l=new AbortController,b=setTimeout(()=>l.abort(),this.client.options.timeout),T={"Content-Type":"application/json",...r.headers||{}};this.client.accessToken&&(T.Authorization=`Bearer ${this.client.accessToken}`);let h={...r};delete h._isRetry;try{let o=await fetch(a,{method:t,headers:T,signal:l.signal,...n?{body:JSON.stringify(n)}:{},...h});if(clearTimeout(b),o.status===401&&!i&&this.client.options.autoRefreshToken&&await this.client.auth._silentRefresh())return this.request(t,e,n,{...r,_isRetry:!0});let s=(o.headers.get("content-type")||"").includes("application/json")?await o.json():await o.text();if(!o.ok)throw{status:o.status,data:s};if(this.client.options.debug&&console.log("%c\u2705 [Dolphin API Success]:","color: #10b981; font-weight: bold;",t.toUpperCase(),e,s),s&&typeof s=="object"&&s.accessToken&&(this.client.setToken(s.accessToken),s.user&&(this.client.auth.user=s.user)),this.client.options.autoBroadcast&&["POST","PUT","PATCH","DELETE"].includes(t.toUpperCase())){let c=e.startsWith("/")?e.substring(1):e;this.client.publish(c,{method:t.toUpperCase(),payload:n,result:s})}return s}catch(o){throw clearTimeout(b),this.client.options.debug&&console.error("%c\u274C [Dolphin API Error]:","color: #ef4444; font-weight: bold;",t.toUpperCase(),e,o),o.name==="AbortError"?{status:408,data:{error:"Request timed out"}}:o}}};var C=class{client;user;_refreshing;constructor(t){this.client=t,this.user=null,this._refreshing=!1}async login(t,e){let n=await this.client.api.post("/api/auth/login",{email:t,password:e});return n.accessToken&&(this.client.setToken(n.accessToken),this.user=n.user||null),n}async register(t){return this.client.api.post("/api/auth/register",t)}async me(){let t=await this.client.api.get("/api/auth/me");return t.success&&(this.user=t.data),t}async logout(){try{await this.client.api.post("/api/auth/logout")}catch{}this.client.setToken(null),this.user=null}async refresh(){return this._silentRefresh()}async _silentRefresh(){if(this._refreshing)return!1;this._refreshing=!0;try{let t=await this.client.api.post("/api/auth/refresh",null,{_isRetry:!0});return t.accessToken?(this.client.setToken(t.accessToken),!0):!1}catch{return this.client.setToken(null),!1}finally{this._refreshing=!1}}async verify2FA(t,e){let n={code:t,email:e||this.user?.email},r=await this.client.api.post("/api/auth/2fa/verify",n);return r.accessToken&&(this.client.setToken(r.accessToken),r.user&&(this.user=r.user)),r}async enable2FA(){return this.client.api.post("/api/auth/2fa/enable")}async disable2FA(t){return this.client.api.post("/api/auth/2fa/disable",{code:t})}async forgotPassword(t){return this.client.api.post("/api/auth/forgot-password",{email:t})}async resetPassword(t,e){return this.client.api.post("/api/auth/reset-password",{token:t,newPassword:e})}};var M=class{client;data;listeners;subscribed;constructor(t){return this.client=t,this.data=new Map,this.listeners=new Set,this.subscribed=new Set,new Proxy(this,{get:(e,n)=>{if(n in e)return e[n];if(typeof n=="string")return this._getCollection(n)}})}_getCollection(t){if(!this.data.has(t)){let e={_rawItems:[],items:[],loading:!0,error:null,success:!1,_filter:null,_sort:null,where:n=>(e._filter=n,this._applyTransform(e),e),orderBy:(n,r="asc")=>(e._sort={key:n,direction:r},this._applyTransform(e),e),reset:()=>(e._filter=null,e._sort=null,this._applyTransform(e),e)};this.data.set(t,e),this._fetchAndSync(t)}return this.data.get(t)}async _fetchAndSync(t){let e=this.data.get(t);try{let n=await this.client.api.get(`/${t.toLowerCase()}`);e._rawItems=Array.isArray(n)?n:n.data||[],e.loading=!1,e.success=!0,e.error=null,this._applyTransform(e),this.subscribed.has(t)||(this.client.subscribe(`db:sync/${t.toLowerCase()}`,r=>{this._handleRemoteUpdate(t,r)}),this.subscribed.add(t))}catch(n){e.loading=!1,e.success=!1,e.error=n.data?.error||n.message||"Fetch failed",this._notify()}}_applyTransform(t){let e=[...t._rawItems];if(t._filter&&(e=e.filter(t._filter)),t._sort){let{key:n,direction:r}=t._sort;e.sort((i,a)=>i[n]===a[n]?0:(i[n]>a[n]?1:-1)*(r==="asc"?1:-1))}t.items=e,this._notify()}_handleRemoteUpdate(t,e){let n=this.data.get(t);if(!n)return;let{type:r,data:i}=e,a=n._rawItems;r==="create"?a=[...a,i]:r==="update"?a=a.map(l=>l.id===i.id||l._id===i._id?{...l,...i}:l):r==="delete"&&(a=a.filter(l=>!(i.id!=null&&l.id===i.id||i._id!=null&&l._id===i._id))),n._rawItems=a,this._applyTransform(n)}subscribe(t){return this.listeners.add(t),()=>this.listeners.delete(t)}getSnapshot(t){return this.data.get(t)||{items:[],loading:!1,error:null,success:!1}}_notify(){this.listeners.forEach(t=>t())}};var v=class{host;httpUrl;deviceId;options;socket;storage;accessToken;api;auth;store;handlers;signalHandlers;fileHandlers;_offlineQueue;reconnectAttempts;_attachedListeners;constructor(t="",e="",n={}){!t&&typeof window<"u"&&(t=window.location.host);let r="http:";t.startsWith("https://")?r="https:":t.startsWith("http://")?r="http:":typeof window<"u"&&(r=window.location.protocol),this.host=(t||"localhost").replace(/\/$/,"").replace(/^https?:\/\//,""),this.httpUrl=`${r}//${this.host}`,this.deviceId=e||"web_"+Math.random().toString(36).substr(2,8),this.options={timeout:15e3,chunkSize:65536,maxReconnect:5,autoRefreshToken:!0,debug:!1,...n},this.socket=null,this.storage=typeof localStorage<"u"?localStorage:{getItem:()=>null,setItem:()=>{},removeItem:()=>{}},this.accessToken=this.storage.getItem("dolphin_token"),this.api=new x(this),this.auth=new C(this),this.store=new M(this),this.handlers=new Map,this.signalHandlers=new Set,this.fileHandlers=new Set,this._offlineQueue=[],this.reconnectAttempts=0,this._attachedListeners=[],typeof window<"u"&&typeof this._initDOMBinding=="function"&&this._initDOMBinding(),typeof this._initOffline=="function"&&this._initOffline(),typeof this._initA11y=="function"&&this._initA11y(),typeof this._initI18n=="function"&&this._initI18n(),typeof this._initDragDrop=="function"&&this._initDragDrop(),typeof this._initCollab=="function"&&this._initCollab()}setToken(t){this.accessToken=t,t?this.storage.setItem("dolphin_token",t):this.storage.removeItem("dolphin_token")}async connect(){return new Promise((t,e)=>{let r=`${this.httpUrl.startsWith("https")?"wss:":"ws:"}//${this.host}/realtime?deviceId=${this.deviceId}`;console.log(`[Dolphin] Connecting to ${r}...`),this.socket=new WebSocket(r),this.socket.onopen=()=>{console.log(`[Dolphin] Connected as "${this.deviceId}" \u{1F42C}`),this.reconnectAttempts=0,this._flushOfflineQueue(),t()},this.socket.onmessage=i=>this._handleMessage(i.data),this.socket.onclose=()=>{console.warn("[Dolphin] Connection closed"),this._maybeReconnect()},this.socket.onerror=i=>{console.error("[Dolphin] WebSocket error:",i),e(i)}})}disconnect(){this.socket&&(this.socket.onclose=null,this.socket.close(),this.socket=null),this.cleanupDomListeners()}_handleMessage(t){try{let e=JSON.parse(t);this.options.debug&&console.log("%c\u{1F4E5} [Dolphin WS Incoming]:","color: #eab308; font-weight: bold;",e),e.type&&e.from&&(e.to===this.deviceId||e.to==="all")&&(e.msgId&&e.type!=="ACK"&&this._sendAck(e.from,e.msgId),this.signalHandlers.forEach(n=>n(e))),e.type==="FILE_AVAILABLE"&&this.fileHandlers.forEach(n=>n(e)),e.type==="FILE_CHUNK"&&(this.saveFileProgress(e.fileId,e.chunkIndex),this._dispatch("file:chunk",e),this._dispatch(`file:chunk/${e.fileId}`,e)),e.type==="FILE_UPLOAD_ACK"&&this._dispatch(`file:upload:ack/${e.fileId}`,e),e.type==="PULL_RESPONSE"&&(this._dispatch("pull:response",e.payload,e.topic),this._dispatch(`pull:response/${e.topic}`,e.payload,e.topic)),e.topic&&e.payload!==void 0&&this.handlers.forEach((n,r)=>{this._matchTopic(r,e.topic)&&n.forEach(i=>i(e.payload,e.topic))})}catch{this._dispatch("raw",t)}}_dispatch(t,e,n){let r=this.handlers.get(t);r&&r.forEach(i=>i(e,n||t))}_sendRaw(t){this.options.debug&&console.log("%c\u{1F4E4} [Dolphin WS Outgoing]:","color: #8b5cf6; font-weight: bold;",t);let e=typeof t=="string"?t:JSON.stringify(t);this.socket&&this.socket.readyState===WebSocket.OPEN?this.socket.send(e):this._offlineQueue.length<100&&this._offlineQueue.push(e)}_flushOfflineQueue(){for(;this._offlineQueue.length>0;){let t=this._offlineQueue.shift();this.socket&&this.socket.readyState===WebSocket.OPEN&&this.socket.send(t)}}_sendAck(t,e){this._sendRaw({type:"ACK",from:this.deviceId,to:t,data:{ackId:e},timestamp:Date.now()})}_matchTopic(t,e){if(t===e||t==="#")return!0;let n=t.split("/"),r=e.split("/");if(n.length!==r.length&&!t.includes("#"))return!1;for(let i=0;i<n.length;i++){if(n[i]==="#")return!0;if(n[i]!=="+"&&n[i]!==r[i])return!1}return n.length===r.length}_maybeReconnect(){if(this.reconnectAttempts<this.options.maxReconnect){this.reconnectAttempts++;let t=Math.pow(2,this.reconnectAttempts)*1e3;console.log(`[Dolphin] Reconnecting in ${t/1e3}s (attempt ${this.reconnectAttempts})...`),setTimeout(()=>this.connect().catch(()=>{}),t)}else console.error("[Dolphin] Max reconnect attempts reached.")}subscribe(t,e){this.handlers.has(t)||(this.handlers.set(t,new Set),this._sendRaw({type:"sub",topic:t})),this.handlers.get(t).add(e)}unsubscribe(t,e){if(this.handlers.has(t)){let n=this.handlers.get(t);n.delete(e),n.size===0&&(this.handlers.delete(t),this._sendRaw({type:"unsub",topic:t}))}}publish(t,e){this._sendRaw({topic:t,payload:e})}pubPush(t,e){this._sendRaw({type:"pub",topic:t,payload:e})}subPull(t,e=10){this._sendRaw({type:"PULL_REQUEST",topic:t,count:e})}async pubFile(t,e,n="",r){let i;e instanceof Blob?i=await e.arrayBuffer():e instanceof ArrayBuffer?i=e:i=e.buffer||e;let a=new Uint8Array(i),l=this.options.chunkSize,b=Math.ceil(a.length/l);this._sendRaw({type:"FILE_UPLOAD_START",fileId:t,name:n,size:a.length,totalChunks:b,chunkSize:l});for(let T=0;T<b;T++){let h=a.slice(T*l,(T+1)*l),o=this._uint8ToBase64(h);this._sendRaw({type:"FILE_UPLOAD_CHUNK",fileId:t,chunkIndex:T,totalChunks:b,data:o}),r&&r(Math.round((T+1)/b*100)),T%10===0&&await new Promise(u=>setTimeout(u,0))}this._sendRaw({type:"FILE_UPLOAD_DONE",fileId:t})}_uint8ToBase64(t){let e="";for(let n=0;n<t.length;n++)e+=String.fromCharCode(t[n]);return typeof btoa<"u"?btoa(e):Buffer.from(e,"binary").toString("base64")}subFile(t,e=0){this._sendRaw({type:"FILE_REQUEST",fileId:t,startChunk:e})}resumeFile(t){let e=parseInt(this.storage.getItem(`dolphin_file_${t}`)||"-1");this.subFile(t,e+1)}saveFileProgress(t,e){this.storage.setItem(`dolphin_file_${t}`,e.toString())}onSignal(t){this.signalHandlers.add(t)}offSignal(t){this.signalHandlers.delete(t)}onFileAvailable(t){this.fileHandlers.add(t)}offFileAvailable(t){this.fileHandlers.delete(t)}addDomListener(t,e,n){t&&(t.addEventListener(e,n),this._attachedListeners=this._attachedListeners||[],this._attachedListeners.push({target:t,event:e,cb:n}))}cleanupDomListeners(){this._attachedListeners&&(this._attachedListeners.forEach(({target:t,event:e,cb:n})=>{try{t.removeEventListener(e,n)}catch{}}),this._attachedListeners=[])}};function R(p){function t(h){return h.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function e(h){let o=h.getAttribute("data-rt-template");if(!o)return null;if(typeof document<"u"&&!o.includes("<"))try{let u=document.querySelector(o);if(u)return u.innerHTML}catch{}return o}function n(h,o){if(!h.includes("{#if")&&!h.includes("{#each")){let u=h;for(let s in o){let c=s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");u=u.replace(new RegExp(`\\{\\{${c}\\}\\}`,"g"),o[s]!==void 0&&o[s]!==null?o[s]:"")}return u}try{let u=g=>g.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/\t/g,"\\t"),s=`let out = "";
2
+ `,c=0,m=/(\{\{([\s\S]*?)\}\}|\{#if\s+([\s\S]*?)\}|\{:else\s+if\s+([\s\S]*?)\}|\{:else\}|\{\/if\}|\{#each\s+([\s\S]*?)\s+as\s+([a-zA-Z_$][a-zA-Z0-9_$]*)(?:\s*,\s*([a-zA-Z_$][a-zA-Z0-9_$]*))?\}|\{\/each\}|\{([^{}]+?)\})/g,f=[],d;for(;(d=m.exec(h))!==null;){let g=h.slice(c,d.index);g&&(s+=`out += "${u(g)}";
3
+ `);let w=d[0];if(w.startsWith("{{")){let A=d[2];s+=`out += (${A} !== undefined && ${A} !== null ? ${A} : "");
4
+ `}else if(w.startsWith("{#if")){let A=d[3];s+=`if (${A}) {
5
+ `}else if(w.startsWith("{:else if")){let A=d[4];s+=`} else if (${A}) {
6
+ `}else if(w.startsWith("{:else}"))s+=`} else {
7
+ `;else if(w.startsWith("{/if}"))s+=`}
8
+ `;else if(w.startsWith("{#each")){let A=d[5],D=d[6],S=d[7];f.push({indexVar:S}),s+=`if (typeof ${A} !== "undefined" && ${A} !== null && Array.isArray(${A})) {
9
+ `,S&&(s+=` let ${S} = 0;
10
+ `),s+=` for (let ${D} of ${A}) {
11
+ `}else if(w.startsWith("{/each}")){let A=f.pop();A&&A.indexVar&&(s+=` ${A.indexVar}++;
12
+ `),s+=` }
13
+ }
14
+ `}else if(w.startsWith("{")){let A=d[8];A&&(s+=`out += (${A} !== undefined && ${A} !== null ? ${A} : "");
15
+ `)}c=m.lastIndex}let y=h.slice(c);y&&(s+=`out += "${u(y)}";
16
+ `),s+=`return out;
17
+ `;let _=`
18
+ with (context) {
19
+ try {
20
+ ${s}
21
+ } catch (innerErr) {
22
+ console.warn('[Dolphin Template Eval Warning]:', innerErr);
23
+ return '';
24
+ }
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);})();
package/dist/index.cjs CHANGED
@@ -108,6 +108,9 @@ var APIHandler = class {
108
108
  async requestDirect(method, path, body = null, options = {}) {
109
109
  const _isRetry = options._isRetry === true;
110
110
  const url = `${this.client.httpUrl}${path.startsWith("/") ? path : "/" + path}`;
111
+ if (this.client.options.debug) {
112
+ console.log(`%c\u{1F680} [Dolphin API Request]:`, "color: #3b82f6; font-weight: bold;", method.toUpperCase(), path, body || "");
113
+ }
111
114
  const controller = new AbortController();
112
115
  const timeoutId = setTimeout(
113
116
  () => controller.abort(),
@@ -140,6 +143,9 @@ var APIHandler = class {
140
143
  const contentType = response.headers.get("content-type") || "";
141
144
  const data = contentType.includes("application/json") ? await response.json() : await response.text();
142
145
  if (!response.ok) throw { status: response.status, data };
146
+ if (this.client.options.debug) {
147
+ console.log(`%c\u2705 [Dolphin API Success]:`, "color: #10b981; font-weight: bold;", method.toUpperCase(), path, data);
148
+ }
143
149
  if (data && typeof data === "object") {
144
150
  if (data.accessToken) {
145
151
  this.client.setToken(data.accessToken);
@@ -153,6 +159,9 @@ var APIHandler = class {
153
159
  return data;
154
160
  } catch (err) {
155
161
  clearTimeout(timeoutId);
162
+ if (this.client.options.debug) {
163
+ console.error(`%c\u274C [Dolphin API Error]:`, "color: #ef4444; font-weight: bold;", method.toUpperCase(), path, err);
164
+ }
156
165
  if (err.name === "AbortError") {
157
166
  throw { status: 408, data: { error: "Request timed out" } };
158
167
  }
@@ -437,6 +446,7 @@ var DolphinClient = class {
437
446
  // 64 KB
438
447
  maxReconnect: 5,
439
448
  autoRefreshToken: true,
449
+ debug: false,
440
450
  ...options
441
451
  };
442
452
  this.socket = null;
@@ -519,6 +529,9 @@ var DolphinClient = class {
519
529
  _handleMessage(data) {
520
530
  try {
521
531
  const msg = JSON.parse(data);
532
+ if (this.options.debug) {
533
+ console.log("%c\u{1F4E5} [Dolphin WS Incoming]:", "color: #eab308; font-weight: bold;", msg);
534
+ }
522
535
  if (msg.type && msg.from && (msg.to === this.deviceId || msg.to === "all")) {
523
536
  if (msg.msgId && msg.type !== "ACK") this._sendAck(msg.from, msg.msgId);
524
537
  this.signalHandlers.forEach((h) => h(msg));
@@ -556,6 +569,9 @@ var DolphinClient = class {
556
569
  }
557
570
  /** @private */
558
571
  _sendRaw(msg) {
572
+ if (this.options.debug) {
573
+ console.log("%c\u{1F4E4} [Dolphin WS Outgoing]:", "color: #8b5cf6; font-weight: bold;", msg);
574
+ }
559
575
  const str = typeof msg === "string" ? msg : JSON.stringify(msg);
560
576
  if (this.socket && this.socket.readyState === WebSocket.OPEN) {
561
577
  this.socket.send(str);
@@ -788,6 +804,108 @@ function attachDOMBinding(clientProto) {
788
804
  }
789
805
  return template;
790
806
  }
807
+ function renderTemplate(templateStr, context) {
808
+ if (!templateStr.includes("{#if") && !templateStr.includes("{#each")) {
809
+ let result = templateStr;
810
+ for (let key in context) {
811
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
812
+ result = result.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
813
+ }
814
+ return result;
815
+ }
816
+ try {
817
+ const escapeString = (str) => {
818
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
819
+ };
820
+ let compiled = 'let out = "";\n';
821
+ let lastIndex = 0;
822
+ const regex = /(\{\{([\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;
823
+ const eachStack = [];
824
+ let match;
825
+ while ((match = regex.exec(templateStr)) !== null) {
826
+ const plainText = templateStr.slice(lastIndex, match.index);
827
+ if (plainText) {
828
+ compiled += `out += "${escapeString(plainText)}";
829
+ `;
830
+ }
831
+ const token = match[0];
832
+ if (token.startsWith("{{")) {
833
+ const expr = match[2];
834
+ compiled += `out += (${expr} !== undefined && ${expr} !== null ? ${expr} : "");
835
+ `;
836
+ } else if (token.startsWith("{#if")) {
837
+ const expr = match[3];
838
+ compiled += `if (${expr}) {
839
+ `;
840
+ } else if (token.startsWith("{:else if")) {
841
+ const expr = match[4];
842
+ compiled += `} else if (${expr}) {
843
+ `;
844
+ } else if (token.startsWith("{:else}")) {
845
+ compiled += `} else {
846
+ `;
847
+ } else if (token.startsWith("{/if}")) {
848
+ compiled += `}
849
+ `;
850
+ } else if (token.startsWith("{#each")) {
851
+ const expr = match[5];
852
+ const itemVar = match[6];
853
+ const indexVar = match[7];
854
+ eachStack.push({ indexVar });
855
+ compiled += `if (typeof ${expr} !== "undefined" && ${expr} !== null && Array.isArray(${expr})) {
856
+ `;
857
+ if (indexVar) {
858
+ compiled += ` let ${indexVar} = 0;
859
+ `;
860
+ }
861
+ compiled += ` for (let ${itemVar} of ${expr}) {
862
+ `;
863
+ } else if (token.startsWith("{/each}")) {
864
+ const info = eachStack.pop();
865
+ if (info && info.indexVar) {
866
+ compiled += ` ${info.indexVar}++;
867
+ `;
868
+ }
869
+ compiled += ` }
870
+ }
871
+ `;
872
+ } else if (token.startsWith("{")) {
873
+ const expr = match[8];
874
+ if (expr) {
875
+ compiled += `out += (${expr} !== undefined && ${expr} !== null ? ${expr} : "");
876
+ `;
877
+ }
878
+ }
879
+ lastIndex = regex.lastIndex;
880
+ }
881
+ const remaining = templateStr.slice(lastIndex);
882
+ if (remaining) {
883
+ compiled += `out += "${escapeString(remaining)}";
884
+ `;
885
+ }
886
+ compiled += "return out;\n";
887
+ const fnBody = `
888
+ with (context) {
889
+ try {
890
+ ${compiled}
891
+ } catch (innerErr) {
892
+ console.warn('[Dolphin Template Eval Warning]:', innerErr);
893
+ return '';
894
+ }
895
+ }
896
+ `;
897
+ const fn = new Function("context", fnBody);
898
+ return fn(context);
899
+ } catch (e) {
900
+ console.error("[Dolphin Template Compiler Error]:", e);
901
+ let fallback = templateStr;
902
+ for (let key in context) {
903
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
904
+ fallback = fallback.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
905
+ }
906
+ return fallback;
907
+ }
908
+ }
791
909
  function sanitizeHTML(html) {
792
910
  if (typeof document === "undefined") return html;
793
911
  try {
@@ -911,6 +1029,9 @@ function attachDOMBinding(clientProto) {
911
1029
  }
912
1030
  const store = this.uiStores.get(storeName);
913
1031
  store[key] = val;
1032
+ if (this.options.debug) {
1033
+ console.log(`%c\u{1F4BE} [Dolphin Store Update]:`, "color: #ec4899; font-weight: bold;", `${storeName}.${key}`, "=", val);
1034
+ }
914
1035
  if (typeof document !== "undefined") {
915
1036
  const readElements = document.querySelectorAll(`[data-store-read="${storeName}.${key}"]`);
916
1037
  readElements.forEach((el) => {
@@ -990,6 +1111,42 @@ function attachDOMBinding(clientProto) {
990
1111
  }
991
1112
  return null;
992
1113
  };
1114
+ clientProto._executeStoreAction = function(expression, element) {
1115
+ this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
1116
+ const context = new Proxy({}, {
1117
+ has: (target, prop) => {
1118
+ return true;
1119
+ },
1120
+ get: (target, prop) => {
1121
+ if (typeof prop === "string") {
1122
+ return new Proxy({}, {
1123
+ get: (subTarget, subProp) => {
1124
+ if (typeof subProp === "string") {
1125
+ return this.getStoreState(prop, subProp);
1126
+ }
1127
+ },
1128
+ set: (subTarget, subProp, val) => {
1129
+ if (typeof subProp === "string") {
1130
+ this.setStoreState(prop, subProp, val);
1131
+ return true;
1132
+ }
1133
+ return false;
1134
+ }
1135
+ });
1136
+ }
1137
+ }
1138
+ });
1139
+ try {
1140
+ const fn = new Function("ctx", `with(ctx) { ${expression} }`);
1141
+ fn(context);
1142
+ } catch (err) {
1143
+ console.error("%c[Dolphin Store Action Error]:", "color: #ef4444; font-weight: bold;", err);
1144
+ if (element) {
1145
+ console.error("%cFailed Element:", "color: #f97316; font-weight: bold;", element);
1146
+ }
1147
+ console.error("%cFailed Expression:", "color: #3b82f6; font-style: italic;", expression);
1148
+ }
1149
+ };
993
1150
  clientProto._initDOMBinding = function() {
994
1151
  if (this._domInitialized) return;
995
1152
  this._domInitialized = true;
@@ -1163,6 +1320,14 @@ function attachDOMBinding(clientProto) {
1163
1320
  console.error(`[Dolphin] API ${evtName} Error:`, err);
1164
1321
  }
1165
1322
  }
1323
+ const storeActionBtn = e.target.closest(`[data-store-${evtName}]`);
1324
+ if (storeActionBtn) {
1325
+ if (evtName === "submit") e.preventDefault();
1326
+ const expr = storeActionBtn.getAttribute(`data-store-${evtName}`);
1327
+ if (expr) {
1328
+ this._executeStoreAction(expr, storeActionBtn);
1329
+ }
1330
+ }
1166
1331
  });
1167
1332
  });
1168
1333
  this.subscribe("#", (payload, topic) => {
@@ -1188,21 +1353,11 @@ function attachDOMBinding(clientProto) {
1188
1353
  if (Array.isArray(result)) {
1189
1354
  let combinedHTML = "";
1190
1355
  for (const item of result) {
1191
- let finalItemHTML = template;
1192
- for (let key in item) {
1193
- const escapedKey = escapeRegExp(key);
1194
- finalItemHTML = finalItemHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), item[key] !== void 0 && item[key] !== null ? item[key] : "");
1195
- }
1196
- combinedHTML += finalItemHTML;
1356
+ combinedHTML += renderTemplate(template, item);
1197
1357
  }
1198
1358
  scheduleDOMUpdate(el, combinedHTML);
1199
1359
  } else {
1200
- let finalHTML = template;
1201
- for (let key in result) {
1202
- const escapedKey = escapeRegExp(key);
1203
- finalHTML = finalHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), result[key] !== void 0 && result[key] !== null ? result[key] : "");
1204
- }
1205
- scheduleDOMUpdate(el, finalHTML);
1360
+ scheduleDOMUpdate(el, renderTemplate(template, result));
1206
1361
  }
1207
1362
  } else {
1208
1363
  if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
@@ -1296,21 +1451,11 @@ function attachDOMBinding(clientProto) {
1296
1451
  if (Array.isArray(payload)) {
1297
1452
  let combinedHTML = "";
1298
1453
  for (const item of payload) {
1299
- let finalItemHTML = template;
1300
- for (let key in item) {
1301
- const escapedKey = escapeRegExp(key);
1302
- finalItemHTML = finalItemHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), item[key] !== void 0 && item[key] !== null ? item[key] : "");
1303
- }
1304
- combinedHTML += finalItemHTML;
1454
+ combinedHTML += renderTemplate(template, item);
1305
1455
  }
1306
1456
  scheduleDOMUpdate(el, combinedHTML);
1307
1457
  } else {
1308
- let finalHTML = template;
1309
- for (let key in payload) {
1310
- const escapedKey = escapeRegExp(key);
1311
- finalHTML = finalHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), payload[key] !== void 0 && payload[key] !== null ? payload[key] : "");
1312
- }
1313
- scheduleDOMUpdate(el, finalHTML);
1458
+ scheduleDOMUpdate(el, renderTemplate(template, payload));
1314
1459
  }
1315
1460
  return;
1316
1461
  }
@@ -2037,4 +2182,19 @@ attachPwa(DolphinClient.prototype);
2037
2182
  attachTesting(DolphinClient.prototype);
2038
2183
  if (typeof window !== "undefined") {
2039
2184
  window.DolphinClient = DolphinClient;
2185
+ document.addEventListener("DOMContentLoaded", () => {
2186
+ if (!window.dolphin) {
2187
+ const scriptEl = document.querySelector('script[src*="dolphin-client"]');
2188
+ const debugMode = scriptEl ? scriptEl.getAttribute("data-debug") === "true" : false;
2189
+ const dolphin = new DolphinClient(void 0, void 0, { debug: debugMode });
2190
+ window.dolphin = dolphin;
2191
+ if (debugMode) {
2192
+ console.log("%c\u{1F42C} [Dolphin Client] Auto-initialized local reactive engine!", "color: #06b6d4; font-weight: bold; font-size: 14px;");
2193
+ console.log('%c\u{1F449} Tip: You can access the client instance via "window.dolphin" in console.', "color: #94a3b8; font-style: italic;");
2194
+ }
2195
+ if (document.querySelector('[data-store-write="app.username"]')) {
2196
+ dolphin.setStoreState("app", "username", "\u0928\u092E\u0938\u094D\u0924\u0947 \u0938\u093E\u0925\u0940!");
2197
+ }
2198
+ }
2199
+ });
2040
2200
  }
package/dist/index.js CHANGED
@@ -83,6 +83,9 @@ var APIHandler = class {
83
83
  async requestDirect(method, path, body = null, options = {}) {
84
84
  const _isRetry = options._isRetry === true;
85
85
  const url = `${this.client.httpUrl}${path.startsWith("/") ? path : "/" + path}`;
86
+ if (this.client.options.debug) {
87
+ console.log(`%c\u{1F680} [Dolphin API Request]:`, "color: #3b82f6; font-weight: bold;", method.toUpperCase(), path, body || "");
88
+ }
86
89
  const controller = new AbortController();
87
90
  const timeoutId = setTimeout(
88
91
  () => controller.abort(),
@@ -115,6 +118,9 @@ var APIHandler = class {
115
118
  const contentType = response.headers.get("content-type") || "";
116
119
  const data = contentType.includes("application/json") ? await response.json() : await response.text();
117
120
  if (!response.ok) throw { status: response.status, data };
121
+ if (this.client.options.debug) {
122
+ console.log(`%c\u2705 [Dolphin API Success]:`, "color: #10b981; font-weight: bold;", method.toUpperCase(), path, data);
123
+ }
118
124
  if (data && typeof data === "object") {
119
125
  if (data.accessToken) {
120
126
  this.client.setToken(data.accessToken);
@@ -128,6 +134,9 @@ var APIHandler = class {
128
134
  return data;
129
135
  } catch (err) {
130
136
  clearTimeout(timeoutId);
137
+ if (this.client.options.debug) {
138
+ console.error(`%c\u274C [Dolphin API Error]:`, "color: #ef4444; font-weight: bold;", method.toUpperCase(), path, err);
139
+ }
131
140
  if (err.name === "AbortError") {
132
141
  throw { status: 408, data: { error: "Request timed out" } };
133
142
  }
@@ -412,6 +421,7 @@ var DolphinClient = class {
412
421
  // 64 KB
413
422
  maxReconnect: 5,
414
423
  autoRefreshToken: true,
424
+ debug: false,
415
425
  ...options
416
426
  };
417
427
  this.socket = null;
@@ -494,6 +504,9 @@ var DolphinClient = class {
494
504
  _handleMessage(data) {
495
505
  try {
496
506
  const msg = JSON.parse(data);
507
+ if (this.options.debug) {
508
+ console.log("%c\u{1F4E5} [Dolphin WS Incoming]:", "color: #eab308; font-weight: bold;", msg);
509
+ }
497
510
  if (msg.type && msg.from && (msg.to === this.deviceId || msg.to === "all")) {
498
511
  if (msg.msgId && msg.type !== "ACK") this._sendAck(msg.from, msg.msgId);
499
512
  this.signalHandlers.forEach((h) => h(msg));
@@ -531,6 +544,9 @@ var DolphinClient = class {
531
544
  }
532
545
  /** @private */
533
546
  _sendRaw(msg) {
547
+ if (this.options.debug) {
548
+ console.log("%c\u{1F4E4} [Dolphin WS Outgoing]:", "color: #8b5cf6; font-weight: bold;", msg);
549
+ }
534
550
  const str = typeof msg === "string" ? msg : JSON.stringify(msg);
535
551
  if (this.socket && this.socket.readyState === WebSocket.OPEN) {
536
552
  this.socket.send(str);
@@ -763,6 +779,108 @@ function attachDOMBinding(clientProto) {
763
779
  }
764
780
  return template;
765
781
  }
782
+ function renderTemplate(templateStr, context) {
783
+ if (!templateStr.includes("{#if") && !templateStr.includes("{#each")) {
784
+ let result = templateStr;
785
+ for (let key in context) {
786
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
787
+ result = result.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
788
+ }
789
+ return result;
790
+ }
791
+ try {
792
+ const escapeString = (str) => {
793
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
794
+ };
795
+ let compiled = 'let out = "";\n';
796
+ let lastIndex = 0;
797
+ const regex = /(\{\{([\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;
798
+ const eachStack = [];
799
+ let match;
800
+ while ((match = regex.exec(templateStr)) !== null) {
801
+ const plainText = templateStr.slice(lastIndex, match.index);
802
+ if (plainText) {
803
+ compiled += `out += "${escapeString(plainText)}";
804
+ `;
805
+ }
806
+ const token = match[0];
807
+ if (token.startsWith("{{")) {
808
+ const expr = match[2];
809
+ compiled += `out += (${expr} !== undefined && ${expr} !== null ? ${expr} : "");
810
+ `;
811
+ } else if (token.startsWith("{#if")) {
812
+ const expr = match[3];
813
+ compiled += `if (${expr}) {
814
+ `;
815
+ } else if (token.startsWith("{:else if")) {
816
+ const expr = match[4];
817
+ compiled += `} else if (${expr}) {
818
+ `;
819
+ } else if (token.startsWith("{:else}")) {
820
+ compiled += `} else {
821
+ `;
822
+ } else if (token.startsWith("{/if}")) {
823
+ compiled += `}
824
+ `;
825
+ } else if (token.startsWith("{#each")) {
826
+ const expr = match[5];
827
+ const itemVar = match[6];
828
+ const indexVar = match[7];
829
+ eachStack.push({ indexVar });
830
+ compiled += `if (typeof ${expr} !== "undefined" && ${expr} !== null && Array.isArray(${expr})) {
831
+ `;
832
+ if (indexVar) {
833
+ compiled += ` let ${indexVar} = 0;
834
+ `;
835
+ }
836
+ compiled += ` for (let ${itemVar} of ${expr}) {
837
+ `;
838
+ } else if (token.startsWith("{/each}")) {
839
+ const info = eachStack.pop();
840
+ if (info && info.indexVar) {
841
+ compiled += ` ${info.indexVar}++;
842
+ `;
843
+ }
844
+ compiled += ` }
845
+ }
846
+ `;
847
+ } else if (token.startsWith("{")) {
848
+ const expr = match[8];
849
+ if (expr) {
850
+ compiled += `out += (${expr} !== undefined && ${expr} !== null ? ${expr} : "");
851
+ `;
852
+ }
853
+ }
854
+ lastIndex = regex.lastIndex;
855
+ }
856
+ const remaining = templateStr.slice(lastIndex);
857
+ if (remaining) {
858
+ compiled += `out += "${escapeString(remaining)}";
859
+ `;
860
+ }
861
+ compiled += "return out;\n";
862
+ const fnBody = `
863
+ with (context) {
864
+ try {
865
+ ${compiled}
866
+ } catch (innerErr) {
867
+ console.warn('[Dolphin Template Eval Warning]:', innerErr);
868
+ return '';
869
+ }
870
+ }
871
+ `;
872
+ const fn = new Function("context", fnBody);
873
+ return fn(context);
874
+ } catch (e) {
875
+ console.error("[Dolphin Template Compiler Error]:", e);
876
+ let fallback = templateStr;
877
+ for (let key in context) {
878
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
879
+ fallback = fallback.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), context[key] !== void 0 && context[key] !== null ? context[key] : "");
880
+ }
881
+ return fallback;
882
+ }
883
+ }
766
884
  function sanitizeHTML(html) {
767
885
  if (typeof document === "undefined") return html;
768
886
  try {
@@ -886,6 +1004,9 @@ function attachDOMBinding(clientProto) {
886
1004
  }
887
1005
  const store = this.uiStores.get(storeName);
888
1006
  store[key] = val;
1007
+ if (this.options.debug) {
1008
+ console.log(`%c\u{1F4BE} [Dolphin Store Update]:`, "color: #ec4899; font-weight: bold;", `${storeName}.${key}`, "=", val);
1009
+ }
889
1010
  if (typeof document !== "undefined") {
890
1011
  const readElements = document.querySelectorAll(`[data-store-read="${storeName}.${key}"]`);
891
1012
  readElements.forEach((el) => {
@@ -965,6 +1086,42 @@ function attachDOMBinding(clientProto) {
965
1086
  }
966
1087
  return null;
967
1088
  };
1089
+ clientProto._executeStoreAction = function(expression, element) {
1090
+ this.uiStores = this.uiStores || /* @__PURE__ */ new Map();
1091
+ const context = new Proxy({}, {
1092
+ has: (target, prop) => {
1093
+ return true;
1094
+ },
1095
+ get: (target, prop) => {
1096
+ if (typeof prop === "string") {
1097
+ return new Proxy({}, {
1098
+ get: (subTarget, subProp) => {
1099
+ if (typeof subProp === "string") {
1100
+ return this.getStoreState(prop, subProp);
1101
+ }
1102
+ },
1103
+ set: (subTarget, subProp, val) => {
1104
+ if (typeof subProp === "string") {
1105
+ this.setStoreState(prop, subProp, val);
1106
+ return true;
1107
+ }
1108
+ return false;
1109
+ }
1110
+ });
1111
+ }
1112
+ }
1113
+ });
1114
+ try {
1115
+ const fn = new Function("ctx", `with(ctx) { ${expression} }`);
1116
+ fn(context);
1117
+ } catch (err) {
1118
+ console.error("%c[Dolphin Store Action Error]:", "color: #ef4444; font-weight: bold;", err);
1119
+ if (element) {
1120
+ console.error("%cFailed Element:", "color: #f97316; font-weight: bold;", element);
1121
+ }
1122
+ console.error("%cFailed Expression:", "color: #3b82f6; font-style: italic;", expression);
1123
+ }
1124
+ };
968
1125
  clientProto._initDOMBinding = function() {
969
1126
  if (this._domInitialized) return;
970
1127
  this._domInitialized = true;
@@ -1138,6 +1295,14 @@ function attachDOMBinding(clientProto) {
1138
1295
  console.error(`[Dolphin] API ${evtName} Error:`, err);
1139
1296
  }
1140
1297
  }
1298
+ const storeActionBtn = e.target.closest(`[data-store-${evtName}]`);
1299
+ if (storeActionBtn) {
1300
+ if (evtName === "submit") e.preventDefault();
1301
+ const expr = storeActionBtn.getAttribute(`data-store-${evtName}`);
1302
+ if (expr) {
1303
+ this._executeStoreAction(expr, storeActionBtn);
1304
+ }
1305
+ }
1141
1306
  });
1142
1307
  });
1143
1308
  this.subscribe("#", (payload, topic) => {
@@ -1163,21 +1328,11 @@ function attachDOMBinding(clientProto) {
1163
1328
  if (Array.isArray(result)) {
1164
1329
  let combinedHTML = "";
1165
1330
  for (const item of result) {
1166
- let finalItemHTML = template;
1167
- for (let key in item) {
1168
- const escapedKey = escapeRegExp(key);
1169
- finalItemHTML = finalItemHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), item[key] !== void 0 && item[key] !== null ? item[key] : "");
1170
- }
1171
- combinedHTML += finalItemHTML;
1331
+ combinedHTML += renderTemplate(template, item);
1172
1332
  }
1173
1333
  scheduleDOMUpdate(el, combinedHTML);
1174
1334
  } else {
1175
- let finalHTML = template;
1176
- for (let key in result) {
1177
- const escapedKey = escapeRegExp(key);
1178
- finalHTML = finalHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), result[key] !== void 0 && result[key] !== null ? result[key] : "");
1179
- }
1180
- scheduleDOMUpdate(el, finalHTML);
1335
+ scheduleDOMUpdate(el, renderTemplate(template, result));
1181
1336
  }
1182
1337
  } else {
1183
1338
  if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {
@@ -1271,21 +1426,11 @@ function attachDOMBinding(clientProto) {
1271
1426
  if (Array.isArray(payload)) {
1272
1427
  let combinedHTML = "";
1273
1428
  for (const item of payload) {
1274
- let finalItemHTML = template;
1275
- for (let key in item) {
1276
- const escapedKey = escapeRegExp(key);
1277
- finalItemHTML = finalItemHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), item[key] !== void 0 && item[key] !== null ? item[key] : "");
1278
- }
1279
- combinedHTML += finalItemHTML;
1429
+ combinedHTML += renderTemplate(template, item);
1280
1430
  }
1281
1431
  scheduleDOMUpdate(el, combinedHTML);
1282
1432
  } else {
1283
- let finalHTML = template;
1284
- for (let key in payload) {
1285
- const escapedKey = escapeRegExp(key);
1286
- finalHTML = finalHTML.replace(new RegExp(`\\{\\{${escapedKey}\\}\\}`, "g"), payload[key] !== void 0 && payload[key] !== null ? payload[key] : "");
1287
- }
1288
- scheduleDOMUpdate(el, finalHTML);
1433
+ scheduleDOMUpdate(el, renderTemplate(template, payload));
1289
1434
  }
1290
1435
  return;
1291
1436
  }
@@ -2012,6 +2157,21 @@ attachPwa(DolphinClient.prototype);
2012
2157
  attachTesting(DolphinClient.prototype);
2013
2158
  if (typeof window !== "undefined") {
2014
2159
  window.DolphinClient = DolphinClient;
2160
+ document.addEventListener("DOMContentLoaded", () => {
2161
+ if (!window.dolphin) {
2162
+ const scriptEl = document.querySelector('script[src*="dolphin-client"]');
2163
+ const debugMode = scriptEl ? scriptEl.getAttribute("data-debug") === "true" : false;
2164
+ const dolphin = new DolphinClient(void 0, void 0, { debug: debugMode });
2165
+ window.dolphin = dolphin;
2166
+ if (debugMode) {
2167
+ console.log("%c\u{1F42C} [Dolphin Client] Auto-initialized local reactive engine!", "color: #06b6d4; font-weight: bold; font-size: 14px;");
2168
+ console.log('%c\u{1F449} Tip: You can access the client instance via "window.dolphin" in console.', "color: #94a3b8; font-style: italic;");
2169
+ }
2170
+ if (document.querySelector('[data-store-write="app.username"]')) {
2171
+ dolphin.setStoreState("app", "username", "\u0928\u092E\u0938\u094D\u0924\u0947 \u0938\u093E\u0925\u0940!");
2172
+ }
2173
+ }
2174
+ });
2015
2175
  }
2016
2176
  export {
2017
2177
  DolphinClient
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dolphin-client",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "HTML is back! Hookless, framework-agnostic real-time reactive DOM-binding client for Dolphin Server with WebSockets, WebRTC signaling, and offline REST API fallbacks.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -19,8 +19,9 @@
19
19
  "LICENSE"
20
20
  ],
21
21
  "scripts": {
22
- "build": "npm run build:iife && npm run build:esm && npm run build:cjs && tsc",
22
+ "build": "npm run build:iife && npm run build:min && npm run build:esm && npm run build:cjs && tsc",
23
23
  "build:iife": "esbuild ./src/index.ts --bundle --outfile=dist/dolphin-client.js --format=iife --global-name=DolphinModule",
24
+ "build:min": "esbuild ./src/index.ts --bundle --minify --outfile=dist/dolphin-client.min.js --format=iife --global-name=DolphinModule",
24
25
  "build:esm": "esbuild ./src/index.ts --bundle --outfile=dist/index.js --format=esm",
25
26
  "build:cjs": "esbuild ./src/index.ts --bundle --outfile=dist/index.cjs --format=cjs",
26
27
  "test": "jest"