@jsenv/navi 0.0.1 → 0.1.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 (138) hide show
  1. package/dist/jsenv_navi.js +22954 -0
  2. package/index.js +66 -16
  3. package/package.json +22 -11
  4. package/src/actions.js +50 -26
  5. package/src/browser_integration/browser_integration.js +31 -6
  6. package/src/browser_integration/via_history.js +42 -9
  7. package/src/components/action_execution/render_actionable_component.jsx +6 -4
  8. package/src/components/action_execution/use_action.js +51 -282
  9. package/src/components/action_execution/use_execute_action.js +106 -92
  10. package/src/components/action_execution/use_run_on_mount.js +9 -0
  11. package/src/components/action_renderer.jsx +21 -32
  12. package/src/components/demos/0_button_demo.html +574 -103
  13. package/src/components/demos/10_column_reordering_debug.html +277 -0
  14. package/src/components/demos/11_table_selection_debug.html +432 -0
  15. package/src/components/demos/1_checkbox_demo.html +579 -202
  16. package/src/components/demos/2_input_textual_demo.html +81 -138
  17. package/src/components/demos/3_radio_demo.html +0 -2
  18. package/src/components/demos/4_select_demo.html +19 -23
  19. package/src/components/demos/6_tablist_demo.html +77 -0
  20. package/src/components/demos/7_table_selection_demo.html +176 -0
  21. package/src/components/demos/8_table_fixed_headers_demo.html +584 -0
  22. package/src/components/demos/9_table_column_drag_demo.html +325 -0
  23. package/src/components/demos/action/0_button_demo.html +2 -4
  24. package/src/components/demos/action/1_input_text_demo.html +643 -222
  25. package/src/components/demos/action/3_details_demo.html +146 -115
  26. package/src/components/demos/action/4_input_checkbox_demo.html +442 -322
  27. package/src/components/demos/action/5_input_checkbox_state_demo.html +270 -0
  28. package/src/components/demos/action/6_checkbox_list_demo.html +304 -72
  29. package/src/components/demos/action/7_radio_list_demo.html +310 -170
  30. package/src/components/demos/action/{8_editable_text_demo.html → 8_editable_demo.html} +65 -76
  31. package/src/components/demos/action/9_link_demo.html +84 -62
  32. package/src/components/demos/ui_transition/0_action_renderer_ui_transition_demo.html +695 -0
  33. package/src/components/demos/ui_transition/1_nested_ui_transition_demo.html +429 -0
  34. package/src/components/demos/ui_transition/2_height_transition_test.html +295 -0
  35. package/src/components/details/details.jsx +62 -64
  36. package/src/components/edition/editable.jsx +186 -0
  37. package/src/components/field/README.md +247 -0
  38. package/src/components/{input → field}/button.jsx +151 -130
  39. package/src/components/field/checkbox_list.jsx +184 -0
  40. package/src/components/{collect_form_element_values.js → field/collect_form_element_values.js} +7 -4
  41. package/src/components/{input → field}/field_css.js +4 -1
  42. package/src/components/field/form.jsx +211 -0
  43. package/src/components/{input → field}/input.jsx +1 -0
  44. package/src/components/{input → field}/input_checkbox.jsx +132 -155
  45. package/src/components/{input → field}/input_radio.jsx +135 -46
  46. package/src/components/{input → field}/input_textual.jsx +247 -173
  47. package/src/components/field/label.jsx +32 -0
  48. package/src/components/field/radio_list.jsx +182 -0
  49. package/src/components/{input → field}/select.jsx +17 -32
  50. package/src/components/field/use_action_events.js +132 -0
  51. package/src/components/field/use_form_events.js +55 -0
  52. package/src/components/field/use_ui_state_controller.js +506 -0
  53. package/src/components/item_tracker/README.md +461 -0
  54. package/src/components/item_tracker/use_isolated_item_tracker.jsx +209 -0
  55. package/src/components/item_tracker/use_isolated_item_tracker_demo.html +148 -0
  56. package/src/components/item_tracker/use_isolated_item_tracker_demo.jsx +460 -0
  57. package/src/components/item_tracker/use_item_tracker.jsx +143 -0
  58. package/src/components/item_tracker/use_item_tracker_demo.html +207 -0
  59. package/src/components/item_tracker/use_item_tracker_demo.jsx +216 -0
  60. package/src/components/keyboard_shortcuts/active_keyboard_shortcuts.jsx +87 -0
  61. package/src/components/keyboard_shortcuts/aria_key_shortcuts.js +61 -0
  62. package/src/components/keyboard_shortcuts/keyboard_key_meta.js +17 -0
  63. package/src/components/keyboard_shortcuts/keyboard_shortcuts.js +371 -0
  64. package/src/components/link/link.jsx +65 -102
  65. package/src/components/link/link_with_icon.jsx +52 -0
  66. package/src/components/loader/loader_background.jsx +85 -64
  67. package/src/components/loader/rectangle_loading.jsx +38 -19
  68. package/src/components/route.jsx +8 -4
  69. package/src/components/selection/selection.jsx +1583 -0
  70. package/src/components/svg/font_sized_svg.jsx +45 -0
  71. package/src/components/svg/icon_and_text.jsx +21 -0
  72. package/src/components/svg/svg_mask_overlay.jsx +105 -0
  73. package/src/components/table/drag/table_drag.jsx +506 -0
  74. package/src/components/table/resize/table_resize.jsx +650 -0
  75. package/src/components/table/resize/table_size.js +43 -0
  76. package/src/components/table/selection/table_selection.js +106 -0
  77. package/src/components/table/selection/table_selection.jsx +203 -0
  78. package/src/components/table/sticky/sticky_group.js +354 -0
  79. package/src/components/table/sticky/table_sticky.js +25 -0
  80. package/src/components/table/sticky/table_sticky.jsx +501 -0
  81. package/src/components/table/table.jsx +721 -0
  82. package/src/components/table/table_css.js +211 -0
  83. package/src/components/table/table_ui.jsx +49 -0
  84. package/src/components/table/use_cells_and_columns.js +90 -0
  85. package/src/components/table/use_object_array_to_cells.js +46 -0
  86. package/src/components/table/z_indexes.js +23 -0
  87. package/src/components/tablist/tablist.jsx +99 -0
  88. package/src/components/text/overflow.jsx +15 -0
  89. package/src/components/text/text_and_count.jsx +28 -0
  90. package/src/components/ui_transition.jsx +128 -0
  91. package/src/components/use_auto_focus.js +58 -7
  92. package/src/components/use_batch_during_render.js +33 -0
  93. package/src/components/use_debounce_true.js +7 -7
  94. package/src/components/use_dependencies_diff.js +35 -0
  95. package/src/components/use_focus_group.js +4 -3
  96. package/src/components/use_initial_value.js +8 -34
  97. package/src/components/use_signal_sync.js +1 -1
  98. package/src/components/use_stable_callback.js +68 -0
  99. package/src/components/use_state_array.js +16 -9
  100. package/src/docs/actions.md +22 -0
  101. package/src/notes.md +33 -12
  102. package/src/route/route.js +97 -47
  103. package/src/store/resource_graph.js +2 -1
  104. package/src/store/tests/{resource_graph_dependencies.test.js → resource_graph_dependencies.test_manual.js} +13 -13
  105. package/src/utils/is_signal.js +20 -0
  106. package/src/utils/stringify_for_display.js +4 -23
  107. package/src/validation/constraints/confirm_constraint.js +14 -0
  108. package/src/validation/constraints/create_unique_value_constraint.js +27 -0
  109. package/src/validation/constraints/native_constraints.js +313 -0
  110. package/src/validation/constraints/readonly_constraint.js +36 -0
  111. package/src/validation/constraints/single_space_constraint.js +13 -0
  112. package/src/validation/custom_constraint_validation.js +599 -0
  113. package/src/validation/custom_message.js +18 -0
  114. package/src/validation/demos/browser_style.png +0 -0
  115. package/src/validation/demos/form_validation_demo.html +142 -0
  116. package/src/validation/demos/form_validation_demo_preact.html +87 -0
  117. package/src/validation/demos/form_validation_native_popover_demo.html +168 -0
  118. package/src/validation/demos/form_validation_vs_native_demo.html +172 -0
  119. package/src/validation/demos/validation_message_demo.html +203 -0
  120. package/src/validation/hooks/use_constraints.js +23 -0
  121. package/src/validation/hooks/use_custom_validation_ref.js +73 -0
  122. package/src/validation/hooks/use_validation_message.js +19 -0
  123. package/src/validation/validation_message.js +741 -0
  124. package/src/components/editable_text/editable_text.jsx +0 -96
  125. package/src/components/form.jsx +0 -144
  126. package/src/components/input/checkbox_list.jsx +0 -294
  127. package/src/components/input/field.jsx +0 -61
  128. package/src/components/input/radio_list.jsx +0 -283
  129. package/src/components/input/use_form_event.js +0 -20
  130. package/src/components/input/use_on_change.js +0 -12
  131. package/src/components/selection/selection.js +0 -5
  132. package/src/components/selection/selection_context.jsx +0 -262
  133. package/src/components/shortcut/shortcut_context.jsx +0 -390
  134. package/src/components/use_action_events.js +0 -37
  135. package/src/utils/iterable_weak_set.js +0 -62
  136. /package/src/components/demos/action/{11_nested_shortcuts_demo.html → 11_nested_shortcuts_demo.xhtml} +0 -0
  137. /package/src/components/{shortcut → keyboard_shortcuts}/os.js +0 -0
  138. /package/src/route/{route.test.html → route.xtest.html} +0 -0
