@jsenv/navi 0.0.1 → 0.1.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.
Files changed (138) hide show
  1. package/dist/jsenv_navi.js +22954 -0
  2. package/index.js +66 -16
  3. package/package.json +22 -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/{input → field}/input_textual.jsx +247 -173
  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/radio_list.jsx +0 -283
  129. package/src/components/input/use_form_event.js +0 -20
  130. package/src/components/input/use_on_change.js +0 -12
  131. package/src/components/selection/selection.js +0 -5
  132. package/src/components/selection/selection_context.jsx +0 -262
  133. package/src/components/shortcut/shortcut_context.jsx +0 -390
  134. package/src/components/use_action_events.js +0 -37
  135. package/src/utils/iterable_weak_set.js +0 -62
  136. /package/src/components/demos/action/{11_nested_shortcuts_demo.html → 11_nested_shortcuts_demo.xhtml} +0 -0
  137. /package/src/components/{shortcut → keyboard_shortcuts}/os.js +0 -0
  138. /package/src/route/{route.test.html → route.xtest.html} +0 -0
@@ -11,6 +11,7 @@
11
11
 
12
12
  <script type="module" jsenv-type="module/jsx">
13
13
  import { render } from "preact";
14
+ import { useState } from "preact/hooks";
14
15
  import {
15
16
  // eslint-disable-next-line no-unused-vars
16
17
  Button,
@@ -18,194 +19,333 @@
18
19
  Form,
19
20
  // eslint-disable-next-line no-unused-vars
20
21
  RadioList,
22
+ // eslint-disable-next-line no-unused-vars
23
+ Radio,
24
+ // eslint-disable-next-line no-unused-vars
25
+ Label,
21
26
  } from "@jsenv/navi";
22
27
 
23
28
  // eslint-disable-next-line no-unused-vars
24
29
  const App = () => {
25
30
  return (
26
- <div>
27
- <div style="display: flex; flex-direction: row; gap: 30px">
28
- <div>
29
- <p>
30
- <strong>No action </strong>
31
- </p>
31
+ <div style="display: flex; flex-direction: column; gap: 40px">
32
+ <DynamicRadioListDemo />
33
+ </div>
34
+ );
35
+ };
32
36
 
