@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,303 @@
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>Form validation test - Multiple elements</title>
8
+ <style>
9
+ .test-container {
10
+ margin: 20px;
11
+ padding: 20px;
12
+ border: 1px solid #ccc;
13
+ border-radius: 4px;
14
+ }
15
+ .field {
16
+ margin-bottom: 15px;
17
+ }
18
+ .field label {
19
+ display: block;
20
+ margin-bottom: 5px;
21
+ font-weight: bold;
22
+ }
23
+ .status {
24
+ margin-top: 20px;
25
+ padding: 10px;
26
+ border-radius: 4px;
27
+ }
28
+ .status.success {
29
+ background-color: #d4edda;
30
+ color: #155724;
31
+ border: 1px solid #c3e6cb;
32
+ }
33
+ .status.error {
34
+ background-color: #f8d7da;
35
+ color: #721c24;
36
+ border: 1px solid #f5c6cb;
37
+ }
38
+ .validation-errors {
39
+ margin-top: 10px;
40
+ }
41
+ .validation-errors ul {
42
+ margin: 0;
43
+ padding-left: 20px;
44
+ }
45
+ /* ✅ Styles for form params display */
46
+ .form-params {
47
+ margin-top: 20px;
48
+ padding: 15px;
49
+ background-color: #f8f9fa;
50
+ border: 1px solid #dee2e6;
51
+ border-radius: 4px;
52
+ }
53
+ .form-params h3 {
54
+ margin-top: 0;
55
+ color: #495057;
56
+ }
57
+ .form-params pre {
58
+ background-color: #e9ecef;
59
+ padding: 10px;
60
+ border-radius: 4px;
61
+ overflow-x: auto;
62
+ margin: 0;
63
+ font-size: 14px;
64
+ }
65
+ </style>
66
+ </head>
67
+ <body>
68
+ <div id="root"></div>
69
+
70
+ <script type="module" jsenv-type="module/jsx">
71
+ import { render } from "preact";
72
+ import { useState } from "preact/hooks";
73
+ import {
74
+ // eslint-disable-next-line no-unused-vars
75
+ Input,
76
+ // eslint-disable-next-line no-unused-vars
77
+ Form,
78
+ // eslint-disable-next-line no-unused-vars
79
+ Button,
80
+ } from "@jsenv/navi";
81
+
82
+ // eslint-disable-next-line no-unused-vars
83
+ const App = () => {
84
+ return (
85
+ <div>
86
+ <h1>Test de validation de formulaire multi-éléments</h1>
87
+ <MultiFieldValidationTest />
88
+ </div>
89
+ );
90
+ };
91
+
92
+ // eslint-disable-next-line no-unused-vars
93
+ const MultiFieldValidationTest = () => {
94
+ const [submitStatus, setSubmitStatus] = useState(null);
95
+ const [validationErrors, setValidationErrors] = useState([]);
96
+ const [formParams, setFormParams] = useState(null); // ✅ New state for form params
97
+
98
+ const handleFormExecutePrevented = (event) => {
99
+ console.log("❌ Form execute prevented event:", event);
100
+ const reason = event.detail;
101
+
102
+ if (reason === "validation_failed") {
103
+ setSubmitStatus("error");
104
+ setFormParams(null); // ✅ Clear params on validation failure
105
+
106
+ // Collecter toutes les erreurs de validation
107
+ const form = event.target;
108
+ const errors = [];
109
+
110
+ // Parcourir tous les éléments du formulaire
111
+ const formElements = form.querySelectorAll(
112
+ "input, select, textarea",
113
+ );
114
+ formElements.forEach((element) => {
115
+ const validationInterface = element.__validationInterface__;
116
+ if (validationInterface) {
117
+ // Vérifier si l'élément a des erreurs
118
+ if (
119
+ !element.checkValidity() ||
120
+ !validationInterface.checkValidity?.()
121
+ ) {
122
+ const fieldName = element.name || element.id || element.type;
123
+ const validationMessage =
124
+ element.validationMessage || "Invalid value";
125
+ errors.push(`${fieldName}: ${validationMessage}`);
126
+ }
127
+ }
128
+ });
129
+
130
+ setValidationErrors(errors);
131
+ }
132
+ };
133
+
134
+ return (
135
+ <div className="test-container">
136
+ <h2>Formulaire avec validation multiple</h2>
137
+ <p>
138
+ Ce formulaire contient plusieurs champs avec différentes
139
+ contraintes. Essayez de soumettre avec des valeurs invalides pour
140
+ voir toutes les erreurs.
141
+ </p>
142
+
143
+ <Form
144
+ onReset={() => {
145
+ setSubmitStatus(null);
146
+ setValidationErrors([]);
147
+ setFormParams(null); // ✅ Clear params on reset
148
+ }}
149
+ onExecutePrevented={handleFormExecutePrevented}
150
+ action={async (params) => {
151
+ console.log("🎯 Form execute event with params:", params);
152
+
153
+ // ✅ Store form params in state
154
+ setFormParams(params);
155
+ setSubmitStatus("executing...");
156
+ setValidationErrors([]);
157
+ await new Promise((resolve) => setTimeout(resolve, 2_000));
158
+ setSubmitStatus("Form submitted successfully!");
159
+ }}
160
+ >
161
+ {/* ✅ Champ requis */}
162
+ <div className="field">
163
+ <label htmlFor="required-field">Nom (obligatoire) :</label>
164
+ <Input
165
+ id="required-field"
166
+ name="name"
167
+ required
168
+ placeholder="Entrez votre nom"
169
+ />
170
+ </div>
171
+
172
+ {/* ✅ Champ email avec validation type */}
173
+ <div className="field">
174
+ <label htmlFor="email-field">Email (format email) :</label>
175
+ <Input
176
+ id="email-field"
177
+ name="email"
178
+ type="email"
179
+ placeholder="exemple@domain.com"
180
+ />
181
+ </div>
182
+
183
+ {/* ✅ Champ avec pattern */}
184
+ <div className="field">
185
+ <label htmlFor="phone-field">
186
+ Téléphone (format: 01-23-45-67-89) :
187
+ </label>
188
+ <Input
189
+ id="phone-field"
190
+ name="phone"
191
+ pattern="[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}"
192
+ pattern-validation-message="Doit respecter le format <strong>01-23-45-67-89</strong>."
193
+ placeholder="01-23-45-67-89"
194
+ />
195
+ </div>
196
+
197
+ {/* ✅ Champ avec longueur minimum */}
198
+ <div className="field">
199
+ <label htmlFor="password-field">
200
+ Mot de passe (min 8 caractères) :
201
+ </label>
202
+ <Input
203
+ id="password-field"
204
+ name="password"
205
+ type="password"
206
+ minLength="8"
207
+ placeholder="Au moins 8 caractères"
208
+ />
209
+ </div>
210
+
211
+ {/* ✅ Champ avec contrainte personnalisée */}
212
+ <div className="field">
213
+ <label htmlFor="age-field">Âge (18-99 ans) :</label>
214
+ <Input
215
+ id="age-field"
216
+ name="age"
217
+ type="number"
218
+ min="18"
219
+ min-validation-message="L'age doit être de <strong>18</strong> ans ou plus."
220
+ max="99"
221
+ max-validation-message="L'age doit être de <strong>99</strong> ans ou moins."
222
+ placeholder="18"
223
+ />
224
+ </div>
225
+
226
+ {/* ✅ Bouton de soumission */}
227
+ <div className="field">
228
+ <Button type="submit">Valider le formulaire</Button>
229
+ <Button type="reset">Reset Status</Button>
230
+ </div>
231
+ </Form>
232
+
233
+ {/* ✅ Affichage des paramètres du formulaire */}
234
+ {formParams && (
235
+ <div className="form-params">
236
+ <h3>📋 Paramètres du formulaire soumis :</h3>
237
+ <pre>{JSON.stringify(formParams, null, 2)}</pre>
238
+ </div>
239
+ )}
240
+
241
+ {/* ✅ Affichage du statut */}
242
+ {submitStatus && (
243
+ <div
244
+ className={`status ${submitStatus === "error" ? "error" : "success"}`}
245
+ >
246
+ {submitStatus === "error" ? (
247
+ <div>
248
+ <strong>❌ Validation échouée</strong>
249
+ {validationErrors.length > 0 && (
250
+ <div className="validation-errors">
251
+ <p>Erreurs détectées :</p>
252
+ <ul>
253
+ {validationErrors.map((error, index) => (
254
+ <li key={index}>{error}</li>
255
+ ))}
256
+ </ul>
257
+ </div>
258
+ )}
259
+ </div>
260
+ ) : (
261
+ <strong>✅ {submitStatus}</strong>
262
+ )}
263
+ </div>
264
+ )}
265
+
266
+ {/* ✅ Instructions de test */}
267
+ <div
268
+ style={{
269
+ marginTop: "30px",
270
+ padding: "15px",
271
+ backgroundColor: "#f8f9fa",
272
+ borderRadius: "4px",
273
+ }}
274
+ >
275
+ <h3>Instructions de test :</h3>
276
+ <ol>
277
+ <li>
278
+ <strong>Test 1</strong> : Laissez tous les champs vides et
279
+ cliquez "Valider" → Doit montrer toutes les erreurs
280
+ </li>
281
+ <li>
282
+ <strong>Test 2</strong> : Remplissez partiellement (ex: nom
283
+ seulement) → Doit montrer les erreurs restantes
284
+ </li>
285
+ <li>
286
+ <strong>Test 3</strong> : Entrez des valeurs invalides (ex:
287
+ email sans @, âge inf. 18) → Doit montrer les erreurs
288
+ spécifiques
289
+ </li>
290
+ <li>
291
+ <strong>Test 4</strong> : Remplissez tout correctement → Doit
292
+ permettre la soumission et afficher les paramètres
293
+ </li>
294
+ </ol>
295
+ </div>
296
+ </div>
297
+ );
298
+ };
299
+
300
+ render(<App />, document.querySelector("#root"));
301
+ </script>
302
+ </body>
303
+ </html>
@@ -0,0 +1,172 @@
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>Details demo</title>
8
+ </head>
9
+ <body>
10
+ <div id="root" style="position: relative"></div>
11
+
12
+ <script type="module" jsenv-type="module/jsx">
13
+ import { render } from "preact";
14
+ import {
15
+ createAction,
16
+ // eslint-disable-next-line no-unused-vars
17
+ Details,
18
+ // eslint-disable-next-line no-unused-vars
19
+ Form,
20
+ } from "@jsenv/navi";
21
+
22
+ // eslint-disable-next-line no-unused-vars
23
+ const App = () => {
24
+ return (
25
+ <>
26
+ <div style="display: flex; flex-direction: row; gap: 30px">
27
+ <div>
28
+ <p>
29
+ <strong>Loaded sync</strong>
30
+ </p>
31
+ <Details
32
+ id="test"
33
+ action={() => {
34
+ return "Hello";
35
+ }}
36
+ >
37
+ {(message) => <span>{message}</span>}
38
+ </Details>
39
+ </div>
40
+
41
+ <div>
42
+ <p>
43
+ <strong>Loaded after 1s</strong>
44
+ </p>
45
+ <Details
46
+ id="test_2"
47
+ action={async () => {
48
+ await new Promise((resolve) => {
49
+ setTimeout(resolve, 1000);
50
+ });
51
+ return "Hello";
52
+ }}
53
+ label="Custom summary"
54
+ >
55
+ {(content) => content}
56
+ </Details>
57
+ </div>
58
+
59
+ <div>
60
+ <p>
61
+ <strong>Throw sync</strong>
62
+ </p>
63
+ <Details
64
+ id="test_3"
65
+ action={() => {
66
+ throw new Error("error");
67
+ }}
68
+ label={
69
+ <>
70
+ <span>A</span>
71
+ <span style="font-weight: bold">mixed summary</span>
72
+ </>
73
+ }
74
+ ></Details>
75
+ </div>
76
+ </div>
77
+
78
+ <div>
79
+ <p>
80
+ <strong>Full control (abort, reload)</strong>
81
+ </p>
82
+ <Details
83
+ id="test_4"
84
+ action={async (_, { signal }) => {
85
+ await new Promise((resolve) => {
86
+ const timeout = setTimeout(resolve, 1000);
87
+ signal.addEventListener("abort", () => {
88
+ clearTimeout(timeout);
89
+ });
90
+ });
91
+ return "Hello";
92
+ }}
93
+ >
94
+ {{
95
+ loading: (action) => {
96
+ return (
97
+ <span>
98
+ Loading...
99
+ <button
100
+ onClick={() => {
101
+ action.abort();
102
+ }}
103
+ >
104
+ Abort
105
+ </button>
106
+ </span>
107
+ );
108
+ },
109
+ aborted: (action) => (
110
+ <>
111
+ <span style="color: red">Aborted</span>
112
+ <button
113
+ onClick={() => {
114
+ action.reload();
115
+ }}
116
+ >
117
+ Reload
118
+ </button>
119
+ </>
120
+ ),
121
+ completed: (message, action) => (
122
+ <>
123
+ <span>{message}</span>
124
+ <button
125
+ onClick={() => {
126
+ action.reload();
127
+ }}
128
+ >
129
+ Reload
130
+ </button>
131
+ </>
132
+ ),
133
+ }}
134
+ </Details>
135
+ </div>
136
+
137
+ <div>
138
+ <p>
139
+ <strong>Action preloaded</strong>
140
+ </p>
141
+
142
+ <Details
143
+ id="bound"
144
+ action={rootAction}
145
+ label="Action using bind params"
146
+ >
147
+ {(content) => content}
148
+ </Details>
149
+ </div>
150
+ </>
151
+ );
152
+ };
153
+
154
+ const rootAction = createAction(
155
+ async ({ name = "default name" }) => {
156
+ await new Promise((resolve) => {
157
+ setTimeout(resolve, 500);
158
+ });
159
+ return name;
160
+ },
161
+ { name: "load user" },
162
+ );
163
+ window.rootAction = rootAction;
164
+ // const boundAction = rootAction.bindParams({
165
+ // name: "dam",
166
+ // });
167
+ rootAction.prerun();
168
+
169
+ render(<App />, document.querySelector("#root"));
170
+ </script>
171
+ </body>
172
+ </html>