@jsenv/navi 0.10.2 → 0.11.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 (207) hide show
  1. package/dist/jsenv_navi.js +13838 -23291
  2. package/dist/jsenv_navi.js.map +1281 -0
  3. package/package.json +6 -8
  4. package/index.js +0 -122
  5. package/src/action_private_properties.js +0 -11
  6. package/src/action_proxy_test.html +0 -353
  7. package/src/action_run_states.js +0 -5
  8. package/src/actions.js +0 -1401
  9. package/src/browser_integration/browser_integration.js +0 -216
  10. package/src/browser_integration/document_back_and_forward.js +0 -17
  11. package/src/browser_integration/document_loading_signal.js +0 -100
  12. package/src/browser_integration/document_state_signal.js +0 -9
  13. package/src/browser_integration/document_url_signal.js +0 -9
  14. package/src/browser_integration/use_is_visited.js +0 -19
  15. package/src/browser_integration/via_history.js +0 -232
  16. package/src/browser_integration/via_navigation.js +0 -168
  17. package/src/components/action_execution/form_context.js +0 -5
  18. package/src/components/action_execution/render_actionable_component.jsx +0 -29
  19. package/src/components/action_execution/use_action.js +0 -99
  20. package/src/components/action_execution/use_execute_action.js +0 -193
  21. package/src/components/action_execution/use_run_on_mount.js +0 -9
  22. package/src/components/action_renderer.jsx +0 -125
  23. package/src/components/callout/callout.js +0 -990
  24. package/src/components/callout/callout_demo.html +0 -201
  25. package/src/components/callout/test_dynamic_positioning.html +0 -161
  26. package/src/components/callout/test_html_document_iframe.html +0 -182
  27. package/src/components/demos/0_button_demo.html +0 -707
  28. package/src/components/demos/10_column_reordering_debug.html +0 -277
  29. package/src/components/demos/11_table_selection_debug.html +0 -432
  30. package/src/components/demos/1_checkbox_demo.html +0 -754
  31. package/src/components/demos/2_input_textual_demo.html +0 -286
  32. package/src/components/demos/3_radio_demo.html +0 -874
  33. package/src/components/demos/4_select_demo.html +0 -100
  34. package/src/components/demos/5_list_scrollable_demo.html +0 -153
  35. package/src/components/demos/6_tablist_demo.html +0 -77
  36. package/src/components/demos/7_table_selection_demo.html +0 -176
  37. package/src/components/demos/8_table_fixed_headers_demo.html +0 -584
  38. package/src/components/demos/9_table_column_drag_demo.html +0 -325
  39. package/src/components/demos/action/0_button_demo.html +0 -204
  40. package/src/components/demos/action/10_shortcuts_demo.html +0 -189
  41. package/src/components/demos/action/11_nested_shortcuts_demo.xhtml +0 -401
  42. package/src/components/demos/action/1_input_text_demo.html +0 -876
  43. package/src/components/demos/action/2_form_multiple.html +0 -303
  44. package/src/components/demos/action/3_details_demo.html +0 -203
  45. package/src/components/demos/action/4_input_checkbox_demo.html +0 -731
  46. package/src/components/demos/action/5_input_checkbox_state_demo.html +0 -270
  47. package/src/components/demos/action/6_checkbox_list_demo.html +0 -341
  48. package/src/components/demos/action/7_radio_list_demo.html +0 -357
  49. package/src/components/demos/action/8_editable_demo.html +0 -431
  50. package/src/components/demos/action/9_link_demo.html +0 -194
  51. package/src/components/demos/demo.md +0 -0
  52. package/src/components/demos/route/basic/basic.html +0 -14
  53. package/src/components/demos/route/basic/basic_route_demo.jsx +0 -224
  54. package/src/components/demos/route/multi/multi.html +0 -14
  55. package/src/components/demos/route/multi/multi_route_demo.jsx +0 -277
  56. package/src/components/demos/ui_transition/0_action_renderer_ui_transition_demo.html +0 -695
  57. package/src/components/demos/ui_transition/1_nested_ui_transition_demo.html +0 -429
  58. package/src/components/demos/ui_transition/2_height_transition_test.html +0 -295
  59. package/src/components/details/details.jsx +0 -245
  60. package/src/components/details/summary_marker.jsx +0 -141
  61. package/src/components/edition/editable.jsx +0 -186
  62. package/src/components/error_boundary_context.js +0 -9
  63. package/src/components/field/README.md +0 -247
  64. package/src/components/field/button.jsx +0 -429
  65. package/src/components/field/checkbox_list.jsx +0 -185
  66. package/src/components/field/collect_form_element_values.js +0 -82
  67. package/src/components/field/custom_field.js +0 -106
  68. package/src/components/field/form.jsx +0 -209
  69. package/src/components/field/input.jsx +0 -16
  70. package/src/components/field/input_checkbox.jsx +0 -434
  71. package/src/components/field/input_radio.jsx +0 -432
  72. package/src/components/field/input_textual.jsx +0 -389
  73. package/src/components/field/label.jsx +0 -46
  74. package/src/components/field/radio_list.jsx +0 -183
  75. package/src/components/field/select.jsx +0 -256
  76. package/src/components/field/use_action_events.js +0 -132
  77. package/src/components/field/use_form_events.js +0 -59
  78. package/src/components/field/use_ui_state_controller.js +0 -506
  79. package/src/components/item_tracker/README.md +0 -461
  80. package/src/components/item_tracker/use_isolated_item_tracker.jsx +0 -209
  81. package/src/components/item_tracker/use_isolated_item_tracker_demo.html +0 -148
  82. package/src/components/item_tracker/use_isolated_item_tracker_demo.jsx +0 -460
  83. package/src/components/item_tracker/use_item_tracker.jsx +0 -143
  84. package/src/components/item_tracker/use_item_tracker_demo.html +0 -207
  85. package/src/components/item_tracker/use_item_tracker_demo.jsx +0 -216
  86. package/src/components/keyboard_shortcuts/active_keyboard_shortcuts.jsx +0 -87
  87. package/src/components/keyboard_shortcuts/aria_key_shortcuts.js +0 -61
  88. package/src/components/keyboard_shortcuts/keyboard_key_meta.js +0 -17
  89. package/src/components/keyboard_shortcuts/keyboard_shortcuts.js +0 -371
  90. package/src/components/keyboard_shortcuts/os.js +0 -9
  91. package/src/components/layout/demos/demo_flex.html +0 -638
  92. package/src/components/layout/demos/demo_layout_style_buttons.html +0 -351
  93. package/src/components/layout/demos/demo_layout_style_input.html +0 -226
  94. package/src/components/layout/demos/demo_layout_style_text.html +0 -514
  95. package/src/components/layout/flex.jsx +0 -109
  96. package/src/components/layout/layout_context.jsx +0 -3
  97. package/src/components/layout/spacing.jsx +0 -20
  98. package/src/components/layout/use_layout_style.js +0 -249
  99. package/src/components/link/link.jsx +0 -267
  100. package/src/components/link/link_with_icon.jsx +0 -52
  101. package/src/components/loader/loader_background.jsx +0 -372
  102. package/src/components/loader/loading_spinner.jsx +0 -68
  103. package/src/components/loader/network_speed.js +0 -83
  104. package/src/components/loader/rectangle_loading.jsx +0 -244
  105. package/src/components/props_composition/demos/demo_with_props_style.html +0 -81
  106. package/src/components/props_composition/with_props_class_name.js +0 -37
  107. package/src/components/props_composition/with_props_style.js +0 -26
  108. package/src/components/route.jsx +0 -19
  109. package/src/components/selection/selection.jsx +0 -1583
  110. package/src/components/svg/font_sized_svg.jsx +0 -59
  111. package/src/components/svg/icon_and_text.jsx +0 -21
  112. package/src/components/svg/svg_mask_overlay.jsx +0 -105
  113. package/src/components/table/drag/table_drag.jsx +0 -506
  114. package/src/components/table/resize/table_resize.jsx +0 -650
  115. package/src/components/table/resize/table_size.js +0 -43
  116. package/src/components/table/selection/table_selection.js +0 -106
  117. package/src/components/table/selection/table_selection.jsx +0 -203
  118. package/src/components/table/sticky/sticky_group.js +0 -354
  119. package/src/components/table/sticky/table_sticky.js +0 -25
  120. package/src/components/table/sticky/table_sticky.jsx +0 -501
  121. package/src/components/table/table.jsx +0 -721
  122. package/src/components/table/table_css.js +0 -211
  123. package/src/components/table/table_ui.jsx +0 -49
  124. package/src/components/table/use_cells_and_columns.js +0 -90
  125. package/src/components/table/use_object_array_to_cells.js +0 -46
  126. package/src/components/table/z_indexes.js +0 -23
  127. package/src/components/tablist/tablist.jsx +0 -99
  128. package/src/components/text/demos/demo_text_and_icon.html +0 -421
  129. package/src/components/text/overflow.jsx +0 -15
  130. package/src/components/text/text.jsx +0 -83
  131. package/src/components/text/text_and_count.jsx +0 -28
  132. package/src/components/ui_transition.jsx +0 -128
  133. package/src/components/use_auto_focus.js +0 -94
  134. package/src/components/use_batch_during_render.js +0 -33
  135. package/src/components/use_debounce_true.js +0 -31
  136. package/src/components/use_dependencies_diff.js +0 -35
  137. package/src/components/use_focus_group.js +0 -20
  138. package/src/components/use_initial_value.js +0 -78
  139. package/src/components/use_is_visited.js +0 -19
  140. package/src/components/use_ref_array.js +0 -38
  141. package/src/components/use_signal_sync.js +0 -50
  142. package/src/components/use_stable_callback.js +0 -68
  143. package/src/components/use_state_array.js +0 -47
  144. package/src/docs/actions.md +0 -250
  145. package/src/docs/demos/resource/action_status.jsx +0 -42
  146. package/src/docs/demos/resource/demo.md +0 -1
  147. package/src/docs/demos/resource/resource_demo_0.html +0 -84
  148. package/src/docs/demos/resource/resource_demo_10_post_gc.html +0 -364
  149. package/src/docs/demos/resource/resource_demo_11_describe_many.html +0 -362
  150. package/src/docs/demos/resource/resource_demo_2.html +0 -173
  151. package/src/docs/demos/resource/resource_demo_3_filtered_users.html +0 -415
  152. package/src/docs/demos/resource/resource_demo_4_details.html +0 -284
  153. package/src/docs/demos/resource/resource_demo_5_renderer_lazy.html +0 -115
  154. package/src/docs/demos/resource/resource_demo_6_gc.html +0 -217
  155. package/src/docs/demos/resource/resource_demo_7_child_gc.html +0 -240
  156. package/src/docs/demos/resource/resource_demo_8_proxy_gc.html +0 -319
  157. package/src/docs/demos/resource/resource_demo_9_describe_one.html +0 -472
  158. package/src/docs/demos/resource/tata.jsx +0 -3
  159. package/src/docs/demos/resource/toto.jsx +0 -3
  160. package/src/docs/demos/user_nav/user_nav.html +0 -12
  161. package/src/docs/demos/user_nav/user_nav.jsx +0 -330
  162. package/src/docs/resource_dependencies.md +0 -103
  163. package/src/docs/resource_with_params.md +0 -80
  164. package/src/navi_css_vars.js +0 -14
  165. package/src/notes.md +0 -34
  166. package/src/route/route.js +0 -596
  167. package/src/route/route.xtest.html +0 -228
  168. package/src/store/array_signal_store.js +0 -537
  169. package/src/store/local_storage_signal.js +0 -17
  170. package/src/store/resource_graph.js +0 -1304
  171. package/src/store/tests/resource_graph_autoreload_demo.html +0 -12
  172. package/src/store/tests/resource_graph_autoreload_demo.jsx +0 -964
  173. package/src/store/tests/resource_graph_dependencies.test_manual.js +0 -95
  174. package/src/store/value_in_local_storage.js +0 -187
  175. package/src/symbol_object_signal.js +0 -1
  176. package/src/use_action_data.js +0 -10
  177. package/src/use_action_status.js +0 -47
  178. package/src/utils/add_many_event_listeners.js +0 -15
  179. package/src/utils/array_add_remove.js +0 -61
  180. package/src/utils/array_signal.js +0 -15
  181. package/src/utils/compare_two_js_values.js +0 -172
  182. package/src/utils/execute_with_cleanup.js +0 -21
  183. package/src/utils/get_caller_info.js +0 -85
  184. package/src/utils/is_signal.js +0 -20
  185. package/src/utils/js_value_weak_map.js +0 -162
  186. package/src/utils/js_value_weak_map_demo.html +0 -690
  187. package/src/utils/merge_two_js_values.js +0 -53
  188. package/src/utils/stringify_for_display.js +0 -131
  189. package/src/utils/weak_effect.js +0 -48
  190. package/src/validation/constraints/confirm_constraint.js +0 -14
  191. package/src/validation/constraints/create_unique_value_constraint.js +0 -27
  192. package/src/validation/constraints/native_constraints.js +0 -338
  193. package/src/validation/constraints/readonly_constraint.js +0 -41
  194. package/src/validation/constraints/same_as_constraint.js +0 -42
  195. package/src/validation/constraints/single_space_constraint.js +0 -13
  196. package/src/validation/custom_constraint_validation.js +0 -793
  197. package/src/validation/custom_message.js +0 -18
  198. package/src/validation/demos/browser_style.png +0 -0
  199. package/src/validation/demos/demo_same_as_constraint.html +0 -259
  200. package/src/validation/demos/form_validation_demo.html +0 -142
  201. package/src/validation/demos/form_validation_demo_preact.html +0 -87
  202. package/src/validation/demos/form_validation_native_popover_demo.html +0 -168
  203. package/src/validation/demos/form_validation_vs_native_demo.html +0 -172
  204. package/src/validation/hooks/use_constraints.js +0 -23
  205. package/src/validation/hooks/use_custom_validation_ref.js +0 -73
  206. package/src/validation/hooks/use_validation_message.js +0 -19
  207. package/src/validation/input_change_effect.js +0 -106
