@jsenv/navi 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/jsenv_navi.js +8399 -3784
- package/package.json +2 -2
- package/src/components/action_execution/form_context.js +1 -4
- package/src/components/action_execution/use_action.js +2 -2
- package/src/components/demos/0_button_demo.html +149 -68
- package/src/components/demos/1_checkbox_demo.html +122 -2
- package/src/components/demos/2_input_textual_demo.html +44 -55
- package/src/components/demos/3_radio_demo.html +811 -157
- package/src/components/demos/action/0_button_demo.html +6 -4
- package/src/components/demos/action/1_input_text_demo.html +19 -25
- package/src/components/field/button.jsx +174 -71
- package/src/components/field/custom_field.js +106 -0
- package/src/components/field/field_css.js +51 -85
- package/src/components/field/form.jsx +9 -4
- package/src/components/field/input_checkbox.jsx +170 -99
- package/src/components/field/input_radio.jsx +179 -153
- package/src/components/field/input_textual.jsx +96 -5
- package/src/components/field/label.jsx +15 -3
- package/src/components/field/navi_css_vars.js +8 -0
- package/src/components/loader/loader_background.jsx +41 -14
- package/src/validation/constraints/readonly_constraint.js +5 -0
- package/src/validation/validation_message.js +78 -70
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
<p>
|
|
47
47
|
<strong>Border radius</strong>
|
|
48
48
|
</p>
|
|
49
|
-
<Button style={{
|
|
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={{
|
|
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={{
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
17
|
+
import { LoaderBackground } from "../loader/loader_background.jsx";
|
|
12
18
|
import { useAutoFocus } from "../use_auto_focus.js";
|
|
13
|
-
import "./
|
|
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,38 +38,87 @@ 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
|
-
|
|
35
|
-
|
|
36
|
-
background: none;
|
|
41
|
+
.navi_button {
|
|
42
|
+
position: relative;
|
|
37
43
|
display: inline-block;
|
|
38
44
|
padding: 0;
|
|
39
|
-
|
|
45
|
+
background: none;
|
|
46
|
+
border: none;
|
|
47
|
+
outline: none;
|
|
40
48
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
position: relative;
|
|
47
|
-
padding-block: 1px;
|
|
48
|
-
padding-inline: 6px;
|
|
49
|
-
}
|
|
49
|
+
--border-width: 1px;
|
|
50
|
+
--outline-width: 1px;
|
|
51
|
+
--outer-width: calc(var(--border-width) + var(--outline-width));
|
|
52
|
+
--padding-x: 6px;
|
|
53
|
+
--padding-y: 1px;
|
|
50
54
|
|
|
51
|
-
|
|
52
|
-
transform: scale(0.9);
|
|
53
|
-
}
|
|
55
|
+
--outline-color: light-dark(#4476ff, #3b82f6);
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
--border-radius: 2px;
|
|
58
|
+
--border-color: light-dark(#767676, #8e8e93);
|
|
59
|
+
--border-color-hover: color-mix(in srgb, var(--border-color) 70%, black);
|
|
60
|
+
--border-color-active: color-mix(in srgb, var(--border-color) 90%, black);
|
|
61
|
+
--border-color-readonly: color-mix(in srgb, var(--border-color) 30%, white);
|
|
62
|
+
--border-color-disabled: var(--border-color-readonly);
|
|
63
|
+
|
|
64
|
+
--background-color: light-dark(#f3f4f6, #2d3748);
|
|
65
|
+
--background-color-hover: color-mix(
|
|
66
|
+
in srgb,
|
|
67
|
+
var(--background-color) 95%,
|
|
68
|
+
black
|
|
69
|
+
);
|
|
70
|
+
--background-color-readonly: var(--background-color);
|
|
71
|
+
--background-color-disabled: var(--background-color);
|
|
58
72
|
|
|
59
|
-
|
|
73
|
+
--color: currentColor;
|
|
74
|
+
--color-readonly: color-mix(in srgb, currentColor 30%, transparent);
|
|
75
|
+
--color-disabled: var(--color-readonly);
|
|
76
|
+
}
|
|
77
|
+
.navi_button_content {
|
|
78
|
+
position: relative;
|
|
79
|
+
display: inline-flex;
|
|
80
|
+
padding-top: var(--padding-y);
|
|
81
|
+
padding-right: var(--padding-x);
|
|
82
|
+
padding-bottom: var(--padding-y);
|
|
83
|
+
padding-left: var(--padding-x);
|
|
84
|
+
color: var(--color);
|
|
85
|
+
background-color: var(--background-color);
|
|
86
|
+
border-width: var(--outer-width);
|
|
87
|
+
border-style: solid;
|
|
88
|
+
border-color: transparent;
|
|
89
|
+
border-radius: var(--border-radius);
|
|
90
|
+
outline-width: var(--border-width);
|
|
91
|
+
outline-style: solid;
|
|
92
|
+
outline-color: var(--border-color);
|
|
93
|
+
outline-offset: calc(-1 * (var(--border-width)));
|
|
94
|
+
transition-property: transform;
|
|
95
|
+
transition-duration: 0.15s;
|
|
96
|
+
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
97
|
+
}
|
|
98
|
+
.navi_button_shadow {
|
|
60
99
|
position: absolute;
|
|
61
|
-
inset: calc(-1 *
|
|
62
|
-
pointer-events: none;
|
|
100
|
+
inset: calc(-1 * var(--outer-width));
|
|
63
101
|
border-radius: inherit;
|
|
102
|
+
pointer-events: none;
|
|
64
103
|
}
|
|
65
|
-
|
|
104
|
+
/* Focus */
|
|
105
|
+
.navi_button[data-focus-visible] .navi_button_content {
|
|
106
|
+
--border-color: var(--outline-color);
|
|
107
|
+
outline-width: var(--outer-width);
|
|
108
|
+
outline-offset: calc(-1 * var(--outer-width));
|
|
109
|
+
}
|
|
110
|
+
/* Hover */
|
|
111
|
+
.navi_button[data-hover] .navi_button_content {
|
|
112
|
+
--border-color: var(--border-color-hover);
|
|
113
|
+
--background-color: var(--background-color-hover);
|
|
114
|
+
}
|
|
115
|
+
/* Active */
|
|
116
|
+
.navi_button[data-active] .navi_button_content {
|
|
117
|
+
--outline-color: var(--border-color-active);
|
|
118
|
+
--background-color: none;
|
|
119
|
+
transform: scale(0.9);
|
|
120
|
+
}
|
|
121
|
+
.navi_button[data-active] .navi_button_shadow {
|
|
66
122
|
box-shadow:
|
|
67
123
|
inset 0 3px 6px rgba(0, 0, 0, 0.2),
|
|
68
124
|
inset 0 1px 2px rgba(0, 0, 0, 0.3),
|
|
@@ -70,9 +126,53 @@ import.meta.css = /* css */ `
|
|
|
70
126
|
inset 2px 0 4px rgba(0, 0, 0, 0.1),
|
|
71
127
|
inset -2px 0 4px rgba(0, 0, 0, 0.1);
|
|
72
128
|
}
|
|
73
|
-
|
|
129
|
+
/* Readonly */
|
|
130
|
+
.navi_button[data-readonly] .navi_button_content {
|
|
131
|
+
--border-color: var(--border-color-disabled);
|
|
132
|
+
--outline-color: var(--border-color-readonly);
|
|
133
|
+
--background-color: var(--background-color-readonly);
|
|
134
|
+
--color: var(--color-readonly);
|
|
135
|
+
}
|
|
136
|
+
/* Disabled */
|
|
137
|
+
.navi_button[data-disabled] .navi_button_content {
|
|
138
|
+
--border-color: var(--border-color-disabled);
|
|
139
|
+
--background-color: var(--background-color-disabled);
|
|
140
|
+
--color: var(--color-disabled);
|
|
141
|
+
transform: none; /* no active effect */
|
|
142
|
+
}
|
|
143
|
+
.navi_button[data-disabled] .navi_button_shadow {
|
|
74
144
|
box-shadow: none;
|
|
75
145
|
}
|
|
146
|
+
/* Invalid */
|
|
147
|
+
.navi_button[aria-invalid="true"] .navi_button_content {
|
|
148
|
+
--border-color: var(--invalid-color);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* Discrete variant */
|
|
152
|
+
.navi_button[data-discrete] .navi_button_content {
|
|
153
|
+
--background-color: transparent;
|
|
154
|
+
--border-color: transparent;
|
|
155
|
+
}
|
|
156
|
+
.navi_button[data-discrete][data-hover] .navi_button_content {
|
|
157
|
+
--border-color: var(--border-color-hover);
|
|
158
|
+
}
|
|
159
|
+
.navi_button[data-discrete][data-readonly] .navi_button_content {
|
|
160
|
+
--border-color: transparent;
|
|
161
|
+
}
|
|
162
|
+
.navi_button[data-discrete][data-disabled] .navi_button_content {
|
|
163
|
+
--border-color: transparent;
|
|
164
|
+
}
|
|
165
|
+
button[data-discrete] {
|
|
166
|
+
background-color: transparent;
|
|
167
|
+
border-color: transparent;
|
|
168
|
+
}
|
|
169
|
+
button[data-discrete]:hover {
|
|
170
|
+
border-color: revert;
|
|
171
|
+
}
|
|
172
|
+
button[data-discrete][data-readonly],
|
|
173
|
+
button[data-discrete][data-disabled] {
|
|
174
|
+
border-color: transparent;
|
|
175
|
+
}
|
|
76
176
|
`;
|
|
77
177
|
export const Button = forwardRef((props, ref) => {
|
|
78
178
|
return renderActionableComponent(props, ref, {
|
|
@@ -94,9 +194,8 @@ const ButtonBasic = forwardRef((props, ref) => {
|
|
|
94
194
|
loading,
|
|
95
195
|
constraints = [],
|
|
96
196
|
autoFocus,
|
|
97
|
-
appearance = "
|
|
197
|
+
appearance = "navi",
|
|
98
198
|
discrete,
|
|
99
|
-
style = {},
|
|
100
199
|
children,
|
|
101
200
|
...rest
|
|
102
201
|
} = props;
|
|
@@ -109,57 +208,49 @@ const ButtonBasic = forwardRef((props, ref) => {
|
|
|
109
208
|
loading || (contextLoading && contextLoadingElement === innerRef.current);
|
|
110
209
|
const innerReadOnly = readOnly || contextReadOnly || innerLoading;
|
|
111
210
|
const innerDisabled = disabled || contextDisabled;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
borderWidth = resolveCSSSize(borderWidth);
|
|
120
|
-
outlineWidth = resolveCSSSize(outlineWidth);
|
|
211
|
+
|
|
212
|
+
let buttonChildren;
|
|
213
|
+
if (appearance === "navi") {
|
|
214
|
+
buttonChildren = <NaviButton buttonRef={innerRef}>{children}</NaviButton>;
|
|
215
|
+
} else {
|
|
216
|
+
buttonChildren = children;
|
|
217
|
+
}
|
|
121
218
|
|
|
122
219
|
return (
|
|
123
220
|
<button
|
|
124
221
|
{...rest}
|
|
125
222
|
ref={innerRef}
|
|
126
|
-
|
|
127
|
-
data-
|
|
223
|
+
className={appearance === "navi" ? "navi_button" : undefined}
|
|
224
|
+
data-discrete={discrete ? "" : undefined}
|
|
128
225
|
data-readonly={innerReadOnly ? "" : undefined}
|
|
226
|
+
data-readonly-silent={innerLoading ? "" : undefined}
|
|
227
|
+
data-disabled={innerDisabled ? "" : undefined}
|
|
228
|
+
data-validation-message-arrow-x="center"
|
|
129
229
|
aria-busy={innerLoading}
|
|
130
|
-
style={{
|
|
131
|
-
...restStyle,
|
|
132
|
-
}}
|
|
133
230
|
>
|
|
134
|
-
<
|
|
231
|
+
<LoaderBackground
|
|
135
232
|
loading={innerLoading}
|
|
136
233
|
inset={-1}
|
|
137
234
|
color="light-dark(#355fcc, #3b82f6)"
|
|
138
235
|
>
|
|
139
|
-
|
|
140
|
-
|
|
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>
|
|
236
|
+
{buttonChildren}
|
|
237
|
+
</LoaderBackground>
|
|
160
238
|
</button>
|
|
161
239
|
);
|
|
162
240
|
});
|
|
241
|
+
const NaviButton = ({ buttonRef, children }) => {
|
|
242
|
+
const ref = useRef();
|
|
243
|
+
useLayoutEffect(() => {
|
|
244
|
+
return initCustomField(buttonRef.current, buttonRef.current);
|
|
245
|
+
}, []);
|
|
246
|
+
|
|
247
|
+
return (
|
|
248
|
+
<span ref={ref} className="navi_button_content">
|
|
249
|
+
{children}
|
|
250
|
+
<span className="navi_button_shadow"></span>
|
|
251
|
+
</span>
|
|
252
|
+
);
|
|
253
|
+
};
|
|
163
254
|
|
|
164
255
|
const ButtonWithAction = forwardRef((props, ref) => {
|
|
165
256
|
const {
|
|
@@ -217,13 +308,22 @@ const ButtonWithAction = forwardRef((props, ref) => {
|
|
|
217
308
|
});
|
|
218
309
|
|
|
219
310
|
const ButtonInsideForm = forwardRef((props, ref) => {
|
|
220
|
-
const {
|
|
221
|
-
|
|
311
|
+
const {
|
|
312
|
+
// eslint-disable-next-line no-unused-vars
|
|
313
|
+
formContext,
|
|
314
|
+
type,
|
|
315
|
+
onClick,
|
|
316
|
+
children,
|
|
317
|
+
loading,
|
|
318
|
+
readOnly,
|
|
319
|
+
...rest
|
|
320
|
+
} = props;
|
|
222
321
|
const innerRef = useRef();
|
|
223
322
|
useImperativeHandle(ref, () => innerRef.current);
|
|
224
323
|
|
|
225
324
|
const wouldSubmitFormByType = type === "submit" || type === "image";
|
|
226
|
-
const innerLoading = loading
|
|
325
|
+
const innerLoading = loading;
|
|
326
|
+
const innerReadOnly = readOnly;
|
|
227
327
|
const handleClick = (event) => {
|
|
228
328
|
const buttonElement = innerRef.current;
|
|
229
329
|
const { form } = buttonElement;
|
|
@@ -262,6 +362,7 @@ const ButtonInsideForm = forwardRef((props, ref) => {
|
|
|
262
362
|
ref={innerRef}
|
|
263
363
|
type={type}
|
|
264
364
|
loading={innerLoading}
|
|
365
|
+
readOnly={innerReadOnly}
|
|
265
366
|
onClick={(event) => {
|
|
266
367
|
handleClick(event);
|
|
267
368
|
onClick?.(event);
|
|
@@ -273,8 +374,10 @@ const ButtonInsideForm = forwardRef((props, ref) => {
|
|
|
273
374
|
});
|
|
274
375
|
|
|
275
376
|
const ButtonWithActionInsideForm = forwardRef((props, ref) => {
|
|
377
|
+
const formAction = useContext(FormActionContext);
|
|
276
378
|
const {
|
|
277
|
-
|
|
379
|
+
// eslint-disable-next-line no-unused-vars
|
|
380
|
+
formContext, // to avoid passing it to the button element
|
|
278
381
|
type,
|
|
279
382
|
action,
|
|
280
383
|
loading,
|
|
@@ -294,7 +397,7 @@ const ButtonWithActionInsideForm = forwardRef((props, ref) => {
|
|
|
294
397
|
`<Button type="${type}" /> should not have their own action`,
|
|
295
398
|
);
|
|
296
399
|
}
|
|
297
|
-
const
|
|
400
|
+
const formParamsSignal = getActionPrivateProperties(formAction).paramsSignal;
|
|
298
401
|
const innerRef = useRef();
|
|
299
402
|
useImperativeHandle(ref, () => innerRef.current);
|
|
300
403
|
const actionBoundToFormParams = useAction(action, formParamsSignal);
|
|
@@ -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
|
+
};
|