@jsenv/navi 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/index.js +51 -0
  2. package/package.json +38 -0
  3. package/src/action_private_properties.js +11 -0
  4. package/src/action_proxy_test.html +353 -0
  5. package/src/action_run_states.js +5 -0
  6. package/src/actions.js +1377 -0
  7. package/src/browser_integration/browser_integration.js +191 -0
  8. package/src/browser_integration/document_back_and_forward.js +17 -0
  9. package/src/browser_integration/document_loading_signal.js +100 -0
  10. package/src/browser_integration/document_state_signal.js +9 -0
  11. package/src/browser_integration/document_url_signal.js +9 -0
  12. package/src/browser_integration/use_is_visited.js +19 -0
  13. package/src/browser_integration/via_history.js +199 -0
  14. package/src/browser_integration/via_navigation.js +168 -0
  15. package/src/components/action_execution/form_context.js +8 -0
  16. package/src/components/action_execution/render_actionable_component.jsx +27 -0
  17. package/src/components/action_execution/use_action.js +330 -0
  18. package/src/components/action_execution/use_execute_action.js +161 -0
  19. package/src/components/action_renderer.jsx +136 -0
  20. package/src/components/collect_form_element_values.js +79 -0
  21. package/src/components/demos/0_button_demo.html +155 -0
  22. package/src/components/demos/1_checkbox_demo.html +257 -0
  23. package/src/components/demos/2_input_textual_demo.html +354 -0
  24. package/src/components/demos/3_radio_demo.html +222 -0
  25. package/src/components/demos/4_select_demo.html +104 -0
  26. package/src/components/demos/5_list_scrollable_demo.html +153 -0
  27. package/src/components/demos/action/0_button_demo.html +204 -0
  28. package/src/components/demos/action/10_shortcuts_demo.html +189 -0
  29. package/src/components/demos/action/11_nested_shortcuts_demo.html +401 -0
  30. package/src/components/demos/action/1_input_text_demo.html +461 -0
  31. package/src/components/demos/action/2_form_multiple.html +303 -0
  32. package/src/components/demos/action/3_details_demo.html +172 -0
  33. package/src/components/demos/action/4_input_checkbox_demo.html +611 -0
  34. package/src/components/demos/action/6_checkbox_list_demo.html +109 -0
  35. package/src/components/demos/action/7_radio_list_demo.html +217 -0
  36. package/src/components/demos/action/8_editable_text_demo.html +442 -0
  37. package/src/components/demos/action/9_link_demo.html +172 -0
  38. package/src/components/demos/demo.md +0 -0
  39. package/src/components/demos/route/basic/basic.html +14 -0
  40. package/src/components/demos/route/basic/basic_route_demo.jsx +224 -0
  41. package/src/components/demos/route/multi/multi.html +14 -0
  42. package/src/components/demos/route/multi/multi_route_demo.jsx +277 -0
  43. package/src/components/details/details.jsx +248 -0
  44. package/src/components/details/summary_marker.jsx +141 -0
  45. package/src/components/editable_text/editable_text.jsx +96 -0
  46. package/src/components/error_boundary_context.js +9 -0
  47. package/src/components/form.jsx +144 -0
  48. package/src/components/input/button.jsx +333 -0
  49. package/src/components/input/checkbox_list.jsx +294 -0
  50. package/src/components/input/field.jsx +61 -0
  51. package/src/components/input/field_css.js +118 -0
  52. package/src/components/input/input.jsx +15 -0
  53. package/src/components/input/input_checkbox.jsx +370 -0
  54. package/src/components/input/input_radio.jsx +299 -0
  55. package/src/components/input/input_textual.jsx +338 -0
  56. package/src/components/input/radio_list.jsx +283 -0
  57. package/src/components/input/select.jsx +273 -0
  58. package/src/components/input/use_form_event.js +20 -0
  59. package/src/components/input/use_on_change.js +12 -0
  60. package/src/components/link/link.jsx +291 -0
  61. package/src/components/loader/loader_background.jsx +324 -0
  62. package/src/components/loader/loading_spinner.jsx +68 -0
  63. package/src/components/loader/network_speed.js +83 -0
  64. package/src/components/loader/rectangle_loading.jsx +225 -0
  65. package/src/components/route.jsx +15 -0
  66. package/src/components/selection/selection.js +5 -0
  67. package/src/components/selection/selection_context.jsx +262 -0
  68. package/src/components/shortcut/os.js +9 -0
  69. package/src/components/shortcut/shortcut_context.jsx +390 -0
  70. package/src/components/use_action_events.js +37 -0
  71. package/src/components/use_auto_focus.js +43 -0
  72. package/src/components/use_debounce_true.js +31 -0
  73. package/src/components/use_focus_group.js +19 -0
  74. package/src/components/use_initial_value.js +104 -0
  75. package/src/components/use_is_visited.js +19 -0
  76. package/src/components/use_ref_array.js +38 -0
  77. package/src/components/use_signal_sync.js +50 -0
  78. package/src/components/use_state_array.js +40 -0
  79. package/src/docs/actions.md +228 -0
  80. package/src/docs/demos/resource/action_status.jsx +42 -0
  81. package/src/docs/demos/resource/demo.md +1 -0
  82. package/src/docs/demos/resource/resource_demo_0.html +84 -0
  83. package/src/docs/demos/resource/resource_demo_10_post_gc.html +364 -0
  84. package/src/docs/demos/resource/resource_demo_11_describe_many.html +362 -0
  85. package/src/docs/demos/resource/resource_demo_2.html +173 -0
  86. package/src/docs/demos/resource/resource_demo_3_filtered_users.html +415 -0
  87. package/src/docs/demos/resource/resource_demo_4_details.html +284 -0
  88. package/src/docs/demos/resource/resource_demo_5_renderer_lazy.html +115 -0
  89. package/src/docs/demos/resource/resource_demo_6_gc.html +217 -0
  90. package/src/docs/demos/resource/resource_demo_7_child_gc.html +240 -0
  91. package/src/docs/demos/resource/resource_demo_8_proxy_gc.html +319 -0
  92. package/src/docs/demos/resource/resource_demo_9_describe_one.html +472 -0
  93. package/src/docs/demos/resource/tata.jsx +3 -0
  94. package/src/docs/demos/resource/toto.jsx +3 -0
  95. package/src/docs/demos/user_nav/user_nav.html +12 -0
  96. package/src/docs/demos/user_nav/user_nav.jsx +330 -0
  97. package/src/docs/resource_dependencies.md +103 -0
  98. package/src/docs/resource_with_params.md +80 -0
  99. package/src/notes.md +13 -0
  100. package/src/route/route.js +518 -0
  101. package/src/route/route.test.html +228 -0
  102. package/src/store/array_signal_store.js +537 -0
  103. package/src/store/local_storage_signal.js +17 -0
  104. package/src/store/resource_graph.js +1303 -0
  105. package/src/store/tests/resource_graph_autoreload_demo.html +12 -0
  106. package/src/store/tests/resource_graph_autoreload_demo.jsx +964 -0
  107. package/src/store/tests/resource_graph_dependencies.test.js +95 -0
  108. package/src/store/value_in_local_storage.js +187 -0
  109. package/src/symbol_object_signal.js +1 -0
  110. package/src/use_action_data.js +10 -0
  111. package/src/use_action_status.js +47 -0
  112. package/src/utils/add_many_event_listeners.js +15 -0
  113. package/src/utils/array_add_remove.js +61 -0
  114. package/src/utils/array_signal.js +15 -0
  115. package/src/utils/compare_two_js_values.js +172 -0
  116. package/src/utils/execute_with_cleanup.js +21 -0
  117. package/src/utils/get_caller_info.js +85 -0
  118. package/src/utils/iterable_weak_set.js +62 -0
  119. package/src/utils/js_value_weak_map.js +162 -0
  120. package/src/utils/js_value_weak_map_demo.html +690 -0
  121. package/src/utils/merge_two_js_values.js +53 -0
  122. package/src/utils/stringify_for_display.js +150 -0
  123. package/src/utils/weak_effect.js +48 -0
