@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,461 @@
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>Input Text 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
+ background: #f5f5f5;
18
+ }
19
+ .demo-grid {
20
+ display: grid;
21
+ grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
22
+ gap: 20px;
23
+ margin-bottom: 30px;
24
+ }
25
+ .demo-card {
26
+ background: white;
27
+ border-radius: 8px;
28
+ padding: 20px;
29
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
30
+ }
31
+ .demo-title {
32
+ margin: 0 0 15px 0;
33
+ color: #333;
34
+ font-size: 16px;
35
+ font-weight: 600;
36
+ border-bottom: 2px solid #e0e0e0;
37
+ padding-bottom: 8px;
38
+ }
39
+ .result-display {
40
+ margin-top: 15px;
41
+ padding: 12px;
42
+ background: #f8f9fa;
43
+ border: 1px solid #dee2e6;
44
+ border-radius: 4px;
45
+ min-height: 20px;
46
+ font-family: monospace;
47
+ font-size: 14px;
48
+ }
49
+ .result-success {
50
+ background: #d4edda;
51
+ border-color: #c3e6cb;
52
+ color: #155724;
53
+ }
54
+ .result-error {
55
+ background: #f8d7da;
56
+ border-color: #f5c6cb;
57
+ color: #721c24;
58
+ }
59
+ .result-loading {
60
+ background: #d1ecf1;
61
+ border-color: #bee5eb;
62
+ color: #0c5460;
63
+ }
64
+ .button-group {
65
+ display: flex;
66
+ gap: 10px;
67
+ margin-top: 10px;
68
+ }
69
+ button {
70
+ padding: 8px 16px;
71
+ border: 1px solid #ccc;
72
+ border-radius: 4px;
73
+ background: white;
74
+ cursor: pointer;
75
+ font-size: 14px;
76
+ }
77
+ button:hover {
78
+ background: #f8f9fa;
79
+ }
80
+ button[type="submit"] {
81
+ background: #007bff;
82
+ color: white;
83
+ border-color: #007bff;
84
+ }
85
+ button[type="submit"]:hover {
86
+ background: #0056b3;
87
+ }
88
+ button[type="reset"] {
89
+ background: #6c757d;
90
+ color: white;
91
+ border-color: #6c757d;
92
+ }
93
+ button[type="reset"]:hover {
94
+ background: #545b62;
95
+ }
96
+ input {
97
+ width: 100%;
98
+ padding: 8px 12px;
99
+ border: 1px solid #ccc;
100
+ border-radius: 4px;
101
+ font-size: 14px;
102
+ box-sizing: border-box;
103
+ }
104
+ .control-group {
105
+ margin-bottom: 15px;
106
+ }
107
+ label {
108
+ display: block;
109
+ margin-bottom: 5px;
110
+ font-weight: 500;
111
+ color: #555;
112
+ }
113
+ </style>
114
+ </head>
115
+ <body>
116
+ <h1>Input Text Demo</h1>
117
+ <div id="root"></div>
118
+
119
+ <script type="module" jsenv-type="module/jsx">
120
+ import { render } from "preact";
121
+ import { useState } from "preact/hooks";
122
+ // eslint-disable-next-line no-unused-vars
123
+ import { Field, Input, Form, Button, createAction } from "@jsenv/navi";
124
+
125
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
126
+
127
+ // eslint-disable-next-line no-unused-vars
128
+ const App = () => {
129
+ return (
130
+ <>
131
+ <div className="demo-grid">
132
+ <BasicInputDemo />
133
+ <InputWithInitialValueDemo />
134
+ <InputWithExternalControlDemo />
135
+ <FormInputDemo />
136
+ <FormWithExternalControlDemo />
137
+ <SharedActionDemo />
138
+ </div>
139
+ </>
140
+ );
141
+ };
142
+
143
+ // eslint-disable-next-line no-unused-vars
144
+ const BasicInputDemo = () => {
145
+ const [result, setResult] = useState("");
146
+ const [isLoading, setIsLoading] = useState(false);
147
+
148
+ return (
149
+ <div className="demo-card">
150
+ <h3 className="demo-title">Basic Input with Action</h3>
151
+ <div className="control-group">
152
+ <label htmlFor="basic-input">Enter some text:</label>
153
+ <Input
154
+ id="basic-input"
155
+ name="text"
156
+ placeholder="Type something..."
157
+ action={async ({ text }) => {
158
+ setIsLoading(true);
159
+ setResult("Processing...");
160
+ try {
161
+ await delay(1000);
162
+ const response = `✅ Processed: "${text}"`;
163
+ setResult(response);
164
+ return response;
165
+ } catch (error) {
166
+ setResult(`❌ Error: ${error.message}`);
167
+ throw error;
168
+ } finally {
169
+ setIsLoading(false);
170
+ }
171
+ }}
172
+ />
173
+ </div>
174
+ <div
175
+ className={`result-display ${isLoading ? "result-loading" : result.startsWith("✅") ? "result-success" : result.startsWith("❌") ? "result-error" : ""}`}
176
+ >
177
+ {result || "Result will appear here..."}
178
+ </div>
179
+ </div>
180
+ );
181
+ };
182
+
183
+ // eslint-disable-next-line no-unused-vars
184
+ const InputWithInitialValueDemo = () => {
185
+ const [result, setResult] = useState("");
186
+
187
+ return (
188
+ <div className="demo-card">
189
+ <h3 className="demo-title">Input with Initial Value</h3>
190
+ <div className="control-group">
191
+ <label htmlFor="initial-input">Input with preset value:</label>
192
+ <Input
193
+ id="initial-input"
194
+ name="text"
195
+ value="Hello World!"
196
+ action={async ({ text }) => {
197
+ setResult("Processing...");
198
+ await delay(800);
199
+ const response = `✅ Submitted: "${text}"`;
200
+ setResult(response);
201
+ return response;
202
+ }}
203
+ />
204
+ </div>
205
+ <div
206
+ className={`result-display ${result.startsWith("✅") ? "result-success" : ""}`}
207
+ >
208
+ {result || "Submit to see result..."}
209
+ </div>
210
+ </div>
211
+ );
212
+ };
213
+
214
+ // eslint-disable-next-line no-unused-vars
215
+ const InputWithExternalControlDemo = () => {
216
+ const [inputValue, setInputValue] = useState(undefined);
217
+ const [result, setResult] = useState("");
218
+
219
+ return (
220
+ <div className="demo-card">
221
+ <h3 className="demo-title">Input with External Control</h3>
222
+ <div className="control-group">
223
+ <label htmlFor="controlled-input">
224
+ Externally controlled input:
225
+ </label>
226
+ <Input
227
+ id="controlled-input"
228
+ name="text"
229
+ value={inputValue}
230
+ placeholder="Use buttons below to set value"
231
+ action={async ({ text }) => {
232
+ setResult("Processing...");
233
+ await delay(600);
234
+ const response = `✅ Action executed with: "${text}"`;
235
+ setResult(response);
236
+ return response;
237
+ }}
238
+ />
239
+ </div>
240
+ <div className="button-group">
241
+ <button
242
+ onClick={() => {
243
+ setInputValue("Preset Value 1");
244
+ }}
245
+ >
246
+ Set Value 1
247
+ </button>
248
+ <button
249
+ onClick={() => {
250
+ setInputValue("Preset Value 2");
251
+ }}
252
+ >
253
+ Set Value 2
254
+ </button>
255
+ <button
256
+ onClick={() => {
257
+ setInputValue("");
258
+ }}
259
+ >
260
+ Clear
261
+ </button>
262
+ </div>
263
+ <div
264
+ className={`result-display ${result.startsWith("✅") ? "result-success" : ""}`}
265
+ >
266
+ Current value: "{inputValue}"<br />
267
+ {result || "Click buttons to change value, then submit..."}
268
+ </div>
269
+ </div>
270
+ );
271
+ };
272
+
273
+ // eslint-disable-next-line no-unused-vars
274
+ const FormInputDemo = () => {
275
+ const [result, setResult] = useState("");
276
+ const [isLoading, setIsLoading] = useState(false);
277
+
278
+ return (
279
+ <div className="demo-card">
280
+ <h3 className="demo-title">Input inside Form</h3>
281
+ <Form
282
+ action={async ({ username, email }) => {
283
+ setIsLoading(true);
284
+ setResult("Submitting form...");
285
+ try {
286
+ await delay(1200);
287
+ const response = `✅ Form submitted!\nUsername: ${username}\nEmail: ${email}`;
288
+ setResult(response);
289
+ return response;
290
+ } catch (error) {
291
+ setResult(`❌ Form error: ${error.message}`);
292
+ throw error;
293
+ } finally {
294
+ setIsLoading(false);
295
+ }
296
+ }}
297
+ >
298
+ <div className="control-group">
299
+ <label htmlFor="form-username">Username:</label>
300
+ <Input
301
+ id="form-username"
302
+ name="username"
303
+ placeholder="Enter username"
304
+ />
305
+ </div>
306
+ <div className="control-group">
307
+ <label htmlFor="form-email">Email:</label>
308
+ <Input
309
+ id="form-email"
310
+ name="email"
311
+ type="email"
312
+ placeholder="Enter email"
313
+ />
314
+ </div>
315
+ <div className="button-group">
316
+ <Button type="submit">Submit Form</Button>
317
+ <Button type="reset">Reset</Button>
318
+ </div>
319
+ </Form>
320
+ <div
321
+ className={`result-display ${isLoading ? "result-loading" : result.startsWith("✅") ? "result-success" : result.startsWith("❌") ? "result-error" : ""}`}
322
+ >
323
+ {result || "Fill form and submit..."}
324
+ </div>
325
+ </div>
326
+ );
327
+ };
328
+
329
+ // eslint-disable-next-line no-unused-vars
330
+ const FormWithExternalControlDemo = () => {
331
+ const [username, setUsername] = useState(undefined);
332
+ const [email, setEmail] = useState(undefined);
333
+ const [result, setResult] = useState("");
334
+
335
+ return (
336
+ <div className="demo-card">
337
+ <h3 className="demo-title">Form with External Control</h3>
338
+ <Form
339
+ action={async (params) => {
340
+ setResult("Submitting form...");
341
+ await delay(1000);
342
+ const response = `✅ Form submitted!\nUsername: ${params.username}\nEmail: ${params.email}`;
343
+ setResult(response);
344
+ return response;
345
+ }}
346
+ >
347
+ <div className="control-group">
348
+ <label htmlFor="controlled-form-username">Username:</label>
349
+ <Input
350
+ id="controlled-form-username"
351
+ name="username"
352
+ value={username}
353
+ placeholder="Controlled by buttons"
354
+ />
355
+ </div>
356
+ <div className="control-group">
357
+ <label htmlFor="controlled-form-email">Email:</label>
358
+ <Input
359
+ id="controlled-form-email"
360
+ name="email"
361
+ value={email}
362
+ placeholder="Controlled by buttons"
363
+ />
364
+ </div>
365
+ <div className="button-group">
366
+ <button
367
+ type="button"
368
+ onClick={() => {
369
+ setUsername("john_doe");
370
+ setEmail("john@example.com");
371
+ }}
372
+ >
373
+ Set User 1
374
+ </button>
375
+ <button
376
+ type="button"
377
+ onClick={() => {
378
+ setUsername("jane_smith");
379
+ setEmail("jane@example.com");
380
+ }}
381
+ >
382
+ Set User 2
383
+ </button>
384
+ <button
385
+ type="button"
386
+ onClick={() => {
387
+ setUsername("");
388
+ setEmail("");
389
+ }}
390
+ >
391
+ Clear All
392
+ </button>
393
+ </div>
394
+ <div className="button-group">
395
+ <Button type="submit">Submit Form</Button>
396
+ <Button type="reset">Reset</Button>
397
+ </div>
398
+ </Form>
399
+ <div
400
+ className={`result-display ${result.startsWith("✅") ? "result-success" : ""}`}
401
+ >
402
+ Current: {username} / {email}
403
+ <br />
404
+ {result || "Use buttons to set values, then submit..."}
405
+ </div>
406
+ </div>
407
+ );
408
+ };
409
+
410
+ // eslint-disable-next-line no-unused-vars
411
+ const SharedActionDemo = () => {
412
+ const [result, setResult] = useState("");
413
+
414
+ return (
415
+ <div className="demo-card">
416
+ <h3 className="demo-title">Shared Action Example</h3>
417
+ <div className="control-group">
418
+ <label htmlFor="shared-input-1">Input 1 (shared action):</label>
419
+ <Input
420
+ id="shared-input-1"
421
+ name="name"
422
+ placeholder="Enter name for input 1"
423
+ action={sharedAction}
424
+ onActionStart={() => {
425
+ setResult("Processing...");
426
+ }}
427
+ onActionEnd={(e) => {
428
+ setResult(e.detail.data);
429
+ }}
430
+ />
431
+ </div>
432
+ <div className="control-group">
433
+ <label htmlFor="shared-input-2">
434
+ Input 2 (same shared action):
435
+ </label>
436
+ <Input
437
+ id="shared-input-2"
438
+ name="name"
439
+ placeholder="Enter name for input 2"
440
+ action={sharedAction}
441
+ />
442
+ </div>
443
+ <div
444
+ className={`result-display ${result.startsWith("✅") ? "result-success" : ""}`}
445
+ >
446
+ {result || "Type in either input and submit..."}
447
+ </div>
448
+ </div>
449
+ );
450
+ };
451
+
452
+ const sharedAction = createAction(async ({ name }) => {
453
+ await delay(800);
454
+ const response = `✅ Hello ${name}! (from shared action)`;
455
+ return response;
456
+ });
457
+
458
+ render(<App />, document.querySelector("#root"));
459
+ </script>
460
+ </body>
461
+ </html>