@jsenv/navi 0.10.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/dist/jsenv_navi.js +13838 -23291
  2. package/dist/jsenv_navi.js.map +1281 -0
  3. package/package.json +5 -7
  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
package/src/actions.js DELETED
@@ -1,1401 +0,0 @@
1
- import { createIterableWeakSet } from "@jsenv/dom";
2
- import { prefixFirstAndIndentRemainingLines } from "@jsenv/humanize";
3
- import { batch, computed, effect, signal } from "@preact/signals";
4
-
5
- import {
6
- getActionPrivateProperties,
7
- setActionPrivateProperties,
8
- } from "./action_private_properties.js";
9
- import {
10
- ABORTED,
11
- COMPLETED,
12
- FAILED,
13
- IDLE,
14
- RUNNING,
15
- } from "./action_run_states.js";
16
- import { SYMBOL_OBJECT_SIGNAL } from "./symbol_object_signal.js";
17
- import { isSignal } from "./utils/is_signal.js";
18
- import { createJsValueWeakMap } from "./utils/js_value_weak_map.js";
19
- import { mergeTwoJsValues } from "./utils/merge_two_js_values.js";
20
- import { stringifyForDisplay } from "./utils/stringify_for_display.js";
21
- import { weakEffect } from "./utils/weak_effect.js";
22
-
23
- let DEBUG = false;
24
- export const enableDebugActions = () => {
25
- DEBUG = true;
26
- };
27
-
28
- const ACTION_AS_FUNCTION = true;
29
-
30
- let dispatchActions = (params) => {
31
- const { requestedResult } = updateActions({
32
- globalAbortSignal: new AbortController().signal,
33
- abortSignal: new AbortController().signal,
34
- ...params,
35
- });
36
- return requestedResult;
37
- };
38
-
39
- const dispatchSingleAction = (action, method, options) => {
40
- const requestedResult = dispatchActions({
41
- prerunSet: method === "prerun" ? new Set([action]) : undefined,
42
- runSet: method === "run" ? new Set([action]) : undefined,
43
- rerunSet: method === "rerun" ? new Set([action]) : undefined,
44
- resetSet: method === "reset" ? new Set([action]) : undefined,
45
- ...options,
46
- });
47
- if (requestedResult && typeof requestedResult.then === "function") {
48
- return requestedResult.then((resolvedResult) =>
49
- resolvedResult ? resolvedResult[0] : undefined,
50
- );
51
- }
52
- return requestedResult ? requestedResult[0] : undefined;
53
- };
54
- export const setActionDispatcher = (value) => {
55
- dispatchActions = value;
56
- };
57
-
58
- export const getActionDispatcher = () => dispatchActions;
59
-
60
- export const rerunActions = async (
61
- actionSet,
62
- { reason = "rerunActions was called" } = {},
63
- ) => {
64
- return dispatchActions({
65
- rerunSet: actionSet,
66
- reason,
67
- });
68
- };
69
-
70
- export const resetActions = async (
71
- actionSet,
72
- { reason = "resetActions was called" } = {},
73
- ) => {
74
- return dispatchActions({
75
- resetSet: actionSet,
76
- reason,
77
- });
78
- };
79
- export const abortRunningActions = (
80
- reason = "abortRunningActions was called",
81
- ) => {
82
- const { runningSet } = getActivationInfo();
83
- for (const runningAction of runningSet) {
84
- runningAction.abort(reason);
85
- }
86
- };
87
-
88
- /**
89
- * Registry that prevents prerun actions from being garbage collected.
90
- *
91
- * When an action is prerun, it might not have any active references yet
92
- * (e.g., the component that will use it hasn't loaded yet due to dynamic imports).
93
- * This registry keeps a reference to prerun actions for a configurable duration
94
- * to ensure they remain available when needed.
95
- *
96
- * Actions are automatically unprotected when:
97
- * - The protection duration expires (default: 5 minutes)
98
- * - The action is explicitly stopped via .stop()
99
- */
100
- const prerunProtectionRegistry = (() => {
101
- const protectedActionMap = new Map(); // action -> { timeoutId, timestamp }
102
- const PROTECTION_DURATION = 5 * 60 * 1000; // 5 minutes en millisecondes
103
-
104
- const unprotect = (action) => {
105
- const protection = protectedActionMap.get(action);
106
- if (protection) {
107
- clearTimeout(protection.timeoutId);
108
- protectedActionMap.delete(action);
109
- if (DEBUG) {
110
- const elapsed = Date.now() - protection.timestamp;
111
- console.debug(`"${action}": GC protection removed after ${elapsed}ms`);
112
- }
113
- }
114
- };
115
-
116
- return {
117
- protect(action) {
118
- // Si déjà protégée, étendre la protection
119
- if (protectedActionMap.has(action)) {
120
- const existing = protectedActionMap.get(action);
121
- clearTimeout(existing.timeoutId);
122
- }
123
-
124
- const timestamp = Date.now();
125
- const timeoutId = setTimeout(() => {
126
- unprotect(action);
127
- if (DEBUG) {
128
- console.debug(
129
- `"${action}": prerun protection expired after ${PROTECTION_DURATION}ms`,
130
- );
131
- }
132
- }, PROTECTION_DURATION);
133
-
134
- protectedActionMap.set(action, { timeoutId, timestamp });
135
-
136
- if (DEBUG) {
137
- console.debug(
138
- `"${action}": protected from GC for ${PROTECTION_DURATION}ms`,
139
- );
140
- }
141
- },
142
-
143
- unprotect,
144
-
145
- isProtected(action) {
146
- return protectedActionMap.has(action);
147
- },
148
-
149
- // Pour debugging
150
- getProtectedActions() {
151
- return Array.from(protectedActionMap.keys());
152
- },
153
-
154
- // Nettoyage manuel si nécessaire
155
- clear() {
156
- for (const [, protection] of protectedActionMap) {
157
- clearTimeout(protection.timeoutId);
158
- }
159
- protectedActionMap.clear();
160
- },
161
- };
162
- })();
163
-
164
- export const formatActionSet = (actionSet, prefix = "") => {
165
- let message = "";
166
- message += `${prefix}`;
167
- for (const action of actionSet) {
168
- message += "\n";
169
- message += prefixFirstAndIndentRemainingLines(String(action), {
170
- prefix: " -",
171
- });
172
- }
173
- return message;
174
- };
175
-
176
- const actionAbortMap = new Map();
177
- const actionPromiseMap = new Map();
178
- const activationWeakSet = createIterableWeakSet("activation");
179
-
180
- const getActivationInfo = () => {
181
- const runningSet = new Set();
182
- const settledSet = new Set();
183
-
184
- for (const action of activationWeakSet) {
185
- const privateProps = getActionPrivateProperties(action);
186
- const runningState = privateProps.runningStateSignal.peek();
187
-
188
- if (runningState === RUNNING) {
189
- runningSet.add(action);
190
- } else if (
191
- runningState === COMPLETED ||
192
- runningState === FAILED ||
193
- runningState === ABORTED
194
- ) {
195
- settledSet.add(action);
196
- } else {
197
- throw new Error(
198
- `An action in the activation weak set must be RUNNING, ABORTED, FAILED or COMPLETED, found "${runningState.id}" for action "${action}"`,
199
- );
200
- }
201
- }
202
-
203
- return {
204
- runningSet,
205
- settledSet,
206
- };
207
- };
208
-
209
- if (import.meta.dev) {
210
- window.__actions__ = {
211
- activationWeakSet,
212
- getActivationInfo,
213
- inspectActivations: () => {
214
- const activations = [];
215
- for (const action of activationWeakSet) {
216
- activations.push({
217
- name: action.name,
218
- runningState: action.runningState.id,
219
- error: action.error,
220
- params: action.params,
221
- isProxy: action.isProxy || false,
222
- });
223
- }
224
- console.table(activations);
225
- return activations;
226
- },
227
- cleanup: {
228
- activation: {
229
- forceCleanup: () => activationWeakSet.forceCleanup(),
230
- schedule: () => activationWeakSet.schedule(),
231
- getStats: () => activationWeakSet.getStats(),
232
- },
233
- },
234
- };
235
- }
236
-
237
- export const updateActions = ({
238
- globalAbortSignal,
239
- abortSignal,
240
- isReplace = false,
241
- reason,
242
- prerunSet = new Set(),
243
- runSet = new Set(),
244
- rerunSet = new Set(),
245
- resetSet = new Set(),
246
- abortSignalMap = new Map(),
247
- onComplete,
248
- onAbort,
249
- onError,
250
- } = {}) => {
251
- /*
252
- * Action update flow:
253
- *
254
- * Input: 4 sets of requested operations
255
- * - prerunSet: actions to prerun (background, low priority)
256
- * - runSet: actions to run (user-visible, medium priority)
257
- * - rerunSet: actions to force rerun (highest priority)
258
- * - resetSet: actions to reset/clear
259
- *
260
- * Priority resolution:
261
- * - reset always wins (explicit cleanup)
262
- * - rerun > run > prerun (rerun forces refresh even if already running)
263
- * - An action in multiple sets triggers warnings in dev mode
264
- *
265
- * Output: Internal operation sets that track what will actually happen
266
- * - willResetSet: actions that will be reset/cleared
267
- * - willPrerunSet: actions that will be prerun
268
- * - willRunSet: actions that will be run
269
- * - willPromoteSet: prerun actions that become run-requested
270
- * - stays*Set: actions that remain in their current state
271
- */
272
-
273
- const { runningSet, settledSet } = getActivationInfo();
274
-
275
- // Warn about overlapping sets in development
276
- if (import.meta.dev) {
277
- const allSets = [
278
- { name: "prerun", set: prerunSet },
279
- { name: "run", set: runSet },
280
- { name: "rerun", set: rerunSet },
281
- { name: "reset", set: resetSet },
282
- ];
283
-
284
- for (let i = 0; i < allSets.length; i++) {
285
- for (let j = i + 1; j < allSets.length; j++) {
286
- const setA = allSets[i];
287
- const setB = allSets[j];
288
- for (const action of setA.set) {
289
- if (setB.set.has(action)) {
290
- console.warn(
291
- `Action "${action}" is found in both ${setA.name}Set and ${setB.name}Set. This may lead to unexpected behavior.`,
292
- );
293
- }
294
- }
295
- }
296
- }
297
- }
298
-
299
- if (DEBUG) {
300
- let argSource = `reason: \`${reason}\``;
301
- if (isReplace) {
302
- argSource += `, isReplace: true`;
303
- }
304
- console.group(`updateActions({ ${argSource} })`);
305
- const lines = [
306
- ...(prerunSet.size ? [formatActionSet(prerunSet, "- prerun:")] : []),
307
- ...(runSet.size ? [formatActionSet(runSet, "- run:")] : []),
308
- ...(rerunSet.size ? [formatActionSet(rerunSet, "- rerun:")] : []),
309
- ...(resetSet.size ? [formatActionSet(resetSet, "- reset:")] : []),
310
- ];
311
- console.debug(
312
- `requested operations:
313
- ${lines.join("\n")}`,
314
- );
315
- }
316
-
317
- // Internal sets that track what operations will actually be performed
318
- const willResetSet = new Set();
319
- const willPrerunSet = new Set();
320
- const willRunSet = new Set();
321
- const willPromoteSet = new Set(); // prerun -> run requested
322
- const staysRunningSet = new Set();
323
- const staysAbortedSet = new Set();
324
- const staysFailedSet = new Set();
325
- const staysCompletedSet = new Set();
326
-
327
- // Step 1: Determine which actions will be reset
328
- collect_actions_to_reset: {
329
- for (const actionToReset of resetSet) {
330
- if (actionToReset.runningState !== IDLE) {
331
- willResetSet.add(actionToReset);
332
- }
333
- }
334
- }
335
-
336
- // Step 2: Process prerun, run, and rerun sets
337
- collect_actions_to_prerun_and_run: {
338
- const handleActionRequest = (
339
- action,
340
- requestType, // "prerun", "run", or "rerun"
341
- ) => {
342
- const isPrerun = requestType === "prerun";
343
- const isRerun = requestType === "rerun";
344
-
345
- if (
346
- action.runningState === RUNNING ||
347
- action.runningState === COMPLETED
348
- ) {
349
- // Action is already running/completed
350
- // By default, we don't interfere with already active actions
351
- // Unless it's a rerun or the action is also being reset
352
- if (isRerun || willResetSet.has(action)) {
353
- // Force reset first, then rerun/run
354
- willResetSet.add(action);
355
- if (isPrerun) {
356
- willPrerunSet.add(action);
357
- } else {
358
- willRunSet.add(action);
359
- }
360
- }
361
- // Otherwise, ignore the request (action stays as-is)
362
- } else if (isPrerun) {
363
- willPrerunSet.add(action);
364
- } else {
365
- willRunSet.add(action);
366
- }
367
- };
368
-
369
- // Process prerunSet (lowest priority)
370
- for (const actionToPrerun of prerunSet) {
371
- if (runSet.has(actionToPrerun) || rerunSet.has(actionToPrerun)) {
372
- // run/rerun wins over prerun - skip prerun
373
- continue;
374
- }
375
- handleActionRequest(actionToPrerun, "prerun");
376
- }
377
-
378
- // Process runSet (medium priority)
379
- for (const actionToRun of runSet) {
380
- if (rerunSet.has(actionToRun)) {
381
- // rerun wins over run - skip run
382
- continue;
383
- }
384
- if (actionToRun.isPrerun && actionToRun.runningState !== IDLE) {
385
- // Special case: action was prerun but not yet requested to run
386
- // Just promote it to "run requested" without rerunning
387
- willPromoteSet.add(actionToRun);
388
- continue;
389
- }
390
- handleActionRequest(actionToRun, "run");
391
- }
392
-
393
- // Process rerunSet (highest priority)
394
- for (const actionToRerun of rerunSet) {
395
- handleActionRequest(actionToRerun, "rerun");
396
- }
397
- }
398
- const allThenableArray = [];
399
-
400
- // Step 3: Determine which actions will stay in their current state
401
- collect_actions_that_stay: {
402
- for (const actionRunning of runningSet) {
403
- if (willResetSet.has(actionRunning)) {
404
- // will be reset (aborted), we don't want to wait
405
- } else if (
406
- willRunSet.has(actionRunning) ||
407
- willPrerunSet.has(actionRunning)
408
- ) {
409
- // will be run, we'll wait for the new run promise
410
- } else {
411
- // an action that was running and not affected by this update
412
- const actionPromise = actionPromiseMap.get(actionRunning);
413
- allThenableArray.push(actionPromise);
414
- staysRunningSet.add(actionRunning);
415
- }
416
- }
417
- for (const actionSettled of settledSet) {
418
- if (willResetSet.has(actionSettled)) {
419
- // will be reset
420
- } else if (actionSettled.runningState === ABORTED) {
421
- staysAbortedSet.add(actionSettled);
422
- } else if (actionSettled.runningState === FAILED) {
423
- staysFailedSet.add(actionSettled);
424
- } else {
425
- staysCompletedSet.add(actionSettled);
426
- }
427
- }
428
- }
429
- if (DEBUG) {
430
- const lines = [
431
- ...(willResetSet.size
432
- ? [formatActionSet(willResetSet, "- will reset:")]
433
- : []),
434
- ...(willPrerunSet.size
435
- ? [formatActionSet(willPrerunSet, "- will prerun:")]
436
- : []),
437
- ...(willPromoteSet.size
438
- ? [formatActionSet(willPromoteSet, "- will promote:")]
439
- : []),
440
- ...(willRunSet.size ? [formatActionSet(willRunSet, "- will run:")] : []),
441
- ...(staysRunningSet.size
442
- ? [formatActionSet(staysRunningSet, "- stays running:")]
443
- : []),
444
- ...(staysAbortedSet.size
445
- ? [formatActionSet(staysAbortedSet, "- stays aborted:")]
446
- : []),
447
- ...(staysFailedSet.size
448
- ? [formatActionSet(staysFailedSet, "- stays failed:")]
449
- : []),
450
- ...(staysCompletedSet.size
451
- ? [formatActionSet(staysCompletedSet, "- stays completed:")]
452
- : []),
453
- ];
454
- console.debug(`operations that will be performed:
455
- ${lines.join("\n")}`);
456
- }
457
-
458
- // Step 4: Execute resets
459
- execute_resets: {
460
- for (const actionToReset of willResetSet) {
461
- const actionToResetPrivateProperties =
462
- getActionPrivateProperties(actionToReset);
463
- actionToResetPrivateProperties.performStop({ reason });
464
- activationWeakSet.delete(actionToReset);
465
- }
466
- }
467
-
468
- const resultArray = []; // Store results with their execution order
469
- let hasAsync = false;
470
-
471
- // Step 5: Execute preruns and runs
472
- execute_preruns_and_runs: {
473
- const onActionToRunOrPrerun = (actionToPrerunOrRun, isPrerun) => {
474
- if (import.meta.dev && actionToPrerunOrRun.isProxy) {
475
- // maybe remove this check one the API is stable because
476
- // nothing in the API should allow this to happen
477
- throw new Error(
478
- `Proxy should not be reach this point, use the underlying action instead`,
479
- );
480
- }
481
- const actionSpecificSignal = abortSignalMap.get(actionToPrerunOrRun);
482
- const effectiveSignal = actionSpecificSignal || abortSignal;
483
-
484
- const actionToRunPrivateProperties =
485
- getActionPrivateProperties(actionToPrerunOrRun);
486
- const performRunResult = actionToRunPrivateProperties.performRun({
487
- globalAbortSignal,
488
- abortSignal: effectiveSignal,
489
- reason,
490
- isPrerun,
491
- onComplete,
492
- onAbort,
493
- onError,
494
- });
495
- activationWeakSet.add(actionToPrerunOrRun);
496
-
497
- if (performRunResult && typeof performRunResult.then === "function") {
498
- actionPromiseMap.set(actionToPrerunOrRun, performRunResult);
499
- allThenableArray.push(performRunResult);
500
- hasAsync = true;
501
- // Store async result with order info
502
- resultArray.push({
503
- type: "async",
504
- promise: performRunResult,
505
- });
506
- } else {
507
- // Store sync result with order info
508
- resultArray.push({
509
- type: "sync",
510
- result: performRunResult,
511
- });
512
- }
513
- };
514
-
515
- // Execute preruns
516
- for (const actionToPrerun of willPrerunSet) {
517
- onActionToRunOrPrerun(actionToPrerun, true);
518
- }
519
-
520
- // Execute runs
521
- for (const actionToRun of willRunSet) {
522
- onActionToRunOrPrerun(actionToRun, false);
523
- }
524
-
525
- // Execute promotions (prerun -> run requested)
526
- for (const actionToPromote of willPromoteSet) {
527
- const actionToPromotePrivateProperties =
528
- getActionPrivateProperties(actionToPromote);
529
- actionToPromotePrivateProperties.isPrerunSignal.value = false;
530
- }
531
- }
532
- if (DEBUG) {
533
- console.groupEnd();
534
- }
535
-
536
- // Calculate requestedResult based on the execution results
537
- let requestedResult;
538
- if (resultArray.length === 0) {
539
- requestedResult = null;
540
- } else if (hasAsync) {
541
- requestedResult = Promise.all(
542
- resultArray.map((item) =>
543
- item.type === "sync" ? item.result : item.promise,
544
- ),
545
- );
546
- } else {
547
- requestedResult = resultArray.map((item) => item.result);
548
- }
549
-
550
- const allResult = allThenableArray.length
551
- ? Promise.allSettled(allThenableArray)
552
- : null;
553
- const runningActionSet = new Set([...willPrerunSet, ...willRunSet]);
554
- return {
555
- requestedResult,
556
- allResult,
557
- runningActionSet,
558
- };
559
- };
560
-
561
- const NO_PARAMS = {};
562
- const initialParamsDefault = NO_PARAMS;
563
-
564
- const actionWeakMap = new WeakMap();
565
- export const createAction = (callback, rootOptions = {}) => {
566
- const existing = actionWeakMap.get(callback);
567
- if (existing) {
568
- return existing;
569
- }
570
-
571
- let rootAction;
572
-
573
- const createActionCore = (options, { parentAction } = {}) => {
574
- let {
575
- name = callback.name || "anonymous",
576
- params,
577
- isPrerun = true,
578
- runningState = IDLE,
579
- aborted = false,
580
- error = null,
581
- data,
582
- computedData,
583
- compute,
584
- completed = false,
585
- renderLoadedAsync,
586
- sideEffect = () => {},
587
- keepOldData = false,
588
- meta = {},
589
- dataEffect,
590
- completeSideEffect,
591
- } = options;
592
- if (!Object.hasOwn(options, "params")) {
593
- // even undefined should be respect it's only when not provided at all we use default
594
- params = initialParamsDefault;
595
- }
596
-
597
- const initialData = data;
598
- const paramsSignal = signal(params);
599
- const isPrerunSignal = signal(isPrerun);
600
- const runningStateSignal = signal(runningState);
601
- const errorSignal = signal(error);
602
- const dataSignal = signal(initialData);
603
- const computedDataSignal = compute
604
- ? computed(() => {
605
- const data = dataSignal.value;
606
- return compute(data);
607
- })
608
- : dataSignal;
609
- computedData =
610
- computedData === undefined
611
- ? compute
612
- ? compute(data)
613
- : data
614
- : computedData;
615
-
616
- const prerun = (options) => {
617
- return dispatchSingleAction(action, "prerun", options);
618
- };
619
- const run = (options) => {
620
- return dispatchSingleAction(action, "run", options);
621
- };
622
- const rerun = (options) => {
623
- return dispatchSingleAction(action, "rerun", options);
624
- };
625
- /**
626
- * Stop the action completely - this will:
627
- * 1. Abort the action if it's currently running
628
- * 2. Reset the action to IDLE state
629
- * 3. Clean up any resources and side effects
630
- * 4. Reset data to initial value (unless keepOldData is true)
631
- */
632
- const stop = (options) => {
633
- return dispatchSingleAction(action, "stop", options);
634
- };
635
- const abort = (reason) => {
636
- if (runningState !== RUNNING) {
637
- return false;
638
- }
639
- const actionAbort = actionAbortMap.get(action);
640
- if (!actionAbort) {
641
- return false;
642
- }
643
- if (DEBUG) {
644
- console.log(`"${action}": aborting (reason: ${reason})`);
645
- }
646
- actionAbort(reason);
647
- return true;
648
- };
649
-
650
- let action;
651
-
652
- const childActionWeakSet = createIterableWeakSet("child_action");
653
- /*
654
- * Ephemeron behavior is critical here: actions must keep params alive.
655
- * Without this, bindParams(params) could create a new action while code
656
- * still references the old action with GC'd params. This would cause:
657
- * - Duplicate actions in activationWeakSet (old + new)
658
- * - Cache misses when looking up existing actions
659
- * - Subtle bugs where different parts of code use different action instances
660
- * The ephemeron pattern ensures params and actions have synchronized lifetimes.
661
- */
662
- const childActionWeakMap = createJsValueWeakMap();
663
- const _bindParams = (newParamsOrSignal, options = {}) => {
664
- // ✅ CAS 1: Signal direct -> proxy
665
- if (isSignal(newParamsOrSignal)) {
666
- const combinedParamsSignal = computed(() => {
667
- const newParams = newParamsOrSignal.value;
668
- const result = mergeTwoJsValues(params, newParams);
669
- return result;
670
- });
671
- return createActionProxyFromSignal(
672
- action,
673
- combinedParamsSignal,
674
- options,
675
- );
676
- }
677
-
678
- // ✅ CAS 2: Objet -> vérifier s'il contient des signals
679
- if (newParamsOrSignal && typeof newParamsOrSignal === "object") {
680
- const staticParams = {};
681
- const signalMap = new Map();
682
-
683
- const keyArray = Object.keys(newParamsOrSignal);
684
- for (const key of keyArray) {
685
- const value = newParamsOrSignal[key];
686
- if (isSignal(value)) {
687
- signalMap.set(key, value);
688
- } else {
689
- const objectSignal = value ? value[SYMBOL_OBJECT_SIGNAL] : null;
690
- if (objectSignal) {
691
- signalMap.set(key, objectSignal);
692
- } else {
693
- staticParams[key] = value;
694
- }
695
- }
696
- }
697
-
698
- if (signalMap.size === 0) {
699
- // Pas de signals, merge statique normal
700
- if (params === null || typeof params !== "object") {
701
- return createChildAction({
702
- ...options,
703
- params: newParamsOrSignal,
704
- });
705
- }
706
- const combinedParams = mergeTwoJsValues(params, newParamsOrSignal);
707
- return createChildAction({
708
- ...options,
709
- params: combinedParams,
710
- });
711
- }
712
-
713
- // Combiner avec les params existants pour les valeurs statiques
714
- const paramsSignal = computed(() => {
715
- const params = {};
716
- for (const key of keyArray) {
717
- const signalForThisKey = signalMap.get(key);
718
- if (signalForThisKey) {
719
- params[key] = signalForThisKey.value;
720
- } else {
721
- params[key] = staticParams[key];
722
- }
723
- }
724
- return params;
725
- });
726
- return createActionProxyFromSignal(action, paramsSignal, options);
727
- }
728
-
729
- // ✅ CAS 3: Primitive -> action enfant
730
- return createChildAction({
731
- params: newParamsOrSignal,
732
- ...options,
733
- });
734
- };
735
- const bindParams = (newParamsOrSignal, options = {}) => {
736
- const existingChildAction = childActionWeakMap.get(newParamsOrSignal);
737
- if (existingChildAction) {
738
- return existingChildAction;
739
- }
740
- const childAction = _bindParams(newParamsOrSignal, options);
741
- childActionWeakMap.set(newParamsOrSignal, childAction);
742
- childActionWeakSet.add(childAction);
743
-
744
- return childAction;
745
- };
746
-
747
- const createChildAction = (childOptions) => {
748
- const childActionOptions = {
749
- ...rootOptions,
750
- ...childOptions,
751
- meta: {
752
- ...rootOptions.meta,
753
- ...childOptions.meta,
754
- },
755
- };
756
- const childAction = createActionCore(childActionOptions, {
757
- parentAction: action,
758
- });
759
- return childAction;
760
- };
761
-
762
- // ✅ Implement matchAllSelfOrDescendant
763
- const matchAllSelfOrDescendant = (predicate, { includeProxies } = {}) => {
764
- const matches = [];
765
-
766
- const traverse = (currentAction) => {
767
- if (currentAction.isProxy && !includeProxies) {
768
- // proxy action should be ignored because the underlying action will be found anyway
769
- // and if we check the proxy action we'll end up with duplicates
770
- // (loading the proxy would load the action it proxies)
771
- // and as they are 2 different objects they would be added to the set
772
- return;
773
- }
774
-
775
- if (predicate(currentAction)) {
776
- matches.push(currentAction);
777
- }
778
-
779
- // Get child actions from the current action
780
- const currentActionPrivateProps =
781
- getActionPrivateProperties(currentAction);
782
- const childActionWeakSet = currentActionPrivateProps.childActionWeakSet;
783
- for (const childAction of childActionWeakSet) {
784
- traverse(childAction);
785
- }
786
- };
787
-
788
- traverse(action);
789
- return matches;
790
- };
791
-
792
- name = generateActionName(name, params);
793
- if (ACTION_AS_FUNCTION) {
794
- // Create the action as a function that can be called directly
795
- action = function actionFunction(params) {
796
- const boundAction = bindParams(params);
797
- return boundAction.rerun();
798
- };
799
- Object.defineProperty(action, "name", {
800
- configurable: true,
801
- writable: true,
802
- value: name,
803
- });
804
- } else {
805
- action = { name };
806
- }
807
-
808
- // Assign all the action properties and methods to the function
809
- Object.assign(action, {
810
- isAction: true,
811
- callback,
812
- rootAction,
813
- parentAction,
814
- params,
815
- isPrerun,
816
- runningState,
817
- aborted,
818
- error,
819
- data,
820
- computedData,
821
- completed,
822
- prerun,
823
- run,
824
- rerun,
825
- stop,
826
- abort,
827
- bindParams,
828
- matchAllSelfOrDescendant, // ✅ Add the new method
829
- replaceParams: (newParams) => {
830
- const currentParams = paramsSignal.value;
831
- const nextParams = mergeTwoJsValues(currentParams, newParams);
832
- if (nextParams === currentParams) {
833
- return false;
834
- }
835
-
836
- // Update the weak map BEFORE updating the signal
837
- // so that any code triggered by the signal update finds this action
838
- if (parentAction) {
839
- const parentActionPrivateProps =
840
- getActionPrivateProperties(parentAction);
841
- const parentChildActionWeakMap =
842
- parentActionPrivateProps.childActionWeakMap;
843
- parentChildActionWeakMap.delete(currentParams);
844
- parentChildActionWeakMap.set(nextParams, action);
845
- }
846
-
847
- params = nextParams;
848
- action.params = nextParams;
849
- action.name = generateActionName(name, nextParams);
850
- paramsSignal.value = nextParams;
851
- return true;
852
- },
853
- toString: () => action.name,
854
- meta,
855
- });
856
- Object.preventExtensions(action);
857
-
858
- // Effects pour synchroniser les propriétés
859
- effects: {
860
- weakEffect([action], (actionRef) => {
861
- isPrerun = isPrerunSignal.value;
862
- actionRef.isPrerun = isPrerun;
863
- });
864
- weakEffect([action], (actionRef) => {
865
- runningState = runningStateSignal.value;
866
- actionRef.runningState = runningState;
867
- aborted = runningState === ABORTED;
868
- actionRef.aborted = aborted;
869
- completed = runningState === COMPLETED;
870
- actionRef.completed = completed;
871
- });
872
- weakEffect([action], (actionRef) => {
873
- error = errorSignal.value;
874
- actionRef.error = error;
875
- });
876
- weakEffect([action], (actionRef) => {
877
- data = dataSignal.value;
878
- computedData = computedDataSignal.value;
879
- actionRef.data = data;
880
- actionRef.computedData = computedData;
881
- });
882
- }
883
-
884
- // Propriétés privées
885
- private_properties: {
886
- const ui = {
887
- renderLoaded: null,
888
- renderLoadedAsync,
889
- hasRenderers: false, // Flag to track if action is bound to UI components
890
- };
891
- let sideEffectCleanup;
892
-
893
- const performRun = (runParams) => {
894
- const {
895
- globalAbortSignal,
896
- abortSignal,
897
- reason,
898
- isPrerun,
899
- onComplete,
900
- onAbort,
901
- onError,
902
- } = runParams;
903
-
904
- if (isPrerun) {
905
- prerunProtectionRegistry.protect(action);
906
- }
907
-
908
- const internalAbortController = new AbortController();
909
- const internalAbortSignal = internalAbortController.signal;
910
- const abort = (abortReason) => {
911
- runningStateSignal.value = ABORTED;
912
- internalAbortController.abort(abortReason);
913
- actionAbortMap.delete(action);
914
- if (isPrerun && (globalAbortSignal.aborted || abortSignal.aborted)) {
915
- prerunProtectionRegistry.unprotect(action);
916
- }
917
- if (DEBUG) {
918
- console.log(`"${action}": aborted (reason: ${abortReason})`);
919
- }
920
- };
921
-
922
- const onAbortFromSpecific = () => {
923
- abort(abortSignal.reason);
924
- };
925
- const onAbortFromGlobal = () => {
926
- abort(globalAbortSignal.reason);
927
- };
928
-
929
- if (abortSignal) {
930
- abortSignal.addEventListener("abort", onAbortFromSpecific);
931
- }
932
- if (globalAbortSignal) {
933
- globalAbortSignal.addEventListener("abort", onAbortFromGlobal);
934
- }
935
-
936
- actionAbortMap.set(action, abort);
937
-
938
- batch(() => {
939
- errorSignal.value = null;
940
- runningStateSignal.value = RUNNING;
941
- if (!isPrerun) {
942
- isPrerunSignal.value = false;
943
- }
944
- });
945
-
946
- const args = [];
947
- args.push(params);
948
- args.push({ signal: internalAbortSignal, reason, isPrerun });
949
- const returnValue = sideEffect(...args);
950
- if (typeof returnValue === "function") {
951
- sideEffectCleanup = returnValue;
952
- }
953
-
954
- let runResult;
955
- let rejected = false;
956
- let rejectedValue;
957
- const onRunEnd = () => {
958
- if (abortSignal) {
959
- abortSignal.removeEventListener("abort", onAbortFromSpecific);
960
- }
961
- if (globalAbortSignal) {
962
- globalAbortSignal.removeEventListener("abort", onAbortFromGlobal);
963
- }
964
- prerunProtectionRegistry.unprotect(action);
965
- actionAbortMap.delete(action);
966
- actionPromiseMap.delete(action);
967
- /*
968
- * Critical: dataEffect, onComplete and completeSideEffect must be batched together to prevent
969
- * UI inconsistencies. The dataEffect might modify shared state (e.g.,
970
- * deleting items from a store), and onLoad callbacks might trigger
971
- * dependent action state changes.
972
- *
973
- * Without batching, the UI could render with partially updated state:
974
- * - dataEffect deletes a resource from the store
975
- * - UI renders immediately and tries to display the deleted resource
976
- * - onLoad hasn't yet updated dependent actions to loading state
977
- *
978
- * Example: When deleting a resource, we need to both update the store
979
- * AND put the action that loaded that resource back into loading state
980
- * before the UI attempts to render the now-missing resource.
981
- */
982
- batch(() => {
983
- dataSignal.value = dataEffect
984
- ? dataEffect(runResult, action)
985
- : runResult;
986
- runningStateSignal.value = COMPLETED;
987
- onComplete?.(computedDataSignal.peek(), action);
988
- completeSideEffect?.(action);
989
- });
990
- if (DEBUG) {
991
- console.log(`"${action}": completed`);
992
- }
993
- return computedDataSignal.peek();
994
- };
995
- const onRunError = (e) => {
996
- if (abortSignal) {
997
- abortSignal.removeEventListener("abort", onAbortFromSpecific);
998
- }
999
- if (globalAbortSignal) {
1000
- globalAbortSignal.removeEventListener("abort", onAbortFromGlobal);
1001
- }
1002
- actionAbortMap.delete(action);
1003
- actionPromiseMap.delete(action);
1004
- if (internalAbortSignal.aborted && e === internalAbortSignal.reason) {
1005
- runningStateSignal.value = ABORTED;
1006
- if (isPrerun && abortSignal.aborted) {
1007
- prerunProtectionRegistry.unprotect(action);
1008
- }
1009
- onAbort(e, action);
1010
- return e;
1011
- }
1012
- if (e.name === "AbortError") {
1013
- throw new Error(
1014
- "never supposed to happen, abort error should be handled by the abort signal",
1015
- );
1016
- }
1017
- if (DEBUG) {
1018
- console.log(
1019
- `"${action}": failed (error: ${e}, handled by ui: ${ui.hasRenderers})`,
1020
- );
1021
- }
1022
- batch(() => {
1023
- errorSignal.value = e;
1024
- runningStateSignal.value = FAILED;
1025
- onError?.(e, action);
1026
- });
1027
-
1028
- if (ui.hasRenderers || onError) {
1029
- console.error(e);
1030
- // For UI-bound actions: error is properly handled by logging + UI display
1031
- // Return error instead of throwing to signal it's handled and prevent:
1032
- // - jsenv error overlay from appearing
1033
- // - error being treated as unhandled by runtime
1034
- return e;
1035
- }
1036
- throw e;
1037
- };
1038
-
1039
- try {
1040
- const thenableArray = [];
1041
- const callbackResult = callback(...args);
1042
- if (callbackResult && typeof callbackResult.then === "function") {
1043
- thenableArray.push(
1044
- callbackResult.then(
1045
- (value) => {
1046
- runResult = value;
1047
- },
1048
- (e) => {
1049
- rejected = true;
1050
- rejectedValue = e;
1051
- },
1052
- ),
1053
- );
1054
- } else {
1055
- runResult = callbackResult;
1056
- }
1057
- if (ui.renderLoadedAsync && !ui.renderLoaded) {
1058
- const renderLoadedPromise = ui.renderLoadedAsync(...args).then(
1059
- (renderLoaded) => {
1060
- ui.renderLoaded = renderLoaded;
1061
- },
1062
- (e) => {
1063
- if (!rejected) {
1064
- rejected = true;
1065
- rejectedValue = e;
1066
- }
1067
- },
1068
- );
1069
- thenableArray.push(renderLoadedPromise);
1070
- }
1071
- if (thenableArray.length === 0) {
1072
- return onRunEnd();
1073
- }
1074
- return Promise.all(thenableArray).then(() => {
1075
- if (rejected) {
1076
- return onRunError(rejectedValue);
1077
- }
1078
- return onRunEnd();
1079
- });
1080
- } catch (e) {
1081
- return onRunError(e);
1082
- }
1083
- };
1084
-
1085
- const performStop = ({ reason }) => {
1086
- abort(reason);
1087
- if (DEBUG) {
1088
- console.log(`"${action}": stopping (reason: ${reason})`);
1089
- }
1090
-
1091
- prerunProtectionRegistry.unprotect(action);
1092
-
1093
- if (sideEffectCleanup) {
1094
- sideEffectCleanup(reason);
1095
- sideEffectCleanup = undefined;
1096
- }
1097
-
1098
- actionPromiseMap.delete(action);
1099
- batch(() => {
1100
- errorSignal.value = null;
1101
- if (!keepOldData) {
1102
- dataSignal.value = initialData;
1103
- }
1104
- isPrerunSignal.value = true;
1105
- runningStateSignal.value = IDLE;
1106
- });
1107
- };
1108
-
1109
- const privateProperties = {
1110
- initialData,
1111
-
1112
- paramsSignal,
1113
- runningStateSignal,
1114
- isPrerunSignal,
1115
- dataSignal,
1116
- computedDataSignal,
1117
- errorSignal,
1118
-
1119
- performRun,
1120
- performStop,
1121
- ui,
1122
-
1123
- childActionWeakSet,
1124
- childActionWeakMap,
1125
- };
1126
- setActionPrivateProperties(action, privateProperties);
1127
- }
1128
-
1129
- return action;
1130
- };
1131
-
1132
- rootAction = createActionCore(rootOptions);
1133
- actionWeakMap.set(callback, rootAction);
1134
- return rootAction;
1135
- };
1136
-
1137
- const createActionProxyFromSignal = (
1138
- action,
1139
- paramsSignal,
1140
- { rerunOnChange = false, onChange } = {},
1141
- ) => {
1142
- const actionTargetChangeCallbackSet = new Set();
1143
- const onActionTargetChange = (callback) => {
1144
- actionTargetChangeCallbackSet.add(callback);
1145
- return () => {
1146
- actionTargetChangeCallbackSet.delete(callback);
1147
- };
1148
- };
1149
- const changeCleanupCallbackSet = new Set();
1150
- const triggerTargetChange = (actionTarget, previousTarget) => {
1151
- for (const changeCleanupCallback of changeCleanupCallbackSet) {
1152
- changeCleanupCallback();
1153
- }
1154
- changeCleanupCallbackSet.clear();
1155
- for (const callback of actionTargetChangeCallbackSet) {
1156
- const returnValue = callback(actionTarget, previousTarget);
1157
- if (typeof returnValue === "function") {
1158
- changeCleanupCallbackSet.add(returnValue);
1159
- }
1160
- }
1161
- };
1162
-
1163
- let actionTarget = null;
1164
- let currentAction = action;
1165
- let currentActionPrivateProperties = getActionPrivateProperties(action);
1166
- let actionTargetPreviousWeakRef = null;
1167
- let isFirstEffect = true;
1168
-
1169
- const _updateTarget = (params) => {
1170
- const previousActionTarget = actionTargetPreviousWeakRef?.deref();
1171
-
1172
- if (params === NO_PARAMS) {
1173
- actionTarget = null;
1174
- currentAction = action;
1175
- currentActionPrivateProperties = getActionPrivateProperties(action);
1176
- } else {
1177
- actionTarget = action.bindParams(params);
1178
- if (previousActionTarget === actionTarget) {
1179
- return;
1180
- }
1181
- currentAction = actionTarget;
1182
- currentActionPrivateProperties = getActionPrivateProperties(actionTarget);
1183
- }
1184
-
1185
- if (isFirstEffect) {
1186
- isFirstEffect = false;
1187
- }
1188
- actionTargetPreviousWeakRef = actionTarget
1189
- ? new WeakRef(actionTarget)
1190
- : null;
1191
- triggerTargetChange(actionTarget, previousActionTarget);
1192
- };
1193
-
1194
- const proxyMethod = (method) => {
1195
- return (...args) => {
1196
- /*
1197
- * Ensure the proxy targets the correct action before method execution.
1198
- * This prevents race conditions where external effects run before our
1199
- * internal parameter synchronization effect. Using peek() avoids creating
1200
- * reactive dependencies within this pass-through method.
1201
- */
1202
- _updateTarget(proxyParamsSignal.peek());
1203
- return currentAction[method](...args);
1204
- };
1205
- };
1206
-
1207
- const nameSignal = signal();
1208
- let actionProxy;
1209
- if (ACTION_AS_FUNCTION) {
1210
- actionProxy = function actionProxyFunction() {
1211
- return actionProxy.rerun();
1212
- };
1213
- Object.defineProperty(actionProxy, "name", {
1214
- configurable: true,
1215
- get() {
1216
- return nameSignal.value;
1217
- },
1218
- });
1219
- } else {
1220
- actionProxy = {
1221
- get name() {
1222
- return nameSignal.value;
1223
- },
1224
- };
1225
- }
1226
- Object.assign(actionProxy, {
1227
- isProxy: true,
1228
- callback: undefined,
1229
- params: undefined,
1230
- isPrerun: undefined,
1231
- runningState: undefined,
1232
- aborted: undefined,
1233
- error: undefined,
1234
- data: undefined,
1235
- computedData: undefined,
1236
- completed: undefined,
1237
- prerun: proxyMethod("prerun"),
1238
- run: proxyMethod("run"),
1239
- rerun: proxyMethod("rerun"),
1240
- stop: proxyMethod("stop"),
1241
- abort: proxyMethod("abort"),
1242
- matchAllSelfOrDescendant: proxyMethod("matchAllSelfOrDescendant"),
1243
- getCurrentAction: () => {
1244
- _updateTarget(proxyParamsSignal.peek());
1245
- return currentAction;
1246
- },
1247
- bindParams: () => {
1248
- throw new Error(
1249
- `bindParams() is not supported on action proxies, use the underlying action instead`,
1250
- );
1251
- },
1252
- replaceParams: null, // Will be set below
1253
- toString: () => actionProxy.name,
1254
- meta: {},
1255
- });
1256
- Object.preventExtensions(actionProxy);
1257
-
1258
- onActionTargetChange((actionTarget) => {
1259
- const currentAction = actionTarget || action;
1260
- nameSignal.value = `[Proxy] ${currentAction.name}`;
1261
- actionProxy.callback = currentAction.callback;
1262
- actionProxy.params = currentAction.params;
1263
- actionProxy.isPrerun = currentAction.isPrerun;
1264
- actionProxy.runningState = currentAction.runningState;
1265
- actionProxy.aborted = currentAction.aborted;
1266
- actionProxy.error = currentAction.error;
1267
- actionProxy.data = currentAction.data;
1268
- actionProxy.computedData = currentAction.computedData;
1269
- actionProxy.completed = currentAction.completed;
1270
- });
1271
-
1272
- const proxyPrivateSignal = (signalPropertyName, propertyName) => {
1273
- const signalProxy = signal();
1274
- let dispose;
1275
- onActionTargetChange(() => {
1276
- if (dispose) {
1277
- dispose();
1278
- dispose = undefined;
1279
- }
1280
- dispose = effect(() => {
1281
- const currentActionSignal =
1282
- currentActionPrivateProperties[signalPropertyName];
1283
- const currentActionSignalValue = currentActionSignal.value;
1284
- signalProxy.value = currentActionSignalValue;
1285
- if (propertyName) {
1286
- actionProxy[propertyName] = currentActionSignalValue;
1287
- }
1288
- });
1289
- return dispose;
1290
- });
1291
- return signalProxy;
1292
- };
1293
- const proxyPrivateMethod = (method) => {
1294
- return (...args) => currentActionPrivateProperties[method](...args);
1295
- };
1296
-
1297
- // Create our own signal for params that we control completely
1298
- const proxyParamsSignal = signal(paramsSignal.value);
1299
-
1300
- // Watch for changes in the original paramsSignal and update ours
1301
- // (original signal wins over any replaceParams calls)
1302
- weakEffect(
1303
- [paramsSignal, proxyParamsSignal],
1304
- (paramsSignalRef, proxyParamsSignalRef) => {
1305
- proxyParamsSignalRef.value = paramsSignalRef.value;
1306
- },
1307
- );
1308
-
1309
- const proxyPrivateProperties = {
1310
- get currentAction() {
1311
- return currentAction;
1312
- },
1313
- paramsSignal: proxyParamsSignal,
1314
- isPrerunSignal: proxyPrivateSignal("isPrerunSignal", "isPrerun"),
1315
- runningStateSignal: proxyPrivateSignal(
1316
- "runningStateSignal",
1317
- "runningState",
1318
- ),
1319
- errorSignal: proxyPrivateSignal("errorSignal", "error"),
1320
- dataSignal: proxyPrivateSignal("dataSignal", "data"),
1321
- computedDataSignal: proxyPrivateSignal("computedDataSignal"),
1322
- performRun: proxyPrivateMethod("performRun"),
1323
- performStop: proxyPrivateMethod("performStop"),
1324
- ui: currentActionPrivateProperties.ui,
1325
- };
1326
-
1327
- onActionTargetChange((actionTarget, previousTarget) => {
1328
- proxyPrivateProperties.ui = currentActionPrivateProperties.ui;
1329
- if (previousTarget && actionTarget) {
1330
- const previousPrivateProps = getActionPrivateProperties(previousTarget);
1331
- if (previousPrivateProps.ui.hasRenderers) {
1332
- const newPrivateProps = getActionPrivateProperties(actionTarget);
1333
- newPrivateProps.ui.hasRenderers = true;
1334
- }
1335
- }
1336
- proxyPrivateProperties.childActionWeakSet =
1337
- currentActionPrivateProperties.childActionWeakSet;
1338
- });
1339
- setActionPrivateProperties(actionProxy, proxyPrivateProperties);
1340
-
1341
- {
1342
- weakEffect([action], () => {
1343
- const params = proxyParamsSignal.value;
1344
- _updateTarget(params);
1345
- });
1346
- }
1347
-
1348
- actionProxy.replaceParams = (newParams) => {
1349
- if (currentAction === action) {
1350
- const currentParams = proxyParamsSignal.value;
1351
- const nextParams = mergeTwoJsValues(currentParams, newParams);
1352
- if (nextParams === currentParams) {
1353
- return false;
1354
- }
1355
- proxyParamsSignal.value = nextParams;
1356
- return true;
1357
- }
1358
- if (!currentAction.replaceParams(newParams)) {
1359
- return false;
1360
- }
1361
- proxyParamsSignal.value =
1362
- currentActionPrivateProperties.paramsSignal.peek();
1363
- return true;
1364
- };
1365
-
1366
- if (rerunOnChange) {
1367
- onActionTargetChange((actionTarget, actionTargetPrevious) => {
1368
- if (
1369
- actionTarget &&
1370
- actionTargetPrevious &&
1371
- !actionTargetPrevious.isPrerun
1372
- ) {
1373
- actionTarget.rerun();
1374
- }
1375
- });
1376
- }
1377
- if (onChange) {
1378
- onActionTargetChange((actionTarget, actionTargetPrevious) => {
1379
- onChange(actionTarget, actionTargetPrevious);
1380
- });
1381
- }
1382
-
1383
- return actionProxy;
1384
- };
1385
-
1386
- const generateActionName = (name, params) => {
1387
- if (params === NO_PARAMS) {
1388
- return `${name}({})`;
1389
- }
1390
- // Use stringifyForDisplay with asFunctionArgs option for the entire args array
1391
- const argsString = stringifyForDisplay([params], 3, 0, {
1392
- asFunctionArgs: true,
1393
- });
1394
- return `${name}${argsString}`;
1395
- };
1396
-
1397
- if (import.meta.hot) {
1398
- import.meta.hot.dispose(() => {
1399
- abortRunningActions();
1400
- });
1401
- }