@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.
- package/index.js +51 -0
- package/package.json +38 -0
- package/src/action_private_properties.js +11 -0
- package/src/action_proxy_test.html +353 -0
- package/src/action_run_states.js +5 -0
- package/src/actions.js +1377 -0
- package/src/browser_integration/browser_integration.js +191 -0
- package/src/browser_integration/document_back_and_forward.js +17 -0
- package/src/browser_integration/document_loading_signal.js +100 -0
- package/src/browser_integration/document_state_signal.js +9 -0
- package/src/browser_integration/document_url_signal.js +9 -0
- package/src/browser_integration/use_is_visited.js +19 -0
- package/src/browser_integration/via_history.js +199 -0
- package/src/browser_integration/via_navigation.js +168 -0
- package/src/components/action_execution/form_context.js +8 -0
- package/src/components/action_execution/render_actionable_component.jsx +27 -0
- package/src/components/action_execution/use_action.js +330 -0
- package/src/components/action_execution/use_execute_action.js +161 -0
- package/src/components/action_renderer.jsx +136 -0
- package/src/components/collect_form_element_values.js +79 -0
- package/src/components/demos/0_button_demo.html +155 -0
- package/src/components/demos/1_checkbox_demo.html +257 -0
- package/src/components/demos/2_input_textual_demo.html +354 -0
- package/src/components/demos/3_radio_demo.html +222 -0
- package/src/components/demos/4_select_demo.html +104 -0
- package/src/components/demos/5_list_scrollable_demo.html +153 -0
- package/src/components/demos/action/0_button_demo.html +204 -0
- package/src/components/demos/action/10_shortcuts_demo.html +189 -0
- package/src/components/demos/action/11_nested_shortcuts_demo.html +401 -0
- package/src/components/demos/action/1_input_text_demo.html +461 -0
- package/src/components/demos/action/2_form_multiple.html +303 -0
- package/src/components/demos/action/3_details_demo.html +172 -0
- package/src/components/demos/action/4_input_checkbox_demo.html +611 -0
- package/src/components/demos/action/6_checkbox_list_demo.html +109 -0
- package/src/components/demos/action/7_radio_list_demo.html +217 -0
- package/src/components/demos/action/8_editable_text_demo.html +442 -0
- package/src/components/demos/action/9_link_demo.html +172 -0
- package/src/components/demos/demo.md +0 -0
- package/src/components/demos/route/basic/basic.html +14 -0
- package/src/components/demos/route/basic/basic_route_demo.jsx +224 -0
- package/src/components/demos/route/multi/multi.html +14 -0
- package/src/components/demos/route/multi/multi_route_demo.jsx +277 -0
- package/src/components/details/details.jsx +248 -0
- package/src/components/details/summary_marker.jsx +141 -0
- package/src/components/editable_text/editable_text.jsx +96 -0
- package/src/components/error_boundary_context.js +9 -0
- package/src/components/form.jsx +144 -0
- package/src/components/input/button.jsx +333 -0
- package/src/components/input/checkbox_list.jsx +294 -0
- package/src/components/input/field.jsx +61 -0
- package/src/components/input/field_css.js +118 -0
- package/src/components/input/input.jsx +15 -0
- package/src/components/input/input_checkbox.jsx +370 -0
- package/src/components/input/input_radio.jsx +299 -0
- package/src/components/input/input_textual.jsx +338 -0
- package/src/components/input/radio_list.jsx +283 -0
- package/src/components/input/select.jsx +273 -0
- package/src/components/input/use_form_event.js +20 -0
- package/src/components/input/use_on_change.js +12 -0
- package/src/components/link/link.jsx +291 -0
- package/src/components/loader/loader_background.jsx +324 -0
- package/src/components/loader/loading_spinner.jsx +68 -0
- package/src/components/loader/network_speed.js +83 -0
- package/src/components/loader/rectangle_loading.jsx +225 -0
- package/src/components/route.jsx +15 -0
- package/src/components/selection/selection.js +5 -0
- package/src/components/selection/selection_context.jsx +262 -0
- package/src/components/shortcut/os.js +9 -0
- package/src/components/shortcut/shortcut_context.jsx +390 -0
- package/src/components/use_action_events.js +37 -0
- package/src/components/use_auto_focus.js +43 -0
- package/src/components/use_debounce_true.js +31 -0
- package/src/components/use_focus_group.js +19 -0
- package/src/components/use_initial_value.js +104 -0
- package/src/components/use_is_visited.js +19 -0
- package/src/components/use_ref_array.js +38 -0
- package/src/components/use_signal_sync.js +50 -0
- package/src/components/use_state_array.js +40 -0
- package/src/docs/actions.md +228 -0
- package/src/docs/demos/resource/action_status.jsx +42 -0
- package/src/docs/demos/resource/demo.md +1 -0
- package/src/docs/demos/resource/resource_demo_0.html +84 -0
- package/src/docs/demos/resource/resource_demo_10_post_gc.html +364 -0
- package/src/docs/demos/resource/resource_demo_11_describe_many.html +362 -0
- package/src/docs/demos/resource/resource_demo_2.html +173 -0
- package/src/docs/demos/resource/resource_demo_3_filtered_users.html +415 -0
- package/src/docs/demos/resource/resource_demo_4_details.html +284 -0
- package/src/docs/demos/resource/resource_demo_5_renderer_lazy.html +115 -0
- package/src/docs/demos/resource/resource_demo_6_gc.html +217 -0
- package/src/docs/demos/resource/resource_demo_7_child_gc.html +240 -0
- package/src/docs/demos/resource/resource_demo_8_proxy_gc.html +319 -0
- package/src/docs/demos/resource/resource_demo_9_describe_one.html +472 -0
- package/src/docs/demos/resource/tata.jsx +3 -0
- package/src/docs/demos/resource/toto.jsx +3 -0
- package/src/docs/demos/user_nav/user_nav.html +12 -0
- package/src/docs/demos/user_nav/user_nav.jsx +330 -0
- package/src/docs/resource_dependencies.md +103 -0
- package/src/docs/resource_with_params.md +80 -0
- package/src/notes.md +13 -0
- package/src/route/route.js +518 -0
- package/src/route/route.test.html +228 -0
- package/src/store/array_signal_store.js +537 -0
- package/src/store/local_storage_signal.js +17 -0
- package/src/store/resource_graph.js +1303 -0
- package/src/store/tests/resource_graph_autoreload_demo.html +12 -0
- package/src/store/tests/resource_graph_autoreload_demo.jsx +964 -0
- package/src/store/tests/resource_graph_dependencies.test.js +95 -0
- package/src/store/value_in_local_storage.js +187 -0
- package/src/symbol_object_signal.js +1 -0
- package/src/use_action_data.js +10 -0
- package/src/use_action_status.js +47 -0
- package/src/utils/add_many_event_listeners.js +15 -0
- package/src/utils/array_add_remove.js +61 -0
- package/src/utils/array_signal.js +15 -0
- package/src/utils/compare_two_js_values.js +172 -0
- package/src/utils/execute_with_cleanup.js +21 -0
- package/src/utils/get_caller_info.js +85 -0
- package/src/utils/iterable_weak_set.js +62 -0
- package/src/utils/js_value_weak_map.js +162 -0
- package/src/utils/js_value_weak_map_demo.html +690 -0
- package/src/utils/merge_two_js_values.js +53 -0
- package/src/utils/stringify_for_display.js +150 -0
- 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>
|