@@ -0,0 +1,182 @@
1
+ import { forwardRef } from "preact/compat";
2
+ import {
3
+ useContext,
4
+ useImperativeHandle,
5
+ useRef,
6
+ useState,
7
+ } from "preact/hooks";
8
+
9
+ import { useActionStatus } from "../../use_action_status.js";
10
+ import { requestAction } from "../../validation/custom_constraint_validation.js";
11
+ import { renderActionableComponent } from "../action_execution/render_actionable_component.jsx";
12
+ import { useActionBoundToOneParam } from "../action_execution/use_action.js";
13
+ import { useExecuteAction } from "../action_execution/use_execute_action.js";
14
+ import { InputRadio } from "./input_radio.jsx";
15
+ import { useActionEvents } from "./use_action_events.js";
16
+ import {
17
+ DisabledContext,
18
+ FieldNameContext,
19
+ LoadingContext,
20
+ LoadingElementContext,
21
+ ParentUIStateControllerContext,
22
+ ReadOnlyContext,
23
+ RequiredContext,
24
+ UIStateContext,
25
+ UIStateControllerContext,
26
+ useUIGroupStateController,
27
+ useUIState,
28
+ } from "./use_ui_state_controller.js";
29
+
30
+ import.meta.css = /* css */ `
31
+ .navi_radio_list {
32
+ display: flex;
33
+ flex-direction: column;
34
+ }
35
+ `;
36
+
37
+ export const RadioList = forwardRef((props, ref) => {
38
+ const uiStateController = useUIGroupStateController(props, "radio_list", {
39
+ childComponentType: "radio",
40
+ aggregateChildStates: (childUIStateControllers) => {
41
+ let activeValue;
42
+ for (const childUIStateController of childUIStateControllers) {
43
+ if (childUIStateController.uiState) {
44
+ activeValue = childUIStateController.uiState;
45
+ break;
46
+ }
47
+ }
48
+ return activeValue;
49
+ },
50
+ });
51
+ const uiState = useUIState(uiStateController);
52
+ const radioList = renderActionableComponent(props, ref, {
53
+ Basic: RadioListBasic,
54
+ WithAction: RadioListWithAction,
55
+ InsideForm: RadioListInsideForm,
56
+ });
57
+ return (
58
+ <UIStateControllerContext.Provider value={uiStateController}>
59
+ <UIStateContext.Provider value={uiState}>
60
+ {radioList}
61
+ </UIStateContext.Provider>
62
+ </UIStateControllerContext.Provider>
63
+ );
64
+ });
65
+ export const Radio = InputRadio;
66
+
67
+ const RadioListBasic = forwardRef((props, ref) => {
68
+ const contextReadOnly = useContext(ReadOnlyContext);
69
+ const contextDisabled = useContext(DisabledContext);
70
+ const contextLoading = useContext(LoadingContext);
71
+ const uiStateController = useContext(UIStateControllerContext);
72
+ const { name, loading, disabled, readOnly, children, required, ...rest } =
73
+ props;
74
+ const innerRef = useRef();
75
+ useImperativeHandle(ref, () => innerRef.current);
76
+
77
+ const innerLoading = loading || contextLoading;
78
+ const innerReadOnly =
79
+ readOnly || contextReadOnly || innerLoading || uiStateController.readOnly;
80
+ const innerDisabled = disabled || contextDisabled;
81
+
82
+ return (
83
+ <div
84
+ data-action={rest["data-action"]}
85
+ {...rest}
86
+ ref={innerRef}
87
+ className="navi_radio_list"
88
+ data-radio-list
89
+ // eslint-disable-next-line react/no-unknown-property
90
+ onresetuistate={(e) => {
91
+ uiStateController.resetUIState(e);
92
+ }}
93
+ >
94
+ <ParentUIStateControllerContext.Provider value={uiStateController}>
95
+ <FieldNameContext.Provider value={name}>
96
+ <ReadOnlyContext.Provider value={innerReadOnly}>
97
+ <DisabledContext.Provider value={innerDisabled}>
98
+ <RequiredContext.Provider value={required}>
99
+ <LoadingContext.Provider value={innerLoading}>
100
+ {children}
101
+ </LoadingContext.Provider>
102
+ </RequiredContext.Provider>
103
+ </DisabledContext.Provider>
104
+ </ReadOnlyContext.Provider>
105
+ </FieldNameContext.Provider>
106
+ </ParentUIStateControllerContext.Provider>
107
+ </div>
108
+ );
109
+ });
110
+ const RadioListWithAction = forwardRef((props, ref) => {
111
+ const uiStateController = useContext(UIStateControllerContext);
112
+ const uiState = useContext(UIStateContext);
113
+ const {
114
+ action,
115
+
116
+ onCancel,
117
+ onActionPrevented,
118
+ onActionStart,
119
+ onActionAbort,
120
+ onActionError,
121
+ onActionEnd,
122
+ actionErrorEffect,
123
+ loading,
124
+ children,
125
+ ...rest
126
+ } = props;
127
+ const innerRef = useRef();
128
+ useImperativeHandle(ref, () => innerRef.current);
129
+ const [boundAction] = useActionBoundToOneParam(action, uiState);
130
+ const { loading: actionLoading } = useActionStatus(boundAction);
131
+ const executeAction = useExecuteAction(innerRef, {
132
+ errorEffect: actionErrorEffect,
133
+ });
134
+ const [actionRequester, setActionRequester] = useState(null);
135
+
136
+ useActionEvents(innerRef, {
137
+ onCancel: (e, reason) => {
138
+ uiStateController.resetUIState(e);
139
+ onCancel?.(e, reason);
140
+ },
141
+ onPrevented: onActionPrevented,
142
+ onAction: (actionEvent) => {
143
+ setActionRequester(actionEvent.detail.requester);
144
+ executeAction(actionEvent);
145
+ },
146
+ onStart: onActionStart,
147
+ onAbort: (e) => {
148
+ uiStateController.resetUIState(e);
149
+ onActionAbort?.(e);
150
+ },
151
+ onError: (e) => {
152
+ uiStateController.resetUIState(e);
153
+ onActionError?.(e);
154
+ },
155
+ onEnd: (e) => {
156
+ onActionEnd?.(e);
157
+ },
158
+ });
159
+
160
+ return (
161
+ <RadioListBasic
162
+ data-action={boundAction}
163
+ {...rest}
164
+ ref={innerRef}
165
+ onChange={(e) => {
166
+ const radio = e.target;
167
+ const radioListContainer = innerRef.current;
168
+ requestAction(radioListContainer, boundAction, {
169
+ event: e,
170
+ requester: radio,
171
+ actionOrigin: "action_prop",
172
+ });
173
+ }}
174
+ loading={loading || actionLoading}
175
+ >
176
+ <LoadingElementContext.Provider value={actionRequester}>
177
+ {children}
178
+ </LoadingElementContext.Provider>
179
+ </RadioListBasic>
180
+ );
181
+ });
182
+ const RadioListInsideForm = RadioListBasic;
@@ -1,18 +1,16 @@
1
- import { requestAction } from "@jsenv/validation";
2
1
  import { forwardRef } from "preact/compat";
