@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 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>;
@@ -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;AA82BzC,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,QAAA,MAAM,SAAS;;;;;;;sBAlpFO,OAAO,CAAC,cAAc,CAAC;CA0pF5C,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"}
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@benqoder/beam",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",