33
- <RadioList
34
- id="no_action"
35
- name="color"
36
- label="Select your favorite color:"
37
- >
38
- {[
39
- { label: "Red", value: "red" },
40
- { label: "Blue", value: "blue" },
41
- { label: "Green", value: "green" },
42
- ]}
43
- </RadioList>
44
- </div>
37
+ // eslint-disable-next-line no-unused-vars
38
+ const DynamicRadioListDemo = () => {
39
+ // Available color choices with their selection state
40
+ const [colorChoices, setColorChoices] = useState([
41
+ { id: 1, color: "red", selected: true },
42
+ { id: 2, color: "blue", selected: false },
43
+ { id: 3, color: "green", selected: false },
44
+ ]);
45
45
 
46
- <div>
47
- <p>
48
- <strong>action log after 1s</strong>
49
- </p>
46
+ // Derived state: currently selected color in data state
47
+ const selectedColor =
48
+ colorChoices.find((choice) => choice.selected)?.color || null;
50
49
 
51
- <RadioList
52
- id="action_alert"
53
- name="color_alert"
54
- label="Select your favorite color:"
55
- action={async ({ color_alert }) => {
56
- await new Promise((resolve) => setTimeout(resolve, 1_000));
57
- console.log(
58
- `Color selected: ${JSON.stringify(color_alert)}`,
59
- );
60
- }}
61
- >
62
- {[
63
- { label: "Red", value: "red" },
64
- { label: "Blue", value: "blue" },
65
- { label: "Green", value: "green" },
66
- ]}
67
- </RadioList>
68
- </div>
50
+ // UI FORM STATE - This represents the current form state (may differ from data state)
51
+ const [uiSelectedColor, setUiSelectedColor] = useState(selectedColor);
52
+
53
+ // Next ID for new colors
54
+ const [nextId, setNextId] = useState(4);
55
+
56
+ // Form submission feedback
57
+ const [isSubmitting, setIsSubmitting] = useState(false);
58
+ const [submitMessage, setSubmitMessage] = useState("");
59
+ const [submitError, setSubmitError] = useState("");
60
+ const [forceError, setForceError] = useState(false);
61
+
62
+ // Random colors pool
63
+ const randomColors = [
64
+ "purple",
65
+ "orange",
66
+ "yellow",
67
+ "pink",
68
+ "cyan",
69
+ "magenta",
70
+ "lime",
71
+ "indigo",
72
+ "coral",
73
+ "teal",
74
+ "gold",
75
+ "silver",
76
+ "navy",
77
+ "maroon",
78
+ ];
79
+
80
+ // Action that simulates submitting form data (takes 1s)
81
+ const submitForm = async ({ color }) => {
82
+ setIsSubmitting(true);
83
+ setSubmitError("");
84
+ setSubmitMessage(`Syncing color: ${color || "none"}...`);
85
+
86
+ try {
87
+ console.log("Submitting form data:", color);
88
+ await new Promise((resolve) => setTimeout(resolve, 1000));
89
+
90
+ // Simulate error if checkbox is checked
91
+ if (forceError) {
92
+ throw new Error(
93
+ "Simulated server error: Failed to update color preference",
94
+ );
95
+ }
96
+
97
+ // Update the data state to match the form submission
98
+ setColorChoices((prev) =>
99
+ prev.map((choice) => ({
100
+ ...choice,
101
+ selected: choice.color === color,
102
+ })),
103
+ );
104
+
105
+ setSubmitMessage(`✅ Successfully synced: ${color || "none"}`);
106
+ console.log("Data state updated from form:", color);
107
+ } catch (error) {
108
+ setSubmitError(error.message);
109
+ setSubmitMessage("");
110
+ } finally {
111
+ setIsSubmitting(false);
112
+ }
113
+ };
114
+
115
+ // Add a new color to the data state
116
+ const addColorToState = (color) => {
117
+ setColorChoices((prev) => [
118
+ ...prev,
119
+ { id: nextId, color, selected: false },
120
+ ]);
121
+ setNextId((prev) => prev + 1);
122
+ };
123
+
124
+ // Remove a color from the data state
125
+ const removeColorFromState = (colorId) => {
126
+ setColorChoices((prev) =>
127
+ prev.filter((choice) => choice.id !== colorId),
128
+ );
129
+ };
130
+
131
+ // Select a color in the data state (radio behavior - only one selected)
132
+ const selectColorInState = (colorId) => {
133
+ setColorChoices((prev) =>
134
+ prev.map((choice) => ({
135
+ ...choice,
136
+ selected: choice.id === colorId,
137
+ })),
138
+ );
139
+ };
69
140
 
70
- <div>
71
- <p>
72
- <strong>Form + log after 1s</strong>
141
+ // Clear selection in the data state
142
+ const clearSelectionInState = () => {
143
+ setColorChoices((prev) =>
144
+ prev.map((choice) => ({
145
+ ...choice,
146
+ selected: false,
147
+ })),
148
+ );
149
+ };
150
+
151
+ // Get the color that reset would sync to
152
+ const getResetColor = () => {
153
+ return colorChoices.find((choice) => choice.selected)?.color || null;
154
+ };
155
+
156
+ // Add a random color choice to the data state
157
+ const addRandomColor = () => {
158
+ const availableColors = randomColors.filter(
159
+ (color) => !colorChoices.some((choice) => choice.color === color),
160
+ );
161
+ if (availableColors.length > 0) {
162
+ const randomColor =
163
+ availableColors[
164
+ Math.floor(Math.random() * availableColors.length)
165
+ ];
166
+ addColorToState(randomColor);
167
+ }
168
+ };
169
+
170
+ return (
171
+ <div style="border: 2px solid #ddd; padding: 20px; border-radius: 8px; background: #f9f9f9;">
172
+ <h2>State ↔ UI Form Demo (Radio Selection)</h2>
173
+
174
+ <div style="display: flex; gap: 20px; margin: 20px 0;">
175
+ {/* DATA STATE SECTION */}
176
+ <div style="flex: 1; padding: 15px; background: #fff; border-radius: 6px; border: 1px solid #ccc;">
177
+ <h3 style="margin: 0 0 15px 0; color: #2196F3;">
178
+ 🗄️ Data State
179
+ </h3>
180
+ <p style="margin: 0 0 15px 0; font-size: 14px; color: #666;">
181
+ Direct manipulation of the data source (single selection)
73
182
  </p>
74
- <Form
75
- action={async ({ color }) => {
76
- await new Promise((resolve) => setTimeout(resolve, 1_000));
77
- console.log(`Color selected: ${JSON.stringify(color)}`);
78
- }}
79
- >
80
- <RadioList
81
- id="color_form_log"
82
- label="Select your favorite color:"
83
- name="color"
183
+
184
+ <div style="margin-bottom: 15px;">
185
+ <button
186
+ onClick={addRandomColor}
187
+ style="padding: 6px 12px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; margin-right: 8px;"
84
188
  >
85
- {[
86
- { label: "Red", id: "red", value: "red" },
87
- { label: "Blue", id: "blue", value: "blue" },
88
- { label: "Green", id: "green", value: "green" },
89
- ]}
90
- </RadioList>
91
-
92
- <Button type="submit">Submit</Button>
93
- <Button type="reset">Reset</Button>
94
- </Form>
95
- </div>
96
- </div>
189
+ + Add Random Color
190
+ </button>
191
+ <button
192
+ onClick={clearSelectionInState}
193
+ style="padding: 6px 12px; background: #ff9800; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px;"
194
+ >
195
+ Clear Selection
196
+ </button>
197
+ </div>
97
198
 
98
- <div>
99
- <p>
100
- <strong>Action + throw after 1s</strong>
101
- </p>
102
-
103
- <RadioList
104
- id="color_action_throw"
105
- name="color_action_throw"
106
- label="Select your favorite color:"
107
- action={async ({ color_action_throw }) => {
108
- await new Promise((resolve) => setTimeout(resolve, 1_000));
109
- throw new Error(
110
- `Color selected: ${JSON.stringify(color_action_throw)}`,
111
- );
112
- }}
113
- >
114
- {[
115
- { label: "Red", id: "red", value: "red" },
116
- { label: "Blue", id: "blue", value: "blue" },
117
- { label: "Green", id: "green", value: "green" },
118
- ]}
119
- </RadioList>
120
- </div>
199
+ <div style="display: flex; flex-direction: column; gap: 8px;">
200
+ {colorChoices.map(({ id, color, selected }) => (
201
+ <div
202
+ key={id}
203
+ style={{
204
+ display: "flex",
205
+ alignItems: "center",
206
+ gap: "8px",
207
+ padding: "6px",
208
+ border: "1px solid #eee",
209
+ borderRadius: "3px",
210
+ background: selected ? "#e8f5e8" : "#fafafa",
211
+ borderColor: selected ? "#4CAF50" : "#eee",
212
+ }}
213
+ >
214
+ <span style="flex: 1; font-size: 14px;">
215
+ {color.charAt(0).toUpperCase() + color.slice(1)}
216
+ </span>
121
217
 
122
- <div>
123
- <div>
124
- <p>
125
- <strong>Form + throw after 1s</strong>
218
+ <button
219
+ onClick={() => selectColorInState(id)}
220
+ style={{
221
+ padding: "4px 8px",
222
+ fontSize: "11px",
223
+ background: selected ? "#4CAF50" : "#2196F3",
224
+ color: "white",
225
+ border: "none",
226
+ borderRadius: "3px",
227
+ cursor: "pointer",
228
+ minWidth: "60px",
229
+ }}
230
+ >
231
+ {selected ? "Selected" : "Select"}
232
+ </button>
233
+
234
+ <button
235
+ onClick={() => removeColorFromState(id)}
236
+ style="padding: 4px 6px; background: #f44336; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;"
237
+ title="Remove this color"
238
+ >
239
+ 🗑
240
+ </button>
241
+ </div>
242
+ ))}
243
+ </div>
244
+ </div>
245
+
246
+ {/* UI FORM SECTION */}
247
+ <div style="flex: 1; padding: 15px; background: #fff; border-radius: 6px; border: 1px solid #ccc;">
248
+ <h3 style="margin: 0 0 15px 0; color: #FF9800;">📝 UI Form</h3>
249
+ <p style="margin: 0 0 15px 0; font-size: 14px; color: #666;">
250
+ Radio button interface for user interaction
126
251
  </p>
127
- <Form
128
- action={async ({ color_form_throw }) => {
129
- await new Promise((resolve) => setTimeout(resolve, 1_000));
130
- throw new Error(
131
- `Color selected: ${JSON.stringify(color_form_throw)}`,
132
- );
133
- }}
134
- >
135
- <RadioList
136
- id="color_form_throw"
137
- name="color_form_throw"
138
- label="Select your favorite color:"
139
- >
140
- {[
141
- { label: "Red", id: "red", value: "red" },
142
- { label: "Blue", id: "blue", value: "blue" },
143
- { label: "Green", id: "green", value: "green" },
144
- ]}
145
- </RadioList>
146
-
147
- <Button type="submit">Submit</Button>
148
- <Button type="reset">Reset</Button>
252
+
253
+ <Form action={submitForm}>
254
+ <fieldset style="border: 1px solid #ddd; padding: 10px; border-radius: 4px;">
255
+ <legend>Select Favorite Color:</legend>
256
+ <RadioList
257
+ name="color"
258
+ onUIStateChange={(color) => {
259
+ setUiSelectedColor(color);
260
+ }}
261
+ >
262
+ {colorChoices.map(({ id, color }) => (
263
+ <Label key={id} style="display: block; margin: 5px 0;">
264
+ {color.charAt(0).toUpperCase() + color.slice(1)}
265
+ <Radio
266
+ value={color}
267
+ checked={selectedColor === color}
268
+ />
269
+ </Label>
270
+ ))}
271
+ </RadioList>
272
+ </fieldset>
273
+
274
+ {/* Error simulation checkbox */}
275
+ <div style="margin: 10px 0; padding: 8px; background: #ffebee; border-radius: 4px; border: 1px solid #ffcdd2;">
276
+ <label style="display: flex; align-items: center; gap: 8px; font-size: 12px; color: #d32f2f;">
277
+ <input
278
+ type="checkbox"
279
+ checked={forceError}
280
+ onChange={(e) => setForceError(e.target.checked)}
281
+ style="margin: 0;"
282
+ />
283
+ 🔧 Force submission error (for testing)
284
+ </label>
285
+ </div>
286
+
287
+ {/* Submission feedback messages */}
288
+ {(isSubmitting || submitMessage || submitError) && (
289
+ <div style="margin: 10px 0; padding: 10px; border-radius: 4px; font-size: 14px;">
290
+ {isSubmitting && (
291
+ <div style="color: #1976d2; background: #e3f2fd; padding: 8px; border-radius: 3px; border-left: 3px solid #1976d2;">
292
+ 🔄 {submitMessage}
293
+ </div>
294
+ )}
295
+ {!isSubmitting && submitMessage && (
296
+ <div style="color: #388e3c; background: #e8f5e8; padding: 8px; border-radius: 3px; border-left: 3px solid #388e3c;">
297
+ {submitMessage}
298
+ </div>
299
+ )}
300
+ {submitError && (
301
+ <div style="color: #d32f2f; background: #ffebee; padding: 8px; border-radius: 3px; border-left: 3px solid #d32f2f;">
302
+ ❌ {submitError}
303
+ </div>
304
+ )}
305
+ </div>
306
+ )}
307
+
308
+ <div style="margin-top: 15px; display: flex; align-items: flex-start; flex-direction: column; gap: 8px;">
309
+ <Button type="submit" disabled={isSubmitting}>
310
+ {isSubmitting ? "⏳ Submitting..." : "📤 Submit Form"}{" "}
311
+ {!isSubmitting && uiSelectedColor && (
312
+ <span style="font-size: 11px; opacity: 0.7;">
313
+ ({uiSelectedColor})
314
+ </span>
315
+ )}
316
+ </Button>
317
+ <Button type="reset">
318
+ 🔄 Reset to State{" "}
319
+ {getResetColor() && (
320
+ <span style="font-size: 11px; opacity: 0.7;">
321
+ ({getResetColor()})
322
+ </span>
323
+ )}
324
+ </Button>
325
+ </div>
149
326
  </Form>
150
327
  </div>
151
328
  </div>
152
329
 
153
- <div>
154
- <p>
155
- <strong>Form + ref checked throw after 1s</strong>
156
- </p>
157
- <Form
158
- action={async ({ color_form_checked_throw }) => {
159
- await new Promise((resolve) => setTimeout(resolve, 1_000));
160
- throw new Error(
161
- `Color selected: ${JSON.stringify(color_form_checked_throw)}`,
162
- );
163
- }}
164
- >
165
- <RadioList
166
- id="color_form_checked_throw"
167
- name="color_form_checked_throw"
168
- label="Select your favorite color:"
169
- value="red"
170
- >
171
- {[
172
- { label: "Red", id: "red", value: "red" },
173
- { label: "Blue", id: "blue", value: "blue" },
174
- { label: "Green", id: "green", value: "green" },
175
- ]}
176
- </RadioList>
177
-
178
- <Button type="submit">Submit</Button>
179
- <Button type="reset">Reset</Button>
180
- </Form>
181
- </div>
182
-
183
- <div>
184
- <p>
185
- <strong>Form + required</strong>
186
- </p>
187
- <Form
188
- action={async () => {
189
- await new Promise((resolve) => setTimeout(resolve, 1_000));
190
- }}
191
- >
192
- <RadioList
193
- id="color_form_required"
194
- label="Select your favorite color:"
195
- name="color"
196
- required
197
- data-required-message="Veuillez sélectionner une couleur"
198
- >
199
- {[
200
- { label: "Red", id: "red", value: "red" },
201
- { label: "Blue", id: "blue", value: "blue" },
202
- { label: "Green", id: "green", value: "green" },
203
- ]}
204
- </RadioList>
205
-
206
- <Button type="submit">Submit</Button>
207
- <Button type="reset">Reset</Button>
208
- </Form>
330
+ <div style="background: #f0f8ff; padding: 15px; border-radius: 6px; margin: 20px 0; border-left: 4px solid #2196F3;">
331
+ <h4 style="margin: 0 0 10px 0;">How it works:</h4>
332
+ <ul style="margin: 0; padding-left: 20px; font-size: 14px;">
333
+ <li>
334
+ <strong>Data State</strong>: The actual data with direct
335
+ manipulation buttons (single selection only)
336
+ </li>
337
+ <li>
338
+ <strong>UI Form</strong>: Radio button interface that can
339
+ differ from the data state
340
+ </li>
341
+ <li>
342
+ <strong>Submit</strong>: Updates data state to match the form
343
+ selection
344
+ </li>
345
+ <li>
346
+ <strong>Reset</strong>: Syncs form back to current data state
347
+ </li>
348
+ </ul>
209
349
  </div>
210
350
  </div>
211
351
  );