@@ -0,0 +1,283 @@
1
+ import { requestAction } from "@jsenv/validation";
2
+ import { forwardRef } from "preact/compat";
3
+ import { useEffect, useImperativeHandle, useRef, useState } from "preact/hooks";
4
+ import { useNavState } from "../../browser_integration/browser_integration.js";
5
+ import { useActionStatus } from "../../use_action_status.js";
6
+ import { renderActionableComponent } from "../action_execution/render_actionable_component.jsx";
7
+ import {
8
+ useActionBoundToOneParam,
9
+ useOneFormParam,
10
+ } from "../action_execution/use_action.js";
11
+ import { useExecuteAction } from "../action_execution/use_execute_action.js";
12
+ import { useActionEvents } from "../use_action_events.js";
13
+ import { useRefArray } from "../use_ref_array.js";
14
+ import { Field } from "./field.jsx";
15
+ import { InputRadio } from "./input_radio.jsx";
16
+ import { useFormEvents } from "./use_form_event.js";
17
+
18
+ import.meta.css = /* css */ `
19
+ .radio_list {
20
+ display: flex;
21
+ flex-direction: column;
22
+ }
23
+ `;
24
+
25
+ export const RadioList = forwardRef((props, ref) => {
26
+ return renderActionableComponent(props, ref, {
27
+ Basic: RadioListBasic,
28
+ WithAction: RadioListWithAction,
29
+ InsideForm: RadioListInsideForm,
30
+ });
31
+ });
32
+
33
+ const RadioListControlled = forwardRef((props, ref) => {
34
+ const {
35
+ name,
36
+ value,
37
+ label,
38
+ loading,
39
+ disabled,
40
+ readOnly,
41
+ children,
42
+ onChange,
43
+ required,
44
+ ...rest
45
+ } = props;
46
+
47
+ const innerRef = useRef();
48
+ useImperativeHandle(ref, () => innerRef.current);
49
+
50
+ return (
51
+ <fieldset className="radio_list" ref={innerRef} {...rest}>
52
+ {label ? <legend>{label}</legend> : null}
53
+ {children.map((child) => {
54
+ const {
55
+ label,
56
+ readOnly: childReadOnly,
57
+ disabled: childDisabled,
58
+ loading: childLoading,
59
+ onChange: childOnChange,
60
+ value: childValue,
61
+ ...childRest
62
+ } = child;
63
+
64
+ const radio = (
65
+ <InputRadio
66
+ {...childRest}
67
+ // ignoreForm: each input is controller by this list
68
+ // we don't want the input to try to update the form because it's already done here
69
+ ignoreForm
70
+ name={name}
71
+ value={childValue}
72
+ checked={childValue === value}
73
+ readOnly={readOnly || childReadOnly}
74
+ disabled={disabled || childDisabled}
75
+ loading={loading || childLoading}
76
+ required={required}
77
+ onChange={(event) => {
78
+ onChange(event);
79
+ childOnChange?.(event);
80
+ }}
81
+ />
82
+ );
83
+
84
+ return <Field key={childValue} input={radio} label={label} />;
85
+ })}
86
+ </fieldset>
87
+ );
88
+ });
89
+
90
+ const RadioListBasic = forwardRef((props, ref) => {
91
+ const { value: initialValue, id, children, ...rest } = props;
92
+
93
+ const innerRef = useRef();
94
+ useImperativeHandle(ref, () => innerRef.current);
95
+
96
+ const [navState, setNavState] = useNavState(id);
97
+ const valueAtStart = navState === undefined ? initialValue : navState;
98
+ const [value, setValue] = useState(valueAtStart);
99
+ useEffect(() => {
100
+ setNavState(value);
101
+ }, [value]);
102
+
103
+ return (
104
+ <RadioListControlled
105
+ ref={innerRef}
106
+ value={value}
107
+ onChange={(event) => {
108
+ const radio = event.target;
109
+ const radioIsChecked = radio.checked;
110
+ if (!radioIsChecked) {
111
+ return;
112
+ }
113
+ const value = radio.value;
114
+ setValue(value);
115
+ }}
116
+ {...rest}
117
+ >
118
+ {children}
119
+ </RadioListControlled>
120
+ );
121
+ });
122
+
123
+ const RadioListWithAction = forwardRef((props, ref) => {
124
+ const {
125
+ id,
126
+ name,
127
+ value: externalValue,
128
+ valueSignal,
129
+ action,
130
+ children,
131
+ onCancel,
132
+ onActionPrevented,
133
+ onActionStart,
134
+ onActionAbort,
135
+ onActionError,
136
+ onActionEnd,
137
+ actionErrorEffect,
138
+ ...rest
139
+ } = props;
140
+
141
+ const innerRef = useRef();
142
+ useImperativeHandle(ref, () => innerRef.current);
143
+
144
+ const [navState, setNavState, resetNavState] = useNavState(id);
145
+ const [boundAction, value, setValue, resetValue] = useActionBoundToOneParam(
146
+ action,
147
+ name,
148
+ valueSignal ? valueSignal : externalValue,
149
+ navState,
150
+ );
151
+ const { loading: actionLoading } = useActionStatus(boundAction);
152
+ const executeAction = useExecuteAction(innerRef, {
153
+ errorEffect: actionErrorEffect,
154
+ });
155
+ const actionRequesterRef = useRef(null);
156
+ useEffect(() => {
157
+ setNavState(value);
158
+ }, [value]);
159
+
160
+ useActionEvents(innerRef, {
161
+ onCancel: (e, reason) => {
162
+ resetNavState();
163
+ resetValue();
164
+ onCancel?.(e, reason);
165
+ },
166
+ onPrevented: onActionPrevented,
167
+ onAction: (actionEvent) => {
168
+ actionRequesterRef.current = actionEvent.detail.requester;
169
+ executeAction(actionEvent);
170
+ },
171
+ onStart: onActionStart,
172
+ onAbort: (e) => {
173
+ resetValue();
174
+ onActionAbort?.(e);
175
+ },
176
+ onError: (error) => {
177
+ resetValue();
178
+ onActionError?.(error);
179
+ },
180
+ onEnd: (e) => {
181
+ resetNavState();
182
+ onActionEnd?.(e);
183
+ },
184
+ });
185
+
186
+ const childRefArray = useRefArray(children, (child) => child.value);
187
+
188
+ return (
189
+ <RadioListControlled
190
+ ref={innerRef}
191
+ name={name}
192
+ value={value}
193
+ data-action={boundAction}
194
+ onChange={(event) => {
195
+ const radio = event.target;
196
+ const radioIsChecked = radio.checked;
197
+ if (!radioIsChecked) {
198
+ return;
199
+ }
200
+ const value = radio.value;
201
+ setValue(value);
202
+ const radioListContainer = innerRef.current;
203
+ requestAction(boundAction, {
204
+ event,
205
+ target: radioListContainer,
206
+ requester: radio,
207
+ });
208
+ }}
209
+ {...rest}
210
+ >
211
+ {children.map((child, i) => {
212
+ const childRef = childRefArray[i];
213
+ return {
214
+ ...child,
215
+ ref: childRef,
216
+ loading:
217
+ child.loading ||
218
+ (actionLoading && actionRequesterRef.current === childRef.current),
219
+ readOnly: child.readOnly || actionLoading,
220
+ };
221
+ })}
222
+ </RadioListControlled>
223
+ );
224
+ });
225
+
226
+ const RadioListInsideForm = forwardRef((props, ref) => {
227
+ const {
228
+ formContext,
229
+ id,
230
+ name,
231
+ readOnly,
232
+ value: externalValue,
233
+ children,
234
+ ...rest
235
+ } = props;
236
+ const { formIsReadOnly } = formContext;
237
+
238
+ const innerRef = useRef();
239
+ useImperativeHandle(ref, () => innerRef.current);
240
+
241
+ const [navState, setNavState] = useNavState(id);
242
+ const [value, setValue, resetValue] = useOneFormParam(
243
+ name,
244
+ externalValue,
245
+ navState,
246
+ );
247
+ useEffect(() => {
248
+ setNavState(value);
249
+ }, [value]);
250
+
251
+ useFormEvents(innerRef, {
252
+ onFormReset: () => {
253
+ setValue(undefined);
254
+ },
255
+ onFormActionAbort: () => {
256
+ resetValue();
257
+ },
258
+ onFormActionError: () => {
259
+ resetValue();
260
+ },
261
+ });
262
+
263
+ return (
264
+ <RadioListControlled
265
+ ref={innerRef}
266
+ name={name}
267
+ value={value}
268
+ readOnly={readOnly || formIsReadOnly}
269
+ onChange={(event) => {
270
+ const radio = event.target;
271
+ const radioIsChecked = radio.checked;
272
+ if (!radioIsChecked) {
273
+ return;
274
+ }
275
+ const value = radio.value;
276
+ setValue(value);
277
+ }}
278
+ {...rest}
279
+ >
280
+ {children}
281
+ </RadioListControlled>
282
+ );
283
+ });
@@ -0,0 +1,273 @@
1
+ import { requestAction } from "@jsenv/validation";
2
+ import { forwardRef } from "preact/compat";
3
+ import { useEffect, useImperativeHandle, useRef, useState } from "preact/hooks";
4
+ import { useNavState } from "../../browser_integration/browser_integration.js";
5
+ import { useActionStatus } from "../../use_action_status.js";
6
+ import { renderActionableComponent } from "../action_execution/render_actionable_component.jsx";
7
+ import {
8
+ useActionBoundToOneParam,
9
+ useOneFormParam,
10
+ } from "../action_execution/use_action.js";
11
+ import { useExecuteAction } from "../action_execution/use_execute_action.js";
12
+ import { LoaderBackground } from "../loader/loader_background.jsx";
13
+ import { useActionEvents } from "../use_action_events.js";
14
+ import { useRefArray } from "../use_ref_array.js";
15
+ import { useFormEvents } from "./use_form_event.js";
16
+
17
+ import.meta.css = /* css */ `
18
+ .navi_select[data-readonly] {
19
+ pointer-events: none;
20
+ }
21
+ `;
22
+
23
+ export const Select = forwardRef((props, ref) => {
24
+ return renderActionableComponent(props, ref, {
25
+ Basic: SelectBasic,
26
+ WithAction: SelectWithAction,
27
+ InsideForm: SelectInsideForm,
28
+ });
29
+ });
30
+
31
+ const SelectControlled = forwardRef((props, ref) => {
32
+ const { name, value, loading, disabled, readOnly, children, ...rest } = props;
33
+
34
+ const innerRef = useRef();
35
+ useImperativeHandle(ref, () => innerRef.current);
36
+
37
+ const selectElement = (
38
+ <select
39
+ className="navi_select"
40
+ ref={innerRef}
41
+ data-field=""
42
+ data-readonly={readOnly && !disabled ? "" : undefined}
43
+ onKeyDown={(e) => {
44
+ if (readOnly) {
45
+ e.preventDefault();
46
+ }
47
+ }}
48
+ {...rest}
49
+ >
50
+ {children.map((child) => {
51
+ const {
52
+ label,
53
+ readOnly: childReadOnly,
54
+ disabled: childDisabled,
55
+ loading: childLoading,
56
+ value: childValue,
57
+ ...childRest
58
+ } = child;
59
+
60
+ return (
61
+ <option
62
+ key={childValue}
63
+ name={name}
64
+ value={childValue}
65
+ selected={childValue === value}
66
+ readOnly={readOnly || childReadOnly}
67
+ disabled={disabled || childDisabled}
68
+ loading={loading || childLoading}
69
+ {...childRest}
70
+ >
71
+ {label}
72
+ </option>
73
+ );
74
+ })}
75
+ </select>
76
+ );
77
+
78
+ return (
79
+ <LoaderBackground
80
+ loading={loading}
81
+ color="light-dark(#355fcc, #3b82f6)"
82
+ inset={-1}
83
+ >
84
+ {selectElement}
85
+ </LoaderBackground>
86
+ );
87
+ });
88
+
89
+ const SelectBasic = forwardRef((props, ref) => {
90
+ const { value: initialValue, id, children, ...rest } = props;
91
+
92
+ const innerRef = useRef();
93
+ useImperativeHandle(ref, () => innerRef.current);
94
+
95
+ const [navState, setNavState] = useNavState(id);
96
+ const valueAtStart = navState === undefined ? initialValue : navState;
97
+ const [value, setValue] = useState(valueAtStart);
98
+ useEffect(() => {
99
+ setNavState(value);
100
+ }, [value]);
101
+
102
+ return (
103
+ <SelectControlled
104
+ ref={innerRef}
105
+ value={value}
106
+ onChange={(event) => {
107
+ const select = event.target;
108
+ const selectedValue = select.value;
109
+ setValue(selectedValue);
110
+ }}
111
+ {...rest}
112
+ >
113
+ {children}
114
+ </SelectControlled>
115
+ );
116
+ });
117
+
118
+ const SelectWithAction = forwardRef((props, ref) => {
119
+ const {
120
+ id,
121
+ name,
122
+ value: externalValue,
123
+ valueSignal,
124
+ action,
125
+ children,
126
+ onCancel,
127
+ onActionPrevented,
128
+ onActionStart,
129
+ onActionAbort,
130
+ onActionError,
131
+ onActionEnd,
132
+ actionErrorEffect,
133
+ ...rest
134
+ } = props;
135
+
136
+ const innerRef = useRef();
137
+ useImperativeHandle(ref, () => innerRef.current);
138
+
139
+ const [navState, setNavState, resetNavState] = useNavState(id);
140
+ const [boundAction, value, setValue, resetValue] = useActionBoundToOneParam(
141
+ action,
142
+ name,
143
+ valueSignal ? valueSignal : externalValue,
144
+ navState,
145
+ );
146
+ const { loading: actionLoading } = useActionStatus(boundAction);
147
+ const executeAction = useExecuteAction(innerRef, {
148
+ errorEffect: actionErrorEffect,
149
+ });
150
+ useEffect(() => {
151
+ setNavState(value);
152
+ }, [value]);
153
+
154
+ const actionRequesterRef = useRef(null);
155
+ useActionEvents(innerRef, {
156
+ onCancel: (e, reason) => {
157
+ resetNavState();
158
+ resetValue();
159
+ onCancel?.(e, reason);
160
+ },
161
+ onPrevented: onActionPrevented,
162
+ onAction: (actionEvent) => {
163
+ actionRequesterRef.current = actionEvent.detail.requester;
164
+ executeAction(actionEvent);
165
+ },
166
+ onStart: onActionStart,
167
+ onAbort: (e) => {
168
+ resetValue();
169
+ onActionAbort?.(e);
170
+ },
171
+ onError: (error) => {
172
+ resetValue();
173
+ onActionError?.(error);
174
+ },
175
+ onEnd: () => {
176
+ resetNavState();
177
+ onActionEnd?.();
178
+ },
179
+ });
180
+
181
+ const childRefArray = useRefArray(children, (child) => child.value);
182
+
183
+ return (
184
+ <SelectControlled
185
+ ref={innerRef}
186
+ name={name}
187
+ value={value}
188
+ data-action={boundAction}
189
+ onChange={(event) => {
190
+ const select = event.target;
191
+ const selectedValue = select.value;
192
+ setValue(selectedValue);
193
+ const radioListContainer = innerRef.current;
194
+ const optionSelected = select.querySelector(
195
+ `option[value="${selectedValue}"]`,
196
+ );
197
+ requestAction(boundAction, {
198
+ event,
199
+ target: radioListContainer,
200
+ requester: optionSelected,
201
+ });
202
+ }}
203
+ {...rest}
204
+ >
205
+ {children.map((child, i) => {
206
+ const childRef = childRefArray[i];
207
+ return {
208
+ ...child,
209
+ ref: childRef,
210
+ loading:
211
+ child.loading ||
212
+ (actionLoading && actionRequesterRef.current === childRef.current),
213
+ readOnly: child.readOnly || actionLoading,
214
+ };
215
+ })}
216
+ </SelectControlled>
217
+ );
218
+ });
219
+
220
+ 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;
231
+
232
+ const innerRef = useRef();
233
+ useImperativeHandle(ref, () => innerRef.current);
234
+
235
+ const [navState, setNavState] = useNavState(id);
236
+ const [value, setValue, resetValue] = useOneFormParam(
237
+ name,
238
+ externalValue,
239
+ navState,
240
+ );
241
+ useEffect(() => {
242
+ setNavState(value);
243
+ }, [value]);
244
+
245
+ useFormEvents(innerRef, {
246
+ onFormReset: () => {
247
+ setValue(undefined);
248
+ },
249
+ onFormActionAbort: () => {
250
+ resetValue();
251
+ },
252
+ onFormActionError: () => {
253
+ resetValue();
254
+ },
255
+ });
256
+
257
+ return (
258
+ <SelectControlled
259
+ ref={innerRef}
260
+ name={name}
261
+ value={value}
262
+ readOnly={readOnly || formIsReadOnly}
263
+ onChange={(event) => {
264
+ const select = event.target;
265
+ const selectedValue = select.checked;
266
+ setValue(selectedValue);
267
+ }}
268
+ {...rest}
269
+ >
270
+ {children}
271
+ </SelectControlled>
272
+ );
273
+ });
@@ -0,0 +1,20 @@
1
+ import { useLayoutEffect } from "preact/hooks";
2
+ import { addManyEventListeners } from "../../utils/add_many_event_listeners.js";
3
+
4
+ export const useFormEvents = (
5
+ elementRef,
6
+ { onFormReset, onFormActionAbort, onFormActionError },
7
+ ) => {
8
+ useLayoutEffect(() => {
9
+ const element = elementRef.current;
10
+ const form = element.form;
11
+
12
+ return addManyEventListeners(form, {
13
+ reset: onFormReset,
14
+ actionabort: onFormActionAbort,
15
+ actionerror: (e) => {
16
+ onFormActionError?.(e.detail.error);
17
+ },
18
+ });
19
+ }, [onFormReset, onFormActionAbort, onFormActionError]);
20
+ };
@@ -0,0 +1,12 @@
1
+ import { useLayoutEffect } from "preact/hooks";
2
+
3
+ export const useOnChange = (innerRef, callback) => {
4
+ // we must use a custom event listener because preact bind onChange to onInput for compat with react
5
+ useLayoutEffect(() => {
6
+ const input = innerRef.current;
7
+ input.addEventListener("change", callback);
8
+ return () => {
9
+ input.removeEventListener("change", callback);
10
+ };
11
+ }, [callback]);
12
+ };