@jsenv/navi 0.0.1 → 0.1.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 (139) hide show
  1. package/dist/jsenv_navi.js +22959 -0
  2. package/index.js +66 -16
  3. package/package.json +23 -11
  4. package/src/actions.js +50 -26
  5. package/src/browser_integration/browser_integration.js +31 -6
  6. package/src/browser_integration/via_history.js +42 -9
  7. package/src/components/action_execution/render_actionable_component.jsx +6 -4
  8. package/src/components/action_execution/use_action.js +51 -282
  9. package/src/components/action_execution/use_execute_action.js +106 -92
  10. package/src/components/action_execution/use_run_on_mount.js +9 -0
  11. package/src/components/action_renderer.jsx +21 -32
  12. package/src/components/demos/0_button_demo.html +574 -103
  13. package/src/components/demos/10_column_reordering_debug.html +277 -0
  14. package/src/components/demos/11_table_selection_debug.html +432 -0
  15. package/src/components/demos/1_checkbox_demo.html +579 -202
  16. package/src/components/demos/2_input_textual_demo.html +81 -138
  17. package/src/components/demos/3_radio_demo.html +0 -2
  18. package/src/components/demos/4_select_demo.html +19 -23
  19. package/src/components/demos/6_tablist_demo.html +77 -0
  20. package/src/components/demos/7_table_selection_demo.html +176 -0
  21. package/src/components/demos/8_table_fixed_headers_demo.html +584 -0
  22. package/src/components/demos/9_table_column_drag_demo.html +325 -0
  23. package/src/components/demos/action/0_button_demo.html +2 -4
  24. package/src/components/demos/action/1_input_text_demo.html +643 -222
  25. package/src/components/demos/action/3_details_demo.html +146 -115
  26. package/src/components/demos/action/4_input_checkbox_demo.html +442 -322
  27. package/src/components/demos/action/5_input_checkbox_state_demo.html +270 -0
  28. package/src/components/demos/action/6_checkbox_list_demo.html +304 -72
  29. package/src/components/demos/action/7_radio_list_demo.html +310 -170
  30. package/src/components/demos/action/{8_editable_text_demo.html → 8_editable_demo.html} +65 -76
  31. package/src/components/demos/action/9_link_demo.html +84 -62
  32. package/src/components/demos/ui_transition/0_action_renderer_ui_transition_demo.html +695 -0
  33. package/src/components/demos/ui_transition/1_nested_ui_transition_demo.html +429 -0
  34. package/src/components/demos/ui_transition/2_height_transition_test.html +295 -0
  35. package/src/components/details/details.jsx +62 -64
  36. package/src/components/edition/editable.jsx +186 -0
  37. package/src/components/field/README.md +247 -0
  38. package/src/components/{input → field}/button.jsx +151 -130
  39. package/src/components/field/checkbox_list.jsx +184 -0
  40. package/src/components/{collect_form_element_values.js → field/collect_form_element_values.js} +7 -4
  41. package/src/components/{input → field}/field_css.js +4 -1
  42. package/src/components/field/form.jsx +211 -0
  43. package/src/components/{input → field}/input.jsx +1 -0
  44. package/src/components/{input → field}/input_checkbox.jsx +132 -155
  45. package/src/components/{input → field}/input_radio.jsx +135 -46
  46. package/src/components/field/input_textual.jsx +418 -0
  47. package/src/components/field/label.jsx +32 -0
  48. package/src/components/field/radio_list.jsx +182 -0
  49. package/src/components/{input → field}/select.jsx +17 -32
  50. package/src/components/field/use_action_events.js +132 -0
  51. package/src/components/field/use_form_events.js +55 -0
  52. package/src/components/field/use_ui_state_controller.js +506 -0
  53. package/src/components/item_tracker/README.md +461 -0
  54. package/src/components/item_tracker/use_isolated_item_tracker.jsx +209 -0
  55. package/src/components/item_tracker/use_isolated_item_tracker_demo.html +148 -0
  56. package/src/components/item_tracker/use_isolated_item_tracker_demo.jsx +460 -0
  57. package/src/components/item_tracker/use_item_tracker.jsx +143 -0
  58. package/src/components/item_tracker/use_item_tracker_demo.html +207 -0
  59. package/src/components/item_tracker/use_item_tracker_demo.jsx +216 -0
  60. package/src/components/keyboard_shortcuts/active_keyboard_shortcuts.jsx +87 -0
  61. package/src/components/keyboard_shortcuts/aria_key_shortcuts.js +61 -0
  62. package/src/components/keyboard_shortcuts/keyboard_key_meta.js +17 -0
  63. package/src/components/keyboard_shortcuts/keyboard_shortcuts.js +371 -0
  64. package/src/components/link/link.jsx +65 -102
  65. package/src/components/link/link_with_icon.jsx +52 -0
  66. package/src/components/loader/loader_background.jsx +85 -64
  67. package/src/components/loader/rectangle_loading.jsx +38 -19
  68. package/src/components/route.jsx +8 -4
  69. package/src/components/selection/selection.jsx +1583 -0
  70. package/src/components/svg/font_sized_svg.jsx +45 -0
  71. package/src/components/svg/icon_and_text.jsx +21 -0
  72. package/src/components/svg/svg_mask_overlay.jsx +105 -0
  73. package/src/components/table/drag/table_drag.jsx +506 -0
  74. package/src/components/table/resize/table_resize.jsx +650 -0
  75. package/src/components/table/resize/table_size.js +43 -0
  76. package/src/components/table/selection/table_selection.js +106 -0
  77. package/src/components/table/selection/table_selection.jsx +203 -0
  78. package/src/components/table/sticky/sticky_group.js +354 -0
  79. package/src/components/table/sticky/table_sticky.js +25 -0
  80. package/src/components/table/sticky/table_sticky.jsx +501 -0
  81. package/src/components/table/table.jsx +721 -0
  82. package/src/components/table/table_css.js +211 -0
  83. package/src/components/table/table_ui.jsx +49 -0
  84. package/src/components/table/use_cells_and_columns.js +90 -0
  85. package/src/components/table/use_object_array_to_cells.js +46 -0
  86. package/src/components/table/z_indexes.js +23 -0
  87. package/src/components/tablist/tablist.jsx +99 -0
  88. package/src/components/text/overflow.jsx +15 -0
  89. package/src/components/text/text_and_count.jsx +28 -0
  90. package/src/components/ui_transition.jsx +128 -0
  91. package/src/components/use_auto_focus.js +58 -7
  92. package/src/components/use_batch_during_render.js +33 -0
  93. package/src/components/use_debounce_true.js +7 -7
  94. package/src/components/use_dependencies_diff.js +35 -0
  95. package/src/components/use_focus_group.js +4 -3
  96. package/src/components/use_initial_value.js +8 -34
  97. package/src/components/use_signal_sync.js +1 -1
  98. package/src/components/use_stable_callback.js +68 -0
  99. package/src/components/use_state_array.js +16 -9
  100. package/src/docs/actions.md +22 -0
  101. package/src/notes.md +33 -12
  102. package/src/route/route.js +97 -47
  103. package/src/store/resource_graph.js +2 -1
  104. package/src/store/tests/{resource_graph_dependencies.test.js → resource_graph_dependencies.test_manual.js} +13 -13
  105. package/src/utils/is_signal.js +20 -0
  106. package/src/utils/stringify_for_display.js +4 -23
  107. package/src/validation/constraints/confirm_constraint.js +14 -0
  108. package/src/validation/constraints/create_unique_value_constraint.js +27 -0
  109. package/src/validation/constraints/native_constraints.js +313 -0
  110. package/src/validation/constraints/readonly_constraint.js +36 -0
  111. package/src/validation/constraints/single_space_constraint.js +13 -0
  112. package/src/validation/custom_constraint_validation.js +599 -0
  113. package/src/validation/custom_message.js +18 -0
  114. package/src/validation/demos/browser_style.png +0 -0
  115. package/src/validation/demos/form_validation_demo.html +142 -0
  116. package/src/validation/demos/form_validation_demo_preact.html +87 -0
  117. package/src/validation/demos/form_validation_native_popover_demo.html +168 -0
  118. package/src/validation/demos/form_validation_vs_native_demo.html +172 -0
  119. package/src/validation/demos/validation_message_demo.html +203 -0
  120. package/src/validation/hooks/use_constraints.js +23 -0
  121. package/src/validation/hooks/use_custom_validation_ref.js +73 -0
  122. package/src/validation/hooks/use_validation_message.js +19 -0
  123. package/src/validation/validation_message.js +741 -0
  124. package/src/components/editable_text/editable_text.jsx +0 -96
  125. package/src/components/form.jsx +0 -144
  126. package/src/components/input/checkbox_list.jsx +0 -294
  127. package/src/components/input/field.jsx +0 -61
  128. package/src/components/input/input_textual.jsx +0 -338
  129. package/src/components/input/radio_list.jsx +0 -283
  130. package/src/components/input/use_form_event.js +0 -20
  131. package/src/components/input/use_on_change.js +0 -12
  132. package/src/components/selection/selection.js +0 -5
  133. package/src/components/selection/selection_context.jsx +0 -262
  134. package/src/components/shortcut/shortcut_context.jsx +0 -390
  135. package/src/components/use_action_events.js +0 -37
  136. package/src/utils/iterable_weak_set.js +0 -62
  137. /package/src/components/demos/action/{11_nested_shortcuts_demo.html → 11_nested_shortcuts_demo.xhtml} +0 -0
  138. /package/src/components/{shortcut → keyboard_shortcuts}/os.js +0 -0
  139. /package/src/route/{route.test.html → route.xtest.html} +0 -0