@@ -1,596 +0,0 @@
1
- import { batch, computed, effect, signal } from "@preact/signals";
2
- import { createAction } from "../actions.js";
3
- import {
4
- SYMBOL_IDENTITY,
5
- compareTwoJsValues,
6
- } from "../utils/compare_two_js_values.js";
7
-
8
- let baseUrl = import.meta.dev
9
- ? new URL(window.HTML_ROOT_PATHNAME, window.location).href
10
- : window.location.origin;
11
-
12
- export const setBaseUrl = (value) => {
13
- baseUrl = new URL(value, window.location).href;
14
- };
15
-
16
- const rawUrlPartSymbol = Symbol("raw_url_part");
17
- export const rawUrlPart = (value) => {
18
- return {
19
- [rawUrlPartSymbol]: true,
20
- value,
21
- };
22
- };
23
-
24
- const DEBUG = false;
25
- const NO_PARAMS = { [SYMBOL_IDENTITY]: Symbol("no_params") };
26
- // Controls what happens to actions when their route becomes inactive:
27
- // 'abort' - Cancel the action immediately when route deactivates
28
- // 'keep-loading' - Allow action to continue running after route deactivation
29
- //
30
- // The 'keep-loading' strategy could act like preloading, keeping data ready for potential return.
31
- // However, since route reactivation triggers action reload anyway, the old data won't be used
32
- // so it's better to abort the action to avoid unnecessary resource usage.
33
- const ROUTE_DEACTIVATION_STRATEGY = "abort"; // 'abort', 'keep-loading'
34
-
35
- const routeSet = new Set();
36
- // Store previous route states to detect changes
37
- const routePreviousStateMap = new WeakMap();
38
- // Store abort controllers per action to control their lifecycle based on route state
39
- const actionAbortControllerWeakMap = new WeakMap();
40
- export const updateRoutes = (
41
- url,
42
- {
43
- // state
44
- replace,
45
- isVisited,
46
- },
47
- ) => {
48
- const routeMatchInfoSet = new Set();
49
- for (const route of routeSet) {
50
- const routePrivateProperties = getRoutePrivateProperties(route);
51
- const { urlPattern } = routePrivateProperties;
52
-
53
- // Get previous state
54
- const previousState = routePreviousStateMap.get(route) || {
55
- active: false,
56
- params: NO_PARAMS,
57
- };
58
- const oldActive = previousState.active;
59
- const oldParams = previousState.params;
60
- // Check if the URL matches the route pattern
61
- const match = urlPattern.exec(url);
62
- const newActive = Boolean(match);
63
- let newParams;
64
- if (match) {
65
- const { optionalParamKeySet } = routePrivateProperties;
66
- const extractedParams = extractParams(
67
- urlPattern,
68
- url,
69
- optionalParamKeySet,
70
- );
71
- if (compareTwoJsValues(oldParams, extractedParams)) {
72
- // No change in parameters, keep the old params
73
- newParams = oldParams;
74
- } else {
75
- newParams = extractedParams;
76
- }
77
- } else {
78
- newParams = NO_PARAMS;
79
- }
80
-
81
- const routeMatchInfo = {
82
- route,
83
- routePrivateProperties,
84
- oldActive,
85
- newActive,
86
- oldParams,
87
- newParams,
88
- };
89
- routeMatchInfoSet.add(routeMatchInfo);
90
- // Store current state for next comparison
91
- routePreviousStateMap.set(route, {
92
- active: newActive,
93
- params: newParams,
94
- });
95
- }
96
-
97
- // Apply all signal updates in a batch
98
- const activeRouteSet = new Set();
99
- batch(() => {
100
- for (const {
101
- route,
102
- routePrivateProperties,
103
- newActive,
104
- newParams,
105
- } of routeMatchInfoSet) {
106
- const { activeSignal, paramsSignal, visitedSignal } =
107
- routePrivateProperties;
108
- const visited = isVisited(route.url);
109
- activeSignal.value = newActive;
110
- paramsSignal.value = newParams;
111
- visitedSignal.value = visited;
112
- route.active = newActive;
113
- route.params = newParams;
114
- route.visited = visited;
115
- if (newActive) {
116
- activeRouteSet.add(route);
117
- }
118
- }
119
- });
120
-
121
- // must be after paramsSignal.value update to ensure the proxy target is set
122
- // (so after the batch call)
123
- const toLoadSet = new Set();
124
- const toReloadSet = new Set();
125
- const abortSignalMap = new Map();
126
- const routeLoadRequestedMap = new Map();
127
-
128
- const shouldLoadOrReload = (route, shouldLoad) => {
129
- const routeAction = route.action;
130
- const currentAction = routeAction.getCurrentAction();
131
- if (shouldLoad) {
132
- if (replace || currentAction.aborted || currentAction.error) {
133
- shouldLoad = false;
134
- }
135
- }
136
- if (shouldLoad) {
137
- toLoadSet.add(currentAction);
138
- } else {
139
- toReloadSet.add(currentAction);
140
- }
141
- routeLoadRequestedMap.set(route, currentAction);
142
- // Create a new abort controller for this action
143
- const actionAbortController = new AbortController();
144
- actionAbortControllerWeakMap.set(currentAction, actionAbortController);
145
- abortSignalMap.set(currentAction, actionAbortController.signal);
146
- };
147
-
148
- const shouldLoad = (route) => {
149
- shouldLoadOrReload(route, true);
150
- };
151
- const shouldReload = (route) => {
152
- shouldLoadOrReload(route, false);
153
- };
154
- const shouldAbort = (route) => {
155
- const routeAction = route.action;
156
- const currentAction = routeAction.getCurrentAction();
157
- const actionAbortController =
158
- actionAbortControllerWeakMap.get(currentAction);
159
- if (actionAbortController) {
160
- actionAbortController.abort(`route no longer matching`);
161
- actionAbortControllerWeakMap.delete(currentAction);
162
- }
163
- };
164
-
165
- for (const {
166
- route,
167
- routePrivateProperties,
168
- newActive,
169
- oldActive,
170
- newParams,
171
- oldParams,
172
- } of routeMatchInfoSet) {
173
- const routeAction = route.action;
174
- if (!routeAction) {
175
- continue;
176
- }
177
-
178
- const becomesActive = newActive && !oldActive;
179
- const becomesInactive = !newActive && oldActive;
180
- const paramsChangedWhileActive =
181
- newActive && oldActive && newParams !== oldParams;
182
-
183
- // Handle actions for routes that become active
184
- if (becomesActive) {
185
- if (DEBUG) {
186
- console.debug(
187
- `Route ${routePrivateProperties.urlPattern} became active with params:`,
188
- newParams,
189
- );
190
- }
191
- shouldLoad(route);
192
- continue;
193
- }
194
-
195
- // Handle actions for routes that become inactive - abort them
196
- if (becomesInactive && ROUTE_DEACTIVATION_STRATEGY === "abort") {
197
- shouldAbort(route);
198
- continue;
199
- }
200
-
201
- // Handle parameter changes while route stays active
202
- if (paramsChangedWhileActive) {
203
- if (DEBUG) {
204
- console.debug(
205
- `Route ${routePrivateProperties.urlPattern} params changed:`,
206
- newParams,
207
- );
208
- }
209
- shouldReload(route);
210
- }
211
- }
212
-
213
- return {
214
- loadSet: toLoadSet,
215
- reloadSet: toReloadSet,
216
- abortSignalMap,
217
- routeLoadRequestedMap,
218
- activeRouteSet,
219
- };
220
- };
221
- const extractParams = (urlPattern, url, ignoreSet = new Set()) => {
222
- const match = urlPattern.exec(url);
223
- if (!match) {
224
- return NO_PARAMS;
225
- }
226
- const params = {};
227
-
228
- // Collect all parameters from URLPattern groups, handling both named and numbered groups
229
- let wildcardOffset = 0;
230
- for (const property of URL_PATTERN_PROPERTIES_WITH_GROUP_SET) {
231
- const urlPartMatch = match[property];
232
- if (urlPartMatch && urlPartMatch.groups) {
233
- let localWildcardCount = 0;
234
- for (const key of Object.keys(urlPartMatch.groups)) {
235
- const value = urlPartMatch.groups[key];
236
- const keyAsNumber = parseInt(key, 10);
237
- if (!isNaN(keyAsNumber)) {
238
- if (value) {
239
- // Only include non-empty values and non-ignored wildcard indices
240
- const wildcardKey = String(wildcardOffset + keyAsNumber);
241
- if (!ignoreSet.has(wildcardKey)) {
242
- params[wildcardKey] = decodeURIComponent(value);
243
- }
244
- localWildcardCount++;
245
- }
246
- } else if (!ignoreSet.has(key)) {
247
- // Named group (:param or {param}) - only include if not ignored
248
- params[key] = decodeURIComponent(value);
249
- }
250
- }
251
- // Update wildcard offset for next URL part
252
- wildcardOffset += localWildcardCount;
253
- }
254
- }
255
- return params;
256
- };
257
- const URL_PATTERN_PROPERTIES_WITH_GROUP_SET = new Set([
258
- "protocol",
259
- "username",
260
- "password",
261
- "hostname",
262
- "pathname",
263
- "search",
264
- "hash",
265
- ]);
266
-
267
- const routePrivatePropertiesMap = new Map();
268
- const getRoutePrivateProperties = (route) => {
269
- return routePrivatePropertiesMap.get(route);
270
- };
271
- const createRoute = (urlPatternInput) => {
272
- const cleanupCallbackSet = new Set();
273
- const cleanup = () => {
274
- for (const cleanupCallback of cleanupCallbackSet) {
275
- cleanupCallback();
276
- }
277
- cleanupCallbackSet.clear();
278
- };
279
-
280
- const route = {
281
- urlPattern: urlPatternInput,
282
- isRoute: true,
283
- active: false,
284
- params: NO_PARAMS,
285
- buildUrl: null,
286
- bindAction: null,
287
- relativeUrl: null,
288
- url: null,
289
- action: null,
290
- cleanup,
291
- toString: () => {
292
- return `route "${urlPatternInput}"`;
293
- },
294
- replaceParams: undefined,
295
- };
296
- routeSet.add(route);
297
-
298
- const routePrivateProperties = {
299
- urlPattern: undefined,
300
- activeSignal: null,
301
- paramsSignal: null,
302
- visitedSignal: null,
303
- relativeUrlSignal: null,
304
- urlSignal: null,
305
- optionalParamKeySet: null,
306
- };
307
- routePrivatePropertiesMap.set(route, routePrivateProperties);
308
-
309
- const buildRelativeUrl = (params = {}) => {
310
- let relativeUrl = urlPatternInput;
311
- let hasRawUrlPartWithInvalidChars = false;
312
-
313
- const encode = (value) => {
314
- if (value && value[rawUrlPartSymbol]) {
315
- const rawValue = value.value;
316
- // Check if raw value contains invalid URL characters
317
- if (/[\s<>{}|\\^`]/.test(rawValue)) {
318
- hasRawUrlPartWithInvalidChars = true;
319
- }
320
- return rawValue;
321
- }
322
- return encodeURIComponent(value);
323
- };
324
-
325
- // Replace named parameters (:param and {param})
326
- for (const key of Object.keys(params)) {
327
- const value = params[key];
328
- const encodedValue = encode(value);
329
- relativeUrl = relativeUrl.replace(`:${key}`, encodedValue);
330
- relativeUrl = relativeUrl.replace(`{${key}}`, encodedValue);
331
- }
332
-
333
- // Handle wildcards: if the pattern ends with /*? (optional wildcard)
334
- // always remove the wildcard part for URL building since it's optional
335
- if (relativeUrl.endsWith("/*?")) {
336
- // Always remove the optional wildcard part for URL building
337
- relativeUrl = relativeUrl.replace(/\/\*\?$/, "");
338
- } else {
339
- // For required wildcards (/*) or other patterns, replace normally
340
- let wildcardIndex = 0;
341
- relativeUrl = relativeUrl.replace(/\*/g, () => {
342
- const paramKey = wildcardIndex.toString();
343
- const paramValue = params[paramKey];
344
- const replacement = paramValue ? encode(paramValue) : "*";
345
- wildcardIndex++;
346
- return replacement;
347
- });
348
- }
349
-
350
- return {
351
- relativeUrl,
352
- hasRawUrlPartWithInvalidChars,
353
- };
354
- };
355
- const buildUrl = (params = {}) => {
356
- const { relativeUrl, hasRawUrlPartWithInvalidChars } =
357
- buildRelativeUrl(params);
358
- let processedRelativeUrl = relativeUrl;
359
- if (processedRelativeUrl[0] === "/") {
360
- processedRelativeUrl = processedRelativeUrl.slice(1);
361
- }
362
- if (hasRawUrlPartWithInvalidChars) {
363
- return `${baseUrl}/${processedRelativeUrl}`;
364
- }
365
- const url = new URL(processedRelativeUrl, baseUrl).href;
366
- return url;
367
- };
368
- route.buildUrl = buildUrl;
369
-
370
- const activeSignal = signal(false);
371
- const paramsSignal = signal(NO_PARAMS);
372
- const visitedSignal = signal(false);
373
- const relativeUrlSignal = computed(() => {
374
- const params = paramsSignal.value;
375
- const { relativeUrl } = buildRelativeUrl(params);
376
- return relativeUrl;
377
- });
378
- const disposeRelativeUrlEffect = effect(() => {
379
- route.relativeUrl = relativeUrlSignal.value;
380
- });
381
- cleanupCallbackSet.add(disposeRelativeUrlEffect);
382
-
383
- const urlSignal = computed(() => {
384
- const relativeUrl = relativeUrlSignal.value;
385
- const url = new URL(relativeUrl, baseUrl).href;
386
- return url;
387
- });
388
- const disposeUrlEffect = effect(() => {
389
- route.url = urlSignal.value;
390
- });
391
- cleanupCallbackSet.add(disposeUrlEffect);
392
-
393
- const replaceParams = (newParams) => {
394
- const currentParams = paramsSignal.peek();
395
- const updatedParams = { ...currentParams, ...newParams };
396
- const updatedUrl = route.buildUrl(updatedParams);
397
- if (route.action) {
398
- route.action.replaceParams(updatedParams);
399
- }
400
- browserIntegration.goTo(updatedUrl, { replace: true });
401
- };
402
- route.replaceParams = replaceParams;
403
-
404
- const bindAction = (action) => {
405
- /*
406
- *
407
- * here I need to check the store for that action (if any)
408
- * and listen store changes to do this:
409
- *
410
- * When we detect changes we want to update the route params
411
- * so we'll need to use goTo(buildUrl(params), { replace: true })
412
- *
413
- * reinserted is useful because the item id might have changed
414
- * but not the mutable key
415
- *
416
- */
417
-
418
- const { store } = action.meta;
419
- if (store) {
420
- const { mutableIdKeys } = store;
421
- if (mutableIdKeys.length) {
422
- const mutableIdKey = mutableIdKeys[0];
423
- const mutableIdValueSignal = computed(() => {
424
- const params = paramsSignal.value;
425
- const mutableIdValue = params[mutableIdKey];
426
- return mutableIdValue;
427
- });
428
- const routeItemSignal = store.signalForMutableIdKey(
429
- mutableIdKey,
430
- mutableIdValueSignal,
431
- );
432
- store.observeProperties(routeItemSignal, (propertyMutations) => {
433
- const mutableIdPropertyMutation = propertyMutations[mutableIdKey];
434
- if (!mutableIdPropertyMutation) {
435
- return;
436
- }
437
- route.replaceParams({
438
- [mutableIdKey]: mutableIdPropertyMutation.newValue,
439
- });
440
- });
441
- }
442
- }
443
-
444
- /*
445
- store.registerPropertyLifecycle(activeItemSignal, key, {
446
- changed: (value) => {
447
- route.replaceParams({
448
- [key]: value,
449
- });
450
- },
451
- dropped: () => {
452
- route.reload();
453
- },
454
- reinserted: () => {
455
- // this will reload all routes which works but
456
- // - most of the time only "route" is impacted, any other route could stay as is
457
- // - we already have the data, reloading the route will refetch the backend which is unnecessary
458
- // we could just remove routing error (which is cause by 404 likely)
459
- // to actually let the data be displayed
460
- // because they are available, but in reality the route has no data
461
- // because the fetch failed
462
- // so conceptually reloading is fine,
463
- // the only thing that bothers me a little is that it reloads all routes
464
- route.reload();
465
- },
466
- });
467
- */
468
-
469
- const actionBoundToThisRoute = action.bindParams(paramsSignal);
470
- route.action = actionBoundToThisRoute;
471
- return actionBoundToThisRoute;
472
- };
473
- route.bindAction = bindAction;
474
-
475
- private_properties: {
476
- // Remove leading slash from urlPattern to make it relative to baseUrl
477
- const normalizedUrlPattern = urlPatternInput.startsWith("/")
478
- ? urlPatternInput.slice(1)
479
- : urlPatternInput;
480
- const urlPattern = new URLPattern(normalizedUrlPattern, baseUrl, {
481
- ignoreCase: true,
482
- });
483
- routePrivateProperties.urlPattern = urlPattern;
484
- routePrivateProperties.activeSignal = activeSignal;
485
- routePrivateProperties.paramsSignal = paramsSignal;
486
- routePrivateProperties.visitedSignal = visitedSignal;
487
- routePrivateProperties.relativeUrlSignal = relativeUrlSignal;
488
- routePrivateProperties.urlSignal = urlSignal;
489
- routePrivateProperties.cleanupCallbackSet = cleanupCallbackSet;
490
-
491
- // Analyze pattern once to detect optional params (named and wildcard indices)
492
- // Note: Wildcard indices are stored as strings ("0", "1", ...) to match keys from extractParams
493
- const optionalParamKeySet = new Set();
494
- normalizedUrlPattern.replace(/:([A-Za-z0-9_]+)\?/g, (_m, name) => {
495
- optionalParamKeySet.add(name);
496
- return "";
497
- });
498
- let wildcardIndex = 0;
499
- normalizedUrlPattern.replace(/\*(\?)?/g, (_m, opt) => {
500
- if (opt === "?") {
501
- optionalParamKeySet.add(String(wildcardIndex));
502
- }
503
- wildcardIndex++;
504
- return "";
505
- });
506
- routePrivateProperties.optionalParamKeySet = optionalParamKeySet;
507
- }
508
-
509
- return route;
510
- };
511
- export const useRouteStatus = (route) => {
512
- if (import.meta.dev && (!route || !route.isRoute)) {
513
- throw new TypeError(
514
- `useRouteStatus() requires a route object, but received ${route}.`,
515
- );
516
- }
517
- const routePrivateProperties = getRoutePrivateProperties(route);
518
- if (!routePrivateProperties) {
519
- if (import.meta.dev) {
520
- let errorMessage = `Cannot find route private properties for ${route}.`;
521
-
522
- errorMessage += `\nThis might be caused by hot reloading - try refreshing the page.`;
523
- throw new Error(errorMessage);
524
- }
525
- throw new Error(`Cannot find route private properties for ${route}`);
526
- }
527
-
528
- const { urlSignal, activeSignal, paramsSignal, visitedSignal } =
529
- routePrivateProperties;
530
-
531
- const url = urlSignal.value;
532
- const active = activeSignal.value;
533
- const params = paramsSignal.value;
534
- const visited = visitedSignal.value;
535
-
536
- return {
537
- url,
538
- active,
539
- params,
540
- visited,
541
- };
542
- };
543
-
544
- let browserIntegration;
545
- export const setBrowserIntegration = (integration) => {
546
- browserIntegration = integration;
547
- };
548
-
549
- let onRouteDefined = () => {};
550
- export const setOnRouteDefined = (v) => {
551
- onRouteDefined = v;
552
- };
553
- /**
554
- * Define all routes for the application.
555
- *
556
- * ⚠️ HOT RELOAD WARNING: When destructuring the returned routes, use 'let' instead of 'const'
557
- * to allow hot reload to update the route references:
558
- *
559
- * ❌ const [ROLE_ROUTE, DATABASE_ROUTE] = defineRoutes({...})
560
- * ✅ let [ROLE_ROUTE, DATABASE_ROUTE] = defineRoutes({...})
561
- *
562
- * @param {Object} routeDefinition - Object mapping URL patterns to actions
563
- * @returns {Array} Array of route objects in the same order as the keys
564
- */
565
- // All routes MUST be created at once because any url can be accessed
566
- // at any given time (url can be shared, reloaded, etc..)
567
- // Later I'll consider adding ability to have dynamic import into the mix
568
- // (An async function returning an action)
569
- export const defineRoutes = (routeDefinition) => {
570
- // Clean up existing routes
571
- for (const route of routeSet) {
572
- route.cleanup();
573
- }
574
- routeSet.clear();
575
-
576
- const routeArray = [];
577
- for (const key of Object.keys(routeDefinition)) {
578
- const value = routeDefinition[key];
579
- const route = createRoute(key);
580
- if (value && value.isAction) {
581
- route.bindAction(value);
582
- } else if (typeof value === "function") {
583
- const actionFromFunction = createAction(value);
584
- route.bindAction(actionFromFunction);
585
- } else if (value) {
586
- route.bindAction(value);
587
- }
588
- routeArray.push(route);
589
- }
590
- onRouteDefined();
591
-
592
- return routeArray;
593
- };
594
-
595
- // unit test exports
596
- export { createRoute };