dolphin-client 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/dom.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function attachDOMBinding(clientProto: any): void;
package/dist/dom.js ADDED
@@ -0,0 +1,316 @@
1
+ export function attachDOMBinding(clientProto) {
2
+ clientProto.getClosestContext = function (element, key) {
3
+ let current = element;
4
+ while (current) {
5
+ if (current._rtContext) {
6
+ const ctx = current._rtContext;
7
+ if (key)
8
+ return ctx[key];
9
+ return ctx;
10
+ }
11
+ current = current.parentElement;
12
+ }
13
+ return null;
14
+ };
15
+ /** @private */
16
+ clientProto._initDOMBinding = function () {
17
+ if (this._domInitialized)
18
+ return;
19
+ this._domInitialized = true;
20
+ // 1. Listen for inputs and value changes across multiple events (input, change, keyup, etc.)
21
+ const PUSH_EVENTS = ['input', 'change', 'keyup', 'paste', 'blur'];
22
+ PUSH_EVENTS.forEach(evtName => {
23
+ document.addEventListener(evtName, (e) => {
24
+ if (!e.target || !e.target.getAttribute)
25
+ return;
26
+ const topic = e.target.getAttribute('data-rt-push');
27
+ if (topic) {
28
+ const payload = { name: e.target.name, value: e.target.value };
29
+ this.pubPush(topic, payload);
30
+ }
31
+ });
32
+ });
33
+ // 2. Listen for form submits (RT + API) - Serializes whole form data
34
+ document.addEventListener('submit', async (e) => {
35
+ if (!e.target || !e.target.getAttribute)
36
+ return;
37
+ const rtTopic = e.target.getAttribute('data-rt-submit');
38
+ const apiTarget = e.target.getAttribute('data-api-submit');
39
+ const parentCtx = this.getClosestContext(e.target) || {};
40
+ if (rtTopic || apiTarget) {
41
+ e.preventDefault();
42
+ const formData = new FormData(e.target);
43
+ const data = Object.fromEntries(formData.entries());
44
+ if (rtTopic) {
45
+ let resolvedTopic = rtTopic;
46
+ for (const k in parentCtx) {
47
+ resolvedTopic = resolvedTopic.replace(new RegExp(`\\{\\{${k}\\}\\}`, 'g'), parentCtx[k] !== undefined && parentCtx[k] !== null ? parentCtx[k] : '');
48
+ }
49
+ this.publish(resolvedTopic, data);
50
+ }
51
+ else if (apiTarget) {
52
+ let resolvedTarget = apiTarget;
53
+ for (const k in parentCtx) {
54
+ resolvedTarget = resolvedTarget.replace(new RegExp(`\\{\\{${k}\\}\\}`, 'g'), parentCtx[k] !== undefined && parentCtx[k] !== null ? parentCtx[k] : '');
55
+ }
56
+ const parts = resolvedTarget.trim().split(' ');
57
+ const method = parts.length > 1 ? parts[0].toUpperCase() : 'POST';
58
+ const path = parts.length > 1 ? parts[1] : parts[0];
59
+ try {
60
+ const result = await this.api.request(method, path, data);
61
+ const resultBind = e.target.getAttribute('data-api-result');
62
+ if (resultBind)
63
+ this._updateDOM(resultBind, result);
64
+ // Auto Navigation (Hookless Routing)
65
+ const redirect = e.target.getAttribute('data-api-redirect');
66
+ if (redirect)
67
+ window.location.href = redirect;
68
+ if (e.target.hasAttribute('data-api-reload'))
69
+ window.location.reload();
70
+ }
71
+ catch (err) {
72
+ console.error('[Dolphin] API Submit Error:', err);
73
+ }
74
+ }
75
+ }
76
+ });
77
+ // 3. Unified listener for custom interaction events (click, change, keydown, keyup, dblclick, etc.)
78
+ const INTERACTION_EVENTS = ['click', 'change', 'submit', 'input', 'keydown', 'keyup', 'dblclick', 'focus', 'blur', 'mouseenter', 'mouseleave'];
79
+ INTERACTION_EVENTS.forEach(evtName => {
80
+ document.addEventListener(evtName, async (e) => {
81
+ if (!e.target || !e.target.closest)
82
+ return;
83
+ const rtBtn = e.target.closest(`[data-rt-${evtName}]`);
84
+ const apiBtn = e.target.closest(`[data-api-${evtName}]`);
85
+ if (rtBtn) {
86
+ if (evtName === 'submit')
87
+ e.preventDefault();
88
+ const topic = rtBtn.getAttribute(`data-rt-${evtName}`);
89
+ const actionData = rtBtn.getAttribute('data-rt-payload');
90
+ const parentCtx = this.getClosestContext(rtBtn) || {};
91
+ let payload = {};
92
+ if (actionData) {
93
+ let resolvedDataStr = actionData;
94
+ for (const k in parentCtx) {
95
+ resolvedDataStr = resolvedDataStr.replace(new RegExp(`\\{\\{${k}\\}\\}`, 'g'), parentCtx[k] !== undefined && parentCtx[k] !== null ? parentCtx[k] : '');
96
+ }
97
+ try {
98
+ payload = JSON.parse(resolvedDataStr);
99
+ }
100
+ catch {
101
+ payload = {};
102
+ }
103
+ }
104
+ this.publish(topic, payload);
105
+ }
106
+ if (apiBtn) {
107
+ if (evtName === 'submit')
108
+ e.preventDefault();
109
+ const apiTarget = apiBtn.getAttribute(`data-api-${evtName}`);
110
+ const actionData = apiBtn.getAttribute('data-api-payload');
111
+ const parentCtx = this.getClosestContext(apiBtn) || {};
112
+ const parts = apiTarget.trim().split(' ');
113
+ const method = parts.length > 1 ? parts[0].toUpperCase() : 'POST';
114
+ const path = parts.length > 1 ? parts[1] : parts[0];
115
+ let payload = null;
116
+ if (actionData) {
117
+ let resolvedDataStr = actionData;
118
+ for (const k in parentCtx) {
119
+ resolvedDataStr = resolvedDataStr.replace(new RegExp(`\\{\\{${k}\\}\\}`, 'g'), parentCtx[k] !== undefined && parentCtx[k] !== null ? parentCtx[k] : '');
120
+ }
121
+ try {
122
+ payload = JSON.parse(resolvedDataStr);
123
+ }
124
+ catch {
125
+ payload = null;
126
+ }
127
+ }
128
+ try {
129
+ const result = await this.api.request(method, path, payload);
130
+ const resultBind = apiBtn.getAttribute('data-api-result');
131
+ if (resultBind)
132
+ this._updateDOM(resultBind, result);
133
+ // Auto Navigation (Hookless Routing)
134
+ const redirect = apiBtn.getAttribute('data-api-redirect');
135
+ if (redirect)
136
+ window.location.href = redirect;
137
+ if (apiBtn.hasAttribute('data-api-reload'))
138
+ window.location.reload();
139
+ }
140
+ catch (err) {
141
+ console.error(`[Dolphin] API ${evtName} Error:`, err);
142
+ }
143
+ }
144
+ });
145
+ });
146
+ // 4. Update DOM when RT data arrives
147
+ // Note: Subscribe to all topics ('#') to auto-update DOM bindings
148
+ this.subscribe('#', (payload, topic) => {
149
+ this._updateDOM(topic, payload);
150
+ });
151
+ // 5. Auto-fetch API GET bindings
152
+ this._scanAndFetchAPIBinds();
153
+ };
154
+ /** @private */
155
+ clientProto._scanAndFetchAPIBinds = async function () {
156
+ if (typeof document === 'undefined')
157
+ return;
158
+ const elements = document.querySelectorAll('[data-api-get]');
159
+ for (const el of Array.from(elements)) {
160
+ const path = el.getAttribute('data-api-get');
161
+ if (!path)
162
+ continue;
163
+ try {
164
+ const result = await this.api.get(path);
165
+ const rtBind = el.getAttribute('data-rt-bind');
166
+ if (rtBind) {
167
+ // Route initial HTTP result through the _updateDOM renderer for full template/context support
168
+ this._updateDOM(rtBind, result);
169
+ }
170
+ else {
171
+ const template = el.getAttribute('data-rt-template');
172
+ if (template && typeof result === 'object' && result !== null) {
173
+ if (Array.isArray(result)) {
174
+ let combinedHTML = '';
175
+ for (const item of result) {
176
+ let finalItemHTML = template;
177
+ for (let key in item) {
178
+ finalItemHTML = finalItemHTML.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), item[key] !== undefined && item[key] !== null ? item[key] : '');
179
+ }
180
+ combinedHTML += finalItemHTML;
181
+ }
182
+ el.innerHTML = combinedHTML;
183
+ }
184
+ else {
185
+ let finalHTML = template;
186
+ for (let key in result) {
187
+ finalHTML = finalHTML.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), result[key] !== undefined && result[key] !== null ? result[key] : '');
188
+ }
189
+ el.innerHTML = finalHTML;
190
+ }
191
+ }
192
+ else {
193
+ if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
194
+ el.value = typeof result === 'object' ? (result.value !== undefined ? result.value : '') : result;
195
+ }
196
+ else {
197
+ el.innerHTML = typeof result === 'object' ? (result.html || result.text || JSON.stringify(result)) : result;
198
+ }
199
+ }
200
+ }
201
+ }
202
+ catch (e) {
203
+ console.error('[Dolphin] API Get Error:', e);
204
+ }
205
+ }
206
+ };
207
+ /** @private */
208
+ clientProto._updateDOM = function (topic, payload) {
209
+ if (typeof document === 'undefined')
210
+ return;
211
+ const elements = document.querySelectorAll(`[data-rt-bind="${topic}"]`);
212
+ elements.forEach(el => {
213
+ if (el.getAttribute('data-rt-type') === 'context' && typeof payload === 'object' && payload !== null) {
214
+ el._rtContext = payload; // Store context on DOM element
215
+ const processNode = (node) => {
216
+ if (node.hasAttribute('data-rt-text')) {
217
+ const key = node.getAttribute('data-rt-text');
218
+ if (key && payload[key] !== undefined && payload[key] !== null)
219
+ node.textContent = payload[key];
220
+ }
221
+ if (node.hasAttribute('data-rt-html')) {
222
+ const key = node.getAttribute('data-rt-html');
223
+ if (key && payload[key] !== undefined && payload[key] !== null)
224
+ node.innerHTML = payload[key];
225
+ }
226
+ if (node.hasAttribute('data-rt-attr')) {
227
+ const attrStr = node.getAttribute('data-rt-attr');
228
+ if (attrStr) {
229
+ attrStr.split(',').forEach(b => {
230
+ const parts = b.split(':');
231
+ if (parts.length === 2) {
232
+ const attrName = parts[0].trim();
233
+ const key = parts[1].trim();
234
+ if (attrName && key && payload[key] !== undefined && payload[key] !== null) {
235
+ node.setAttribute(attrName, payload[key]);
236
+ }
237
+ }
238
+ });
239
+ }
240
+ }
241
+ if (node.hasAttribute('data-rt-class')) {
242
+ const classStr = node.getAttribute('data-rt-class');
243
+ if (classStr) {
244
+ classStr.split(',').forEach(b => {
245
+ const parts = b.split(':');
246
+ if (parts.length === 2) {
247
+ const className = parts[0].trim();
248
+ const key = parts[1].trim();
249
+ if (payload[key]) {
250
+ node.classList.add(className);
251
+ }
252
+ else {
253
+ node.classList.remove(className);
254
+ }
255
+ }
256
+ });
257
+ }
258
+ }
259
+ if (node.hasAttribute('data-rt-if')) {
260
+ const key = node.getAttribute('data-rt-if');
261
+ if (key) {
262
+ if (payload[key]) {
263
+ node.style.display = '';
264
+ }
265
+ else {
266
+ node.style.display = 'none';
267
+ }
268
+ }
269
+ }
270
+ if (node.hasAttribute('data-rt-hide')) {
271
+ const key = node.getAttribute('data-rt-hide');
272
+ if (key) {
273
+ if (payload[key]) {
274
+ node.style.display = 'none';
275
+ }
276
+ else {
277
+ node.style.display = '';
278
+ }
279
+ }
280
+ }
281
+ };
282
+ processNode(el);
283
+ el.querySelectorAll('[data-rt-text], [data-rt-html], [data-rt-attr], [data-rt-class], [data-rt-if], [data-rt-hide]').forEach(processNode);
284
+ return;
285
+ }
286
+ const template = el.getAttribute('data-rt-template');
287
+ if (template && typeof payload === 'object' && payload !== null) {
288
+ if (Array.isArray(payload)) {
289
+ let combinedHTML = '';
290
+ for (const item of payload) {
291
+ let finalItemHTML = template;
292
+ for (let key in item) {
293
+ finalItemHTML = finalItemHTML.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), item[key] !== undefined && item[key] !== null ? item[key] : '');
294
+ }
295
+ combinedHTML += finalItemHTML;
296
+ }
297
+ el.innerHTML = combinedHTML;
298
+ }
299
+ else {
300
+ let finalHTML = template;
301
+ for (let key in payload) {
302
+ finalHTML = finalHTML.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), payload[key] !== undefined && payload[key] !== null ? payload[key] : '');
303
+ }
304
+ el.innerHTML = finalHTML;
305
+ }
306
+ return;
307
+ }
308
+ if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
309
+ el.value = typeof payload === 'object' ? (payload.value !== undefined ? payload.value : '') : payload;
310
+ }
311
+ else {
312
+ el.innerHTML = typeof payload === 'object' ? (payload.html || payload.text || JSON.stringify(payload)) : payload;
313
+ }
314
+ });
315
+ };
316
+ }
@@ -0,0 +1 @@
1
+ export declare function attachDragDrop(clientProto: any): void;
package/dist/i18n.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function attachI18n(clientProto: any): void;