@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.
- package/dist/jsenv_navi.js +8349 -3784
- package/package.json +3 -4
- 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 +193 -84
- package/src/components/field/checkbox_list.jsx +5 -3
- package/src/components/field/custom_field.js +106 -0
- package/src/components/field/form.jsx +9 -4
- package/src/components/field/input_checkbox.jsx +206 -125
- package/src/components/field/input_radio.jsx +224 -188
- package/src/components/field/input_textual.jsx +98 -6
- package/src/components/field/label.jsx +18 -4
- package/src/components/field/navi_css_vars.js +10 -0
- package/src/components/field/radio_list.jsx +5 -3
- package/src/components/loader/loader_background.jsx +41 -14
- package/src/validation/constraints/readonly_constraint.js +5 -0
- package/src/validation/validation_message.js +159 -147
- package/src/components/field/field_css.js +0 -121
|
@@ -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,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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
52
|
-
transform: scale(0.9);
|
|
53
|
-
}
|
|
56
|
+
--outline-color: light-dark(#4476ff, #3b82f6);
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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 = "
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
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
|
-
|
|
127
|
-
data-
|
|
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
|
-
<
|
|
237
|
+
<LoaderBackground
|
|
135
238
|
loading={innerLoading}
|
|
136
239
|
inset={-1}
|
|
137
240
|
color="light-dark(#355fcc, #3b82f6)"
|
|
138
241
|
>
|
|
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>
|
|
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 {
|
|
221
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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 {
|
|
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
|
-
<
|
|
197
|
-
{
|
|
198
|
-
|
|
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
|
});
|