@jsenv/navi 0.0.1

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.
Files changed (123) hide show
  1. package/index.js +51 -0
  2. package/package.json +38 -0
  3. package/src/action_private_properties.js +11 -0
  4. package/src/action_proxy_test.html +353 -0
  5. package/src/action_run_states.js +5 -0
  6. package/src/actions.js +1377 -0
  7. package/src/browser_integration/browser_integration.js +191 -0
  8. package/src/browser_integration/document_back_and_forward.js +17 -0
  9. package/src/browser_integration/document_loading_signal.js +100 -0
  10. package/src/browser_integration/document_state_signal.js +9 -0
  11. package/src/browser_integration/document_url_signal.js +9 -0
  12. package/src/browser_integration/use_is_visited.js +19 -0
  13. package/src/browser_integration/via_history.js +199 -0
  14. package/src/browser_integration/via_navigation.js +168 -0
  15. package/src/components/action_execution/form_context.js +8 -0
  16. package/src/components/action_execution/render_actionable_component.jsx +27 -0
  17. package/src/components/action_execution/use_action.js +330 -0
  18. package/src/components/action_execution/use_execute_action.js +161 -0
  19. package/src/components/action_renderer.jsx +136 -0
  20. package/src/components/collect_form_element_values.js +79 -0
  21. package/src/components/demos/0_button_demo.html +155 -0
  22. package/src/components/demos/1_checkbox_demo.html +257 -0
  23. package/src/components/demos/2_input_textual_demo.html +354 -0
  24. package/src/components/demos/3_radio_demo.html +222 -0
  25. package/src/components/demos/4_select_demo.html +104 -0
  26. package/src/components/demos/5_list_scrollable_demo.html +153 -0
  27. package/src/components/demos/action/0_button_demo.html +204 -0
  28. package/src/components/demos/action/10_shortcuts_demo.html +189 -0
  29. package/src/components/demos/action/11_nested_shortcuts_demo.html +401 -0
  30. package/src/components/demos/action/1_input_text_demo.html +461 -0
  31. package/src/components/demos/action/2_form_multiple.html +303 -0
  32. package/src/components/demos/action/3_details_demo.html +172 -0
  33. package/src/components/demos/action/4_input_checkbox_demo.html +611 -0
  34. package/src/components/demos/action/6_checkbox_list_demo.html +109 -0
  35. package/src/components/demos/action/7_radio_list_demo.html +217 -0
  36. package/src/components/demos/action/8_editable_text_demo.html +442 -0
  37. package/src/components/demos/action/9_link_demo.html +172 -0
  38. package/src/components/demos/demo.md +0 -0
  39. package/src/components/demos/route/basic/basic.html +14 -0
  40. package/src/components/demos/route/basic/basic_route_demo.jsx +224 -0
  41. package/src/components/demos/route/multi/multi.html +14 -0
  42. package/src/components/demos/route/multi/multi_route_demo.jsx +277 -0
  43. package/src/components/details/details.jsx +248 -0
  44. package/src/components/details/summary_marker.jsx +141 -0
  45. package/src/components/editable_text/editable_text.jsx +96 -0
  46. package/src/components/error_boundary_context.js +9 -0
  47. package/src/components/form.jsx +144 -0
  48. package/src/components/input/button.jsx +333 -0
  49. package/src/components/input/checkbox_list.jsx +294 -0
  50. package/src/components/input/field.jsx +61 -0
  51. package/src/components/input/field_css.js +118 -0
  52. package/src/components/input/input.jsx +15 -0
  53. package/src/components/input/input_checkbox.jsx +370 -0
  54. package/src/components/input/input_radio.jsx +299 -0
  55. package/src/components/input/input_textual.jsx +338 -0
  56. package/src/components/input/radio_list.jsx +283 -0
  57. package/src/components/input/select.jsx +273 -0
  58. package/src/components/input/use_form_event.js +20 -0
  59. package/src/components/input/use_on_change.js +12 -0
  60. package/src/components/link/link.jsx +291 -0
  61. package/src/components/loader/loader_background.jsx +324 -0
  62. package/src/components/loader/loading_spinner.jsx +68 -0
  63. package/src/components/loader/network_speed.js +83 -0
  64. package/src/components/loader/rectangle_loading.jsx +225 -0
  65. package/src/components/route.jsx +15 -0
  66. package/src/components/selection/selection.js +5 -0
  67. package/src/components/selection/selection_context.jsx +262 -0
  68. package/src/components/shortcut/os.js +9 -0
  69. package/src/components/shortcut/shortcut_context.jsx +390 -0
  70. package/src/components/use_action_events.js +37 -0
  71. package/src/components/use_auto_focus.js +43 -0
  72. package/src/components/use_debounce_true.js +31 -0
  73. package/src/components/use_focus_group.js +19 -0
  74. package/src/components/use_initial_value.js +104 -0
  75. package/src/components/use_is_visited.js +19 -0
  76. package/src/components/use_ref_array.js +38 -0
  77. package/src/components/use_signal_sync.js +50 -0
  78. package/src/components/use_state_array.js +40 -0
  79. package/src/docs/actions.md +228 -0
  80. package/src/docs/demos/resource/action_status.jsx +42 -0
  81. package/src/docs/demos/resource/demo.md +1 -0
  82. package/src/docs/demos/resource/resource_demo_0.html +84 -0
  83. package/src/docs/demos/resource/resource_demo_10_post_gc.html +364 -0
  84. package/src/docs/demos/resource/resource_demo_11_describe_many.html +362 -0
  85. package/src/docs/demos/resource/resource_demo_2.html +173 -0
  86. package/src/docs/demos/resource/resource_demo_3_filtered_users.html +415 -0
  87. package/src/docs/demos/resource/resource_demo_4_details.html +284 -0
  88. package/src/docs/demos/resource/resource_demo_5_renderer_lazy.html +115 -0
  89. package/src/docs/demos/resource/resource_demo_6_gc.html +217 -0
  90. package/src/docs/demos/resource/resource_demo_7_child_gc.html +240 -0
  91. package/src/docs/demos/resource/resource_demo_8_proxy_gc.html +319 -0
  92. package/src/docs/demos/resource/resource_demo_9_describe_one.html +472 -0
  93. package/src/docs/demos/resource/tata.jsx +3 -0
  94. package/src/docs/demos/resource/toto.jsx +3 -0
  95. package/src/docs/demos/user_nav/user_nav.html +12 -0
  96. package/src/docs/demos/user_nav/user_nav.jsx +330 -0
  97. package/src/docs/resource_dependencies.md +103 -0
  98. package/src/docs/resource_with_params.md +80 -0
  99. package/src/notes.md +13 -0
  100. package/src/route/route.js +518 -0
  101. package/src/route/route.test.html +228 -0
  102. package/src/store/array_signal_store.js +537 -0
  103. package/src/store/local_storage_signal.js +17 -0
  104. package/src/store/resource_graph.js +1303 -0
  105. package/src/store/tests/resource_graph_autoreload_demo.html +12 -0
  106. package/src/store/tests/resource_graph_autoreload_demo.jsx +964 -0
  107. package/src/store/tests/resource_graph_dependencies.test.js +95 -0
  108. package/src/store/value_in_local_storage.js +187 -0
  109. package/src/symbol_object_signal.js +1 -0
  110. package/src/use_action_data.js +10 -0
  111. package/src/use_action_status.js +47 -0
  112. package/src/utils/add_many_event_listeners.js +15 -0
  113. package/src/utils/array_add_remove.js +61 -0
  114. package/src/utils/array_signal.js +15 -0
  115. package/src/utils/compare_two_js_values.js +172 -0
  116. package/src/utils/execute_with_cleanup.js +21 -0
  117. package/src/utils/get_caller_info.js +85 -0
  118. package/src/utils/iterable_weak_set.js +62 -0
  119. package/src/utils/js_value_weak_map.js +162 -0
  120. package/src/utils/js_value_weak_map_demo.html +690 -0
  121. package/src/utils/merge_two_js_values.js +53 -0
  122. package/src/utils/stringify_for_display.js +150 -0
  123. package/src/utils/weak_effect.js +48 -0
