@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.
Files changed (139) hide show
  1. package/dist/jsenv_navi.js +22959 -0
  2. package/index.js +66 -16
  3. package/package.json +23 -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/field/input_textual.jsx +418 -0
  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/input_textual.jsx +0 -338
  129. package/src/components/input/radio_list.jsx +0 -283
  130. package/src/components/input/use_form_event.js +0 -20
  131. package/src/components/input/use_on_change.js +0 -12
  132. package/src/components/selection/selection.js +0 -5
  133. package/src/components/selection/selection_context.jsx +0 -262
  134. package/src/components/shortcut/shortcut_context.jsx +0 -390
  135. package/src/components/use_action_events.js +0 -37
  136. package/src/utils/iterable_weak_set.js +0 -62
  137. /package/src/components/demos/action/{11_nested_shortcuts_demo.html → 11_nested_shortcuts_demo.xhtml} +0 -0
  138. /package/src/components/{shortcut → keyboard_shortcuts}/os.js +0 -0
  139. /package/src/route/{route.test.html → route.xtest.html} +0 -0
@@ -1,338 +0,0 @@
1
- /**
2
- * Input component for all textual input types.
3
- *
4
- * Supports:
5
- * - text (default)
6
- * - password
7
- * - hidden
8
- * - email
9
- * - url
10
- * - search
11
- * - tel
12
- * - etc.
13
- *
14
- * For non-textual inputs, specialized components will be used:
15
- * - <InputCheckbox /> for type="checkbox"
16
- * - <InputRadio /> for type="radio"
17
- */
18
-
19
- import { requestAction, useConstraints } from "@jsenv/validation";
20
- import { forwardRef } from "preact/compat";
21
- import { useEffect, useImperativeHandle, useRef } from "preact/hooks";
22
- import { useNavState } from "../../browser_integration/browser_integration.js";
23
- import { useActionStatus } from "../../use_action_status.js";
24
- import { renderActionableComponent } from "../action_execution/render_actionable_component.jsx";
25
- import {
26
- useActionBoundToOneParam,
27
- useOneFormParam,
28
- } from "../action_execution/use_action.js";
29
- import { useExecuteAction } from "../action_execution/use_execute_action.js";
30
- import { LoaderBackground } from "../loader/loader_background.jsx";
31
- import { useActionEvents } from "../use_action_events.js";
32
- import { useAutoFocus } from "../use_auto_focus.js";
33
- import "./field_css.js";
34
- import { useOnChange } from "./use_on_change.js";
35
-
36
- export const InputTextual = forwardRef((props, ref) => {
37
- return renderActionableComponent(props, ref, {
38
- Basic: InputTextualBasic,
39
- WithAction: InputTextualWithAction,
40
- InsideForm: InputTextualInsideForm,
41
- });
42
- });
43
-
44
- const InputTextualBasic = forwardRef((props, ref) => {
45
- let {
46
- type,
47
- value,
48
- autoFocus,
49
- autoFocusVisible,
50
- autoSelect,
51
- constraints = [],
52
- loading,
53
- appearance = "custom",
54
- ...rest
55
- } = props;
56
-
57
- const innerRef = useRef();
58
- useImperativeHandle(ref, () => innerRef.current);
59
- useAutoFocus(innerRef, autoFocus, {
60
- autoFocusVisible,
61
- autoSelect,
62
- });
63
- useConstraints(innerRef, constraints);
64
-
65
- if (type === "datetime-local") {
66
- value = convertToLocalTimezone(value);
67
- }
68
-
69
- const inputTextual = (
70
- <input
71
- ref={innerRef}
72
- type={type}
73
- value={value}
74
- data-field=""
75
- data-field-with-border=""
76
- data-custom={appearance === "custom" ? "" : undefined}
77
- {...rest}
78
- />
79
- );
80
-
81
- return (
82
- <LoaderBackground loading={loading} color="light-dark(#355fcc, #3b82f6)">
83
- {inputTextual}
84
- </LoaderBackground>
85
- );
86
- });
87
-
88
- // As explained in https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/datetime-local#setting_timezones
89
- // datetime-local does not support timezones
90
- const convertToLocalTimezone = (dateTimeString) => {
91
- const date = new Date(dateTimeString);
92
- // Check if the date is valid
93
- if (isNaN(date.getTime())) {
94
- return dateTimeString;
95
- }
96
-
97
- // Format to YYYY-MM-DDThh:mm:ss
98
- const year = date.getFullYear();
99
- const month = String(date.getMonth() + 1).padStart(2, "0");
100
- const day = String(date.getDate()).padStart(2, "0");
101
- const hours = String(date.getHours()).padStart(2, "0");
102
- const minutes = String(date.getMinutes()).padStart(2, "0");
103
- const seconds = String(date.getSeconds()).padStart(2, "0");
104
-
105
- return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
106
- };
107
-
108
- /**
109
- * Converts a datetime string without timezone (local time) to UTC format with 'Z' notation
110
- *
111
- * @param {string} localDateTimeString - Local datetime string without timezone (e.g., "2023-07-15T14:30:00")
112
- * @returns {string} Datetime string in UTC with 'Z' notation (e.g., "2023-07-15T12:30:00Z")
113
- */
114
- const convertToUTCTimezone = (localDateTimeString) => {
115
- if (!localDateTimeString) {
116
- return localDateTimeString;
117
- }
118
-
119
- try {
120
- // Create a Date object using the local time string
121
- // The browser will interpret this as local timezone
122
- const localDate = new Date(localDateTimeString);
123
-
124
- // Check if the date is valid
125
- if (isNaN(localDate.getTime())) {
126
- return localDateTimeString;
127
- }
128
-
129
- // Convert to UTC ISO string
130
- const utcString = localDate.toISOString();
131
-
132
- // Return the UTC string (which includes the 'Z' notation)
133
- return utcString;
134
- } catch (error) {
135
- console.error("Error converting local datetime to UTC:", error);
136
- return localDateTimeString;
137
- }
138
- };
139
-
140
- const InputTextualWithAction = forwardRef((props, ref) => {
141
- const {
142
- id,
143
- type,
144
- action,
145
- name,
146
- value: externalValue,
147
- valueSignal,
148
- cancelOnBlurInvalid,
149
- cancelOnEscape,
150
- actionErrorEffect,
151
- readOnly,
152
- loading,
153
- onInput,
154
- onCancel,
155
- onActionPrevented,
156
- onActionStart,
157
- onActionError,
158
- onActionEnd,
159
- ...rest
160
- } = props;
161
- if (import.meta.dev && !name && !valueSignal) {
162
- console.warn(`InputTextual with action requires a name prop to be set.`);
163
- }
164
-
165
- const innerRef = useRef(null);
166
- useImperativeHandle(ref, () => innerRef.current);
167
-
168
- const [navState, setNavState] = useNavState(id);
169
- const [boundAction, value, setValue, resetValue] = useActionBoundToOneParam(
170
- action,
171
- name,
172
- valueSignal ? valueSignal : externalValue,
173
- navState,
174
- "",
175
- );
176
- const { loading: actionLoading } = useActionStatus(boundAction);
177
- const executeAction = useExecuteAction(innerRef, {
178
- errorEffect: actionErrorEffect,
179
- });
180
- useEffect(() => {
181
- setNavState(value);
182
- }, [value]);
183
-
184
- const valueAtInteractionRef = useRef(null);
185
- useOnChange(innerRef, (e) => {
186
- if (
187
- valueAtInteractionRef.current !== null &&
188
- e.target.value === valueAtInteractionRef.current
189
- ) {
190
- valueAtInteractionRef.current = null;
191
- return;
192
- }
193
- requestAction(boundAction, { event: e });
194
- });
195
-
196
- useActionEvents(innerRef, {
197
- onCancel: (e, reason) => {
198
- if (reason.startsWith("blur_invalid")) {
199
- if (!cancelOnBlurInvalid) {
200
- return;
201
- }
202
- if (
203
- // error prevent cancellation until the user closes it (or something closes it)
204
- e.detail.failedConstraintInfo.level === "error" &&
205
- e.detail.failedConstraintInfo.reportStatus !== "closed"
206
- ) {
207
- return;
208
- }
209
- }
210
- if (reason === "escape_key") {
211
- if (!cancelOnEscape) {
212
- return;
213
- }
214
- /**
215
- * Browser trigger a "change" event right after the escape is pressed
216
- * if the input value has changed.
217
- * We need to prevent the next change event otherwise we would request action when
218
- * we actually want to cancel
219
- */
220
- valueAtInteractionRef.current = e.target.value;
221
- }
222
- resetValue();
223
- onCancel?.(e, reason);
224
- },
225
- onPrevented: onActionPrevented,
226
- onAction: executeAction,
227
- onStart: onActionStart,
228
- onError: onActionError,
229
- onEnd: (e) => {
230
- setNavState(undefined);
231
- onActionEnd?.(e);
232
- },
233
- });
234
-
235
- const innerLoading = loading || actionLoading;
236
-
237
- return (
238
- <InputTextualBasic
239
- {...rest}
240
- data-action={boundAction}
241
- ref={innerRef}
242
- type={type}
243
- id={id}
244
- name={name}
245
- value={value}
246
- data-form-value={
247
- type === "datetime-local" ? convertToUTCTimezone(value) : undefined
248
- }
249
- loading={innerLoading}
250
- readOnly={readOnly || innerLoading}
251
- onInput={(e) => {
252
- valueAtInteractionRef.current = null;
253
- const inputValue =
254
- type === "number" ? e.target.valueAsNumber : e.target.value;
255
- setValue(
256
- type === "datetime-local"
257
- ? convertToUTCTimezone(inputValue)
258
- : inputValue,
259
- );
260
- onInput?.(e);
261
- }}
262
- onKeyDown={(e) => {
263
- if (e.key !== "Enter") {
264
- return;
265
- }
266
- e.preventDefault();
267
- /**
268
- * Browser trigger a "change" event right after the enter is pressed
269
- * if the input value has changed.
270
- * We need to prevent the next change event otherwise we would request action twice
271
- */
272
- valueAtInteractionRef.current = e.target.value;
273
- requestAction(boundAction, { event: e });
274
- }}
275
- />
276
- );
277
- });
278
-
279
- const InputTextualInsideForm = forwardRef((props, ref) => {
280
- const {
281
- formContext,
282
- id,
283
- name,
284
- value: externalValue,
285
- loading,
286
- readOnly,
287
- onInput,
288
- onKeyDown,
289
- ...rest
290
- } = props;
291
-
292
- const innerRef = useRef(null);
293
- useImperativeHandle(ref, () => innerRef.current);
294
-
295
- const [navState, setNavState] = useNavState(id);
296
- const { formAction, formIsBusy, formIsReadOnly, formActionRequester } =
297
- formContext;
298
- const [value, setValue] = useOneFormParam(name, externalValue, navState, "");
299
- useEffect(() => {
300
- setNavState(value);
301
- }, [value]);
302
-
303
- return (
304
- <InputTextualBasic
305
- {...rest}
306
- ref={innerRef}
307
- id={id}
308
- name={name}
309
- value={value}
310
- data-form-value={convertToUTCTimezone(value)}
311
- loading={
312
- loading || (formIsBusy && formActionRequester === innerRef.current)
313
- }
314
- readOnly={readOnly || formIsReadOnly}
315
- onInput={(e) => {
316
- const inputValue = e.target.value;
317
- setValue(convertToUTCTimezone(inputValue));
318
- onInput?.(e);
319
- }}
320
- onKeyDown={(e) => {
321
- if (e.key === "Enter") {
322
- const inputElement = e.target;
323
- const { form } = inputElement;
324
- const formSubmitButton = form.querySelector(
325
- "button[type='submit'], input[type='submit'], input[type='image']",
326
- );
327
- e.preventDefault();
328
- requestAction(formAction, {
329
- event: e,
330
- target: form,
331
- requester: formSubmitButton ? formSubmitButton : inputElement,
332
- });
333
- }
334
- onKeyDown?.(e);
335
- }}
336
- />
337
- );
338
- });
@@ -1,283 +0,0 @@
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
- });
@@ -1,20 +0,0 @@
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
- };
@@ -1,12 +0,0 @@
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
- };
@@ -1,5 +0,0 @@
1
- export {
2
- SelectionProvider,
3
- useRegisterSelectionValue,
4
- useSelectionContext,
5
- } from "./selection_context.jsx";