@jsenv/navi 0.1.1 → 0.2.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.
@@ -46,7 +46,7 @@
46
46
  <p>
47
47
  <strong>Border radius</strong>
48
48
  </p>
49
- <Button style={{ borderRadius: "20px" }}>C</Button>
49
+ <Button style={{ "--border-radius": "20px" }}>C</Button>
50
50
  </div>
51
51
  </div>
52
52
 
@@ -55,21 +55,23 @@
55
55
  <p>
56
56
  <strong>Violet border</strong>
57
57
  </p>
58
- <Button style={{ borderColor: "violet" }}>D</Button>
58
+ <Button style={{ "--border-color": "violet" }}>D</Button>
59
59
  </div>
60
60
 
61
61
  <div>
62
62
  <p>
63
63
  <strong>Border width</strong>
64
64
  </p>
65
- <Button style={{ borderWidth: "10px" }}>D</Button>
65
+ <Button style={{ "--border-width": "10px" }}>D</Button>
66
66
  </div>
67
67
 
68
68
  <div>
69
69
  <p>
70
70
  <strong>Outline width</strong>
71
71
  </p>
72
- <Button style={{ borderWidth: "5px", outlineWidth: "5px" }}>
72
+ <Button
73
+ style={{ "--border-width": "5px", "--outline-width": "5px" }}
74
+ >
73
75
  D
74
76
  </Button>
75
77
  </div>
@@ -7,59 +7,59 @@
7
7
  <title>Input Text Demo</title>
8
8
  <style>
9
9
  body {
10
+ max-width: 1200px;
11
+ margin: 0 auto;
12
+ padding: 20px;
10
13
  font-family:
11
14
  system-ui,
12
15
  -apple-system,
13
16
  sans-serif;
14
- max-width: 1200px;
15
- margin: 0 auto;
16
- padding: 20px;
17
17
  background: #f5f5f5;
18
18
  }
19
19
  .demo-grid {
20
20
  display: grid;
21
+ margin-bottom: 30px;
21
22
  grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
22
23
  gap: 20px;
23
- margin-bottom: 30px;
24
24
  }
25
25
  .demo-card {
26
+ padding: 20px;
26
27
  background: white;
27
28
  border-radius: 8px;
28
- padding: 20px;
29
29
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
30
30
  }
31
31
  .demo-title {
32
32
  margin: 0 0 15px 0;
33
+ padding-bottom: 8px;
33
34
  color: #333;
34
- font-size: 16px;
35
35
  font-weight: 600;
36
+ font-size: 16px;
36
37
  border-bottom: 2px solid #e0e0e0;
37
- padding-bottom: 8px;
38
38
  }
39
39
  .result-display {
40
+ min-height: 20px;
40
41
  margin-top: 15px;
41
42
  padding: 12px;
43
+ font-size: 14px;
44
+ font-family: monospace;
42
45
  background: #f8f9fa;
43
46
  border: 1px solid #dee2e6;
44
47
  border-radius: 4px;
45
- min-height: 20px;
46
- font-family: monospace;
47
- font-size: 14px;
48
48
  }
49
49
  .result-success {
50
+ color: #155724;
50
51
  background: #d4edda;
51
52
  border-color: #c3e6cb;
52
- color: #155724;
53
53
  }
54
54
  .result-error {
55
+ color: #721c24;
55
56
  background: #f8d7da;
56
57
  border-color: #f5c6cb;
57
- color: #721c24;
58
58
  }
59
59
  .result-loading {
60
+ color: #0c5460;
60
61
  background: #d1ecf1;
61
62
  border-color: #bee5eb;
62
- color: #0c5460;
63
63
  }
64
64
  nav a {
65
65
  color: #007bff;
@@ -76,21 +76,21 @@
76
76
  }
77
77
  h2:first-of-type {
78
78
  margin-top: 0;
79
- border-top: none;
80
79
  padding-top: 0;
80
+ border-top: none;
81
81
  }
82
82
  .button-group {
83
83
  display: flex;
84
- gap: 10px;
85
84
  margin-top: 10px;
85
+ gap: 10px;
86
86
  }
87
87
  button {
88
88
  padding: 8px 16px;
89
+ font-size: 14px;
90
+ background: white;
89
91
  border: 1px solid #ccc;
90
92
  border-radius: 4px;
91
- background: white;
92
93
  cursor: pointer;
93
- font-size: 14px;
94
94
  }
