@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,224 @@
1
+ import {
2
+ ActionRenderer,
3
+ createAction,
4
+ defineRoutes,
5
+ enableDebugActions,
6
+ // enableDebugOnDocumentLoading,
7
+ useActionStatus,
8
+ useRouteStatus,
9
+ } from "@jsenv/navi";
10
+ import { render } from "preact";
11
+
12
+ enableDebugActions();
13
+ // enableDebugOnDocumentLoading();
14
+
15
+ const loadPageAction = createAction(
16
+ async ({ pageName }) => {
17
+ // Simulate some loading time
18
+ await new Promise((resolve) => setTimeout(resolve, 800));
19
+ return `${pageName}: content loaded at ${new Date().toLocaleTimeString()}`;
20
+ },
21
+ {
22
+ name: "loadPage",
23
+ },
24
+ );
25
+ const [pageRoute] = defineRoutes({ "page/:pageName": loadPageAction });
26
+ const loadPageFromUrlAction = pageRoute.action;
27
+
28
+ const RouteStatus = ({ route }) => {
29
+ const { active, params } = useRouteStatus(route);
30
+
31
+ return (
32
+ <div
33
+ style={{
34
+ padding: "10px",
35
+ margin: "10px 0",
36
+ border: "1px solid #ccc",
37
+ borderRadius: "4px",
38
+ backgroundColor: "#f8f9fa",
39
+ }}
40
+ >
41
+ <h3>Route Status:</h3>
42
+ <div>
43
+ <strong>Pattern:</strong> {route.pattern}
44
+ </div>
45
+ <div>
46
+ <strong>Current URL:</strong> {route.relativeUrl}
47
+ </div>
48
+ <div>
49
+ <strong>Active:</strong> {active ? "✅ Yes" : "❌ No"}
50
+ </div>
51
+ <div>
52
+ <strong>Params:</strong> {JSON.stringify(params)}
53
+ </div>
54
+ </div>
55
+ );
56
+ };
57
+
58
+ const getStatusColor = (loadingState) => {
59
+ switch (loadingState) {
60
+ case "IDLE":
61
+ return "#6c757d";
62
+ case "LOADING":
63
+ return "#0d6efd";
64
+ case "LOADED":
65
+ return "#198754";
66
+ case "FAILED":
67
+ return "#dc3545";
68
+ case "ABORTED":
69
+ return "#fd7e14";
70
+ default:
71
+ return "#6c757d";
72
+ }
73
+ };
74
+ const getStatusIcon = (loadingState) => {
75
+ switch (loadingState) {
76
+ case "IDLE":
77
+ return "⏸️";
78
+ case "LOADING":
79
+ return "⏳";
80
+ case "LOADED":
81
+ return "✅";
82
+ case "FAILED":
83
+ return "❌";
84
+ case "ABORTED":
85
+ return "⏹️";
86
+ default:
87
+ return "❓";
88
+ }
89
+ };
90
+ const ActionStatus = ({ action }) => {
91
+ const { loadingState, loadRequested, data, error, params } =
92
+ useActionStatus(action);
93
+
94
+ return (
95
+ <div
96
+ style={{
97
+ padding: "10px",
98
+ margin: "10px 0",
99
+ border: "1px solid #ccc",
100
+ borderRadius: "4px",
101
+ backgroundColor: "#f8f9fa",
102
+ }}
103
+ >
104
+ <h3>Action Status:</h3>
105
+ <div>
106
+ <strong>Name:</strong> {action.name}
107
+ </div>
108
+ <div>
109
+ <strong>Status:</strong>
110
+ <span
111
+ style={{
112
+ color: getStatusColor(loadingState),
113
+ marginLeft: "8px",
114
+ fontWeight: "bold",
115
+ }}
116
+ >
117
+ {getStatusIcon(loadingState)} {loadingState.id}
118
+ </span>
119
+ </div>
120
+ <div>
121
+ <strong>Load Requested:</strong> {loadRequested ? "✅ Yes" : "❌ No"}
122
+ </div>
123
+ <div>
124
+ <strong>Params:</strong> {JSON.stringify(params)}
125
+ </div>
126
+ {error && (
127
+ <div style={{ color: "#dc3545" }}>
128
+ <strong>Error:</strong> {error.message}
129
+ </div>
130
+ )}
131
+ {data && (
132
+ <div style={{ color: "#198754" }}>
133
+ <strong>Data:</strong> {data}
134
+ </div>
135
+ )}
136
+ </div>
137
+ );
138
+ };
139
+
140
+ const App = () => {
141
+ const pageAUrl = pageRoute.buildUrl({ pageName: "a" });
142
+ const pageBUrl = pageRoute.buildUrl({ pageName: "b" });
143
+
144
+ return (
145
+ <>
146
+ <nav
147
+ style={{
148
+ padding: "20px",
149
+ borderBottom: "1px solid #ccc",
150
+ marginBottom: "20px",
151
+ }}
152
+ >
153
+ <ul
154
+ style={{
155
+ listStyle: "none",
156
+ display: "flex",
157
+ gap: "20px",
158
+ margin: 0,
159
+ padding: 0,
160
+ }}
161
+ >
162
+ <li>
163
+ <a
164
+ draggable="false"
165
+ href={pageAUrl}
166
+ style={{
167
+ textDecoration: "none",
168
+ padding: "8px 16px",
169
+ border: "1px solid #007bff",
170
+ borderRadius: "4px",
171
+ color: "#007bff",
172
+ }}
173
+ >
174
+ Page A
175
+ </a>
176
+ </li>
177
+ <li>
178
+ <a
179
+ draggable="false"
180
+ href={pageBUrl}
181
+ style={{
182
+ textDecoration: "none",
183
+ padding: "8px 16px",
184
+ border: "1px solid #007bff",
185
+ borderRadius: "4px",
186
+ color: "#007bff",
187
+ }}
188
+ >
189
+ Page B
190
+ </a>
191
+ </li>
192
+ </ul>
193
+ </nav>
194
+
195
+ <div style={{ padding: "0 20px" }}>
196
+ <h1>Routing Demo</h1>
197
+
198
+ <RouteStatus route={pageRoute} />
199
+ <ActionStatus action={loadPageFromUrlAction} />
200
+
201
+ <main
202
+ style={{
203
+ padding: "20px",
204
+ margin: "20px 0",
205
+ border: "2px solid #007bff",
206
+ borderRadius: "8px",
207
+ backgroundColor: "#e7f3ff",
208
+ }}
209
+ >
210
+ <h2>Page Content:</h2>
211
+ <ActionRenderer action={loadPageFromUrlAction}>
212
+ {(content) => (
213
+ <div style={{ fontSize: "18px", fontWeight: "bold" }}>
214
+ {content}
215
+ </div>
216
+ )}
217
+ </ActionRenderer>
218
+ </main>
219
+ </div>
220
+ </>
221
+ );
222
+ };
223
+
224
+ render(<App />, document.querySelector("#root"));
@@ -0,0 +1,14 @@
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>Routing demo</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+
12
+ <script type="module" src="./multi_route_demo.jsx"></script>
13
+ </body>
14
+ </html>
@@ -0,0 +1,277 @@
1
+ import {
2
+ createAction,
3
+ defineRoutes,
4
+ enableDebugActions,
5
+ enableDebugOnDocumentLoading,
6
+ Route,
7
+ useActionStatus,
8
+ useRouteStatus,
9
+ } from "@jsenv/navi";
10
+ import { render } from "preact";
11
+
12
+ enableDebugActions();
13
+ enableDebugOnDocumentLoading();
14
+
15
+ const loadUserAction = createAction(
16
+ async ({ userName }) => {
17
+ await new Promise((resolve) => setTimeout(resolve, 800));
18
+ return `User ${userName}: loaded at ${new Date().toLocaleTimeString()}`;
19
+ },
20
+ { name: "loadUser" },
21
+ );
22
+ const loadPageAction = createAction(
23
+ async ({ pageName }) => {
24
+ await new Promise((resolve) => setTimeout(resolve, 800));
25
+ return `Page ${pageName}: loaded at ${new Date().toLocaleTimeString()}`;
26
+ },
27
+ {
28
+ name: "loadPage",
29
+ },
30
+ );
31
+ const [userRoute, pageRoute] = defineRoutes({
32
+ "user/:userName": loadUserAction,
33
+ "page/:pageName": loadPageAction,
34
+ });
35
+
36
+ const RouteStatus = ({ name, route }) => {
37
+ const { active, params } = useRouteStatus(route);
38
+
39
+ return (
40
+ <div
41
+ style={{
42
+ padding: "10px",
43
+ margin: "10px 0",
44
+ border: "1px solid #ccc",
45
+ borderRadius: "4px",
46
+ backgroundColor: "#f8f9fa",
47
+ }}
48
+ >
49
+ <h3>{name} route status:</h3>
50
+ <div>
51
+ <strong>Pattern:</strong> {route.pattern}
52
+ </div>
53
+ <div>
54
+ <strong>Current URL:</strong> {route.relativeUrl}
55
+ </div>
56
+ <div>
57
+ <strong>Active:</strong> {active ? "✅ Yes" : "❌ No"}
58
+ </div>
59
+ <div>
60
+ <strong>Params:</strong> {JSON.stringify(params)}
61
+ </div>
62
+ </div>
63
+ );
64
+ };
65
+
66
+ const getStatusColor = (loadingState) => {
67
+ switch (loadingState) {
68
+ case "IDLE":
69
+ return "#6c757d";
70
+ case "LOADING":
71
+ return "#0d6efd";
72
+ case "LOADED":
73
+ return "#198754";
74
+ case "FAILED":
75
+ return "#dc3545";
76
+ case "ABORTED":
77
+ return "#fd7e14";
78
+ default:
79
+ return "#6c757d";
80
+ }
81
+ };
82
+ const getStatusIcon = (loadingState) => {
83
+ switch (loadingState) {
84
+ case "IDLE":
85
+ return "⏸️";
86
+ case "LOADING":
87
+ return "⏳";
88
+ case "LOADED":
89
+ return "✅";
90
+ case "FAILED":
91
+ return "❌";
92
+ case "ABORTED":
93
+ return "⏹️";
94
+ default:
95
+ return "❓";
96
+ }
97
+ };
98
+ const ActionStatus = ({ action }) => {
99
+ const { loadingState, loadRequested, data, error, params } =
100
+ useActionStatus(action);
101
+
102
+ return (
103
+ <div
104
+ style={{
105
+ padding: "10px",
106
+ margin: "10px 0",
107
+ border: "1px solid #ccc",
108
+ borderRadius: "4px",
109
+ backgroundColor: "#f8f9fa",
110
+ }}
111
+ >
112
+ <h3>Action Status:</h3>
113
+ <div>
114
+ <strong>Name:</strong> {action.name}
115
+ </div>
116
+ <div>
117
+ <strong>Status:</strong>
118
+ <span
119
+ style={{
120
+ color: getStatusColor(loadingState),
121
+ marginLeft: "8px",
122
+ fontWeight: "bold",
123
+ }}
124
+ >
125
+ {getStatusIcon(loadingState)} {loadingState.id}
126
+ </span>
127
+ </div>
128
+ <div>
129
+ <strong>Load Requested:</strong> {loadRequested ? "✅ Yes" : "❌ No"}
130
+ </div>
131
+ <div>
132
+ <strong>Params:</strong> {JSON.stringify(params)}
133
+ </div>
134
+ {error && (
135
+ <div style={{ color: "#dc3545" }}>
136
+ <strong>Error:</strong> {error.message}
137
+ </div>
138
+ )}
139
+ {data && (
140
+ <div style={{ color: "#198754" }}>
141
+ <strong>Data:</strong> {data}
142
+ </div>
143
+ )}
144
+ </div>
145
+ );
146
+ };
147
+
148
+ const App = () => {
149
+ const damUrl = userRoute.buildUrl({ userName: "damilola" });
150
+ const floreUrl = userRoute.buildUrl({ userName: "flore" });
151
+ const pageAUrl = pageRoute.buildUrl({ pageName: "a" });
152
+ const pageBUrl = pageRoute.buildUrl({ pageName: "b" });
153
+
154
+ return (
155
+ <>
156
+ <nav
157
+ style={{
158
+ padding: "20px",
159
+ borderBottom: "1px solid #ccc",
160
+ marginBottom: "20px",
161
+ }}
162
+ >
163
+ <ul
164
+ style={{
165
+ listStyle: "none",
166
+ display: "flex",
167
+ gap: "20px",
168
+ margin: 0,
169
+ padding: 0,
170
+ }}
171
+ >
172
+ <li>
173
+ <a
174
+ draggable="false"
175
+ href={damUrl}
176
+ style={{
177
+ textDecoration: "none",
178
+ padding: "8px 16px",
179
+ border: "1px solid #007bff",
180
+ borderRadius: "4px",
181
+ color: "#007bff",
182
+ }}
183
+ >
184
+ Dam
185
+ </a>
186
+ </li>
187
+ <li>
188
+ <a
189
+ draggable="false"
190
+ href={floreUrl}
191
+ style={{
192
+ textDecoration: "none",
193
+ padding: "8px 16px",
194
+ border: "1px solid #007bff",
195
+ borderRadius: "4px",
196
+ color: "#007bff",
197
+ }}
198
+ >
199
+ Flore
200
+ </a>
201
+ </li>
202
+ <li>
203
+ <a
204
+ draggable="false"
205
+ href={pageAUrl}
206
+ style={{
207
+ textDecoration: "none",
208
+ padding: "8px 16px",
209
+ border: "1px solid #007bff",
210
+ borderRadius: "4px",
211
+ color: "#007bff",
212
+ }}
213
+ >
214
+ Page A
215
+ </a>
216
+ </li>
217
+ <li>
218
+ <a
219
+ draggable="false"
220
+ href={pageBUrl}
221
+ style={{
222
+ textDecoration: "none",
223
+ padding: "8px 16px",
224
+ border: "1px solid #007bff",
225
+ borderRadius: "4px",
226
+ color: "#007bff",
227
+ }}
228
+ >
229
+ Page B
230
+ </a>
231
+ </li>
232
+ </ul>
233
+ </nav>
234
+
235
+ <div style={{ padding: "0 20px" }}>
236
+ <h1>Routing Demo</h1>
237
+
238
+ <main
239
+ style={{
240
+ padding: "20px",
241
+ margin: "20px 0",
242
+ border: "2px solid #007bff",
243
+ borderRadius: "8px",
244
+ backgroundColor: "#e7f3ff",
245
+ }}
246
+ >
247
+ <h2>User route:</h2>
248
+ <Route route={userRoute}>
249
+ {(content) => (
250
+ <div style={{ fontSize: "18px", fontWeight: "bold" }}>
251
+ {content}
252
+ </div>
253
+ )}
254
+ </Route>
255
+
256
+ <h2>Page route:</h2>
257
+ <Route route={pageRoute}>
258
+ {(content) => (
259
+ <div style={{ fontSize: "18px", fontWeight: "bold" }}>
260
+ {content}
261
+ </div>
262
+ )}
263
+ </Route>
264
+ </main>
265
+
266
+ <aside>
267
+ <RouteStatus name="User" route={userRoute} />
268
+ <RouteStatus name="Page" route={pageRoute} />
269
+ <ActionStatus action={userRoute.action} />
270
+ <ActionStatus action={pageRoute.action} />
271
+ </aside>
272
+ </div>
273
+ </>
274
+ );
275
+ };
276
+
277
+ render(<App />, document.querySelector("#root"));