@jsenv/navi 0.1.0 → 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 +8413 -3793
- package/package.json +4 -3
- 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 +103 -6
- 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
|
@@ -8,9 +8,16 @@ import {
|
|
|
8
8
|
|
|
9
9
|
import { useConstraints } from "../../validation/hooks/use_constraints.js";
|
|
10
10
|
import { renderActionableComponent } from "../action_execution/render_actionable_component.jsx";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
LoadableInlineElement,
|
|
13
|
+
LoaderBackground,
|
|
14
|
+
} from "../loader/loader_background.jsx";
|
|
12
15
|
import { useAutoFocus } from "../use_auto_focus.js";
|
|
13
|
-
import {
|
|
16
|
+
import { initCustomField } from "./custom_field.js";
|
|
17
|
+
import {
|
|
18
|
+
ReportDisabledOnLabelContext,
|
|
19
|
+
ReportReadOnlyOnLabelContext,
|
|
20
|
+
} from "./label.jsx";
|
|
14
21
|
import {
|
|
15
22
|
DisabledContext,
|
|
16
23
|
FieldNameContext,
|
|
@@ -25,169 +32,152 @@ import {
|
|
|
25
32
|
} from "./use_ui_state_controller.js";
|
|
26
33
|
|
|
27
34
|
import.meta.css = /* css */ `
|
|
28
|
-
|
|
35
|
+
:root {
|
|
36
|
+
--navi-radiomark-color: light-dark(#4476ff, #3b82f6);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.navi_radio {
|
|
29
40
|
position: relative;
|
|
30
41
|
display: inline-flex;
|
|
31
42
|
box-sizing: content-box;
|
|
32
43
|
|
|
33
|
-
--
|
|
34
|
-
--
|
|
44
|
+
--outline-offset: 1px;
|
|
45
|
+
--outline-width: 2px;
|
|
46
|
+
--width: 13px;
|
|
47
|
+
--height: 13px;
|
|
35
48
|
|
|
36
|
-
--
|
|
37
|
-
--
|
|
38
|
-
|
|
49
|
+
--outline-color: light-dark(#4476ff, #3b82f6);
|
|
50
|
+
--border-color: light-dark(#767676, #8e8e93);
|
|
51
|
+
--background-color: white;
|
|
52
|
+
--accent-color: var(--navi-radiomark-color);
|
|
53
|
+
--mark-color: var(--accent-color);
|
|
54
|
+
|
|
55
|
+
/* light-dark(rgba(239, 239, 239, 0.3), rgba(59, 59, 59, 0.3)); */
|
|
56
|
+
--accent-color-checked: color-mix(in srgb, var(--accent-color) 70%, black);
|
|
39
57
|
|
|
40
|
-
|
|
58
|
+
--border-color-readonly: color-mix(in srgb, var(--border-color) 30%, white);
|
|
59
|
+
--border-color-disabled: var(--border-color-readonly);
|
|
60
|
+
--border-color-hover: color-mix(in srgb, var(--border-color) 70%, black);
|
|
61
|
+
--border-color-checked: var(--accent-color);
|
|
62
|
+
--border-color-checked-hover: var(--accent-color-checked);
|
|
63
|
+
--border-color-checked-readonly: #d3d3d3;
|
|
64
|
+
--border-color-checked-disabled: #d3d3d3;
|
|
65
|
+
--background-color-readonly: var(--background-color);
|
|
66
|
+
--background-color-disabled: var(--background-color);
|
|
67
|
+
--background-color-checked-readonly: #d3d3d3;
|
|
68
|
+
--background-color-checked-disabled: var(--background-color);
|
|
69
|
+
--mark-color-hover: var(--accent-color-checked);
|
|
70
|
+
--mark-color-readonly: grey;
|
|
71
|
+
--mark-color-disabled: #eeeeee;
|
|
72
|
+
}
|
|
73
|
+
.navi_radio input {
|
|
41
74
|
position: absolute;
|
|
42
|
-
opacity: 0;
|
|
43
75
|
inset: 0;
|
|
44
76
|
margin: 0;
|
|
45
77
|
padding: 0;
|
|
46
|
-
|
|
78
|
+
opacity: 0;
|
|
79
|
+
cursor: inherit;
|
|
47
80
|
}
|
|
48
|
-
|
|
49
|
-
.custom_radio {
|
|
50
|
-
width: 13px;
|
|
51
|
-
height: 13px;
|
|
52
|
-
background: transparent;
|
|
53
|
-
border-radius: 50%;
|
|
81
|
+
.navi_radio_field {
|
|
54
82
|
display: inline-flex;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
margin-left: 5px;
|
|
83
|
+
width: var(--width);
|
|
84
|
+
height: var(--height);
|
|
58
85
|
margin-top: 3px;
|
|
59
86
|
margin-right: 3px;
|
|
60
|
-
|
|
87
|
+
margin-left: 5px;
|
|
88
|
+
align-items: center;
|
|
89
|
+
justify-content: center;
|
|
90
|
+
border-radius: 50%;
|
|
91
|
+
outline-width: var(--outline-width);
|
|
92
|
+
|
|
93
|
+
outline-style: none;
|
|
61
94
|
|
|
62
|
-
|
|
95
|
+
outline-color: var(--outline-color);
|
|
96
|
+
|
|
97
|
+
outline-offset: var(--outline-offset);
|
|
98
|
+
}
|
|
99
|
+
.navi_radio_field svg {
|
|
100
|
+
overflow: visible;
|
|
101
|
+
}
|
|
102
|
+
.navi_radio_border {
|
|
103
|
+
fill: var(--background-color);
|
|
104
|
+
stroke: var(--border-color);
|
|
105
|
+
}
|
|
106
|
+
.navi_radio_marker {
|
|
63
107
|
width: 100%;
|
|
64
108
|
height: 100%;
|
|
109
|
+
opacity: 0;
|
|
110
|
+
fill: var(--mark-color);
|
|
111
|
+
transform: scale(0.3);
|
|
112
|
+
transform-origin: center;
|
|
65
113
|
pointer-events: none;
|
|
66
114
|
}
|
|
67
|
-
|
|
68
|
-
.custom_radio svg .custom_radio_dashed_border {
|
|
115
|
+
.navi_radio_dashed_border {
|
|
69
116
|
display: none;
|
|
70
117
|
}
|
|
71
|
-
|
|
72
|
-
.custom_radio svg .custom_radio_marker {
|
|
73
|
-
fill: var(--checkmark-color);
|
|
74
|
-
opacity: 0;
|
|
75
|
-
transform-origin: center;
|
|
76
|
-
transform: scale(0.3);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
.custom_radio[data-transition] svg {
|
|
118
|
+
.navi_radio[data-transition] .navi_radio_marker {
|
|
80
119
|
transition: all 0.15s ease;
|
|
81
120
|
}
|
|
82
|
-
.
|
|
121
|
+
.navi_radio[data-transition] .navi_radio_dashed_border {
|
|
83
122
|
transition: all 0.15s ease;
|
|
84
123
|
}
|
|
85
|
-
.
|
|
124
|
+
.navi_radio[data-transition] .navi_radio_border {
|
|
86
125
|
transition: all 0.15s ease;
|
|
87
126
|
}
|
|
88
127
|
|
|
89
|
-
/*
|
|
90
|
-
.
|
|
91
|
-
|
|
128
|
+
/* Focus */
|
|
129
|
+
.navi_radio[data-focus-visible] .navi_radio_field {
|
|
130
|
+
outline-style: solid;
|
|
92
131
|
}
|
|
93
|
-
|
|
94
|
-
|
|
132
|
+
/* Hover */
|
|
133
|
+
.navi_radio[data-hover] .navi_radio_border {
|
|
134
|
+
stroke: var(--border-color-hover);
|
|
95
135
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
input:checked
|
|
99
|
-
+ .custom_radio
|
|
100
|
-
svg
|
|
101
|
-
.custom_radio_border {
|
|
102
|
-
stroke: var(--field-strong-color);
|
|
136
|
+
.navi_radio[data-hover] .navi_radio_marker {
|
|
137
|
+
fill: var(--mark-color-hover);
|
|
103
138
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
stroke: var(--field-strong-color);
|
|
139
|
+
/* Checked */
|
|
140
|
+
.navi_radio[data-checked] .navi_radio_border {
|
|
141
|
+
stroke: var(--border-color-checked);
|
|
108
142
|
}
|
|
109
|
-
|
|
110
|
-
.custom_radio_wrapper input:checked + .custom_radio svg .custom_radio_marker {
|
|
143
|
+
.navi_radio[data-checked] .navi_radio_marker {
|
|
111
144
|
opacity: 1;
|
|
112
145
|
transform: scale(1);
|
|
113
146
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
.custom_radio_wrapper
|
|
117
|
-
input[disabled]
|
|
118
|
-
+ .custom_radio
|
|
119
|
-
svg
|
|
120
|
-
.custom_radio_border {
|
|
121
|
-
fill: light-dark(rgba(239, 239, 239, 0.3), rgba(59, 59, 59, 0.3));
|
|
122
|
-
stroke: var(--field-disabled-border-color);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
.custom_radio_wrapper
|
|
126
|
-
input[disabled]:checked
|
|
127
|
-
+ .custom_radio
|
|
128
|
-
svg
|
|
129
|
-
.custom_radio_border {
|
|
130
|
-
stroke: var(--checked-disabled-color);
|
|
147
|
+
.navi_radio[data-hover][data-checked] .navi_radio_border {
|
|
148
|
+
stroke: var(--border-color-checked-hover);
|
|
131
149
|
}
|
|
132
|
-
|
|
133
|
-
.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
svg
|
|
137
|
-
.custom_radio_marker {
|
|
138
|
-
fill: var(--checkmark-disabled-color);
|
|
150
|
+
/* Readonly */
|
|
151
|
+
.navi_radio[data-readonly] .navi_radio_border {
|
|
152
|
+
fill: var(--background-color-readonly);
|
|
153
|
+
stroke: var(--border-color-readonly);
|
|
139
154
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
input[data-readonly]
|
|
143
|
-
+ .custom_radio
|
|
144
|
-
svg
|
|
145
|
-
.custom_radio_border {
|
|
146
|
-
fill: light-dark(rgba(239, 239, 239, 0.3), rgba(59, 59, 59, 0.3));
|
|
147
|
-
stroke: var(--field-disabled-border-color);
|
|
155
|
+
.navi_radio[data-readonly] .navi_radio_marker {
|
|
156
|
+
fill: var(--mark-color-readonly);
|
|
148
157
|
}
|
|
149
|
-
.
|
|
150
|
-
input[data-readonly]
|
|
151
|
-
+ .custom_radio
|
|
152
|
-
svg
|
|
153
|
-
.custom_radio_dashed_border {
|
|
158
|
+
.navi_radio[data-readonly] .navi_radio_dashed_border {
|
|
154
159
|
display: none;
|
|
155
160
|
}
|
|
156
|
-
.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
svg
|
|
160
|
-
.custom_radio_border {
|
|
161
|
-
stroke: var(--checked-disabled-color);
|
|
161
|
+
.navi_radio[data-checked][data-readonly] .navi_radio_border {
|
|
162
|
+
fill: var(--background-color-checked-readonly);
|
|
163
|
+
stroke: var(--border-color-checked-readonly);
|
|
162
164
|
}
|
|
163
|
-
.
|
|
164
|
-
|
|
165
|
-
+ .custom_radio
|
|
166
|
-
svg
|
|
167
|
-
.custom_radio_marker {
|
|
168
|
-
fill: var(--checkmark-disabled-color);
|
|
165
|
+
.navi_radio[data-checked][data-readonly] .navi_radio_marker {
|
|
166
|
+
fill: var(--mark-color-readonly);
|
|
169
167
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
.custom_radio_border {
|
|
175
|
-
fill: light-dark(rgba(239, 239, 239, 0.3), rgba(59, 59, 59, 0.3));
|
|
176
|
-
stroke: var(--field-disabled-border-color);
|
|
168
|
+
/* Disabled */
|
|
169
|
+
.navi_radio[data-disabled] .navi_radio_border {
|
|
170
|
+
fill: var(--background-color-disabled);
|
|
171
|
+
stroke: var(--border-color-disabled);
|
|
177
172
|
}
|
|
178
|
-
.
|
|
179
|
-
|
|
180
|
-
+ .custom_radio
|
|
181
|
-
svg
|
|
182
|
-
.custom_radio_border {
|
|
183
|
-
stroke: var(--checked-disabled-color);
|
|
173
|
+
.navi_radio[data-disabled] .navi_radio_marker {
|
|
174
|
+
fill: var(--mark-color-disabled);
|
|
184
175
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
border-radius: 50%;
|
|
176
|
+
.navi_radio[data-hover][data-checked][data-disabled] .navi_radio_border {
|
|
177
|
+
stroke: var(--border-color-disabled);
|
|
178
|
+
}
|
|
179
|
+
.navi_radio[data-checked][data-disabled] .navi_radio_marker {
|
|
180
|
+
fill: var(--mark-color-disabled);
|
|
191
181
|
}
|
|
192
182
|
`;
|
|
193
183
|
|
|
@@ -223,6 +213,7 @@ const InputRadioBasic = forwardRef((props, ref) => {
|
|
|
223
213
|
const uiStateController = useContext(UIStateControllerContext);
|
|
224
214
|
const uiState = useContext(UIStateContext);
|
|
225
215
|
const reportReadOnlyOnLabel = useContext(ReportReadOnlyOnLabelContext);
|
|
216
|
+
const reportDisabledOnLabel = useContext(ReportDisabledOnLabelContext);
|
|
226
217
|
const {
|
|
227
218
|
name,
|
|
228
219
|
readOnly,
|
|
@@ -233,10 +224,11 @@ const InputRadioBasic = forwardRef((props, ref) => {
|
|
|
233
224
|
autoFocus,
|
|
234
225
|
constraints = [],
|
|
235
226
|
|
|
236
|
-
appeareance = "
|
|
227
|
+
appeareance = "navi", // "navi" or "default"
|
|
237
228
|
accentColor,
|
|
238
229
|
onClick,
|
|
239
230
|
onInput,
|
|
231
|
+
style,
|
|
240
232
|
...rest
|
|
241
233
|
} = props;
|
|
242
234
|
const innerRef = useRef(null);
|
|
@@ -251,14 +243,10 @@ const InputRadioBasic = forwardRef((props, ref) => {
|
|
|
251
243
|
readOnly || contextReadOnly || innerLoading || uiStateController.readOnly;
|
|
252
244
|
|
|
253
245
|
reportReadOnlyOnLabel?.(innerReadOnly);
|
|
246
|
+
reportDisabledOnLabel?.(innerDisabled);
|
|
254
247
|
useAutoFocus(innerRef, autoFocus);
|
|
255
248
|
useConstraints(innerRef, constraints);
|
|
256
249
|
const checked = Boolean(uiState);
|
|
257
|
-
const actionName = rest["data-action"];
|
|
258
|
-
if (actionName) {
|
|
259
|
-
delete rest["data-action"];
|
|
260
|
-
}
|
|
261
|
-
|
|
262
250
|
// we must first dispatch an event to inform all other radios they where unchecked
|
|
263
251
|
// this way each other radio uiStateController knows thery are unchecked
|
|
264
252
|
// we do this on "input"
|
|
@@ -266,6 +254,9 @@ const InputRadioBasic = forwardRef((props, ref) => {
|
|
|
266
254
|
const updateOtherRadiosInGroup = () => {
|
|
267
255
|
const thisRadio = innerRef.current;
|
|
268
256
|
const radioList = thisRadio.closest("[data-radio-list]");
|
|
257
|
+
if (!radioList) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
269
260
|
const radioInputs = radioList.querySelectorAll(
|
|
270
261
|
`input[type="radio"][name="${thisRadio.name}"]`,
|
|
271
262
|
);
|
|
@@ -284,14 +275,18 @@ const InputRadioBasic = forwardRef((props, ref) => {
|
|
|
284
275
|
}
|
|
285
276
|
}, [checked]);
|
|
286
277
|
|
|
278
|
+
const actionName = rest["data-action"];
|
|
279
|
+
if (actionName) {
|
|
280
|
+
delete rest["data-action"];
|
|
281
|
+
}
|
|
287
282
|
const inputRadio = (
|
|
288
283
|
<input
|
|
289
284
|
{...rest}
|
|
290
285
|
ref={innerRef}
|
|
291
286
|
type="radio"
|
|
287
|
+
style={appeareance === "default" ? style : undefined}
|
|
292
288
|
name={innerName}
|
|
293
289
|
checked={checked}
|
|
294
|
-
data-readonly={innerReadOnly ? "" : undefined}
|
|
295
290
|
disabled={innerDisabled}
|
|
296
291
|
required={innerRequired}
|
|
297
292
|
data-validation-message-arrow-x="center"
|
|
@@ -320,30 +315,65 @@ const InputRadioBasic = forwardRef((props, ref) => {
|
|
|
320
315
|
}}
|
|
321
316
|
/>
|
|
322
317
|
);
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
318
|
+
const loaderProps = {
|
|
319
|
+
loading: innerLoading,
|
|
320
|
+
inset: -1,
|
|
321
|
+
style: {
|
|
322
|
+
"--accent-color": accentColor || "light-dark(#355fcc, #4476ff)",
|
|
323
|
+
},
|
|
324
|
+
color: "var(--accent-color)",
|
|
325
|
+
};
|
|
326
|
+
if (appeareance === "navi") {
|
|
327
|
+
return (
|
|
328
|
+
<NaviRadio
|
|
329
|
+
data-action={actionName}
|
|
330
|
+
inputRef={innerRef}
|
|
331
|
+
accentColor={accentColor}
|
|
332
|
+
readOnly={innerReadOnly}
|
|
333
|
+
disabled={innerDisabled}
|
|
334
|
+
style={style}
|
|
335
|
+
>
|
|
336
|
+
<LoaderBackground {...loaderProps} targetSelector=".navi_radio_field">
|
|
337
|
+
{inputRadio}
|
|
338
|
+
</LoaderBackground>
|
|
339
|
+
</NaviRadio>
|
|
328
340
|
);
|
|
341
|
+
}
|
|
329
342
|
|
|
330
343
|
return (
|
|
331
|
-
<LoadableInlineElement
|
|
332
|
-
|
|
333
|
-
loading={innerLoading}
|
|
334
|
-
targetSelector={appeareance === "custom" ? ".custom_radio" : ""}
|
|
335
|
-
inset={-1}
|
|
336
|
-
color="light-dark(#355fcc, #3b82f6)"
|
|
337
|
-
>
|
|
338
|
-
{inputRadioDisplayed}
|
|
344
|
+
<LoadableInlineElement {...loaderProps} data-action={actionName}>
|
|
345
|
+
{inputRadio}
|
|
339
346
|
</LoadableInlineElement>
|
|
340
347
|
);
|
|
341
348
|
});
|
|
342
|
-
const
|
|
349
|
+
const NaviRadio = ({
|
|
350
|
+
inputRef,
|
|
351
|
+
accentColor,
|
|
352
|
+
readOnly,
|
|
353
|
+
disabled,
|
|
354
|
+
style,
|
|
355
|
+
children,
|
|
356
|
+
...rest
|
|
357
|
+
}) => {
|
|
358
|
+
const ref = useRef();
|
|
359
|
+
useLayoutEffect(() => {
|
|
360
|
+
return initCustomField(ref.current, inputRef.current);
|
|
361
|
+
}, []);
|
|
362
|
+
|
|
343
363
|
return (
|
|
344
|
-
<
|
|
364
|
+
<span
|
|
365
|
+
{...rest}
|
|
366
|
+
ref={ref}
|
|
367
|
+
className="navi_radio"
|
|
368
|
+
style={{
|
|
369
|
+
...(accentColor ? { "--accent-color": accentColor } : {}),
|
|
370
|
+
...style,
|
|
371
|
+
}}
|
|
372
|
+
data-readonly={readOnly ? "" : undefined}
|
|
373
|
+
data-disabled={disabled ? "" : undefined}
|
|
374
|
+
>
|
|
345
375
|
{children}
|
|
346
|
-
<
|
|
376
|
+
<span className="navi_radio_field">
|
|
347
377
|
<svg
|
|
348
378
|
viewBox="0 0 12 12"
|
|
349
379
|
aria-hidden="true"
|
|
@@ -351,31 +381,27 @@ const CustomRadio = ({ children }) => {
|
|
|
351
381
|
>
|
|
352
382
|
{/* Border circle - always visible */}
|
|
353
383
|
<circle
|
|
354
|
-
className="
|
|
384
|
+
className="navi_radio_border"
|
|
355
385
|
cx="6"
|
|
356
386
|
cy="6"
|
|
357
387
|
r="5.5"
|
|
358
|
-
fill="white"
|
|
359
|
-
stroke="var(--field-border-color)"
|
|
360
388
|
strokeWidth="1"
|
|
361
389
|
/>
|
|
362
390
|
{/* Dashed border for readonly - calculated for even distribution */}
|
|
363
391
|
<circle
|
|
364
|
-
className="
|
|
392
|
+
className="navi_radio_dashed_border"
|
|
365
393
|
cx="6"
|
|
366
394
|
cy="6"
|
|
367
395
|
r="5.5"
|
|
368
|
-
fill="var(--field-readonly-background-color)"
|
|
369
|
-
stroke="var(--field-border-color)"
|
|
370
396
|
strokeWidth="1"
|
|
371
397
|
strokeDasharray="2.16 2.16"
|
|
372
398
|
strokeDashoffset="0"
|
|
373
399
|
/>
|
|
374
400
|
{/* Inner fill circle - only visible when checked */}
|
|
375
|
-
<circle className="
|
|
401
|
+
<circle className="navi_radio_marker" cx="6" cy="6" r="3.5" />
|
|
376
402
|
</svg>
|
|
377
|
-
</
|
|
378
|
-
</
|
|
403
|
+
</span>
|
|
404
|
+
</span>
|
|
379
405
|
);
|
|
380
406
|
};
|
|
381
407
|
|
|
@@ -33,6 +33,7 @@ import { useActionBoundToOneParam } from "../action_execution/use_action.js";
|
|
|
33
33
|
import { useExecuteAction } from "../action_execution/use_execute_action.js";
|
|
34
34
|
import { LoadableInlineElement } from "../loader/loader_background.jsx";
|
|
35
35
|
import { useAutoFocus } from "../use_auto_focus.js";
|
|
36
|
+
import { initCustomField } from "./custom_field.js";
|
|
36
37
|
import "./field_css.js";
|
|
37
38
|
import { ReportReadOnlyOnLabelContext } from "./label.jsx";
|
|
38
39
|
import { useActionEvents } from "./use_action_events.js";
|
|
@@ -47,6 +48,84 @@ import {
|
|
|
47
48
|
useUIStateController,
|
|
48
49
|
} from "./use_ui_state_controller.js";
|
|
49
50
|
|
|
51
|
+
import.meta.css = /* css */ `
|
|
52
|
+
.navi_input {
|
|
53
|
+
--border-width: 1px;
|
|
54
|
+
--outline-width: 1px;
|
|
55
|
+
--outer-width: calc(var(--border-width) + var(--outline-width));
|
|
56
|
+
--padding-x: 6px;
|
|
57
|
+
--padding-y: 1px;
|
|
58
|
+
|
|
59
|
+
--outline-color: light-dark(#4476ff, #3b82f6);
|
|
60
|
+
|
|
61
|
+
--border-radius: 2px;
|
|
62
|
+
--border-color: light-dark(#767676, #8e8e93);
|
|
63
|
+
--border-color-hover: color-mix(in srgb, var(--border-color) 70%, black);
|
|
64
|
+
--border-color-active: color-mix(in srgb, var(--border-color) 90%, black);
|
|
65
|
+
--border-color-readonly: color-mix(
|
|
66
|
+
in srgb,
|
|
67
|
+
var(--border-color) 45%,
|
|
68
|
+
transparent
|
|
69
|
+
);
|
|
70
|
+
--border-color-disabled: var(--border-color-readonly);
|
|
71
|
+
|
|
72
|
+
--background-color: white;
|
|
73
|
+
--background-color-hover: color-mix(
|
|
74
|
+
in srgb,
|
|
75
|
+
var(--background-color) 95%,
|
|
76
|
+
black
|
|
77
|
+
);
|
|
78
|
+
--background-color-readonly: var(--background-color);
|
|
79
|
+
--background-color-disabled: color-mix(
|
|
80
|
+
in srgb,
|
|
81
|
+
var(--background-color) 60%,
|
|
82
|
+
transparent
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
--color: currentColor;
|
|
86
|
+
--color-readonly: color-mix(in srgb, currentColor 60%, transparent);
|
|
87
|
+
--color-disabled: var(--color-readonly);
|
|
88
|
+
color: var(--color);
|
|
89
|
+
|
|
90
|
+
background-color: var(--background-color);
|
|
91
|
+
border-width: var(--outer-width);
|
|
92
|
+
border-width: var(--outer-width);
|
|
93
|
+
border-style: solid;
|
|
94
|
+
border-color: transparent;
|
|
95
|
+
border-radius: var(--border-radius);
|
|
96
|
+
outline-width: var(--border-width);
|
|
97
|
+
outline-style: solid;
|
|
98
|
+
outline-color: var(--border-color);
|
|
99
|
+
outline-offset: calc(-1 * (var(--border-width)));
|
|
100
|
+
}
|
|
101
|
+
/* Focus */
|
|
102
|
+
.navi_input[data-focus] {
|
|
103
|
+
border-color: var(--outline-color);
|
|
104
|
+
outline-width: var(--outer-width);
|
|
105
|
+
outline-color: var(--outline-color);
|
|
106
|
+
outline-offset: calc(-1 * var(--outer-width));
|
|
107
|
+
}
|
|
108
|
+
/* Readonly */
|
|
109
|
+
.navi_input[data-readonly] {
|
|
110
|
+
color: var(--color-readonly);
|
|
111
|
+
background-color: var(--background-color-readonly);
|
|
112
|
+
outline-color: var(--border-color-readonly);
|
|
113
|
+
}
|
|
114
|
+
.navi_input[data-readonly]::placeholder {
|
|
115
|
+
color: var(--color-readonly);
|
|
116
|
+
}
|
|
117
|
+
/* Disabled */
|
|
118
|
+
.navi_input[data-disabled] {
|
|
119
|
+
color: var(--color-disabled);
|
|
120
|
+
background-color: var(--background-color-disabled);
|
|
121
|
+
outline-color: var(--border-color-disabled);
|
|
122
|
+
}
|
|
123
|
+
/* Invalid */
|
|
124
|
+
.navi_input[aria-invalid="true"] {
|
|
125
|
+
border-color: var(--invalid-color);
|
|
126
|
+
}
|
|
127
|
+
`;
|
|
128
|
+
|
|
50
129
|
export const InputTextual = forwardRef((props, ref) => {
|
|
51
130
|
const uiStateController = useUIStateController(props, "input");
|
|
52
131
|
const uiState = useUIState(uiStateController);
|
|
@@ -83,7 +162,8 @@ const InputTextualBasic = forwardRef((props, ref) => {
|
|
|
83
162
|
autoFocus,
|
|
84
163
|
autoFocusVisible,
|
|
85
164
|
autoSelect,
|
|
86
|
-
appearance = "
|
|
165
|
+
appearance = "navi",
|
|
166
|
+
accentColor,
|
|
87
167
|
width,
|
|
88
168
|
height,
|
|
89
169
|
...rest
|
|
@@ -110,14 +190,14 @@ const InputTextualBasic = forwardRef((props, ref) => {
|
|
|
110
190
|
<input
|
|
111
191
|
{...rest}
|
|
112
192
|
ref={innerRef}
|
|
193
|
+
className={appearance === "navi" ? "navi_input" : undefined}
|
|
113
194
|
type={type}
|
|
114
195
|
data-value={uiState}
|
|
115
196
|
value={innerValue}
|
|
116
|
-
data-field=""
|
|
117
|
-
data-field-with-border=""
|
|
118
|
-
data-custom={appearance === "custom" ? "" : undefined}
|
|
119
197
|
readOnly={innerReadOnly}
|
|
120
198
|
disabled={innerDisabled}
|
|
199
|
+
data-readOnly={innerReadOnly ? "" : undefined}
|
|
200
|
+
data-disabled={innerDisabled ? "" : undefined}
|
|
121
201
|
onInput={(e) => {
|
|
122
202
|
let inputValue;
|
|
123
203
|
if (type === "number") {
|
|
@@ -141,12 +221,23 @@ const InputTextualBasic = forwardRef((props, ref) => {
|
|
|
141
221
|
/>
|
|
142
222
|
);
|
|
143
223
|
|
|
224
|
+
useLayoutEffect(() => {
|
|
225
|
+
return initCustomField(innerRef.current, innerRef.current);
|
|
226
|
+
}, []);
|
|
227
|
+
|
|
228
|
+
if (type === "hidden") {
|
|
229
|
+
return inputTextual;
|
|
230
|
+
}
|
|
144
231
|
return (
|
|
145
232
|
<LoadableInlineElement
|
|
146
233
|
loading={innerLoading}
|
|
147
|
-
|
|
234
|
+
style={{
|
|
235
|
+
"--accent-color": accentColor || "light-dark(#355fcc, #4476ff)",
|
|
236
|
+
}}
|
|
237
|
+
color="var(--accent-color)"
|
|
148
238
|
width={width}
|
|
149
239
|
height={height}
|
|
240
|
+
inset={-1}
|
|
150
241
|
>
|
|
151
242
|
{inputTextual}
|
|
152
243
|
</LoadableInlineElement>
|
|
@@ -262,7 +353,13 @@ const InputTextualWithAction = forwardRef((props, ref) => {
|
|
|
262
353
|
);
|
|
263
354
|
});
|
|
264
355
|
const InputTextualInsideForm = forwardRef((props, ref) => {
|
|
265
|
-
const {
|
|
356
|
+
const {
|
|
357
|
+
onKeyDown,
|
|
358
|
+
// We destructure formContext to avoid passing it to the underlying input element
|
|
359
|
+
// eslint-disable-next-line no-unused-vars
|
|
360
|
+
formContext,
|
|
361
|
+
...rest
|
|
362
|
+
} = props;
|
|
266
363
|
|
|
267
364
|
return (
|
|
268
365
|
<InputTextualBasic
|
|
@@ -3,29 +3,41 @@ import { forwardRef } from "preact/compat";
|
|
|
3
3
|
import { useImperativeHandle, useRef, useState } from "preact/hooks";
|
|
4
4
|
|
|
5
5
|
import.meta.css = /* css */ `
|
|
6
|
-
label
|
|
6
|
+
label {
|
|
7
|
+
cursor: pointer;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
label[data-readonly],
|
|
11
|
+
label[data-disabled] {
|
|
7
12
|
color: rgba(0, 0, 0, 0.5);
|
|
13
|
+
cursor: default;
|
|
8
14
|
}
|
|
9
15
|
`;
|
|
10
16
|
|
|
11
17
|
export const ReportReadOnlyOnLabelContext = createContext();
|
|
18
|
+
export const ReportDisabledOnLabelContext = createContext();
|
|
12
19
|
|
|
13
20
|
export const Label = forwardRef((props, ref) => {
|
|
14
|
-
const { readOnly, children, ...rest } = props;
|
|
21
|
+
const { readOnly, disabled, children, ...rest } = props;
|
|
15
22
|
const innerRef = useRef();
|
|
16
23
|
useImperativeHandle(ref, () => innerRef.current);
|
|
17
24
|
|
|
18
25
|
const [inputReadOnly, setInputReadOnly] = useState(false);
|
|
19
26
|
const innerReadOnly = readOnly || inputReadOnly;
|
|
27
|
+
const [inputDisabled, setInputDisabled] = useState(false);
|
|
28
|
+
const innerDisabled = disabled || inputDisabled;
|
|
20
29
|
|
|
21
30
|
return (
|
|
22
31
|
<label
|
|
23
32
|
ref={innerRef}
|
|
24
33
|
data-readonly={innerReadOnly ? "" : undefined}
|
|
34
|
+
data-disabled={innerDisabled ? "" : undefined}
|
|
25
35
|
{...rest}
|
|
26
36
|
>
|
|
27
37
|
<ReportReadOnlyOnLabelContext.Provider value={setInputReadOnly}>
|
|
28
|
-
{
|
|
38
|
+
<ReportDisabledOnLabelContext.Provider value={setInputDisabled}>
|
|
39
|
+
{children}
|
|
40
|
+
</ReportDisabledOnLabelContext.Provider>
|
|
29
41
|
</ReportReadOnlyOnLabelContext.Provider>
|
|
30
42
|
</label>
|
|
31
43
|
);
|