95
95
  button:hover {
96
96
  background: #f8f9fa;
@@ -100,18 +100,12 @@
100
100
  }
101
101
  label {
102
102
  margin-bottom: 15px;
103
- font-weight: 500;
104
103
  color: #555;
104
+ font-weight: 500;
105
105
  }
106
106
  label input {
107
- display: block;
108
- margin-top: 5px;
109
- width: 100%;
107
+ margin-left: 5px;
110
108
  padding: 8px 12px;
111
- border: 1px solid #ccc;
112
- border-radius: 4px;
113
- font-size: 14px;
114
- box-sizing: border-box;
115
109
  }
116
110
  </style>
117
111
  </head>
@@ -1,16 +1,23 @@
1
- import { resolveCSSSize } from "@jsenv/dom";
2
1
  import { forwardRef } from "preact/compat";
3
- import { useContext, useImperativeHandle, useRef } from "preact/hooks";
2
+ import {
3
+ useContext,
4
+ useImperativeHandle,
5
+ useLayoutEffect,
6
+ useRef,
7
+ } from "preact/hooks";
4
8
 
9
+ import { getActionPrivateProperties } from "../../action_private_properties.js";
5
10
  import { useActionStatus } from "../../use_action_status.js";
6
11
  import { requestAction } from "../../validation/custom_constraint_validation.js";
7
12
  import { useConstraints } from "../../validation/hooks/use_constraints.js";
13
+ import { FormActionContext } from "../action_execution/form_context.js";
8
14
  import { renderActionableComponent } from "../action_execution/render_actionable_component.jsx";
9
15
  import { useAction } from "../action_execution/use_action.js";
10
16
  import { useExecuteAction } from "../action_execution/use_execute_action.js";
11
- import { LoadableInlineElement } from "../loader/loader_background.jsx";
17
+ import { LoaderBackground } from "../loader/loader_background.jsx";
12
18
  import { useAutoFocus } from "../use_auto_focus.js";
13
- import "./field_css.js";
19
+ import { initCustomField } from "./custom_field.js";
20
+ import "./navi_css_vars.js";
14
21
  import { useActionEvents } from "./use_action_events.js";
15
22
  import { useFormEvents } from "./use_form_events.js";
