@jsenv/navi 0.0.1 → 0.1.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.
- package/dist/jsenv_navi.js +22959 -0
- package/index.js +66 -16
- package/package.json +23 -11
- package/src/actions.js +50 -26
- package/src/browser_integration/browser_integration.js +31 -6
- package/src/browser_integration/via_history.js +42 -9
- package/src/components/action_execution/render_actionable_component.jsx +6 -4
- package/src/components/action_execution/use_action.js +51 -282
- package/src/components/action_execution/use_execute_action.js +106 -92
- package/src/components/action_execution/use_run_on_mount.js +9 -0
- package/src/components/action_renderer.jsx +21 -32
- package/src/components/demos/0_button_demo.html +574 -103
- package/src/components/demos/10_column_reordering_debug.html +277 -0
- package/src/components/demos/11_table_selection_debug.html +432 -0
- package/src/components/demos/1_checkbox_demo.html +579 -202
- package/src/components/demos/2_input_textual_demo.html +81 -138
- package/src/components/demos/3_radio_demo.html +0 -2
- package/src/components/demos/4_select_demo.html +19 -23
- package/src/components/demos/6_tablist_demo.html +77 -0
- package/src/components/demos/7_table_selection_demo.html +176 -0
- package/src/components/demos/8_table_fixed_headers_demo.html +584 -0
- package/src/components/demos/9_table_column_drag_demo.html +325 -0
- package/src/components/demos/action/0_button_demo.html +2 -4
- package/src/components/demos/action/1_input_text_demo.html +643 -222
- package/src/components/demos/action/3_details_demo.html +146 -115
- package/src/components/demos/action/4_input_checkbox_demo.html +442 -322
- package/src/components/demos/action/5_input_checkbox_state_demo.html +270 -0
- package/src/components/demos/action/6_checkbox_list_demo.html +304 -72
- package/src/components/demos/action/7_radio_list_demo.html +310 -170
- package/src/components/demos/action/{8_editable_text_demo.html → 8_editable_demo.html} +65 -76
- package/src/components/demos/action/9_link_demo.html +84 -62
- package/src/components/demos/ui_transition/0_action_renderer_ui_transition_demo.html +695 -0
- package/src/components/demos/ui_transition/1_nested_ui_transition_demo.html +429 -0
- package/src/components/demos/ui_transition/2_height_transition_test.html +295 -0
- package/src/components/details/details.jsx +62 -64
- package/src/components/edition/editable.jsx +186 -0
- package/src/components/field/README.md +247 -0
- package/src/components/{input → field}/button.jsx +151 -130
- package/src/components/field/checkbox_list.jsx +184 -0
- package/src/components/{collect_form_element_values.js → field/collect_form_element_values.js} +7 -4
- package/src/components/{input → field}/field_css.js +4 -1
- package/src/components/field/form.jsx +211 -0
- package/src/components/{input → field}/input.jsx +1 -0
- package/src/components/{input → field}/input_checkbox.jsx +132 -155
- package/src/components/{input → field}/input_radio.jsx +135 -46
- package/src/components/field/input_textual.jsx +418 -0
- package/src/components/field/label.jsx +32 -0
- package/src/components/field/radio_list.jsx +182 -0
- package/src/components/{input → field}/select.jsx +17 -32
- package/src/components/field/use_action_events.js +132 -0
- package/src/components/field/use_form_events.js +55 -0
- package/src/components/field/use_ui_state_controller.js +506 -0
- package/src/components/item_tracker/README.md +461 -0
- package/src/components/item_tracker/use_isolated_item_tracker.jsx +209 -0
- package/src/components/item_tracker/use_isolated_item_tracker_demo.html +148 -0
- package/src/components/item_tracker/use_isolated_item_tracker_demo.jsx +460 -0
- package/src/components/item_tracker/use_item_tracker.jsx +143 -0
- package/src/components/item_tracker/use_item_tracker_demo.html +207 -0
- package/src/components/item_tracker/use_item_tracker_demo.jsx +216 -0
- package/src/components/keyboard_shortcuts/active_keyboard_shortcuts.jsx +87 -0
- package/src/components/keyboard_shortcuts/aria_key_shortcuts.js +61 -0
- package/src/components/keyboard_shortcuts/keyboard_key_meta.js +17 -0
- package/src/components/keyboard_shortcuts/keyboard_shortcuts.js +371 -0
- package/src/components/link/link.jsx +65 -102
- package/src/components/link/link_with_icon.jsx +52 -0
- package/src/components/loader/loader_background.jsx +85 -64
- package/src/components/loader/rectangle_loading.jsx +38 -19
- package/src/components/route.jsx +8 -4
- package/src/components/selection/selection.jsx +1583 -0
- package/src/components/svg/font_sized_svg.jsx +45 -0
- package/src/components/svg/icon_and_text.jsx +21 -0
- package/src/components/svg/svg_mask_overlay.jsx +105 -0
- package/src/components/table/drag/table_drag.jsx +506 -0
- package/src/components/table/resize/table_resize.jsx +650 -0
- package/src/components/table/resize/table_size.js +43 -0
- package/src/components/table/selection/table_selection.js +106 -0
- package/src/components/table/selection/table_selection.jsx +203 -0
- package/src/components/table/sticky/sticky_group.js +354 -0
- package/src/components/table/sticky/table_sticky.js +25 -0
- package/src/components/table/sticky/table_sticky.jsx +501 -0
- package/src/components/table/table.jsx +721 -0
- package/src/components/table/table_css.js +211 -0
- package/src/components/table/table_ui.jsx +49 -0
- package/src/components/table/use_cells_and_columns.js +90 -0
- package/src/components/table/use_object_array_to_cells.js +46 -0
- package/src/components/table/z_indexes.js +23 -0
- package/src/components/tablist/tablist.jsx +99 -0
- package/src/components/text/overflow.jsx +15 -0
- package/src/components/text/text_and_count.jsx +28 -0
- package/src/components/ui_transition.jsx +128 -0
- package/src/components/use_auto_focus.js +58 -7
- package/src/components/use_batch_during_render.js +33 -0
- package/src/components/use_debounce_true.js +7 -7
- package/src/components/use_dependencies_diff.js +35 -0
- package/src/components/use_focus_group.js +4 -3
- package/src/components/use_initial_value.js +8 -34
- package/src/components/use_signal_sync.js +1 -1
- package/src/components/use_stable_callback.js +68 -0
- package/src/components/use_state_array.js +16 -9
- package/src/docs/actions.md +22 -0
- package/src/notes.md +33 -12
- package/src/route/route.js +97 -47
- package/src/store/resource_graph.js +2 -1
- package/src/store/tests/{resource_graph_dependencies.test.js → resource_graph_dependencies.test_manual.js} +13 -13
- package/src/utils/is_signal.js +20 -0
- package/src/utils/stringify_for_display.js +4 -23
- package/src/validation/constraints/confirm_constraint.js +14 -0
- package/src/validation/constraints/create_unique_value_constraint.js +27 -0
- package/src/validation/constraints/native_constraints.js +313 -0
- package/src/validation/constraints/readonly_constraint.js +36 -0
- package/src/validation/constraints/single_space_constraint.js +13 -0
- package/src/validation/custom_constraint_validation.js +599 -0
- package/src/validation/custom_message.js +18 -0
- package/src/validation/demos/browser_style.png +0 -0
- package/src/validation/demos/form_validation_demo.html +142 -0
- package/src/validation/demos/form_validation_demo_preact.html +87 -0
- package/src/validation/demos/form_validation_native_popover_demo.html +168 -0
- package/src/validation/demos/form_validation_vs_native_demo.html +172 -0
- package/src/validation/demos/validation_message_demo.html +203 -0
- package/src/validation/hooks/use_constraints.js +23 -0
- package/src/validation/hooks/use_custom_validation_ref.js +73 -0
- package/src/validation/hooks/use_validation_message.js +19 -0
- package/src/validation/validation_message.js +741 -0
- package/src/components/editable_text/editable_text.jsx +0 -96
- package/src/components/form.jsx +0 -144
- package/src/components/input/checkbox_list.jsx +0 -294
- package/src/components/input/field.jsx +0 -61
- package/src/components/input/input_textual.jsx +0 -338
- package/src/components/input/radio_list.jsx +0 -283
- package/src/components/input/use_form_event.js +0 -20
- package/src/components/input/use_on_change.js +0 -12
- package/src/components/selection/selection.js +0 -5
- package/src/components/selection/selection_context.jsx +0 -262
- package/src/components/shortcut/shortcut_context.jsx +0 -390
- package/src/components/use_action_events.js +0 -37
- package/src/utils/iterable_weak_set.js +0 -62
- /package/src/components/demos/action/{11_nested_shortcuts_demo.html → 11_nested_shortcuts_demo.xhtml} +0 -0
- /package/src/components/{shortcut → keyboard_shortcuts}/os.js +0 -0
- /package/src/route/{route.test.html → route.xtest.html} +0 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// TOFIX: select in data then reset, it reset to red/blue instead of red/blue/green
|
|
2
|
+
|
|
3
|
+
import { forwardRef } from "preact/compat";
|
|
4
|
+
import {
|
|
5
|
+
useContext,
|
|
6
|
+
useImperativeHandle,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from "preact/hooks";
|
|
10
|
+
|
|
11
|
+
import { useActionStatus } from "../../use_action_status.js";
|
|
12
|
+
import { requestAction } from "../../validation/custom_constraint_validation.js";
|
|
13
|
+
import { renderActionableComponent } from "../action_execution/render_actionable_component.jsx";
|
|
14
|
+
import { useActionBoundToOneArrayParam } from "../action_execution/use_action.js";
|
|
15
|
+
import { useExecuteAction } from "../action_execution/use_execute_action.js";
|
|
16
|
+
import { InputCheckbox } from "./input_checkbox.jsx";
|
|
17
|
+
import { useActionEvents } from "./use_action_events.js";
|
|
18
|
+
import {
|
|
19
|
+
DisabledContext,
|
|
20
|
+
FieldNameContext,
|
|
21
|
+
LoadingContext,
|
|
22
|
+
LoadingElementContext,
|
|
23
|
+
ParentUIStateControllerContext,
|
|
24
|
+
ReadOnlyContext,
|
|
25
|
+
RequiredContext,
|
|
26
|
+
UIStateContext,
|
|
27
|
+
UIStateControllerContext,
|
|
28
|
+
useUIGroupStateController,
|
|
29
|
+
useUIState,
|
|
30
|
+
} from "./use_ui_state_controller.js";
|
|
31
|
+
|
|
32
|
+
import.meta.css = /* css */ `
|
|
33
|
+
.navi_checkbox_list {
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
export const CheckboxList = forwardRef((props, ref) => {
|
|
40
|
+
const uiStateController = useUIGroupStateController(props, "checkbox_list", {
|
|
41
|
+
childComponentType: "checkbox",
|
|
42
|
+
aggregateChildStates: (childUIStateControllers) => {
|
|
43
|
+
const values = [];
|
|
44
|
+
for (const childUIStateController of childUIStateControllers) {
|
|
45
|
+
if (childUIStateController.uiState) {
|
|
46
|
+
values.push(childUIStateController.uiState);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return values.length === 0 ? undefined : values;
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
const uiState = useUIState(uiStateController);
|
|
53
|
+
|
|
54
|
+
const checkboxList = renderActionableComponent(props, ref, {
|
|
55
|
+
Basic: CheckboxListBasic,
|
|
56
|
+
WithAction: CheckboxListWithAction,
|
|
57
|
+
InsideForm: CheckboxListInsideForm,
|
|
58
|
+
});
|
|
59
|
+
return (
|
|
60
|
+
<UIStateControllerContext.Provider value={uiStateController}>
|
|
61
|
+
<UIStateContext.Provider value={uiState}>
|
|
62
|
+
{checkboxList}
|
|
63
|
+
</UIStateContext.Provider>
|
|
64
|
+
</UIStateControllerContext.Provider>
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
export const Checkbox = InputCheckbox;
|
|
68
|
+
|
|
69
|
+
const CheckboxListBasic = forwardRef((props, ref) => {
|
|
70
|
+
const contextReadOnly = useContext(ReadOnlyContext);
|
|
71
|
+
const contextDisabled = useContext(DisabledContext);
|
|
72
|
+
const contextLoading = useContext(LoadingContext);
|
|
73
|
+
const uiStateController = useContext(UIStateControllerContext);
|
|
74
|
+
const { name, readOnly, disabled, required, loading, children, ...rest } =
|
|
75
|
+
props;
|
|
76
|
+
const innerRef = useRef();
|
|
77
|
+
useImperativeHandle(ref, () => innerRef.current);
|
|
78
|
+
|
|
79
|
+
const innerLoading = loading || contextLoading;
|
|
80
|
+
const innerReadOnly =
|
|
81
|
+
readOnly || contextReadOnly || innerLoading || uiStateController.readOnly;
|
|
82
|
+
const innerDisabled = disabled || contextDisabled;
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div
|
|
86
|
+
{...rest}
|
|
87
|
+
ref={innerRef}
|
|
88
|
+
name={name}
|
|
89
|
+
className="navi_checkbox_list"
|
|
90
|
+
data-checkbox-list
|
|
91
|
+
// eslint-disable-next-line react/no-unknown-property
|
|
92
|
+
onresetuistate={(e) => {
|
|
93
|
+
uiStateController.resetUIState(e);
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
<ParentUIStateControllerContext.Provider value={uiStateController}>
|
|
97
|
+
<FieldNameContext.Provider value={name}>
|
|
98
|
+
<ReadOnlyContext.Provider value={innerReadOnly}>
|
|
99
|
+
<DisabledContext.Provider value={innerDisabled}>
|
|
100
|
+
<RequiredContext.Provider value={required}>
|
|
101
|
+
<LoadingContext.Provider value={innerLoading}>
|
|
102
|
+
{children}
|
|
103
|
+
</LoadingContext.Provider>
|
|
104
|
+
</RequiredContext.Provider>
|
|
105
|
+
</DisabledContext.Provider>
|
|
106
|
+
</ReadOnlyContext.Provider>
|
|
107
|
+
</FieldNameContext.Provider>
|
|
108
|
+
</ParentUIStateControllerContext.Provider>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const CheckboxListWithAction = forwardRef((props, ref) => {
|
|
114
|
+
const uiStateController = useContext(UIStateControllerContext);
|
|
115
|
+
const uiState = useContext(UIStateContext);
|
|
116
|
+
const {
|
|
117
|
+
action,
|
|
118
|
+
actionErrorEffect,
|
|
119
|
+
onCancel,
|
|
120
|
+
onActionPrevented,
|
|
121
|
+
onActionStart,
|
|
122
|
+
onActionAbort,
|
|
123
|
+
onActionError,
|
|
124
|
+
onActionEnd,
|
|
125
|
+
loading,
|
|
126
|
+
children,
|
|
127
|
+
...rest
|
|
128
|
+
} = props;
|
|
129
|
+
const innerRef = useRef();
|
|
130
|
+
useImperativeHandle(ref, () => innerRef.current);
|
|
131
|
+
const [boundAction] = useActionBoundToOneArrayParam(action, uiState);
|
|
132
|
+
const { loading: actionLoading } = useActionStatus(boundAction);
|
|
133
|
+
const executeAction = useExecuteAction(innerRef, {
|
|
134
|
+
errorEffect: actionErrorEffect,
|
|
135
|
+
});
|
|
136
|
+
const [actionRequester, setActionRequester] = useState(null);
|
|
137
|
+
|
|
138
|
+
useActionEvents(innerRef, {
|
|
139
|
+
onCancel: (e, reason) => {
|
|
140
|
+
uiStateController.resetUIState(e);
|
|
141
|
+
onCancel?.(e, reason);
|
|
142
|
+
},
|
|
143
|
+
onPrevented: onActionPrevented,
|
|
144
|
+
onAction: (actionEvent) => {
|
|
145
|
+
setActionRequester(actionEvent.detail.requester);
|
|
146
|
+
executeAction(actionEvent);
|
|
147
|
+
},
|
|
148
|
+
onStart: onActionStart,
|
|
149
|
+
onAbort: (e) => {
|
|
150
|
+
uiStateController.resetUIState(e);
|
|
151
|
+
onActionAbort?.(e);
|
|
152
|
+
},
|
|
153
|
+
onError: (e) => {
|
|
154
|
+
uiStateController.resetUIState(e);
|
|
155
|
+
onActionError?.(e);
|
|
156
|
+
},
|
|
157
|
+
onEnd: (e) => {
|
|
158
|
+
onActionEnd?.(e);
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<CheckboxListBasic
|
|
164
|
+
data-action={boundAction.name}
|
|
165
|
+
{...rest}
|
|
166
|
+
ref={innerRef}
|
|
167
|
+
onChange={(event) => {
|
|
168
|
+
const checkboxList = innerRef.current;
|
|
169
|
+
const checkbox = event.target;
|
|
170
|
+
requestAction(checkboxList, boundAction, {
|
|
171
|
+
event,
|
|
172
|
+
requester: checkbox,
|
|
173
|
+
actionOrigin: "action_prop",
|
|
174
|
+
});
|
|
175
|
+
}}
|
|
176
|
+
loading={loading || actionLoading}
|
|
177
|
+
>
|
|
178
|
+
<LoadingElementContext.Provider value={actionRequester}>
|
|
179
|
+
{children}
|
|
180
|
+
</LoadingElementContext.Provider>
|
|
181
|
+
</CheckboxListBasic>
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
const CheckboxListInsideForm = CheckboxListBasic;
|
package/src/components/{collect_form_element_values.js → field/collect_form_element_values.js}
RENAMED
|
@@ -72,8 +72,11 @@ const getFormElementValue = (formElement) => {
|
|
|
72
72
|
};
|
|
73
73
|
|
|
74
74
|
const getValue = (formElement) => {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
const hasDataValueAttribute = formElement.hasAttribute("data-value");
|
|
76
|
+
if (hasDataValueAttribute) {
|
|
77
|
+
// happens for "datetime-local" inputs to keep the timezone
|
|
78
|
+
// consistent when sending to the server
|
|
79
|
+
return formElement.getAttribute("data-value");
|
|
80
|
+
}
|
|
81
|
+
return formElement.value;
|
|
79
82
|
};
|
|
@@ -112,7 +112,10 @@ import.meta.css = /* css */ `
|
|
|
112
112
|
[data-field-with-hover]:disabled:hover,
|
|
113
113
|
[data-field-with-hover][data-disabled]:hover {
|
|
114
114
|
outline-color: var(--field-disabled-border-color);
|
|
115
|
-
background-color: var(--field-disabled-background-color);
|
|
116
115
|
color: var(--field-disabled-text-color);
|
|
117
116
|
}
|
|
117
|
+
[data-field-with-background]:disabled,
|
|
118
|
+
[data-field-with-background][disabled] {
|
|
119
|
+
background-color: var(--field-disabled-background-color);
|
|
120
|
+
}
|
|
118
121
|
`;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Here we want the same behaviour as web standards:
|
|
4
|
+
*
|
|
5
|
+
* 1. When submitting the form URL does not change
|
|
6
|
+
* 2. When form submission id done user is redirected (by default the current one)
|
|
7
|
+
* (we can configure this using target)
|
|
8
|
+
* So for example user might be reidrect to a page with the resource he just created
|
|
9
|
+
* I could create an example where we would put a link on the page to let user see what he created
|
|
10
|
+
* but by default user stays on the form allowing to create multiple resources at once
|
|
11
|
+
* And an other where he is redirected to the resource he created
|
|
12
|
+
* 3. If form submission fails ideally we should display this somewhere on the UI
|
|
13
|
+
* right now it's just logged to the console I need to see how we can achieve this
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { forwardRef } from "preact/compat";
|
|
17
|
+
import { useContext, useImperativeHandle, useMemo, useRef } from "preact/hooks";
|
|
18
|
+
|
|
19
|
+
import { requestAction } from "../../validation/custom_constraint_validation.js";
|
|
20
|
+
import { useConstraints } from "../../validation/hooks/use_constraints.js";
|
|
21
|
+
import { FormContext } from "../action_execution/form_context.js";
|
|
22
|
+
import { renderActionableComponent } from "../action_execution/render_actionable_component.jsx";
|
|
23
|
+
import { useActionBoundToOneParam } from "../action_execution/use_action.js";
|
|
24
|
+
import { useExecuteAction } from "../action_execution/use_execute_action.js";
|
|
25
|
+
import { collectFormElementValues } from "./collect_form_element_values.js";
|
|
26
|
+
import {
|
|
27
|
+
useActionEvents,
|
|
28
|
+
useRequestedActionStatus,
|
|
29
|
+
} from "./use_action_events.js";
|
|
30
|
+
import {
|
|
31
|
+
LoadingContext,
|
|
32
|
+
LoadingElementContext,
|
|
33
|
+
ParentUIStateControllerContext,
|
|
34
|
+
ReadOnlyContext,
|
|
35
|
+
UIStateContext,
|
|
36
|
+
UIStateControllerContext,
|
|
37
|
+
useUIGroupStateController,
|
|
38
|
+
useUIState,
|
|
39
|
+
} from "./use_ui_state_controller.js";
|
|
40
|
+
|
|
41
|
+
export const Form = forwardRef((props, ref) => {
|
|
42
|
+
const uiStateController = useUIGroupStateController(props, "form", {
|
|
43
|
+
childComponentType: "*",
|
|
44
|
+
aggregateChildStates: (childUIStateControllers) => {
|
|
45
|
+
const formValues = {};
|
|
46
|
+
for (const childUIStateController of childUIStateControllers) {
|
|
47
|
+
const { name, uiState } = childUIStateController;
|
|
48
|
+
if (!name) {
|
|
49
|
+
console.warn(
|
|
50
|
+
"A form child component is missing a name property, its state won't be included in the form state",
|
|
51
|
+
childUIStateController,
|
|
52
|
+
);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
formValues[name] = uiState;
|
|
56
|
+
}
|
|
57
|
+
return formValues;
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
const uiState = useUIState(uiStateController);
|
|
61
|
+
|
|
62
|
+
const form = renderActionableComponent(props, ref, {
|
|
63
|
+
Basic: FormBasic,
|
|
64
|
+
WithAction: FormWithAction,
|
|
65
|
+
});
|
|
66
|
+
return (
|
|
67
|
+
<UIStateControllerContext.Provider value={uiStateController}>
|
|
68
|
+
<UIStateContext.Provider value={uiState}>{form}</UIStateContext.Provider>
|
|
69
|
+
</UIStateControllerContext.Provider>
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const FormBasic = forwardRef((props, ref) => {
|
|
74
|
+
const uiStateController = useContext(UIStateControllerContext);
|
|
75
|
+
const { readOnly, loading, children, ...rest } = props;
|
|
76
|
+
const innerRef = useRef();
|
|
77
|
+
useImperativeHandle(ref, () => innerRef.current);
|
|
78
|
+
|
|
79
|
+
// instantiation validation to:
|
|
80
|
+
// - receive "requestsubmit" custom event ensure submit is prevented
|
|
81
|
+
// (and also execute action without validation if form.submit() is ever called)
|
|
82
|
+
useConstraints(innerRef, []);
|
|
83
|
+
|
|
84
|
+
const innerReadOnly = readOnly || loading;
|
|
85
|
+
|
|
86
|
+
const formContextValue = useMemo(() => {
|
|
87
|
+
return { loading };
|
|
88
|
+
}, [loading]);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<form
|
|
92
|
+
{...rest}
|
|
93
|
+
ref={innerRef}
|
|
94
|
+
onReset={(e) => {
|
|
95
|
+
// browser would empty all fields to their default values (likely empty/unchecked)
|
|
96
|
+
// we want to reset to the last known external state instead
|
|
97
|
+
e.preventDefault();
|
|
98
|
+
uiStateController.resetUIState(e);
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
<ParentUIStateControllerContext.Provider value={uiStateController}>
|
|
102
|
+
<ReadOnlyContext.Provider value={innerReadOnly}>
|
|
103
|
+
<LoadingContext.Provider value={loading}>
|
|
104
|
+
<FormContext.Provider value={formContextValue}>
|
|
105
|
+
{children}
|
|
106
|
+
</FormContext.Provider>
|
|
107
|
+
</LoadingContext.Provider>
|
|
108
|
+
</ReadOnlyContext.Provider>
|
|
109
|
+
</ParentUIStateControllerContext.Provider>
|
|
110
|
+
</form>
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const FormWithAction = forwardRef((props, ref) => {
|
|
115
|
+
const uiStateController = useContext(UIStateControllerContext);
|
|
116
|
+
const uiState = useContext(UIStateContext);
|
|
117
|
+
const {
|
|
118
|
+
action,
|
|
119
|
+
method,
|
|
120
|
+
actionErrorEffect = "show_validation_message", // "show_validation_message" or "throw"
|
|
121
|
+
onActionPrevented,
|
|
122
|
+
onActionStart,
|
|
123
|
+
onActionAbort,
|
|
124
|
+
onActionError,
|
|
125
|
+
onActionEnd,
|
|
126
|
+
loading,
|
|
127
|
+
children,
|
|
128
|
+
...rest
|
|
129
|
+
} = props;
|
|
130
|
+
const innerRef = useRef();
|
|
131
|
+
useImperativeHandle(ref, () => innerRef.current);
|
|
132
|
+
const [actionBoundToUIState] = useActionBoundToOneParam(action, uiState);
|
|
133
|
+
const executeAction = useExecuteAction(innerRef, {
|
|
134
|
+
errorEffect: actionErrorEffect,
|
|
135
|
+
});
|
|
136
|
+
const { actionPending, actionRequester: formActionRequester } =
|
|
137
|
+
useRequestedActionStatus(innerRef);
|
|
138
|
+
|
|
139
|
+
useActionEvents(innerRef, {
|
|
140
|
+
onPrevented: onActionPrevented,
|
|
141
|
+
onRequested: (e) => {
|
|
142
|
+
const form = innerRef.current;
|
|
143
|
+
requestAction(form, actionBoundToUIState, {
|
|
144
|
+
requester: e.detail?.requester,
|
|
145
|
+
event: e.detail?.event || e,
|
|
146
|
+
meta: e.detail?.meta,
|
|
147
|
+
actionOrigin: e.detail?.actionOrigin,
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
onAction: (e) => {
|
|
151
|
+
const form = innerRef.current;
|
|
152
|
+
const formElementValues = collectFormElementValues(form);
|
|
153
|
+
uiStateController.setUIState(formElementValues, e);
|
|
154
|
+
executeAction(e);
|
|
155
|
+
},
|
|
156
|
+
onStart: onActionStart,
|
|
157
|
+
onAbort: (e) => {
|
|
158
|
+
// user might want to re-submit as is
|
|
159
|
+
// or change the ui state before re-submitting
|
|
160
|
+
// we can't decide for him
|
|
161
|
+
onActionAbort?.(e);
|
|
162
|
+
},
|
|
163
|
+
onError: (e) => {
|
|
164
|
+
// user might want to re-submit as is
|
|
165
|
+
// or change the ui state before re-submitting
|
|
166
|
+
// we can't decide for him
|
|
167
|
+
onActionError?.(e);
|
|
168
|
+
},
|
|
169
|
+
onEnd: (e) => {
|
|
170
|
+
// form side effect is a success
|
|
171
|
+
// we can get rid of the nav state
|
|
172
|
+
// that was keeping the ui state in case user navigates away without submission
|
|
173
|
+
uiStateController.actionEnd(e);
|
|
174
|
+
onActionEnd?.(e);
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
const innerLoading = loading || actionPending;
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<FormBasic
|
|
181
|
+
data-action={actionBoundToUIState.name}
|
|
182
|
+
data-method={action.meta?.httpVerb || method || "GET"}
|
|
183
|
+
{...rest}
|
|
184
|
+
ref={innerRef}
|
|
185
|
+
loading={innerLoading}
|
|
186
|
+
onrequestsubmit={(e) => {
|
|
187
|
+
// prevent "submit" event that would be dispatched by the browser after form.requestSubmit()
|
|
188
|
+
// (not super important because our <form> listen the "action" and do does preventDefault on "submit")
|
|
189
|
+
e.preventDefault();
|
|
190
|
+
requestAction(e.target, actionBoundToUIState, {
|
|
191
|
+
event: e,
|
|
192
|
+
actionOrigin: "action_prop",
|
|
193
|
+
});
|
|
194
|
+
}}
|
|
195
|
+
>
|
|
196
|
+
<LoadingElementContext.Provider value={formActionRequester}>
|
|
197
|
+
{children}
|
|
198
|
+
</LoadingElementContext.Provider>
|
|
199
|
+
</FormBasic>
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// const dispatchCustomEventOnFormAndFormElements = (type, options) => {
|
|
204
|
+
// const form = innerRef.current;
|
|
205
|
+
// const customEvent = new CustomEvent(type, options);
|
|
206
|
+
// // https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/elements
|
|
207
|
+
// for (const element of form.elements) {
|
|
208
|
+
// element.dispatchEvent(customEvent);
|
|
209
|
+
// }
|
|
210
|
+
// form.dispatchEvent(customEvent);
|
|
211
|
+
// };
|