@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
|
@@ -3,29 +3,43 @@ import { forwardRef } from "preact/compat";
|
|
|
3
3
|
import { useImperativeHandle, useRef, useState } from "preact/hooks";
|
|
4
4
|
|
|
5
5
|
import.meta.css = /* css */ `
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
@layer navi {
|
|
7
|
+
label {
|
|
8
|
+
cursor: pointer;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
label[data-readonly],
|
|
12
|
+
label[data-disabled] {
|
|
13
|
+
color: rgba(0, 0, 0, 0.5);
|
|
14
|
+
cursor: default;
|
|
15
|
+
}
|
|
8
16
|
}
|
|
9
17
|
`;
|
|
10
18
|
|
|
11
19
|
export const ReportReadOnlyOnLabelContext = createContext();
|
|
20
|
+
export const ReportDisabledOnLabelContext = createContext();
|
|
12
21
|
|
|
13
22
|
export const Label = forwardRef((props, ref) => {
|
|
14
|
-
const { readOnly, children, ...rest } = props;
|
|
23
|
+
const { readOnly, disabled, children, ...rest } = props;
|
|
15
24
|
const innerRef = useRef();
|
|
16
25
|
useImperativeHandle(ref, () => innerRef.current);
|
|
17
26
|
|
|
18
27
|
const [inputReadOnly, setInputReadOnly] = useState(false);
|
|
19
28
|
const innerReadOnly = readOnly || inputReadOnly;
|
|
29
|
+
const [inputDisabled, setInputDisabled] = useState(false);
|
|
30
|
+
const innerDisabled = disabled || inputDisabled;
|
|
20
31
|
|
|
21
32
|
return (
|
|
22
33
|
<label
|
|
23
34
|
ref={innerRef}
|
|
24
35
|
data-readonly={innerReadOnly ? "" : undefined}
|
|
36
|
+
data-disabled={innerDisabled ? "" : undefined}
|
|
25
37
|
{...rest}
|
|
26
38
|
>
|
|
27
39
|
<ReportReadOnlyOnLabelContext.Provider value={setInputReadOnly}>
|
|
28
|
-
{
|
|
40
|
+
<ReportDisabledOnLabelContext.Provider value={setInputDisabled}>
|
|
41
|
+
{children}
|
|
42
|
+
</ReportDisabledOnLabelContext.Provider>
|
|
29
43
|
</ReportReadOnlyOnLabelContext.Provider>
|
|
30
44
|
</label>
|
|
31
45
|
);
|
|
@@ -28,9 +28,11 @@ import {
|
|
|
28
28
|
} from "./use_ui_state_controller.js";
|
|
29
29
|
|
|
30
30
|
import.meta.css = /* css */ `
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
@layer navi {
|
|
32
|
+
.navi_radio_list {
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
}
|
|
34
36
|
}
|
|
35
37
|
`;
|
|
36
38
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveCSSSize } from "@jsenv/dom";
|
|
2
|
-
import { createPortal } from "preact/compat";
|
|
2
|
+
import { createPortal, forwardRef } from "preact/compat";
|
|
3
3
|
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
|
4
|
+
|
|
4
5
|
import { useDebounceTrue } from "../use_debounce_true.js";
|
|
5
6
|
import { RectangleLoading } from "./rectangle_loading.jsx";
|
|
6
7
|
|
|
@@ -10,6 +11,8 @@ import.meta.css = /* css */ `
|
|
|
10
11
|
width: fit-content;
|
|
11
12
|
display: inline-flex;
|
|
12
13
|
height: fit-content;
|
|
14
|
+
border-radius: inherit;
|
|
15
|
+
cursor: inherit;
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
.navi_loading_rectangle_wrapper {
|
|
@@ -27,31 +30,53 @@ import.meta.css = /* css */ `
|
|
|
27
30
|
}
|
|
28
31
|
`;
|
|
29
32
|
|
|
30
|
-
export const LoadableInlineElement = ({
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
export const LoadableInlineElement = forwardRef((props, ref) => {
|
|
34
|
+
const {
|
|
35
|
+
// background props
|
|
36
|
+
loading,
|
|
37
|
+
containerRef,
|
|
38
|
+
targetSelector,
|
|
39
|
+
color,
|
|
40
|
+
inset,
|
|
41
|
+
spacingTop,
|
|
42
|
+
spacingLeft,
|
|
43
|
+
spacingBottom,
|
|
44
|
+
spacingRight,
|
|
45
|
+
// other props
|
|
46
|
+
width,
|
|
47
|
+
height,
|
|
48
|
+
children,
|
|
49
|
+
...rest
|
|
50
|
+
} = props;
|
|
40
51
|
|
|
41
52
|
return (
|
|
42
53
|
<span
|
|
54
|
+
{...rest}
|
|
55
|
+
ref={ref}
|
|
43
56
|
className="navi_inline_wrapper"
|
|
44
57
|
style={{
|
|
58
|
+
...rest.style,
|
|
45
59
|
...(width ? { width } : {}),
|
|
46
60
|
...(height ? { height } : {}),
|
|
47
61
|
}}
|
|
48
|
-
data-action={actionName}
|
|
49
62
|
>
|
|
50
|
-
<LoaderBackground
|
|
63
|
+
<LoaderBackground
|
|
64
|
+
{...{
|
|
65
|
+
loading,
|
|
66
|
+
containerRef,
|
|
67
|
+
targetSelector,
|
|
68
|
+
color,
|
|
69
|
+
inset,
|
|
70
|
+
spacingTop,
|
|
71
|
+
spacingLeft,
|
|
72
|
+
spacingBottom,
|
|
73
|
+
spacingRight,
|
|
74
|
+
}}
|
|
75
|
+
/>
|
|
51
76
|
{children}
|
|
52
77
|
</span>
|
|
53
78
|
);
|
|
54
|
-
};
|
|
79
|
+
});
|
|
55
80
|
|
|
56
81
|
export const LoaderBackground = ({
|
|
57
82
|
loading,
|
|
@@ -249,6 +274,8 @@ const LoaderBackgroundBasic = ({
|
|
|
249
274
|
setPaddingBottom(paddingBottom);
|
|
250
275
|
|
|
251
276
|
if (color) {
|
|
277
|
+
// const resolvedColor = resolveCSSColor(color, rectangle, "css");
|
|
278
|
+
// console.log(resolvedColor);
|
|
252
279
|
setCurrentColor(color);
|
|
253
280
|
} else if (
|
|
254
281
|
newOutlineColor &&
|
|
@@ -7,6 +7,9 @@ export const READONLY_CONSTRAINT = {
|
|
|
7
7
|
if (!element.readonly && !element.hasAttribute("data-readonly")) {
|
|
8
8
|
return null;
|
|
9
9
|
}
|
|
10
|
+
if (element.type === "hidden") {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
10
13
|
const readonlySilent = element.hasAttribute("data-readonly-silent");
|
|
11
14
|
if (readonlySilent) {
|
|
12
15
|
return { silent: true };
|
|
@@ -21,11 +24,13 @@ export const READONLY_CONSTRAINT = {
|
|
|
21
24
|
const isBusy = element.getAttribute("aria-busy") === "true";
|
|
22
25
|
if (isBusy) {
|
|
23
26
|
return {
|
|
27
|
+
target: element,
|
|
24
28
|
message: `Cette action est en cours. Veuillez patienter.`,
|
|
25
29
|
level: "info",
|
|
26
30
|
};
|
|
27
31
|
}
|
|
28
32
|
return {
|
|
33
|
+
target: element,
|
|
29
34
|
message:
|
|
30
35
|
element.tagName === "BUTTON"
|
|
31
36
|
? `Cet action n'est pas disponible pour l'instant.`
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
allowWheelThrough,
|
|
3
|
+
createPubSub,
|
|
4
|
+
createStyleController,
|
|
3
5
|
getBorderSizes,
|
|
4
6
|
pickPositionRelativeTo,
|
|
5
7
|
visibleRectEffect,
|
|
@@ -23,126 +25,149 @@ import {
|
|
|
23
25
|
* @returns {Function} - Function to hide and remove the validation message
|
|
24
26
|
*/
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
inset: 0;
|
|
33
|
-
overflow: hidden;
|
|
34
|
-
}
|
|
28
|
+
// Configuration parameters for validation message appearance
|
|
29
|
+
const BORDER_WIDTH = 1;
|
|
30
|
+
const CORNER_RADIUS = 3;
|
|
31
|
+
const ARROW_WIDTH = 16;
|
|
32
|
+
const ARROW_HEIGHT = 8;
|
|
33
|
+
const ARROW_SPACING = 8;
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
top: 0;
|
|
45
|
-
/* will be positioned with transform: translate */
|
|
46
|
-
transition: opacity 0.2s ease-in-out;
|
|
47
|
-
}
|
|
35
|
+
import.meta.css = /* css */ `
|
|
36
|
+
@layer navi {
|
|
37
|
+
:root {
|
|
38
|
+
--navi-info-color: #2196f3;
|
|
39
|
+
--navi-warning-color: #ff9800;
|
|
40
|
+
--navi-error-color: #f44336;
|
|
41
|
+
--navi-validation-message-background-color: white;
|
|
42
|
+
}
|
|
48
43
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
44
|
+
/* Ensure the validation message CANNOT cause overflow */
|
|
45
|
+
/* might be important to ensure it cannot create scrollbars in the document */
|
|
46
|
+
/* When measuring the size it should take */
|
|
47
|
+
.jsenv_validation_message_container {
|
|
48
|
+
position: fixed;
|
|
49
|
+
inset: 0;
|
|
50
|
+
overflow: hidden;
|
|
51
|
+
}
|
|
54
52
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
.jsenv_validation_message {
|
|
54
|
+
position: absolute;
|
|
55
|
+
top: 0;
|
|
56
|
+
left: 0;
|
|
57
|
+
z-index: 1;
|
|
58
|
+
display: block;
|
|
59
|
+
height: auto;
|
|
60
|
+
opacity: 0;
|
|
61
|
+
/* will be positioned with transform: translate */
|
|
62
|
+
transition: opacity 0.2s ease-in-out;
|
|
63
|
+
overflow: visible;
|
|
64
|
+
}
|
|
60
65
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
flex-direction: row;
|
|
67
|
-
gap: 10px;
|
|
68
|
-
}
|
|
66
|
+
.jsenv_validation_message_border {
|
|
67
|
+
position: absolute;
|
|
68
|
+
filter: drop-shadow(4px 4px 3px rgba(0, 0, 0, 0.2));
|
|
69
|
+
pointer-events: none;
|
|
70
|
+
}
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
width: 22px;
|
|
76
|
-
height: 22px;
|
|
77
|
-
border-radius: 2px;
|
|
78
|
-
flex-shrink: 0;
|
|
79
|
-
}
|
|
72
|
+
.jsenv_validation_message_body_wrapper {
|
|
73
|
+
position: relative;
|
|
74
|
+
border-style: solid;
|
|
75
|
+
border-color: transparent;
|
|
76
|
+
}
|
|
80
77
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
78
|
+
.jsenv_validation_message_body {
|
|
79
|
+
position: relative;
|
|
80
|
+
display: flex;
|
|
81
|
+
max-width: 47vw;
|
|
82
|
+
padding: 8px;
|
|
83
|
+
flex-direction: row;
|
|
84
|
+
gap: 10px;
|
|
85
|
+
}
|
|
86
86
|
|
|
87
|
-
.jsenv_validation_message[data-level="info"] .jsenv_validation_message_icon {
|
|
88
|
-
background-color: #2196f3;
|
|
89
|
-
}
|
|
90
|
-
.jsenv_validation_message[data-level="warning"]
|
|
91
87
|
.jsenv_validation_message_icon {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
88
|
+
display: flex;
|
|
89
|
+
width: 22px;
|
|
90
|
+
height: 22px;
|
|
91
|
+
flex-shrink: 0;
|
|
92
|
+
align-items: center;
|
|
93
|
+
align-self: flex-start;
|
|
94
|
+
justify-content: center;
|
|
95
|
+
border-radius: 2px;
|
|
96
|
+
}
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
98
|
+
.jsenv_validation_message_exclamation_svg {
|
|
99
|
+
width: 16px;
|
|
100
|
+
height: 12px;
|
|
101
|
+
color: white;
|
|
102
|
+
}
|
|
104
103
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
.jsenv_validation_message[data-level="info"] .border_path {
|
|
105
|
+
fill: var(--navi-info-color);
|
|
106
|
+
}
|
|
107
|
+
.jsenv_validation_message[data-level="info"]
|
|
108
|
+
.jsenv_validation_message_icon {
|
|
109
|
+
background-color: var(--navi-info-color);
|
|
110
|
+
}
|
|
111
|
+
.jsenv_validation_message[data-level="warning"] .border_path {
|
|
112
|
+
fill: var(--navi-warning-color);
|
|
113
|
+
}
|
|
114
|
+
.jsenv_validation_message[data-level="warning"]
|
|
115
|
+
.jsenv_validation_message_icon {
|
|
116
|
+
background-color: var(--navi-warning-color);
|
|
117
|
+
}
|
|
118
|
+
.jsenv_validation_message[data-level="error"] .border_path {
|
|
119
|
+
fill: var(--navi-error-color);
|
|
120
|
+
}
|
|
121
|
+
.jsenv_validation_message[data-level="error"]
|
|
122
|
+
.jsenv_validation_message_icon {
|
|
123
|
+
background-color: var(--navi-error-color);
|
|
124
|
+
}
|
|
110
125
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
126
|
+
.jsenv_validation_message_content {
|
|
127
|
+
min-width: 0;
|
|
128
|
+
align-self: center;
|
|
129
|
+
word-break: break-word;
|
|
130
|
+
overflow-wrap: anywhere;
|
|
131
|
+
}
|
|
114
132
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
133
|
+
.jsenv_validation_message_border svg {
|
|
134
|
+
position: absolute;
|
|
135
|
+
inset: 0;
|
|
136
|
+
overflow: visible;
|
|
137
|
+
}
|
|
118
138
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
.background_path {
|
|
140
|
+
fill: var(--navi-validation-message-background-color);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.jsenv_validation_message_close_button_column {
|
|
144
|
+
display: flex;
|
|
145
|
+
height: 22px;
|
|
146
|
+
}
|
|
147
|
+
.jsenv_validation_message_close_button {
|
|
148
|
+
width: 1em;
|
|
149
|
+
height: 1em;
|
|
150
|
+
padding: 0;
|
|
151
|
+
align-self: center;
|
|
152
|
+
color: currentColor;
|
|
153
|
+
font-size: inherit;
|
|
154
|
+
background: none;
|
|
155
|
+
border: none;
|
|
156
|
+
border-radius: 0.2em;
|
|
157
|
+
cursor: pointer;
|
|
158
|
+
}
|
|
159
|
+
.jsenv_validation_message_close_button:hover {
|
|
160
|
+
background: rgba(0, 0, 0, 0.1);
|
|
161
|
+
}
|
|
162
|
+
.close_svg {
|
|
163
|
+
width: 100%;
|
|
164
|
+
height: 100%;
|
|
165
|
+
}
|
|
142
166
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
167
|
+
.error_stack {
|
|
168
|
+
max-height: 200px;
|
|
169
|
+
overflow: auto;
|
|
170
|
+
}
|
|
146
171
|
}
|
|
147
172
|
`;
|
|
148
173
|
|
|
@@ -191,6 +216,9 @@ const validationMessageTemplate = /* html */ `
|
|
|
191
216
|
</div>
|
|
192
217
|
`;
|
|
193
218
|
|
|
219
|
+
const validationMessageStyleController =
|
|
220
|
+
createStyleController("validation_message");
|
|
221
|
+
|
|
194
222
|
export const openValidationMessage = (
|
|
195
223
|
targetElement,
|
|
196
224
|
message,
|
|
@@ -210,8 +238,8 @@ export const openValidationMessage = (
|
|
|
210
238
|
});
|
|
211
239
|
}
|
|
212
240
|
|
|
241
|
+
const [teardown, addTeardown] = createPubSub(true);
|
|
213
242
|
let opened = true;
|
|
214
|
-
const closeCallbackSet = new Set();
|
|
215
243
|
const close = (reason) => {
|
|
216
244
|
if (!opened) {
|
|
217
245
|
return;
|
|
@@ -220,10 +248,7 @@ export const openValidationMessage = (
|
|
|
220
248
|
console.debug(`validation message closed (reason: ${reason})`);
|
|
221
249
|
}
|
|
222
250
|
opened = false;
|
|
223
|
-
|
|
224
|
-
closeCallback();
|
|
225
|
-
}
|
|
226
|
-
closeCallbackSet.clear();
|
|
251
|
+
teardown(reason);
|
|
227
252
|
};
|
|
228
253
|
|
|
229
254
|
// Create and add validation message to document
|
|
@@ -244,15 +269,6 @@ export const openValidationMessage = (
|
|
|
244
269
|
{ level = "warning", closeOnClickOutside = level === "info" } = {},
|
|
245
270
|
) => {
|
|
246
271
|
_closeOnClickOutside = closeOnClickOutside;
|
|
247
|
-
const borderColor =
|
|
248
|
-
level === "info" ? "blue" : level === "warning" ? "grey" : "red";
|
|
249
|
-
const backgroundColor = "white";
|
|
250
|
-
|
|
251
|
-
jsenvValidationMessage.style.setProperty("--border-color", borderColor);
|
|
252
|
-
jsenvValidationMessage.style.setProperty(
|
|
253
|
-
"--background-color",
|
|
254
|
-
backgroundColor,
|
|
255
|
-
);
|
|
256
272
|
|
|
257
273
|
if (Error.isError(newMessage)) {
|
|
258
274
|
const error = newMessage;
|
|
@@ -265,7 +281,7 @@ export const openValidationMessage = (
|
|
|
265
281
|
};
|
|
266
282
|
update(message, { level });
|
|
267
283
|
|
|
268
|
-
jsenvValidationMessage
|
|
284
|
+
validationMessageStyleController.set(jsenvValidationMessage, { opacity: 0 });
|
|
269
285
|
|
|
270
286
|
allowWheelThrough(jsenvValidationMessage, targetElement);
|
|
271
287
|
|
|
@@ -274,13 +290,18 @@ export const openValidationMessage = (
|
|
|
274
290
|
jsenvValidationMessage.id = validationMessageId;
|
|
275
291
|
targetElement.setAttribute("aria-invalid", "true");
|
|
276
292
|
targetElement.setAttribute("aria-errormessage", validationMessageId);
|
|
277
|
-
|
|
293
|
+
targetElement.style.setProperty(
|
|
294
|
+
"--invalid-color",
|
|
295
|
+
`var(--navi-${level}-color)`,
|
|
296
|
+
);
|
|
297
|
+
addTeardown(() => {
|
|
278
298
|
targetElement.removeAttribute("aria-invalid");
|
|
279
299
|
targetElement.removeAttribute("aria-errormessage");
|
|
300
|
+
targetElement.style.removeProperty("--invalid-color");
|
|
280
301
|
});
|
|
281
302
|
|
|
282
303
|
document.body.appendChild(jsenvValidationMessage);
|
|
283
|
-
|
|
304
|
+
addTeardown(() => {
|
|
284
305
|
jsenvValidationMessage.remove();
|
|
285
306
|
});
|
|
286
307
|
|
|
@@ -291,12 +312,12 @@ export const openValidationMessage = (
|
|
|
291
312
|
debug,
|
|
292
313
|
},
|
|
293
314
|
);
|
|
294
|
-
|
|
315
|
+
addTeardown(() => {
|
|
295
316
|
positionFollower.stop();
|
|
296
317
|
});
|
|
297
318
|
|
|
298
319
|
if (onClose) {
|
|
299
|
-
|
|
320
|
+
addTeardown(onClose);
|
|
300
321
|
}
|
|
301
322
|
close_on_target_focus: {
|
|
302
323
|
const onfocus = () => {
|
|
@@ -310,7 +331,7 @@ export const openValidationMessage = (
|
|
|
310
331
|
close("target_element_focus");
|
|
311
332
|
};
|
|
312
333
|
targetElement.addEventListener("focus", onfocus);
|
|
313
|
-
|
|
334
|
+
addTeardown(() => {
|
|
314
335
|
targetElement.removeEventListener("focus", onfocus);
|
|
315
336
|
});
|
|
316
337
|
}
|
|
@@ -337,7 +358,7 @@ export const openValidationMessage = (
|
|
|
337
358
|
close("click_outside");
|
|
338
359
|
};
|
|
339
360
|
document.addEventListener("click", handleClickOutside, true);
|
|
340
|
-
|
|
361
|
+
addTeardown(() => {
|
|
341
362
|
document.removeEventListener("click", handleClickOutside, true);
|
|
342
363
|
});
|
|
343
364
|
}
|
|
@@ -349,19 +370,12 @@ export const openValidationMessage = (
|
|
|
349
370
|
updatePosition: positionFollower.updatePosition,
|
|
350
371
|
};
|
|
351
372
|
targetElement.jsenvValidationMessage = validationMessage;
|
|
352
|
-
|
|
373
|
+
addTeardown(() => {
|
|
353
374
|
delete targetElement.jsenvValidationMessage;
|
|
354
375
|
});
|
|
355
376
|
return validationMessage;
|
|
356
377
|
};
|
|
357
378
|
|
|
358
|
-
// Configuration parameters for validation message appearance
|
|
359
|
-
const ARROW_WIDTH = 16;
|
|
360
|
-
const ARROW_HEIGHT = 8;
|
|
361
|
-
const CORNER_RADIUS = 3;
|
|
362
|
-
const BORDER_WIDTH = 1;
|
|
363
|
-
const ARROW_SPACING = 8;
|
|
364
|
-
|
|
365
379
|
/**
|
|
366
380
|
* Generates SVG path for validation message with arrow on top
|
|
367
381
|
* @param {number} width - Validation message width
|
|
@@ -567,6 +581,8 @@ const stickValidationMessageToTarget = (validationMessage, targetElement) => {
|
|
|
567
581
|
spaceBelowTarget,
|
|
568
582
|
} = pickPositionRelativeTo(validationMessageClone, targetElement, {
|
|
569
583
|
alignToViewportEdgeWhenTargetNearEdge: 20,
|
|
584
|
+
// when fully to the left, the border color is collé to the browser window making it hard to see
|
|
585
|
+
minLeft: 1,
|
|
570
586
|
});
|
|
571
587
|
|
|
572
588
|
// Get element padding and border to properly position arrow
|
|
@@ -581,7 +597,7 @@ const stickValidationMessageToTarget = (validationMessage, targetElement) => {
|
|
|
581
597
|
let arrowTargetLeft;
|
|
582
598
|
if (arrowPositionAttribute === "center") {
|
|
583
599
|
// Target the center of the element
|
|
584
|
-
arrowTargetLeft = targetRight / 2;
|
|
600
|
+
arrowTargetLeft = (targetLeft + targetRight) / 2;
|
|
585
601
|
} else {
|
|
586
602
|
// Default behavior: target the left edge of the element (after borders)
|
|
587
603
|
arrowTargetLeft = targetLeft + targetBorderSizes.left;
|
|
@@ -626,14 +642,6 @@ const stickValidationMessageToTarget = (validationMessage, targetElement) => {
|
|
|
626
642
|
contentHeight -= 16; // padding * 2
|
|
627
643
|
const spaceRemainingAfterContent =
|
|
628
644
|
spaceAvailableForContent - contentHeight;
|
|
629
|
-
console.log({
|
|
630
|
-
position,
|
|
631
|
-
spaceBelowTarget,
|
|
632
|
-
validationMessageHeight,
|
|
633
|
-
spaceAvailableForContent,
|
|
634
|
-
contentHeight,
|
|
635
|
-
spaceRemainingAfterContent,
|
|
636
|
-
});
|
|
637
645
|
if (spaceRemainingAfterContent < 2) {
|
|
638
646
|
const maxHeight = spaceAvailableForContent;
|
|
639
647
|
validationMessageContent.style.maxHeight = `${maxHeight}px`;
|
|
@@ -667,10 +675,14 @@ const stickValidationMessageToTarget = (validationMessage, targetElement) => {
|
|
|
667
675
|
);
|
|
668
676
|
}
|
|
669
677
|
|
|
670
|
-
validationMessage.style.opacity = visibilityRatio ? "1" : "0";
|
|
671
678
|
validationMessage.setAttribute("data-position", position);
|
|
672
|
-
|
|
673
|
-
|
|
679
|
+
validationMessageStyleController.set(validationMessage, {
|
|
680
|
+
opacity: visibilityRatio ? 1 : 0,
|
|
681
|
+
transform: {
|
|
682
|
+
translateX: validationMessageLeft,
|
|
683
|
+
translateY: validationMessageTop,
|
|
684
|
+
},
|
|
685
|
+
});
|
|
674
686
|
validationMessageClone.remove();
|
|
675
687
|
},
|
|
676
688
|
);
|