@@ -65,33 +65,6 @@
65
65
  gap: 10px;
66
66
  margin-top: 10px;
67
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
68
  .control-group {
96
69
  margin-bottom: 15px;
97
70
  }
@@ -140,6 +113,24 @@
140
113
  opacity: 0.5;
141
114
  }
142
115
  }
116
+ nav a {
117
+ color: #007bff;
118
+ text-decoration: none;
119
+ }
120
+ nav a:hover {
121
+ text-decoration: underline;
122
+ }
123
+ h2 {
124
+ margin-top: 40px;
125
+ margin-bottom: 20px;
126
+ padding-top: 20px;
127
+ border-top: 2px solid #e0e0e0;
128
+ }
129
+ h2:first-of-type {
130
+ margin-top: 0;
131
+ border-top: none;
132
+ padding-top: 0;
133
+ }
143
134
  </style>
144
135
  </head>
145
136
  <body>
@@ -148,13 +139,10 @@
148
139
 
149
140
  <script type="module" jsenv-type="module/jsx">
150
141
  import { render } from "preact";
151
- import { useState } from "preact/hooks";
152
- import { signal } from "@preact/signals";
142
+ import { useState, useRef } from "preact/hooks";
153
143
  import {
154
144
  createAction,
155
145
  // eslint-disable-next-line no-unused-vars
156
- Field,
157
- // eslint-disable-next-line no-unused-vars
158
146
  Form,
159
147
  // eslint-disable-next-line no-unused-vars
160
148
  Input,
@@ -166,53 +154,148 @@
166
154
 
167
155
  // eslint-disable-next-line no-unused-vars
168
156
  const App = () => {
157
+ const [checkboxBackgroundColor, setCheckboxBackgroundColor] =
158
+ useState(undefined);
159
+
169
160
  return (
170
161
  <>
162
+ <div className="demo-card" style={{ marginBottom: "30px" }}>
163
+ <h2>Checkbox Demo - Overview</h2>
164
+ <p>
165
+ This demo showcases different ways to use the Input component
166
+ with checkboxes.
167
+ </p>
168
+
169
+ <div
170
+ className="control-group"
171
+ style={{
172
+ marginBottom: "20px",
173
+ padding: "15px",
174
+ backgroundColor: "#f8f9fa",
175
+ borderRadius: "8px",
176
+ }}
177
+ >
178
+ <label>
179
+ <strong>Global Checkbox Background Color:</strong>
180
+ <select
181
+ value={checkboxBackgroundColor || ""}
182
+ onChange={(e) =>
183
+ setCheckboxBackgroundColor(e.target.value || undefined)
184
+ }
185
+ style={{
186
+ marginLeft: "10px",
187
+ padding: "5px 10px",
188
+ borderRadius: "4px",
189
+ border: "1px solid #ccc",
190
+ }}
191
+ >
192
+ <option value="">Default</option>
193
+ <option value="#ff6b6b">Red</option>
194
+ <option value="#51cf66">Green</option>
195
+ </select>
196
+ </label>
197
+ <p
198
+ style={{
199
+ fontSize: "14px",
200
+ color: "#666",
201
+ margin: "8px 0 0 0",
202
+ }}
203
+ >
204
+ This affects all checkboxes below and demonstrates that
205
+ background color changes don't affect checked state.
206
+ </p>
207
+ </div>
208
+
209
+ <nav>
210
+ <h3>Sections:</h3>
211
+ <ul style={{ marginLeft: "20px" }}>
212
+ <li>
213
+ <a href="#basic-checkboxes">
214
+ 1. Basic Checkbox (no action/no form)
215
+ </a>{" "}
216
+ - Simple checkboxes with value handling
217
+ </li>
218
+ <li>
219
+ <a href="#checkbox-actions">2. Checkbox with Actions</a> -
220
+ Checkboxes that trigger actions on change, including error
221
+ handling
222
+ </li>
223
+ <li>
224
+ <a href="#checkbox-forms">3. Checkbox inside Form</a> - Form
225
+ integration patterns
226
+ </li>
227
+ </ul>
228
+ </nav>
229
+ </div>
230
+
231
+ <h2 id="basic-checkboxes">1. Basic Checkbox (no action/no form)</h2>
232
+ <div className="demo-grid">
233
+ <CheckboxDemo backgroundColor={checkboxBackgroundColor} />
234
+ <CheckboxDefaultCheckedDemo
235
+ backgroundColor={checkboxBackgroundColor}
236
+ />
237
+ </div>
238
+
239
+ <h2 id="checkbox-actions">2. Checkbox with Actions</h2>
240
+ <div className="demo-grid">
241
+ <CheckboxActionDemo backgroundColor={checkboxBackgroundColor} />
242
+ <CheckboxActionDefaultCheckedDemo
243
+ backgroundColor={checkboxBackgroundColor}
244
+ />
245
+ <CheckboxActionErrorDemo
246
+ backgroundColor={checkboxBackgroundColor}
247
+ />
248
+ <CheckboxActionErrorDefaultCheckedDemo
249
+ backgroundColor={checkboxBackgroundColor}
250
+ />
251
+ <CheckboxActionErrorOnUncheckedDemo
252
+ backgroundColor={checkboxBackgroundColor}
253
+ />
254
+ <CheckboxActionObjectDemo
255
+ backgroundColor={checkboxBackgroundColor}
256
+ />
257
+ </div>
258
+
259
+ <h2 id="checkbox-forms">3. Checkbox inside Form</h2>
171
260
  <div className="demo-grid">
172
- <BasicCheckboxDemo />
173
- <CheckboxWithInitialValueDemo />
174
- <CheckboxWithExternalControlDemo />
175
- <CheckboxWithSignalControlDemo />
176
- <CheckboxErrorDemo />
177
- <FormCheckboxDemo />
178
- <SharedActionDemo />
261
+ <CheckboxFormControlledErrorDemo
262
+ backgroundColor={checkboxBackgroundColor}
263
+ />
179
264
  </div>
180
265
  </>
181
266
  );
182
267
  };
183
268
 
184
269
  // eslint-disable-next-line no-unused-vars
185
- const BasicCheckboxDemo = () => {
270
+ const CheckboxDemo = ({ backgroundColor }) => {
186
271
  const [result, setResult] = useState("");
187
- const [status, setStatus] = useState("idle");
272
+ const [status] = useState("idle");
273
+ const ref = useRef();
188
274
 
189
275
  return (
190
276
  <div className="demo-card">
191
- <h3 className="demo-title">Basic Checkbox with Action</h3>
277
+ <h3 className="demo-title">
278
+ Basic Checkbox
279
+ <button
280
+ onClick={() => {
281
+ ref.current.dispatchEvent(new CustomEvent("resetuistate"));
282
+ }}
283
+ >
284
+ Reset
285
+ </button>
286
+ </h3>
192
287
  <div className="control-group">
193
288
  <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>
289
+ <label>
290
+ <Input
291
+ ref={ref}
292
+ type="checkbox"
293
+ name="enabled"
294
+ accentColor={backgroundColor}
295
+ onUIStateChange={(checked) => {
296
+ setResult(checked ? "checked" : "unchecked");
297
+ }}
298
+ />
216
299
  Enable feature
217
300
  </label>
218
301
  </div>
@@ -225,223 +308,198 @@
225
308
  </div>
226
309
  );
227
310
  };
228
-
229
311
  // eslint-disable-next-line no-unused-vars
230
- const CheckboxWithInitialValueDemo = () => {
312
+ const CheckboxDefaultCheckedDemo = ({ backgroundColor }) => {
231
313
  const [result, setResult] = useState("");
314
+ const [status] = useState("idle");
315
+ const ref = useRef();
232
316
 
233
317
  return (
234
318
  <div className="demo-card">
235
- <h3 className="demo-title">Checkbox with Initial Value</h3>
319
+ <h3 className="demo-title">
320
+ Basic Checkbox (Default Checked)
321
+ <button
322
+ onClick={() => {
323
+ ref.current.dispatchEvent(new CustomEvent("resetuistate"));
324
+ }}
325
+ >
326
+ Reset
327
+ </button>
328
+ </h3>
236
329
  <div className="control-group">
237
330
  <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
331
+ <label>
332
+ <Input
333
+ ref={ref}
334
+ type="checkbox"
335
+ name="enabled"
336
+ defaultChecked
337
+ accentColor={backgroundColor}
338
+ onUIStateChange={(checked) => {
339
+ setResult(checked ? "checked" : "unchecked");
340
+ }}
341
+ />
342
+ Enable feature
253
343
  </label>
254
344
  </div>
255
345
  </div>
256
346
  <div
257
- className={`result-display ${result.startsWith("") || result.startsWith("") ? "result-success" : ""}`}
347
+ className={`result-display ${status === "loading" ? "result-loading" : status === "success" ? "result-success" : status === "error" ? "result-error" : ""}`}
258
348
  >
259
- {result || "Change checkbox to see action..."}
349
+ {result || "Check or uncheck to see action..."}
260
350
  </div>
261
351
  </div>
262
352
  );
263
353
  };
264
-
265
354
  // eslint-disable-next-line no-unused-vars
266
- const CheckboxWithExternalControlDemo = () => {
267
- const [isChecked, setIsChecked] = useState(undefined);
355
+ const CheckboxActionDemo = ({ backgroundColor }) => {
268
356
  const [result, setResult] = useState("");
357
+ const [status, setStatus] = useState("idle");
269
358
 
270
- // Debug logging
271
- console.log(
272
- "CheckboxWithExternalControlDemo render - isChecked:",
273
- isChecked,
359
+ return (
360
+ <div className="demo-card">
361
+ <h3 className="demo-title">Checkbox with Action</h3>
362
+ <div className="control-group">
363
+ <div className="checkbox-group">
364
+ <label>
365
+ <Input
366
+ type="checkbox"
367
+ name="enabled"
368
+ accentColor={backgroundColor}
369
+ action={async (enabled) => {
370
+ setStatus("loading");
371
+ setResult("Processing...");
372
+ try {
373
+ await delay(1000);
374
+ const response = `${enabled ? "✅" : "☐"} Checkbox ${enabled ? "checked" : "unchecked"}`;
375
+ setResult(response);
376
+ setStatus("success");
377
+ return response;
378
+ } catch (error) {
379
+ setResult(`❌ Error: ${error.message}`);
380
+ setStatus("error");
381
+ throw error;
382
+ }
383
+ }}
384
+ />
385
+ <span className={`status-indicator status-${status}`}></span>
386
+ Enable feature
387
+ </label>
388
+ </div>
389
+ </div>
390
+ <div
391
+ className={`result-display ${status === "loading" ? "result-loading" : status === "success" ? "result-success" : status === "error" ? "result-error" : ""}`}
392
+ >
393
+ {result || "Check or uncheck to see action..."}
394
+ </div>
395
+ </div>
274
396
  );
397
+ };
398
+ // eslint-disable-next-line no-unused-vars
399
+ const CheckboxActionDefaultCheckedDemo = ({ backgroundColor }) => {
400
+ const [result, setResult] = useState("");
275
401
 
276
402
  return (
277
403
  <div className="demo-card">
278
- <h3 className="demo-title">Checkbox with External Control</h3>
404
+ <h3 className="demo-title">
405
+ Checkbox with Action (Default Checked)
406
+ </h3>
279
407
  <div className="control-group">
280
408
  <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
409
+ <label>
410
+ <Input
411
+ type="checkbox"
412
+ name="agreed"
413
+ defaultChecked
414
+ accentColor={backgroundColor}
415
+ action={async (agreed) => {
416
+ setResult("Processing...");
417
+ await delay(800);
418
+ const response = `${agreed ? "✅" : "☐"} Agreement: ${agreed ? "accepted" : "declined"}`;
419
+ setResult(response);
420
+ return response;
421
+ }}
422
+ />
423
+ I agree to the terms and conditions
296
424
  </label>
297
425
  </div>
298
426
  </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
427
  <div
329
428
  className={`result-display ${result.startsWith("✅") || result.startsWith("☐") ? "result-success" : ""}`}
330
429
  >
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..."}
430
+ {result || "Change checkbox to see action..."}
340
431
  </div>
341
432
  </div>
342
433
  );
343
434
  };
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
435
  // eslint-disable-next-line no-unused-vars
357
- const CheckboxWithSignalControlDemo = () => {
436
+ const CheckboxActionErrorDemo = ({ backgroundColor }) => {
358
437
  const [result, setResult] = useState("");
438
+ const [status, setStatus] = useState("idle");
359
439
 
360
440
  return (
361
441
  <div className="demo-card">
362
- <h3 className="demo-title">Checkbox with Signal Control</h3>
442
+ <h3 className="demo-title">Checkbox with Action (Always Fails)</h3>
363
443
  <div className="control-group">
364
444
  <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)
445
+ <label>
446
+ <Input
447
+ type="checkbox"
448
+ name="dangerous"
449
+ accentColor={backgroundColor}
450
+ action={async (dangerous) => {
451
+ setStatus("loading");
452
+ setResult("Processing...");
453
+ try {
454
+ await delay(1500);
455
+ throw new Error(
456
+ `Action failed for ${dangerous ? "checked" : "unchecked"} state`,
457
+ );
458
+ } catch (error) {
459
+ setResult(`❌ ${error.message}`);
460
+ setStatus("error");
461
+ throw error;
462
+ }
463
+ }}
464
+ />
465
+ <span className={`status-indicator status-${status}`}></span>
466
+ Dangerous operation (always fails)
380
467
  </label>
381
468
  </div>
382
469
  </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
470
  <div
413
- className={`result-display ${result.startsWith("") || result.startsWith("") ? "result-success" : ""}`}
471
+ className={`result-display ${status === "loading" ? "result-loading" : status === "error" ? "result-error" : ""}`}
414
472
  >
415
- Signal value: {valueSignal.value}
416
- <br />
417
- {result ||
418
- "Use buttons to control checkbox via signal, action will trigger automatically..."}
473
+ {result || "This checkbox will always trigger an error..."}
419
474
  </div>
420
475
  </div>
421
476
  );
422
477
  };
423
-
424
478
  // eslint-disable-next-line no-unused-vars
425
- const CheckboxErrorDemo = () => {
479
+ const CheckboxActionErrorDefaultCheckedDemo = ({ backgroundColor }) => {
426
480
  const [result, setResult] = useState("");
427
481
  const [status, setStatus] = useState("idle");
428
482
 
429
483
  return (
430
484
  <div className="demo-card">
431
- <h3 className="demo-title">Checkbox with Error Handling</h3>
485
+ <h3 className="demo-title">
486
+ Checkbox with Action (Default Checked, Always Fails)
487
+ </h3>
432
488
  <div className="control-group">
433
489
  <div className="checkbox-group">
434
490
  <Input
435
- id="error-checkbox"
491
+ id="error-initial-checkbox"
436
492
  type="checkbox"
437
- name="dangerous"
438
- action={async ({ dangerous }) => {
493
+ name="risky"
494
+ defaultChecked
495
+ accentColor={backgroundColor}
496
+ action={async (risky) => {
439
497
  setStatus("loading");
440
498
  setResult("Processing...");
441
499
  try {
442
- await delay(1500);
500
+ await delay(1200);
443
501
  throw new Error(
444
- `Action failed for ${dangerous ? "checked" : "unchecked"} state`,
502
+ `Operation failed for ${risky ? "enabled" : "disabled"} risky feature`,
445
503
  );
446
504
  } catch (error) {
447
505
  setResult(`❌ ${error.message}`);
@@ -450,160 +508,222 @@
450
508
  }
451
509
  }}
452
510
  />
453
- <label htmlFor="error-checkbox">
511
+ <label htmlFor="error-initial-checkbox">
454
512
  <span className={`status-indicator status-${status}`}></span>
455
- Dangerous operation (always fails)
513
+ Enable risky feature (starts checked, always fails)
456
514
  </label>
457
515
  </div>
458
516
  </div>
459
517
  <div
460
518
  className={`result-display ${status === "loading" ? "result-loading" : status === "error" ? "result-error" : ""}`}
461
519
  >
462
- {result || "This checkbox will always trigger an error..."}
520
+ {result ||
521
+ "This checkbox starts checked and will always trigger an error..."}
463
522
  </div>
464
523
  </div>
465
524
  );
466
525
  };
467
526
 
468
527
  // eslint-disable-next-line no-unused-vars
469
- const FormCheckboxDemo = () => {
528
+ const CheckboxActionErrorOnUncheckedDemo = ({ backgroundColor }) => {
529
+ // for this example the checkbox is going to be controlled because that's how you would
530
+ // implement it in a real world scenario:
531
+ // the fact checkbox is checked comes from a backend so when the operation succeeds
532
+ // the component is re-rendered as checked
533
+
470
534
  const [result, setResult] = useState("");
471
- const [isLoading, setIsLoading] = useState(false);
535
+ const [status, setStatus] = useState("idle");
536
+ const [checked, setChecked] = useState(false);
472
537
 
473
538
  return (
474
539
  <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">
540
+ <h3 className="demo-title">
541
+ Checkbox with Action (Fails When Unchecked)
542
+ </h3>
543
+ <div className="control-group">
544
+ <div className="checkbox-group">
545
+ <label>
500
546
  <Input
501
- id="form-newsletter"
502
547
  type="checkbox"
503
- name="newsletter"
504
- checked={true}
548
+ name="protection"
549
+ checked={checked}
550
+ accentColor={backgroundColor}
551
+ action={async (protection) => {
552
+ setStatus("loading");
553
+ setResult("Processing...");
554
+ await delay(1000);
555
+ try {
556
+ if (!protection) {
557
+ throw new Error(
558
+ "Protection must be enabled for security",
559
+ );
560
+ }
561
+ const response = `✅ Protection ${protection ? "enabled" : "disabled"}`;
562
+ setResult(response);
563
+ setStatus("success");
564
+ setChecked(true);
565
+ return response;
566
+ } catch (error) {
567
+ setResult(`❌ ${error.message}`);
568
+ setStatus("error");
569
+ throw error;
570
+ }
571
+ }}
505
572
  />
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>
573
+ <span className={`status-indicator status-${status}`}></span>
574
+ Enable security protection (fails when unchecked)
575
+ </label>
522
576
  </div>
523
- </Form>
577
+ </div>
524
578
  <div
525
- className={`result-display ${isLoading ? "result-loading" : result.startsWith("") ? "result-success" : result.startsWith("") ? "result-error" : ""}`}
579
+ className={`result-display ${status === "loading" ? "result-loading" : status === "success" ? "result-success" : status === "error" ? "result-error" : ""}`}
526
580
  >
527
- {result || "Select preferences and submit..."}
581
+ {result ||
582
+ "Toggle checkbox - succeeds when checked, fails when unchecked..."}
528
583
  </div>
529
584
  </div>
530
585
  );
531
586
  };
532
587
 
588
+ const toggleAction = createAction(async ({ notifications }) => {
589
+ await delay(600);
590
+ return `${notifications ? "✅" : "☐"} Notifications ${notifications ? "enabled" : "disabled"}`;
591
+ });
533
592
  // eslint-disable-next-line no-unused-vars
534
- const SharedActionDemo = () => {
593
+ const CheckboxActionObjectDemo = ({ backgroundColor }) => {
535
594
  const [result, setResult] = useState("");
536
595
 
537
596
  return (
538
597
  <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>
598
+ <h3 className="demo-title">Checkbox with Action Object</h3>
553
599
  <div className="control-group">
554
600
  <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)
601
+ <label>
602
+ <Input
603
+ type="checkbox"
604
+ name="notifications"
605
+ accentColor={backgroundColor}
606
+ action={(notifications) => {
607
+ return toggleAction({ notifications });
608
+ }}
609
+ onActionStart={() => {
610
+ setResult("Processing...");
611
+ }}
612
+ onActionEnd={(e) => {
613
+ setResult(e.detail.data);
614
+ }}
615
+ />
616
+ Enable notifications (with action object)
586
617
  </label>
587
618
  </div>
588
619
  </div>
589
620
  <div
590
621
  className={`result-display ${result.startsWith("✅") || result.startsWith("☐") ? "result-success" : ""}`}
591
622
  >
592
- {result || "Toggle either checkbox to see shared action..."}
623
+ {result || "Toggle checkbox to trigger action object..."}
593
624
  </div>
594
625
  </div>
595
626
  );
596
627
  };
597
628
 
598
- const sharedToggleAction = createAction(async (params) => {
599
- await delay(800);
600
- const featureName = Object.keys(params).find((key) =>
601
- key.startsWith("feature"),
629
+ // eslint-disable-next-line no-unused-vars
630
+ const CheckboxFormControlledErrorDemo = ({ backgroundColor }) => {
631
+ const [checked, setChecked] = useState(true);
632
+ const [result, setResult] = useState("");
633
+ const [status, setStatus] = useState("idle");
634
+ const ref = useRef();
635
+
636
+ return (
637
+ <div className="demo-card">
638
+ <h3 className="demo-title">
639
+ Form with Controlled Checkbox (Fails When Submit Without Check)
640
+ </h3>
641
+ <p
642
+ style={{ fontSize: "14px", color: "#666", marginBottom: "15px" }}
643
+ >
644
+ This demonstrates backend validation when frontend forgets to add
645
+ "required" - the server throws an error during form submission if
646
+ terms aren't agreed.
647
+ </p>
648
+ <Form
649
+ action={async ({ terms }) => {
650
+ setStatus("loading");
651
+ setResult("Processing form...");
652
+ try {
653
+ await delay(1000);
654
+ if (!terms) {
655
+ throw new Error(
656
+ "You must agree to the terms and conditions to proceed",
657
+ );
658
+ }
659
+ setChecked(Boolean(terms));
660
+ const response = "✅ Form submitted successfully!";
661
+ setResult(response);
662
+ setStatus("success");
663
+ return response;
664
+ } catch (error) {
665
+ setResult(`❌ ${error.message}`);
666
+ setStatus("error");
667
+ throw error;
668
+ }
669
+ }}
670
+ >
671
+ <div className="control-group">
672
+ <div className="checkbox-group">
673
+ <label>
674
+ <Input
675
+ ref={ref}
676
+ type="checkbox"
677
+ name="terms"
678
+ checked={checked}
679
+ accentColor={backgroundColor}
680
+ />
681
+ <span
682
+ className={`status-indicator status-${status}`}
683
+ ></span>
684
+ I agree to the terms and conditions
685
+ </label>
686
+ </div>
687
+ </div>
688
+ <div className="button-group">
689
+ <Button type="submit">Submit Form</Button>
690
+ <button
691
+ type="button"
692
+ onClick={() => {
693
+ ref.current.dispatchEvent(
694
+ new CustomEvent("setuistate", {
695
+ detail: { value: true },
696
+ }),
697
+ );
698
+ }}
699
+ >
700
+ Accept Terms
701
+ </button>
702
+ <button
703
+ type="button"
704
+ onClick={() => {
705
+ ref.current.dispatchEvent(
706
+ new CustomEvent("setuistate", {
707
+ detail: { value: false },
708
+ }),
709
+ );
710
+ }}
711
+ >
712
+ Decline Terms
713
+ </button>
714
+ </div>
715
+ </Form>
716
+ <div
717
+ className={`result-display ${status === "loading" ? "result-loading" : status === "success" ? "result-success" : status === "error" ? "result-error" : ""}`}
718
+ >
719
+ Terms agreed: {checked ? "Yes" : "No"}
720
+ <br />
721
+ {result ||
722
+ "Fill out the form - frontend will simulate backend submission rejected when terms not agreed..."}
723
+ </div>
724
+ </div>
602
725
  );
603
- const isEnabled = params[featureName];
604
- const response = `${isEnabled ? "✅" : "☐"} ${featureName} ${isEnabled ? "enabled" : "disabled"} (from shared action)`;
605
- return response;
606
- });
726
+ };
607
727
 
608
728
  render(<App />, document.querySelector("#root"));
609
729
  </script>