@@ -0,0 +1,611 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" href="data:," />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Checkbox Demo</title>
8
+ <style>
9
+ body {
10
+ font-family:
11
+ system-ui,
12
+ -apple-system,
13
+ sans-serif;
14
+ max-width: 1200px;
15
+ margin: 0 auto;
16
+ padding: 20px;
17
+ }
18
+ .demo-grid {
19
+ display: grid;
20
+ grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
21
+ gap: 20px;
22
+ margin-bottom: 30px;
23
+ }
24
+ .demo-card {
25
+ background: white;
26
+ border-radius: 8px;
27
+ padding: 20px;
28
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
29
+ }
30
+ .demo-title {
31
+ margin: 0 0 15px 0;
32
+ color: #333;
33
+ font-size: 16px;
34
+ font-weight: 600;
35
+ border-bottom: 2px solid #e0e0e0;
36
+ padding-bottom: 8px;
37
+ }
38
+ .result-display {
39
+ margin-top: 15px;
40
+ padding: 12px;
41
+ background: #f8f9fa;
42
+ border: 1px solid #dee2e6;
43
+ border-radius: 4px;
44
+ min-height: 20px;
45
+ font-family: monospace;
46
+ font-size: 14px;
47
+ }
48
+ .result-success {
49
+ background: #d4edda;
50
+ border-color: #c3e6cb;
51
+ color: #155724;
52
+ }
53
+ .result-error {
54
+ background: #f8d7da;
55
+ border-color: #f5c6cb;
56
+ color: #721c24;
57
+ }
58
+ .result-loading {
59
+ background: #d1ecf1;
60
+ border-color: #bee5eb;
61
+ color: #0c5460;
62
+ }
63
+ .button-group {
64
+ display: flex;
65
+ gap: 10px;
66
+ margin-top: 10px;
67
+ }
68
+ button {
69
+ padding: 8px 16px;
70
+ border: 1px solid #ccc;
71
+ border-radius: 4px;
72
+ background: white;
73
+ cursor: pointer;
74
+ font-size: 14px;
75
+ }
76
+ button:hover {
77
+ background: #f8f9fa;
78
+ }
79
+ button[type="submit"] {
80
+ background: #007bff;
81
+ color: white;
82
+ border-color: #007bff;
83
+ }
84
+ button[type="submit"]:hover {
85
+ background: #0056b3;
86
+ }
87
+ button[type="reset"] {
88
+ background: #6c757d;
89
+ color: white;
90
+ border-color: #6c757d;
91
+ }
92
+ button[type="reset"]:hover {
93
+ background: #545b62;
94
+ }
95
+ .control-group {
96
+ margin-bottom: 15px;
97
+ }
98
+ .checkbox-group {
99
+ display: flex;
100
+ align-items: center;
101
+ gap: 8px;
102
+ margin-bottom: 10px;
103
+ }
104
+ .checkbox-group input[type="checkbox"] {
105
+ width: 18px;
106
+ height: 18px;
107
+ cursor: pointer;
108
+ }
109
+ .checkbox-group label {
110
+ font-weight: 500;
111
+ color: #555;
112
+ cursor: pointer;
113
+ }
114
+ .status-indicator {
115
+ display: inline-block;
116
+ width: 12px;
117
+ height: 12px;
118
+ border-radius: 50%;
119
+ margin-right: 8px;
120
+ }
121
+ .status-idle {
122
+ background: #6c757d;
123
+ }
124
+ .status-loading {
125
+ background: #007bff;
126
+ animation: pulse 1s infinite;
127
+ }
128
+ .status-success {
129
+ background: #28a745;
130
+ }
131
+ .status-error {
132
+ background: #dc3545;
133
+ }
134
+ @keyframes pulse {
135
+ 0%,
136
+ 100% {
137
+ opacity: 1;
138
+ }
139
+ 50% {
140
+ opacity: 0.5;
141
+ }
142
+ }
143
+ </style>
144
+ </head>
145
+ <body>
146
+ <h1>Checkbox Demo</h1>
147
+ <div id="root"></div>
148
+
149
+ <script type="module" jsenv-type="module/jsx">
150
+ import { render } from "preact";
151
+ import { useState } from "preact/hooks";
152
+ import { signal } from "@preact/signals";
153
+ import {
154
+ createAction,
155
+ // eslint-disable-next-line no-unused-vars
156
+ Field,
157
+ // eslint-disable-next-line no-unused-vars
158
+ Form,
159
+ // eslint-disable-next-line no-unused-vars
160
+ Input,
161
+ // eslint-disable-next-line no-unused-vars
162
+ Button,
163
+ } from "@jsenv/navi";
164
+
165
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
166
+
167
+ // eslint-disable-next-line no-unused-vars
168
+ const App = () => {
169
+ return (
170
+ <>
171
+ <div className="demo-grid">
172
+ <BasicCheckboxDemo />
173
+ <CheckboxWithInitialValueDemo />
174
+ <CheckboxWithExternalControlDemo />
175
+ <CheckboxWithSignalControlDemo />
176
+ <CheckboxErrorDemo />
177
+ <FormCheckboxDemo />
178
+ <SharedActionDemo />
179
+ </div>
180
+ </>
181
+ );
182
+ };
183
+
184
+ // eslint-disable-next-line no-unused-vars
185
+ const BasicCheckboxDemo = () => {
186
+ const [result, setResult] = useState("");
187
+ const [status, setStatus] = useState("idle");
188
+
189
+ return (
190
+ <div className="demo-card">
191
+ <h3 className="demo-title">Basic Checkbox with Action</h3>
192
+ <div className="control-group">
193
+ <div className="checkbox-group">
194
+ <Input
195
+ id="basic-checkbox"
196
+ type="checkbox"
197
+ name="enabled"
198
+ action={async ({ enabled }) => {
199
+ setStatus("loading");
200
+ setResult("Processing...");
201
+ try {
202
+ await delay(1000);
203
+ const response = `${enabled ? "✅" : "☐"} Checkbox ${enabled ? "checked" : "unchecked"}`;
204
+ setResult(response);
205
+ setStatus("success");
206
+ return response;
207
+ } catch (error) {
208
+ setResult(`❌ Error: ${error.message}`);
209
+ setStatus("error");
210
+ throw error;
211
+ }
212
+ }}
213
+ />
214
+ <label htmlFor="basic-checkbox">
215
+ <span className={`status-indicator status-${status}`}></span>
216
+ Enable feature
217
+ </label>
218
+ </div>
219
+ </div>
220
+ <div
221
+ className={`result-display ${status === "loading" ? "result-loading" : status === "success" ? "result-success" : status === "error" ? "result-error" : ""}`}
222
+ >
223
+ {result || "Check or uncheck to see action..."}
224
+ </div>
225
+ </div>
226
+ );
227
+ };
228
+
229
+ // eslint-disable-next-line no-unused-vars
230
+ const CheckboxWithInitialValueDemo = () => {
231
+ const [result, setResult] = useState("");
232
+
233
+ return (
234
+ <div className="demo-card">
235
+ <h3 className="demo-title">Checkbox with Initial Value</h3>
236
+ <div className="control-group">
237
+ <div className="checkbox-group">
238
+ <Input
239
+ id="initial-checkbox"
240
+ type="checkbox"
241
+ name="agreed"
242
+ checked={true}
243
+ action={async ({ agreed }) => {
244
+ setResult("Processing...");
245
+ await delay(800);
246
+ const response = `${agreed ? "✅" : "☐"} Agreement: ${agreed ? "accepted" : "declined"}`;
247
+ setResult(response);
248
+ return response;
249
+ }}
250
+ />
251
+ <label htmlFor="initial-checkbox">
252
+ I agree to the terms and conditions
253
+ </label>
254
+ </div>
255
+ </div>
256
+ <div
257
+ className={`result-display ${result.startsWith("✅") || result.startsWith("☐") ? "result-success" : ""}`}
258
+ >
259
+ {result || "Change checkbox to see action..."}
260
+ </div>
261
+ </div>
262
+ );
263
+ };
264
+
265
+ // eslint-disable-next-line no-unused-vars
266
+ const CheckboxWithExternalControlDemo = () => {
267
+ const [isChecked, setIsChecked] = useState(undefined);
268
+ const [result, setResult] = useState("");
269
+
270
+ // Debug logging
271
+ console.log(
272
+ "CheckboxWithExternalControlDemo render - isChecked:",
273
+ isChecked,
274
+ );
275
+
276
+ return (
277
+ <div className="demo-card">
278
+ <h3 className="demo-title">Checkbox with External Control</h3>
279
+ <div className="control-group">
280
+ <div className="checkbox-group">
281
+ <Input
282
+ id="controlled-checkbox"
283
+ type="checkbox"
284
+ name="notifications"
285
+ checked={isChecked}
286
+ action={async ({ notifications }) => {
287
+ setResult("Processing...");
288
+ await delay(600);
289
+ const response = `${notifications ? "✅" : "☐"} Notifications ${notifications ? "enabled" : "disabled"}`;
290
+ setResult(response);
291
+ return response;
292
+ }}
293
+ />
294
+ <label htmlFor="controlled-checkbox">
295
+ Enable notifications
296
+ </label>
297
+ </div>
298
+ </div>
299
+ <div className="button-group">
300
+ <button
301
+ onClick={() => {
302
+ console.log("Setting checkbox to checked");
303
+ setIsChecked(true);
304
+ }}
305
+ >
306
+ Enable
307
+ </button>
308
+ <button
309
+ onClick={() => {
310
+ console.log("Setting checkbox to unchecked");
311
+ setIsChecked(false);
312
+ }}
313
+ >
314
+ Disable
315
+ </button>
316
+ <button
317
+ onClick={() => {
318
+ console.log(
319
+ "Resetting checkbox, current isChecked:",
320
+ isChecked,
321
+ );
322
+ setIsChecked(undefined);
323
+ }}
324
+ >
325
+ Reset
326
+ </button>
327
+ </div>
328
+ <div
329
+ className={`result-display ${result.startsWith("✅") || result.startsWith("☐") ? "result-success" : ""}`}
330
+ >
331
+ Current state:{" "}
332
+ {isChecked === undefined
333
+ ? "undefined"
334
+ : isChecked
335
+ ? "checked"
336
+ : "unchecked"}
337
+ <br />
338
+ {result ||
339
+ "Use buttons to control checkbox, then it will trigger action..."}
340
+ </div>
341
+ </div>
342
+ );
343
+ };
344
+
345
+ // Create an action that's bound to the signal
346
+ const toggleAction = createAction(async ({ notifications }) => {
347
+ await delay(600);
348
+ return `${notifications ? "✅" : "☐"} Notifications ${notifications ? "enabled" : "disabled"}`;
349
+ });
350
+ // Create a signal to control the checkbox state
351
+ const valueSignal = signal(undefined);
352
+ const toggleActionBound = toggleAction.bindParams({
353
+ notifications: valueSignal,
354
+ });
355
+
356
+ // eslint-disable-next-line no-unused-vars
357
+ const CheckboxWithSignalControlDemo = () => {
358
+ const [result, setResult] = useState("");
359
+
360
+ return (
361
+ <div className="demo-card">
362
+ <h3 className="demo-title">Checkbox with Signal Control</h3>
363
+ <div className="control-group">
364
+ <div className="checkbox-group">
365
+ <Input
366
+ id="signal-controlled-checkbox"
367
+ type="checkbox"
368
+ name="notifications_signal"
369
+ valueSignal={valueSignal}
370
+ action={toggleActionBound}
371
+ onActionStart={() => {
372
+ setResult("Processing...");
373
+ }}
374
+ onActionEnd={(e) => {
375
+ setResult(e.detail.data);
376
+ }}
377
+ />
378
+ <label htmlFor="signal-controlled-checkbox">
379
+ Enable notifications (signal controlled)
380
+ </label>
381
+ </div>
382
+ </div>
383
+ <div className="button-group">
384
+ <button
385
+ onClick={() => {
386
+ console.log("Setting signal to true");
387
+ valueSignal.value = "on";
388
+ }}
389
+ >
390
+ Enable via Signal
391
+ </button>
392
+ <button
393
+ onClick={() => {
394
+ console.log("Setting signal to false");
395
+ valueSignal.value = false;
396
+ }}
397
+ >
398
+ Disable via Signal
399
+ </button>
400
+ <button
401
+ onClick={() => {
402
+ console.log(
403
+ "Toggling signal, current value:",
404
+ valueSignal.value,
405
+ );
406
+ valueSignal.value = valueSignal.value === "on" ? false : "on";
407
+ }}
408
+ >
409
+ Toggle Signal
410
+ </button>
411
+ </div>
412
+ <div
413
+ className={`result-display ${result.startsWith("✅") || result.startsWith("☐") ? "result-success" : ""}`}
414
+ >
415
+ Signal value: {valueSignal.value}
416
+ <br />
417
+ {result ||
418
+ "Use buttons to control checkbox via signal, action will trigger automatically..."}
419
+ </div>
420
+ </div>
421
+ );
422
+ };
423
+
424
+ // eslint-disable-next-line no-unused-vars
425
+ const CheckboxErrorDemo = () => {
426
+ const [result, setResult] = useState("");
427
+ const [status, setStatus] = useState("idle");
428
+
429
+ return (
430
+ <div className="demo-card">
431
+ <h3 className="demo-title">Checkbox with Error Handling</h3>
432
+ <div className="control-group">
433
+ <div className="checkbox-group">
434
+ <Input
435
+ id="error-checkbox"
436
+ type="checkbox"
437
+ name="dangerous"
438
+ action={async ({ dangerous }) => {
439
+ setStatus("loading");
440
+ setResult("Processing...");
441
+ try {
442
+ await delay(1500);
443
+ throw new Error(
444
+ `Action failed for ${dangerous ? "checked" : "unchecked"} state`,
445
+ );
446
+ } catch (error) {
447
+ setResult(`❌ ${error.message}`);
448
+ setStatus("error");
449
+ throw error;
450
+ }
451
+ }}
452
+ />
453
+ <label htmlFor="error-checkbox">
454
+ <span className={`status-indicator status-${status}`}></span>
455
+ Dangerous operation (always fails)
456
+ </label>
457
+ </div>
458
+ </div>
459
+ <div
460
+ className={`result-display ${status === "loading" ? "result-loading" : status === "error" ? "result-error" : ""}`}
461
+ >
462
+ {result || "This checkbox will always trigger an error..."}
463
+ </div>
464
+ </div>
465
+ );
466
+ };
467
+
468
+ // eslint-disable-next-line no-unused-vars
469
+ const FormCheckboxDemo = () => {
470
+ const [result, setResult] = useState("");
471
+ const [isLoading, setIsLoading] = useState(false);
472
+
473
+ return (
474
+ <div className="demo-card">
475
+ <h3 className="demo-title">Checkboxes inside Form</h3>
476
+ <Form
477
+ action={async ({ username, newsletter, updates, marketing }) => {
478
+ setIsLoading(true);
479
+ setResult("Submitting preferences...");
480
+ try {
481
+ await delay(1200);
482
+ const prefs = [];
483
+ if (newsletter) prefs.push("newsletter");
484
+ if (updates) prefs.push("updates");
485
+ if (marketing) prefs.push("marketing");
486
+ const response = `✅ User: ${username}\nSubscriptions: ${prefs.length > 0 ? prefs.join(", ") : "none"}`;
487
+ setResult(response);
488
+ return response;
489
+ } catch (error) {
490
+ setResult(`❌ Form error: ${error.message}`);
491
+ throw error;
492
+ } finally {
493
+ setIsLoading(false);
494
+ }
495
+ }}
496
+ >
497
+ <input type="hidden" name="username" value="john_doe" />
498
+ <div className="control-group">
499
+ <div className="checkbox-group">
500
+ <Input
501
+ id="form-newsletter"
502
+ type="checkbox"
503
+ name="newsletter"
504
+ checked={true}
505
+ />
506
+ <label htmlFor="form-newsletter">
507
+ Newsletter subscription
508
+ </label>
509
+ </div>
510
+ <div className="checkbox-group">
511
+ <Input id="form-updates" type="checkbox" name="updates" />
512
+ <label htmlFor="form-updates">Product updates</label>
513
+ </div>
514
+ <div className="checkbox-group">
515
+ <Input id="form-marketing" type="checkbox" name="marketing" />
516
+ <label htmlFor="form-marketing">Marketing emails</label>
517
+ </div>
518
+ </div>
519
+ <div className="button-group">
520
+ <Button type="submit">Save Preferences</Button>
521
+ <Button type="reset">Reset</Button>
522
+ </div>
523
+ </Form>
524
+ <div
525
+ className={`result-display ${isLoading ? "result-loading" : result.startsWith("✅") ? "result-success" : result.startsWith("❌") ? "result-error" : ""}`}
526
+ >
527
+ {result || "Select preferences and submit..."}
528
+ </div>
529
+ </div>
530
+ );
531
+ };
532
+
533
+ // eslint-disable-next-line no-unused-vars
534
+ const SharedActionDemo = () => {
535
+ const [result, setResult] = useState("");
536
+
537
+ return (
538
+ <div className="demo-card">
539
+ <h3 className="demo-title">Shared Action Example</h3>
540
+ <p
541
+ style={{
542
+ margin: "0 0 15px 0",
543
+ fontSize: "14px",
544
+ color: "#666",
545
+ lineHeight: "1.4",
546
+ }}
547
+ >
548
+ <strong>Note:</strong> The checkbox states are not synced here,
549
+ which is normal. In a real application, both checkboxes would
550
+ typically be controlled by the same external state to keep them
551
+ synchronized.
552
+ </p>
553
+ <div className="control-group">
554
+ <div className="checkbox-group">
555
+ <Input
556
+ id="shared-checkbox-1"
557
+ type="checkbox"
558
+ name="feature1"
559
+ action={sharedToggleAction}
560
+ onActionStart={() => {
561
+ setResult("Processing...");
562
+ }}
563
+ onActionEnd={(e) => {
564
+ setResult(e.detail.data);
565
+ }}
566
+ />
567
+ <label htmlFor="shared-checkbox-1">
568
+ Feature 1 (shared action)
569
+ </label>
570
+ </div>
571
+ <div className="checkbox-group">
572
+ <Input
573
+ id="shared-checkbox-2"
574
+ type="checkbox"
575
+ name="feature2"
576
+ action={sharedToggleAction}
577
+ onActionStart={() => {
578
+ setResult("Processing...");
579
+ }}
580
+ onActionEnd={(e) => {
581
+ setResult(e.detail.data);
582
+ }}
583
+ />
584
+ <label htmlFor="shared-checkbox-2">
585
+ Feature 2 (same shared action)
586
+ </label>
587
+ </div>
588
+ </div>
589
+ <div
590
+ className={`result-display ${result.startsWith("✅") || result.startsWith("☐") ? "result-success" : ""}`}
591
+ >
592
+ {result || "Toggle either checkbox to see shared action..."}
593
+ </div>
594
+ </div>
595
+ );
596
+ };
597
+
598
+ const sharedToggleAction = createAction(async (params) => {
599
+ await delay(800);
600
+ const featureName = Object.keys(params).find((key) =>
601
+ key.startsWith("feature"),
602
+ );
603
+ const isEnabled = params[featureName];
604
+ const response = `${isEnabled ? "✅" : "☐"} ${featureName} ${isEnabled ? "enabled" : "disabled"} (from shared action)`;
605
+ return response;
606
+ });
607
+
608
+ render(<App />, document.querySelector("#root"));
609
+ </script>
610
+ </body>
611
+ </html>