3
2
  import { useEffect, useImperativeHandle, useRef, useState } from "preact/hooks";
3
+
4
4
  import { useNavState } from "../../browser_integration/browser_integration.js";
5
5
  import { useActionStatus } from "../../use_action_status.js";
6
+ import { requestAction } from "../../validation/custom_constraint_validation.js";
6
7
  import { renderActionableComponent } from "../action_execution/render_actionable_component.jsx";
7
- import {
8
- useActionBoundToOneParam,
9
- useOneFormParam,
10
- } from "../action_execution/use_action.js";
8
+ import { useActionBoundToOneParam } from "../action_execution/use_action.js";
11
9
  import { useExecuteAction } from "../action_execution/use_execute_action.js";
12
10
  import { LoaderBackground } from "../loader/loader_background.jsx";
13
- import { useActionEvents } from "../use_action_events.js";
14
11
  import { useRefArray } from "../use_ref_array.js";
15
- import { useFormEvents } from "./use_form_event.js";
12
+ import { useActionEvents } from "./use_action_events.js";
13
+ import { useFormEvents } from "./use_form_events.js";
16
14
 
17
15
  import.meta.css = /* css */ `
18
16
  .navi_select[data-readonly] {
@@ -21,11 +19,12 @@ import.meta.css = /* css */ `
21
19
  `;
22
20
 
23
21
  export const Select = forwardRef((props, ref) => {
24
- return renderActionableComponent(props, ref, {
22
+ const select = renderActionableComponent(props, ref, {
25
23
  Basic: SelectBasic,
26
24
  WithAction: SelectWithAction,
27
25
  InsideForm: SelectInsideForm,
28
26
  });
27
+ return select;
29
28
  });
30
29
 
31
30
  const SelectControlled = forwardRef((props, ref) => {
@@ -137,7 +136,7 @@ const SelectWithAction = forwardRef((props, ref) => {
137
136
  useImperativeHandle(ref, () => innerRef.current);
138
137
 
139
138
  const [navState, setNavState, resetNavState] = useNavState(id);
140
- const [boundAction, value, setValue, resetValue] = useActionBoundToOneParam(
139
+ const [boundAction, value, setValue, initialValue] = useActionBoundToOneParam(
141
140
  action,
142
141
  name,
143
142
  valueSignal ? valueSignal : externalValue,
@@ -155,7 +154,7 @@ const SelectWithAction = forwardRef((props, ref) => {
155
154
  useActionEvents(innerRef, {
156
155
  onCancel: (e, reason) => {
157
156
  resetNavState();
158
- resetValue();
157
+ setValue(initialValue);
159
158
  onCancel?.(e, reason);
160
159
  },
161
160
  onPrevented: onActionPrevented,
@@ -165,11 +164,11 @@ const SelectWithAction = forwardRef((props, ref) => {
165
164
  },
166
165
  onStart: onActionStart,
167
166
  onAbort: (e) => {
168
- resetValue();
167
+ setValue(initialValue);
169
168
  onActionAbort?.(e);
170
169
  },
171
170
  onError: (error) => {
172
- resetValue();
171
+ setValue(initialValue);
173
172
  onActionError?.(error);
174
173
  },
175
174
  onEnd: () => {
@@ -194,10 +193,10 @@ const SelectWithAction = forwardRef((props, ref) => {
194
193
  const optionSelected = select.querySelector(
195
194
  `option[value="${selectedValue}"]`,
196
195
  );
197
- requestAction(boundAction, {
196
+ requestAction(radioListContainer, boundAction, {
198
197
  event,
199
- target: radioListContainer,
200
198
  requester: optionSelected,
199
+ actionOrigin: "action_prop",
201
200
  });
202
201
  }}
203
202
  {...rest}
@@ -218,26 +217,13 @@ const SelectWithAction = forwardRef((props, ref) => {
218
217
  });
219
218
 
220
219
  const SelectInsideForm = forwardRef((props, ref) => {
221
- const {
222
- formContext,
223
- id,
224
- name,
225
- readOnly,
226
- value: externalValue,
227
- children,
228
- ...rest
229
- } = props;
230
- const { formIsReadOnly } = formContext;
220
+ const { id, name, value: externalValue, children, ...rest } = props;
231
221
 
232
222
  const innerRef = useRef();
233
223
  useImperativeHandle(ref, () => innerRef.current);
234
224
 
235
225
  const [navState, setNavState] = useNavState(id);
236
- const [value, setValue, resetValue] = useOneFormParam(
237
- name,
238
- externalValue,
239
- navState,
240
- );
226
+ const [value, setValue, initialValue] = [name, externalValue, navState];
241
227
  useEffect(() => {
242
228
  setNavState(value);
243
229
  }, [value]);
@@ -247,10 +233,10 @@ const SelectInsideForm = forwardRef((props, ref) => {
247
233
  setValue(undefined);
248
234
  },
249
235
  onFormActionAbort: () => {
250
- resetValue();
236
+ setValue(initialValue);
251
237
  },
252
238
  onFormActionError: () => {
253
- resetValue();
239
+ setValue(initialValue);
254
240
  },
255
241
  });
256
242
 
@@ -259,7 +245,6 @@ const SelectInsideForm = forwardRef((props, ref) => {
259
245
  ref={innerRef}
260
246
  name={name}
261
247
  value={value}
262
- readOnly={readOnly || formIsReadOnly}
263
248
  onChange={(event) => {
264
249
  const select = event.target;
265
250
  const selectedValue = select.checked;
@@ -0,0 +1,132 @@
1
+ import { useLayoutEffect, useState } from "preact/hooks";
2
+
3
+ import { addManyEventListeners } from "../../utils/add_many_event_listeners.js";
4
+ import { useStableCallback } from "../use_stable_callback.js";
5
+
6
+ export const useActionEvents = (
7
+ elementRef,
8
+ {
9
+ actionOrigin = "action_prop",
10
+ /**
11
+ * @param {Event} e - L'événement original
12
+ * @param {"form_reset" | "blur_invalid" | "escape_key"} reason - Raison du cancel
13
+ */
14
+ onCancel,
15
+ onRequested,
16
+ onPrevented,
17
+ onAction,
18
+ onStart,
19
+ onAbort,
20
+ onError,
21
+ onEnd,
22
+ },
23
+ ) => {
24
+ onCancel = useStableCallback(onCancel);
25
+ onRequested = useStableCallback(onRequested);
26
+ onPrevented = useStableCallback(onPrevented);
27
+ onAction = useStableCallback(onAction);
28
+ onStart = useStableCallback(onStart);
29
+ onAbort = useStableCallback(onAbort);
30
+ onError = useStableCallback(onError);
31
+ onEnd = useStableCallback(onEnd);
32
+
33
+ useLayoutEffect(() => {
34
+ const element = elementRef.current;
35
+ if (!element) {
36
+ return null;
37
+ }
38
+
39
+ return addManyEventListeners(element, {
40
+ cancel: (e) => {
41
+ // cancel don't need to check for actionOrigin because
42
+ // it's actually unrelated to a specific actions
43
+ // in that sense it should likely be moved elsewhere as it's related to
44
+ // interaction and constraint validation, not to a specific action
45
+ onCancel?.(e, e.detail.reason);
46
+ },
47
+ actionrequested: (e) => {
48
+ if (e.detail.actionOrigin !== actionOrigin) {
49
+ return;
50
+ }
51
+ onRequested?.(e);
52
+ },
53
+ actionprevented: (e) => {
54
+ if (e.detail.actionOrigin !== actionOrigin) {
55
+ return;
56
+ }
57
+ onPrevented?.(e);
58
+ },
59
+ action: (e) => {
60
+ if (e.detail.actionOrigin !== actionOrigin) {
61
+ return;
62
+ }
63
+ onAction?.(e);
64
+ },
65
+ actionstart: (e) => {
66
+ if (e.detail.actionOrigin !== actionOrigin) {
67
+ return;
68
+ }
69
+ onStart?.(e);
70
+ },
71
+ actionabort: (e) => {
72
+ if (e.detail.actionOrigin !== actionOrigin) {
73
+ return;
74
+ }
75
+ onAbort?.(e);
76
+ },
77
+ actionerror: (e) => {
78
+ if (e.detail.actionOrigin !== actionOrigin) {
79
+ return;
80
+ }
81
+ onError?.(e.detail.error, e);
82
+ },
83
+ actionend: onEnd,
84
+ });
85
+ }, [
86
+ actionOrigin,
87
+ onCancel,
88
+ onRequested,
89
+ onPrevented,
90
+ onAction,
91
+ onStart,
92
+ onAbort,
93
+ onError,
94
+ onEnd,
95
+ ]);
96
+ };
97
+
98
+ export const useRequestedActionStatus = (elementRef) => {
99
+ const [actionRequester, setActionRequester] = useState(null);
100
+ const [actionPending, setActionPending] = useState(false);
101
+ const [actionAborted, setActionAborted] = useState(false);
102
+ const [actionError, setActionError] = useState(null);
103
+
104
+ useActionEvents(elementRef, {
105
+ onAction: (actionEvent) => {
106
+ setActionRequester(actionEvent.detail.requester);
107
+ },
108
+ onStart: () => {
109
+ setActionPending(true);
110
+ setActionAborted(false);
111
+ setActionError(null);
112
+ },
113
+ onAbort: () => {
114
+ setActionPending(false);
115
+ setActionAborted(true);
116
+ },
117
+ onError: (error) => {
118
+ setActionPending(false);
119
+ setActionError(error);
120
+ },
121
+ onEnd: () => {
122
+ setActionPending(false);
123
+ },
124
+ });
125
+
126
+ return {
127
+ actionRequester,
128
+ actionPending,
129
+ actionAborted,
130
+ actionError,
131
+ };
132
+ };
@@ -0,0 +1,55 @@
1
+ import { useLayoutEffect } from "preact/hooks";
2
+
3
+ import { addManyEventListeners } from "../../utils/add_many_event_listeners.js";
4
+ import { useStableCallback } from "../use_stable_callback.js";
5
+
6
+ export const useFormEvents = (
7
+ elementRef,
8
+ {
9
+ onFormReset,
10
+ onFormActionPrevented,
11
+ onFormActionStart,
12
+ onFormActionAbort,
13
+ onFormActionError,
14
+ onFormActionEnd,
15
+ },
16
+ ) => {
17
+ onFormReset = useStableCallback(onFormReset);
18
+ onFormActionPrevented = useStableCallback(onFormActionPrevented);
19
+ onFormActionStart = useStableCallback(onFormActionStart);
20
+ onFormActionAbort = useStableCallback(onFormActionAbort);
21
+ onFormActionError = useStableCallback(onFormActionError);
22
+ onFormActionEnd = useStableCallback(onFormActionEnd);
23
+
24
+ useLayoutEffect(() => {
25
+ const element = elementRef.current;
26
+ if (!element) {
27
+ return null;
28
+ }
29
+
30
+ let form = element.form;
31
+ if (!form) {
32
+ // some non input elements may want to listen form events (<RadioList> is a <div>)
33
+ form = element.closest("form");
34
+ if (!form) {
35
+ console.warn("No form found for element", element);
36
+ return null;
37
+ }
38
+ }
39
+ return addManyEventListeners(form, {
40
+ reset: onFormReset,
41
+ actionprevented: onFormActionPrevented,
42
+ actionstart: onFormActionStart,
43
+ actionabort: onFormActionAbort,
44
+ actionerror: onFormActionError,
45
+ actionend: onFormActionEnd,
46
+ });
47
+ }, [
48
+ onFormReset,
49
+ onFormActionPrevented,
50
+ onFormActionStart,
51
+ onFormActionAbort,
52
+ onFormActionError,
53
+ onFormActionEnd,
54
+ ]);
55
+ };