16
23
  import {
@@ -31,47 +38,146 @@ import {
31
38
  * So we redefine chrome styles so that loader can keep up with the actual color visible to the user
32
39
  */
33
40
  import.meta.css = /* css */ `
34
- button[data-custom] {
35
- border: none;
36
- background: none;
37
- display: inline-block;
38
- padding: 0;
39
- }
41
+ @layer navi {
42
+ .navi_button {
43
+ position: relative;
44
+ display: inline-block;
45
+ padding: 0;
46
+ background: none;
47
+ border: none;
48
+ outline: none;
40
49
 
41
- button[data-custom] .navi_button_content {
42
- transition-duration: 0.15s;
43
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
44
- transition-property: transform;
45
- display: inline-flex;
46
- position: relative;
47
- padding-block: 1px;
48
- padding-inline: 6px;
49
- }
50
+ --border-width: 1px;
51
+ --outline-width: 1px;
52
+ --outer-width: calc(var(--border-width) + var(--outline-width));
53
+ --padding-x: 6px;
54
+ --padding-y: 1px;
50
55
 
51
- button[data-custom]:active .navi_button_content {
52
- transform: scale(0.9);
53
- }
56
+ --outline-color: light-dark(#4476ff, #3b82f6);
54
57
 
55
- button[data-custom]:disabled .navi_button_content {
56
- transform: none;
57
- }
58
+ --border-radius: 2px;
59
+ --border-color: light-dark(#767676, #8e8e93);
60
+ --border-color-hover: color-mix(in srgb, var(--border-color) 70%, black);
61
+ --border-color-active: color-mix(in srgb, var(--border-color) 90%, black);
62
+ --border-color-readonly: color-mix(
63
+ in srgb,
64
+ var(--border-color) 30%,
65
+ white
66
+ );
67
+ --border-color-disabled: var(--border-color-readonly);
58
68
 
59
- button[data-custom] .navi_button_shadow {
60
- position: absolute;
61
- inset: calc(-1 * (var(--field-border-width) + var(--field-outline-width)));
62
- pointer-events: none;
63
- border-radius: inherit;
64
- }
65
- button[data-custom]:active .navi_button_shadow {
66
- box-shadow:
67
- inset 0 3px 6px rgba(0, 0, 0, 0.2),
68
- inset 0 1px 2px rgba(0, 0, 0, 0.3),
69
- inset 0 0 0 1px rgba(0, 0, 0, 0.1),
70
- inset 2px 0 4px rgba(0, 0, 0, 0.1),
71
- inset -2px 0 4px rgba(0, 0, 0, 0.1);
72
- }
73
- button[data-custom]:disabled > .navi_button_shadow {
74
- box-shadow: none;
69
+ --background-color: light-dark(#f3f4f6, #2d3748);
70
+ --background-color-hover: color-mix(
71
+ in srgb,
72
+ var(--background-color) 95%,
73
+ black
74
+ );
75
+ --background-color-readonly: var(--background-color);
76
+ --background-color-disabled: var(--background-color);
77
+
78
+ --color: currentColor;
79
+ --color-readonly: color-mix(in srgb, currentColor 30%, transparent);
80
+ --color-disabled: var(--color-readonly);
81
+ }
82
+ .navi_button_content {
83
+ position: relative;
84
+ display: inline-flex;
85
+ padding-top: var(--padding-y);
86
+ padding-right: var(--padding-x);
87
+ padding-bottom: var(--padding-y);
88
+ padding-left: var(--padding-x);
89
+ color: var(--color);
90
+ background-color: var(--background-color);
91
+ border-width: var(--outer-width);
92
+ border-style: solid;
93
+ border-color: transparent;
94
+ border-radius: var(--border-radius);
95
+ outline-width: var(--border-width);
96
+ outline-style: solid;
97
+ outline-color: var(--border-color);
98
+ outline-offset: calc(-1 * (var(--border-width)));
99
+ transition-property: transform;
100
+ transition-duration: 0.15s;
101
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
102
+ }
103
+ .navi_button_shadow {
104
+ position: absolute;
105
+ inset: calc(-1 * var(--outer-width));
106
+ border-radius: inherit;
107
+ pointer-events: none;
108
+ }
109
+ /* Focus */
110
+ .navi_button[data-focus-visible] .navi_button_content {
111
+ --border-color: var(--outline-color);
112
+ outline-width: var(--outer-width);
113
+ outline-offset: calc(-1 * var(--outer-width));
114
+ }
115
+ /* Hover */
116
+ .navi_button[data-hover] .navi_button_content {
117
+ --border-color: var(--border-color-hover);
118
+ --background-color: var(--background-color-hover);
119
+ }
120
+ /* Active */
121
+ .navi_button[data-active] .navi_button_content {
122
+ --outline-color: var(--border-color-active);
123
+ --background-color: none;
124
+ transform: scale(0.9);
125
+ }
126
+ .navi_button[data-active] .navi_button_shadow {
127
+ box-shadow:
128
+ inset 0 3px 6px rgba(0, 0, 0, 0.2),
129
+ inset 0 1px 2px rgba(0, 0, 0, 0.3),
130
+ inset 0 0 0 1px rgba(0, 0, 0, 0.1),
131
+ inset 2px 0 4px rgba(0, 0, 0, 0.1),
132
+ inset -2px 0 4px rgba(0, 0, 0, 0.1);
133
+ }
134
+ /* Readonly */
135
+ .navi_button[data-readonly] .navi_button_content {
136
+ --border-color: var(--border-color-disabled);
137
+ --outline-color: var(--border-color-readonly);
138
+ --background-color: var(--background-color-readonly);
139
+ --color: var(--color-readonly);
140
+ }
141
+ /* Disabled */
142
+ .navi_button[data-disabled] .navi_button_content {
143
+ --border-color: var(--border-color-disabled);
144
+ --background-color: var(--background-color-disabled);
145
+ --color: var(--color-disabled);
146
+ transform: none; /* no active effect */
147
+ }
148
+ .navi_button[data-disabled] .navi_button_shadow {
149
+ box-shadow: none;
150
+ }
151
+ /* Invalid */
152
+ .navi_button[aria-invalid="true"] .navi_button_content {
153
+ --border-color: var(--invalid-color);
154
+ }
155
+
156
+ /* Discrete variant */
157
+ .navi_button[data-discrete] .navi_button_content {
158
+ --background-color: transparent;
159
+ --border-color: transparent;
160
+ }
161
+ .navi_button[data-discrete][data-hover] .navi_button_content {
162
+ --border-color: var(--border-color-hover);
163
+ }
164
+ .navi_button[data-discrete][data-readonly] .navi_button_content {
165
+ --border-color: transparent;
166
+ }
167
+ .navi_button[data-discrete][data-disabled] .navi_button_content {
168
+ --border-color: transparent;
169
+ }
170
+ button[data-discrete] {
171
+ background-color: transparent;
172
+ border-color: transparent;
173
+ }
174
+ button[data-discrete]:hover {
175
+ border-color: revert;
176
+ }
177
+ button[data-discrete][data-readonly],
178
+ button[data-discrete][data-disabled] {
179
+ border-color: transparent;
180
+ }
75
181
  }
76
182
  `;
77
183
  export const Button = forwardRef((props, ref) => {
@@ -94,9 +200,8 @@ const ButtonBasic = forwardRef((props, ref) => {
94
200
  loading,
95
201
  constraints = [],
96
202
  autoFocus,
97
- appearance = "custom",
203
+ appearance = "navi",
98
204
  discrete,
99
- style = {},
100
205
  children,
101
206
  ...rest
102
207
  } = props;
@@ -109,57 +214,49 @@ const ButtonBasic = forwardRef((props, ref) => {
109
214
  loading || (contextLoading && contextLoadingElement === innerRef.current);
110
215
  const innerReadOnly = readOnly || contextReadOnly || innerLoading;
111
216
  const innerDisabled = disabled || contextDisabled;
112
- let {
113
- border,
114
- borderWidth = border === "none" || discrete ? 0 : 1,
115
- outlineWidth = discrete ? 0 : 1,
116
- borderColor = "light-dark(#767676, #8e8e93)",
117
- ...restStyle
118
- } = style;
119
- borderWidth = resolveCSSSize(borderWidth);
120
- outlineWidth = resolveCSSSize(outlineWidth);
217
+
218
+ let buttonChildren;
219
+ if (appearance === "navi") {
220
+ buttonChildren = <NaviButton buttonRef={innerRef}>{children}</NaviButton>;
221
+ } else {
222
+ buttonChildren = children;
223
+ }
121
224
 
122
225
  return (
123
226
  <button
124
227
  {...rest}
125
228
  ref={innerRef}
126
- data-custom={appearance === "custom" ? "" : undefined}
127
- data-readonly-silent={innerReadOnly ? "" : undefined}
229
+ className={appearance === "navi" ? "navi_button" : undefined}
230
+ data-discrete={discrete ? "" : undefined}
128
231
  data-readonly={innerReadOnly ? "" : undefined}
232
+ data-readonly-silent={innerLoading ? "" : undefined}
233
+ data-disabled={innerDisabled ? "" : undefined}
234
+ data-validation-message-arrow-x="center"
129
235
  aria-busy={innerLoading}
130
- style={{
131
- ...restStyle,
132
- }}
133
236
  >
134
- <LoadableInlineElement
237
+ <LoaderBackground
135
238
  loading={innerLoading}
136
239
  inset={-1}
137
240
  color="light-dark(#355fcc, #3b82f6)"
138
241
  >
139
- <span
140
- className="navi_button_content"
141
- data-field=""
142
- data-field-with-background=""
143
- data-field-with-hover=""
144
- data-field-with-border={borderWidth ? "" : undefined}
145
- data-field-with-border-hover={discrete ? "" : undefined}
146
- data-field-with-background-hover={discrete ? "" : undefined}
147
- data-validation-message-arrow-x="center"
148
- data-readonly={innerReadOnly ? "" : undefined}
149
- data-disabled={innerDisabled ? "" : undefined}
150
- style={{
151
- "--field-border-width": `${borderWidth}px`,
152
- "--field-outline-width": `${outlineWidth}px`,
153
- "--field-border-color": borderColor,
154
- }}
155
- >
156
- {children}
157
- <span className="navi_button_shadow"></span>
158
- </span>
159
- </LoadableInlineElement>
242
+ {buttonChildren}
243
+ </LoaderBackground>
160
244
  </button>
161
245
  );
162
246
  });
247
+ const NaviButton = ({ buttonRef, children }) => {
248
+ const ref = useRef();
249
+ useLayoutEffect(() => {
250
+ return initCustomField(buttonRef.current, buttonRef.current);
251
+ }, []);
252
+
253
+ return (
254
+ <span ref={ref} className="navi_button_content">
255
+ {children}
256
+ <span className="navi_button_shadow"></span>
257
+ </span>
258
+ );
259
+ };
163
260
 
164
261
  const ButtonWithAction = forwardRef((props, ref) => {
165
262
  const {
@@ -217,13 +314,22 @@ const ButtonWithAction = forwardRef((props, ref) => {
217
314
  });
218
315
 
219
316
  const ButtonInsideForm = forwardRef((props, ref) => {
220
- const { formContext, type, onClick, children, loading, ...rest } = props;
221
- const formLoading = formContext.loading;
317
+ const {
318
+ // eslint-disable-next-line no-unused-vars
319
+ formContext,
320
+ type,
321
+ onClick,
322
+ children,
323
+ loading,
324
+ readOnly,
325
+ ...rest
326
+ } = props;
222
327
  const innerRef = useRef();
223
328
  useImperativeHandle(ref, () => innerRef.current);
224
329
 
225
330
  const wouldSubmitFormByType = type === "submit" || type === "image";
226
- const innerLoading = loading || (formLoading && wouldSubmitFormByType);
331
+ const innerLoading = loading;
332
+ const innerReadOnly = readOnly;
227
333
  const handleClick = (event) => {
228
334
  const buttonElement = innerRef.current;
229
335
  const { form } = buttonElement;
@@ -262,6 +368,7 @@ const ButtonInsideForm = forwardRef((props, ref) => {
262
368
  ref={innerRef}
263
369
  type={type}
264
370
  loading={innerLoading}
371
+ readOnly={innerReadOnly}
265
372
  onClick={(event) => {
266
373
  handleClick(event);
267
374
  onClick?.(event);
@@ -273,8 +380,10 @@ const ButtonInsideForm = forwardRef((props, ref) => {
273
380
  });
274
381
 
275
382
  const ButtonWithActionInsideForm = forwardRef((props, ref) => {
383
+ const formAction = useContext(FormActionContext);
276
384
  const {
277
- formContext,
385
+ // eslint-disable-next-line no-unused-vars
386
+ formContext, // to avoid passing it to the button element
278
387
  type,
279
388
  action,
280
389
  loading,
@@ -294,7 +403,7 @@ const ButtonWithActionInsideForm = forwardRef((props, ref) => {
294
403
  `<Button type="${type}" /> should not have their own action`,
295
404
  );
296
405
  }
297
- const { formParamsSignal } = formContext;
406
+ const formParamsSignal = getActionPrivateProperties(formAction).paramsSignal;
298
407
  const innerRef = useRef();
299
408
  useImperativeHandle(ref, () => innerRef.current);
300
409
  const actionBoundToFormParams = useAction(action, formParamsSignal);
@@ -30,9 +30,11 @@ import {
30
30
  } from "./use_ui_state_controller.js";
31
31
 
32
32
  import.meta.css = /* css */ `
33
- .navi_checkbox_list {
34
- display: flex;
35
- flex-direction: column;
33
+ @layer navi {
34
+ .navi_checkbox_list {
35
+ display: flex;
36
+ flex-direction: column;
37
+ }
36
38
  }
37
39
  `;
38
40
 
@@ -0,0 +1,106 @@
1
+ import { createPubSub } from "@jsenv/dom";
2
+
3
+ export const initCustomField = (customField, field) => {
4
+ const [teardown, addTeardown] = createPubSub();
5
+
6
+ const addEventListener = (element, eventType, listener) => {
7
+ element.addEventListener(eventType, listener);
8
+ return addTeardown(() => {
9
+ element.removeEventListener(eventType, listener);
10
+ });
11
+ };
12
+ const updateBooleanAttribute = (attributeName, isPresent) => {
13
+ if (isPresent) {
14
+ customField.setAttribute(attributeName, "");
15
+ } else {
16
+ customField.removeAttribute(attributeName);
17
+ }
18
+ };
19
+ const checkPseudoClasses = () => {
20
+ const hover = field.matches(":hover");
21
+ const active = field.matches(":active");
22
+ const checked = field.matches(":checked");
23
+ const focus = field.matches(":focus");
24
+ const focusVisible = field.matches(":focus-visible");
25
+ const valid = field.matches(":valid");
26
+ const invalid = field.matches(":invalid");
27
+ updateBooleanAttribute(`data-hover`, hover);
28
+ updateBooleanAttribute(`data-active`, active);
29
+ updateBooleanAttribute(`data-checked`, checked);
30
+ updateBooleanAttribute(`data-focus`, focus);
31
+ updateBooleanAttribute(`data-focus-visible`, focusVisible);
32
+ updateBooleanAttribute(`data-valid`, valid);
33
+ updateBooleanAttribute(`data-invalid`, invalid);
34
+ };
35
+
36
+ // :hover
37
+ addEventListener(field, "mouseenter", checkPseudoClasses);
38
+ addEventListener(field, "mouseleave", checkPseudoClasses);
39
+ // :active
40
+ addEventListener(field, "mousedown", checkPseudoClasses);
41
+ addEventListener(document, "mouseup", checkPseudoClasses);
42
+ // :focus
43
+ addEventListener(field, "focusin", checkPseudoClasses);
44
+ addEventListener(field, "focusout", checkPseudoClasses);
45
+ // :focus-visible
46
+ addEventListener(document, "keydown", checkPseudoClasses);
47
+ addEventListener(document, "keyup", checkPseudoClasses);
48
+ // :checked
49
+ if (field.type === "checkbox") {
50
+ // Listen to user interactions
51
+ addEventListener(field, "input", checkPseudoClasses);
52
+
53
+ // Intercept programmatic changes to .checked property
54
+ const originalDescriptor = Object.getOwnPropertyDescriptor(
55
+ HTMLInputElement.prototype,
56
+ "checked",
57
+ );
58
+ Object.defineProperty(field, "checked", {
59
+ get: originalDescriptor.get,
60
+ set(value) {
61
+ originalDescriptor.set.call(this, value);
62
+ checkPseudoClasses();
63
+ },
64
+ configurable: true,
65
+ });
66
+ addTeardown(() => {
67
+ // Restore original property descriptor
68
+ Object.defineProperty(field, "checked", originalDescriptor);
69
+ });
70
+ } else if (field.type === "radio") {
71
+ // Listen to changes on the radio group
72
+ const radioSet =
73
+ field.closest("[data-radio-list], fieldset, form") || document;
74
+ addEventListener(radioSet, "input", checkPseudoClasses);
75
+
76
+ // Intercept programmatic changes to .checked property
77
+ const originalDescriptor = Object.getOwnPropertyDescriptor(
78
+ HTMLInputElement.prototype,
79
+ "checked",
80
+ );
81
+ Object.defineProperty(field, "checked", {
82
+ get: originalDescriptor.get,
83
+ set(value) {
84
+ originalDescriptor.set.call(this, value);
85
+ checkPseudoClasses();
86
+ },
87
+ configurable: true,
88
+ });
89
+ addTeardown(() => {
90
+ // Restore original property descriptor
91
+ Object.defineProperty(field, "checked", originalDescriptor);
92
+ });
93
+ } else if (field.tagName === "INPUT") {
94
+ addEventListener(field, "input", checkPseudoClasses);
95
+ }
96
+
97
+ // just in case + catch use forcing them in chrome devtools
98
+ const interval = setInterval(() => {
99
+ checkPseudoClasses();
100
+ }, 150);
101
+ addTeardown(() => {
102
+ clearInterval(interval);
103
+ });
104
+
105
+ return teardown;
106
+ };
@@ -18,7 +18,10 @@ import { useContext, useImperativeHandle, useMemo, useRef } from "preact/hooks";
18
18
 
19
19
  import { requestAction } from "../../validation/custom_constraint_validation.js";
20
20
  import { useConstraints } from "../../validation/hooks/use_constraints.js";
21
- import { FormContext } from "../action_execution/form_context.js";
21
+ import {
22
+ FormActionContext,
23
+ FormContext,
24
+ } from "../action_execution/form_context.js";
22
25
  import { renderActionableComponent } from "../action_execution/render_actionable_component.jsx";
23
26
  import { useActionBoundToOneParam } from "../action_execution/use_action.js";
24
27
  import { useExecuteAction } from "../action_execution/use_execute_action.js";
@@ -193,9 +196,11 @@ const FormWithAction = forwardRef((props, ref) => {
193
196
  });
194
197
  }}
195
198
  >
196
- <LoadingElementContext.Provider value={formActionRequester}>
197
- {children}
198
- </LoadingElementContext.Provider>
199
+ <FormActionContext.Provider value={actionBoundToUIState}>
200
+ <LoadingElementContext.Provider value={formActionRequester}>
201
+ {children}
202
+ </LoadingElementContext.Provider>
203
+ </FormActionContext.Provider>
199
204
  </FormBasic>
200
205
  );
201
206
  });