@benqoder/beam 0.3.0 → 0.4.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/README.md +37 -0
- package/dist/client.d.ts +4 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +92 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -125,6 +125,42 @@ export function greet(c) {
|
|
|
125
125
|
<div id="greeting"></div>
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
+
### Including Input Values
|
|
129
|
+
|
|
130
|
+
Use `beam-include` to collect values from input elements and include them in action params. Elements are found by `beam-id`, `id`, or `name` (in that priority order):
|
|
131
|
+
|
|
132
|
+
```html
|
|
133
|
+
<!-- Define inputs with beam-id, id, or name -->
|
|
134
|
+
<input beam-id="name" type="text" value="Ben"/>
|
|
135
|
+
<input id="email" type="email" value="ben@example.com"/>
|
|
136
|
+
<input name="age" type="number" value="30"/>
|
|
137
|
+
<input beam-id="subscribe" type="checkbox" checked/>
|
|
138
|
+
|
|
139
|
+
<!-- Button includes specific inputs -->
|
|
140
|
+
<button
|
|
141
|
+
beam-action="saveUser"
|
|
142
|
+
beam-include="name,email,age,subscribe"
|
|
143
|
+
beam-data-source="form"
|
|
144
|
+
beam-target="#result"
|
|
145
|
+
>Save</button>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The action receives merged params with proper type conversion:
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"source": "form",
|
|
152
|
+
"name": "Ben",
|
|
153
|
+
"email": "ben@example.com",
|
|
154
|
+
"age": 30,
|
|
155
|
+
"subscribe": true
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Type conversion:
|
|
160
|
+
- `checkbox` → `boolean` (checked state)
|
|
161
|
+
- `number`/`range` → `number`
|
|
162
|
+
- All others → `string`
|
|
163
|
+
|
|
128
164
|
### Modals
|
|
129
165
|
|
|
130
166
|
Two ways to open modals:
|
|
@@ -308,6 +344,7 @@ Async components are awaited automatically - no manual `Promise.resolve()` or he
|
|
|
308
344
|
| `beam-action` | Action name to call | `beam-action="increment"` |
|
|
309
345
|
| `beam-target` | CSS selector for where to render response | `beam-target="#counter"` |
|
|
310
346
|
| `beam-data-*` | Pass data to the action | `beam-data-id="123"` |
|
|
347
|
+
| `beam-include` | Include values from inputs by beam-id, id, or name | `beam-include="name,email,age"` |
|
|
311
348
|
| `beam-swap` | How to swap content: `morph`, `append`, `prepend`, `replace` | `beam-swap="append"` |
|
|
312
349
|
| `beam-confirm` | Show confirmation dialog before action | `beam-confirm="Delete this item?"` |
|
|
313
350
|
| `beam-confirm-prompt` | Require typing text to confirm | `beam-confirm-prompt="Type DELETE\|DELETE"` |
|
package/dist/client.d.ts
CHANGED
|
@@ -31,6 +31,8 @@ interface CallOptions {
|
|
|
31
31
|
swap?: string;
|
|
32
32
|
}
|
|
33
33
|
declare function clearScrollState(actionOrAll?: string | boolean): void;
|
|
34
|
+
declare function checkWsConnected(): boolean;
|
|
35
|
+
declare function manualReconnect(): Promise<BeamServerStub>;
|
|
34
36
|
declare const beamUtils: {
|
|
35
37
|
showToast: typeof showToast;
|
|
36
38
|
closeModal: typeof closeModal;
|
|
@@ -38,6 +40,8 @@ declare const beamUtils: {
|
|
|
38
40
|
clearCache: typeof clearCache;
|
|
39
41
|
clearScrollState: typeof clearScrollState;
|
|
40
42
|
isOnline: () => boolean;
|
|
43
|
+
isConnected: typeof checkWsConnected;
|
|
44
|
+
reconnect: typeof manualReconnect;
|
|
41
45
|
getSession: () => Promise<BeamServerStub>;
|
|
42
46
|
};
|
|
43
47
|
type ActionCaller = (data?: Record<string, unknown>, options?: string | CallOptions) => Promise<ActionResponse>;
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAA0B,KAAK,OAAO,EAAE,MAAM,SAAS,CAAA;AA8B9D,UAAU,cAAc;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACxB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAClE,MAAM,CAAC,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACvF;AAGD,UAAU,UAAU;IAClB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;IAC7E,gBAAgB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClF;AAQD,KAAK,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAA0B,KAAK,OAAO,EAAE,MAAM,SAAS,CAAA;AA8B9D,UAAU,cAAc;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACxB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAClE,MAAM,CAAC,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACvF;AAGD,UAAU,UAAU;IAClB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;IAC7E,gBAAgB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClF;AAQD,KAAK,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;AAq8BzC,iBAAS,UAAU,IAAI,IAAI,CAU1B;AAkCD,iBAAS,WAAW,IAAI,IAAI,CAU3B;AAID,iBAAS,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,SAAS,GAAG,OAAmB,GAAG,IAAI,CAsB/E;AAkrBD,iBAAS,UAAU,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAUzC;AAmlCD,UAAU,WAAW;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAMD,iBAAS,gBAAgB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CA0B9D;AAGD,iBAAS,gBAAgB,IAAI,OAAO,CAEnC;AAED,iBAAS,eAAe,IAAI,OAAO,CAAC,cAAc,CAAC,CAGlD;AAED,QAAA,MAAM,SAAS;;;;;;;;;sBA5rFO,OAAO,CAAC,cAAc,CAAC;CAssF5C,CAAA;AAGD,KAAK,YAAY,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,KAAK,OAAO,CAAC,cAAc,CAAC,CAAA;AAE/G,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,IAAI,EAAE,OAAO,SAAS,GAAG;YACvB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAAA;SAC/B,CAAA;KACF;CACF"}
|
package/dist/client.js
CHANGED
|
@@ -27,6 +27,10 @@ function getAuthToken() {
|
|
|
27
27
|
let isOnline = navigator.onLine;
|
|
28
28
|
let rpcSession = null;
|
|
29
29
|
let connectingPromise = null;
|
|
30
|
+
let wsConnected = false;
|
|
31
|
+
let reconnectAttempts = 0;
|
|
32
|
+
const MAX_RECONNECT_ATTEMPTS = 5;
|
|
33
|
+
const RECONNECT_DELAY_BASE = 1000;
|
|
30
34
|
// Client callback handler for server-initiated updates
|
|
31
35
|
function handleServerEvent(event, data) {
|
|
32
36
|
// Dispatch custom event for app to handle
|
|
@@ -42,6 +46,43 @@ function handleServerEvent(event, data) {
|
|
|
42
46
|
window.dispatchEvent(new CustomEvent('beam:refresh', { detail: { selector } }));
|
|
43
47
|
}
|
|
44
48
|
}
|
|
49
|
+
// Handle WebSocket disconnection
|
|
50
|
+
function handleWsDisconnect(error) {
|
|
51
|
+
console.warn('[beam] WebSocket disconnected:', error);
|
|
52
|
+
wsConnected = false;
|
|
53
|
+
rpcSession = null;
|
|
54
|
+
connectingPromise = null;
|
|
55
|
+
// Dispatch event for app to handle
|
|
56
|
+
window.dispatchEvent(new CustomEvent('beam:disconnected', { detail: { error } }));
|
|
57
|
+
document.body.classList.add('beam-disconnected');
|
|
58
|
+
// Show any disconnect indicators
|
|
59
|
+
document.querySelectorAll('[beam-disconnected]').forEach((el) => {
|
|
60
|
+
el.style.display = '';
|
|
61
|
+
});
|
|
62
|
+
// Auto-reconnect with exponential backoff
|
|
63
|
+
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
|
64
|
+
const delay = RECONNECT_DELAY_BASE * Math.pow(2, reconnectAttempts);
|
|
65
|
+
reconnectAttempts++;
|
|
66
|
+
console.log(`[beam] Reconnecting in ${delay}ms (${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
|
|
67
|
+
setTimeout(() => {
|
|
68
|
+
connect().then(() => {
|
|
69
|
+
console.log('[beam] Reconnected');
|
|
70
|
+
document.body.classList.remove('beam-disconnected');
|
|
71
|
+
document.querySelectorAll('[beam-disconnected]').forEach((el) => {
|
|
72
|
+
el.style.display = 'none';
|
|
73
|
+
});
|
|
74
|
+
window.dispatchEvent(new CustomEvent('beam:reconnected'));
|
|
75
|
+
}).catch((err) => {
|
|
76
|
+
console.error('[beam] Reconnect failed:', err);
|
|
77
|
+
});
|
|
78
|
+
}, delay);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
console.error('[beam] Max reconnect attempts reached');
|
|
82
|
+
showToast('Connection lost. Please refresh the page.', 'error');
|
|
83
|
+
window.dispatchEvent(new CustomEvent('beam:reconnect-failed'));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
45
86
|
function connect() {
|
|
46
87
|
if (connectingPromise) {
|
|
47
88
|
return connectingPromise;
|
|
@@ -70,8 +111,16 @@ function connect() {
|
|
|
70
111
|
authenticatedSession.registerCallback?.(handleServerEvent)?.catch?.(() => {
|
|
71
112
|
// Server may not support callbacks, that's ok
|
|
72
113
|
});
|
|
114
|
+
// Handle connection broken (WebSocket disconnect)
|
|
115
|
+
// @ts-ignore - onRpcBroken is available on capnweb stubs
|
|
116
|
+
if (typeof authenticatedSession.onRpcBroken === 'function') {
|
|
117
|
+
authenticatedSession.onRpcBroken(handleWsDisconnect);
|
|
118
|
+
}
|
|
73
119
|
rpcSession = authenticatedSession;
|
|
74
120
|
connectingPromise = null;
|
|
121
|
+
wsConnected = true;
|
|
122
|
+
reconnectAttempts = 0;
|
|
123
|
+
window.dispatchEvent(new CustomEvent('beam:connected'));
|
|
75
124
|
return authenticatedSession;
|
|
76
125
|
}
|
|
77
126
|
catch (err) {
|
|
@@ -174,8 +223,42 @@ function getParams(el) {
|
|
|
174
223
|
}
|
|
175
224
|
}
|
|
176
225
|
}
|
|
226
|
+
// Handle beam-include: collect values from referenced inputs
|
|
227
|
+
const includeAttr = el.getAttribute('beam-include');
|
|
228
|
+
if (includeAttr) {
|
|
229
|
+
const ids = includeAttr.split(',').map(id => id.trim());
|
|
230
|
+
for (const id of ids) {
|
|
231
|
+
// Find element by beam-id, id, or name (priority order)
|
|
232
|
+
const inputEl = document.querySelector(`[beam-id="${id}"]`) ||
|
|
233
|
+
document.getElementById(id) ||
|
|
234
|
+
document.querySelector(`[name="${id}"]`);
|
|
235
|
+
if (inputEl) {
|
|
236
|
+
params[id] = getIncludedInputValue(inputEl);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
177
240
|
return params;
|
|
178
241
|
}
|
|
242
|
+
// Get value from an included input element with proper type conversion
|
|
243
|
+
function getIncludedInputValue(el) {
|
|
244
|
+
if (el.tagName === 'INPUT') {
|
|
245
|
+
const input = el;
|
|
246
|
+
if (input.type === 'checkbox')
|
|
247
|
+
return input.checked;
|
|
248
|
+
if (input.type === 'radio')
|
|
249
|
+
return input.checked ? input.value : '';
|
|
250
|
+
if (input.type === 'number' || input.type === 'range') {
|
|
251
|
+
const num = parseFloat(input.value);
|
|
252
|
+
return isNaN(num) ? 0 : num;
|
|
253
|
+
}
|
|
254
|
+
return input.value;
|
|
255
|
+
}
|
|
256
|
+
if (el.tagName === 'TEXTAREA')
|
|
257
|
+
return el.value;
|
|
258
|
+
if (el.tagName === 'SELECT')
|
|
259
|
+
return el.value;
|
|
260
|
+
return '';
|
|
261
|
+
}
|
|
179
262
|
// ============ CONFIRMATION DIALOGS ============
|
|
180
263
|
// Usage: <button beam-action="delete" beam-confirm="Are you sure?">Delete</button>
|
|
181
264
|
// Usage: <button beam-action="delete" beam-confirm.prompt="Type DELETE to confirm|DELETE">Delete</button>
|
|
@@ -2482,6 +2565,13 @@ function clearScrollState(actionOrAll) {
|
|
|
2482
2565
|
}
|
|
2483
2566
|
}
|
|
2484
2567
|
// Base utilities that are always available on window.beam
|
|
2568
|
+
function checkWsConnected() {
|
|
2569
|
+
return wsConnected;
|
|
2570
|
+
}
|
|
2571
|
+
function manualReconnect() {
|
|
2572
|
+
reconnectAttempts = 0;
|
|
2573
|
+
return connect();
|
|
2574
|
+
}
|
|
2485
2575
|
const beamUtils = {
|
|
2486
2576
|
showToast,
|
|
2487
2577
|
closeModal,
|
|
@@ -2489,6 +2579,8 @@ const beamUtils = {
|
|
|
2489
2579
|
clearCache,
|
|
2490
2580
|
clearScrollState,
|
|
2491
2581
|
isOnline: () => isOnline,
|
|
2582
|
+
isConnected: checkWsConnected,
|
|
2583
|
+
reconnect: manualReconnect,
|
|
2492
2584
|
getSession: api.getSession,
|
|
2493
2585
|
};
|
|
2494
2586
|
// Create a Proxy that handles both utility methods and dynamic action calls
|