@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
|
@@ -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,7 +25,21 @@ import {
|
|
|
23
25
|
* @returns {Function} - Function to hide and remove the validation message
|
|
24
26
|
*/
|
|
25
27
|
|
|
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;
|
|
34
|
+
|
|
26
35
|
import.meta.css = /* css */ `
|
|
36
|
+
:root {
|
|
37
|
+
--navi-info-color: #2196f3;
|
|
38
|
+
--navi-warning-color: #ff9800;
|
|
39
|
+
--navi-error-color: #f44336;
|
|
40
|
+
--navi-validation-message-background-color: white;
|
|
41
|
+
}
|
|
42
|
+
|
|
27
43
|
/* Ensure the validation message CANNOT cause overflow */
|
|
28
44
|
/* might be important to ensure it cannot create scrollbars in the document */
|
|
29
45
|
/* When measuring the size it should take */
|
|
@@ -34,48 +50,48 @@ import.meta.css = /* css */ `
|
|
|
34
50
|
}
|
|
35
51
|
|
|
36
52
|
.jsenv_validation_message {
|
|
37
|
-
display: block;
|
|
38
|
-
overflow: visible;
|
|
39
|
-
height: auto;
|
|
40
53
|
position: absolute;
|
|
54
|
+
top: 0;
|
|
55
|
+
left: 0;
|
|
41
56
|
z-index: 1;
|
|
57
|
+
display: block;
|
|
58
|
+
height: auto;
|
|
42
59
|
opacity: 0;
|
|
43
|
-
left: 0;
|
|
44
|
-
top: 0;
|
|
45
60
|
/* will be positioned with transform: translate */
|
|
46
61
|
transition: opacity 0.2s ease-in-out;
|
|
62
|
+
overflow: visible;
|
|
47
63
|
}
|
|
48
64
|
|
|
49
65
|
.jsenv_validation_message_border {
|
|
50
66
|
position: absolute;
|
|
51
|
-
pointer-events: none;
|
|
52
67
|
filter: drop-shadow(4px 4px 3px rgba(0, 0, 0, 0.2));
|
|
68
|
+
pointer-events: none;
|
|
53
69
|
}
|
|
54
70
|
|
|
55
71
|
.jsenv_validation_message_body_wrapper {
|
|
72
|
+
position: relative;
|
|
56
73
|
border-style: solid;
|
|
57
74
|
border-color: transparent;
|
|
58
|
-
position: relative;
|
|
59
75
|
}
|
|
60
76
|
|
|
61
77
|
.jsenv_validation_message_body {
|
|
62
|
-
padding: 8px;
|
|
63
78
|
position: relative;
|
|
64
|
-
max-width: 47vw;
|
|
65
79
|
display: flex;
|
|
80
|
+
max-width: 47vw;
|
|
81
|
+
padding: 8px;
|
|
66
82
|
flex-direction: row;
|
|
67
83
|
gap: 10px;
|
|
68
84
|
}
|
|
69
85
|
|
|
70
86
|
.jsenv_validation_message_icon {
|
|
71
87
|
display: flex;
|
|
72
|
-
align-self: flex-start;
|
|
73
|
-
align-items: center;
|
|
74
|
-
justify-content: center;
|
|
75
88
|
width: 22px;
|
|
76
89
|
height: 22px;
|
|
77
|
-
border-radius: 2px;
|
|
78
90
|
flex-shrink: 0;
|
|
91
|
+
align-items: center;
|
|
92
|
+
align-self: flex-start;
|
|
93
|
+
justify-content: center;
|
|
94
|
+
border-radius: 2px;
|
|
79
95
|
}
|
|
80
96
|
|
|
81
97
|
.jsenv_validation_message_exclamation_svg {
|
|
@@ -84,21 +100,30 @@ import.meta.css = /* css */ `
|
|
|
84
100
|
color: white;
|
|
85
101
|
}
|
|
86
102
|
|
|
103
|
+
.jsenv_validation_message[data-level="info"] .border_path {
|
|
104
|
+
fill: var(--navi-info-color);
|
|
105
|
+
}
|
|
87
106
|
.jsenv_validation_message[data-level="info"] .jsenv_validation_message_icon {
|
|
88
|
-
background-color:
|
|
107
|
+
background-color: var(--navi-info-color);
|
|
108
|
+
}
|
|
109
|
+
.jsenv_validation_message[data-level="warning"] .border_path {
|
|
110
|
+
fill: var(--navi-warning-color);
|
|
89
111
|
}
|
|
90
112
|
.jsenv_validation_message[data-level="warning"]
|
|
91
113
|
.jsenv_validation_message_icon {
|
|
92
|
-
background-color:
|
|
114
|
+
background-color: var(--navi-warning-color);
|
|
115
|
+
}
|
|
116
|
+
.jsenv_validation_message[data-level="error"] .border_path {
|
|
117
|
+
fill: var(--navi-error-color);
|
|
93
118
|
}
|
|
94
119
|
.jsenv_validation_message[data-level="error"] .jsenv_validation_message_icon {
|
|
95
|
-
background-color:
|
|
120
|
+
background-color: var(--navi-error-color);
|
|
96
121
|
}
|
|
97
122
|
|
|
98
123
|
.jsenv_validation_message_content {
|
|
124
|
+
min-width: 0;
|
|
99
125
|
align-self: center;
|
|
100
126
|
word-break: break-word;
|
|
101
|
-
min-width: 0;
|
|
102
127
|
overflow-wrap: anywhere;
|
|
103
128
|
}
|
|
104
129
|
|
|
@@ -108,12 +133,8 @@ import.meta.css = /* css */ `
|
|
|
108
133
|
overflow: visible;
|
|
109
134
|
}
|
|
110
135
|
|
|
111
|
-
.border_path {
|
|
112
|
-
fill: var(--border-color);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
136
|
.background_path {
|
|
116
|
-
fill: var(--background-color);
|
|
137
|
+
fill: var(--navi-validation-message-background-color);
|
|
117
138
|
}
|
|
118
139
|
|
|
119
140
|
.jsenv_validation_message_close_button_column {
|
|
@@ -121,16 +142,16 @@ import.meta.css = /* css */ `
|
|
|
121
142
|
height: 22px;
|
|
122
143
|
}
|
|
123
144
|
.jsenv_validation_message_close_button {
|
|
124
|
-
border: none;
|
|
125
|
-
background: none;
|
|
126
|
-
padding: 0;
|
|
127
145
|
width: 1em;
|
|
128
146
|
height: 1em;
|
|
129
|
-
|
|
130
|
-
cursor: pointer;
|
|
131
|
-
border-radius: 0.2em;
|
|
147
|
+
padding: 0;
|
|
132
148
|
align-self: center;
|
|
133
149
|
color: currentColor;
|
|
150
|
+
font-size: inherit;
|
|
151
|
+
background: none;
|
|
152
|
+
border: none;
|
|
153
|
+
border-radius: 0.2em;
|
|
154
|
+
cursor: pointer;
|
|
134
155
|
}
|
|
135
156
|
.jsenv_validation_message_close_button:hover {
|
|
136
157
|
background: rgba(0, 0, 0, 0.1);
|
|
@@ -141,8 +162,8 @@ import.meta.css = /* css */ `
|
|
|
141
162
|
}
|
|
142
163
|
|
|
143
164
|
.error_stack {
|
|
144
|
-
overflow: auto;
|
|
145
165
|
max-height: 200px;
|
|
166
|
+
overflow: auto;
|
|
146
167
|
}
|
|
147
168
|
`;
|
|
148
169
|
|
|
@@ -191,6 +212,9 @@ const validationMessageTemplate = /* html */ `
|
|
|
191
212
|
</div>
|
|
192
213
|
`;
|
|
193
214
|
|
|
215
|
+
const validationMessageStyleController =
|
|
216
|
+
createStyleController("validation_message");
|
|
217
|
+
|
|
194
218
|
export const openValidationMessage = (
|
|
195
219
|
targetElement,
|
|
196
220
|
message,
|
|
@@ -210,8 +234,8 @@ export const openValidationMessage = (
|
|
|
210
234
|
});
|
|
211
235
|
}
|
|
212
236
|
|
|
237
|
+
const [teardown, addTeardown] = createPubSub(true);
|
|
213
238
|
let opened = true;
|
|
214
|
-
const closeCallbackSet = new Set();
|
|
215
239
|
const close = (reason) => {
|
|
216
240
|
if (!opened) {
|
|
217
241
|
return;
|
|
@@ -220,10 +244,7 @@ export const openValidationMessage = (
|
|
|
220
244
|
console.debug(`validation message closed (reason: ${reason})`);
|
|
221
245
|
}
|
|
222
246
|
opened = false;
|
|
223
|
-
|
|
224
|
-
closeCallback();
|
|
225
|
-
}
|
|
226
|
-
closeCallbackSet.clear();
|
|
247
|
+
teardown(reason);
|
|
227
248
|
};
|
|
228
249
|
|
|
229
250
|
// Create and add validation message to document
|
|
@@ -244,15 +265,6 @@ export const openValidationMessage = (
|
|
|
244
265
|
{ level = "warning", closeOnClickOutside = level === "info" } = {},
|
|
245
266
|
) => {
|
|
246
267
|
_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
268
|
|
|
257
269
|
if (Error.isError(newMessage)) {
|
|
258
270
|
const error = newMessage;
|
|
@@ -265,7 +277,7 @@ export const openValidationMessage = (
|
|
|
265
277
|
};
|
|
266
278
|
update(message, { level });
|
|
267
279
|
|
|
268
|
-
jsenvValidationMessage
|
|
280
|
+
validationMessageStyleController.set(jsenvValidationMessage, { opacity: 0 });
|
|
269
281
|
|
|
270
282
|
allowWheelThrough(jsenvValidationMessage, targetElement);
|
|
271
283
|
|
|
@@ -274,13 +286,18 @@ export const openValidationMessage = (
|
|
|
274
286
|
jsenvValidationMessage.id = validationMessageId;
|
|
275
287
|
targetElement.setAttribute("aria-invalid", "true");
|
|
276
288
|
targetElement.setAttribute("aria-errormessage", validationMessageId);
|
|
277
|
-
|
|
289
|
+
targetElement.style.setProperty(
|
|
290
|
+
"--invalid-color",
|
|
291
|
+
`var(--navi-${level}-color)`,
|
|
292
|
+
);
|
|
293
|
+
addTeardown(() => {
|
|
278
294
|
targetElement.removeAttribute("aria-invalid");
|
|
279
295
|
targetElement.removeAttribute("aria-errormessage");
|
|
296
|
+
targetElement.style.removeProperty("--invalid-color");
|
|
280
297
|
});
|
|
281
298
|
|
|
282
299
|
document.body.appendChild(jsenvValidationMessage);
|
|
283
|
-
|
|
300
|
+
addTeardown(() => {
|
|
284
301
|
jsenvValidationMessage.remove();
|
|
285
302
|
});
|
|
286
303
|
|
|
@@ -291,12 +308,12 @@ export const openValidationMessage = (
|
|
|
291
308
|
debug,
|
|
292
309
|
},
|
|
293
310
|
);
|
|
294
|
-
|
|
311
|
+
addTeardown(() => {
|
|
295
312
|
positionFollower.stop();
|
|
296
313
|
});
|
|
297
314
|
|
|
298
315
|
if (onClose) {
|
|
299
|
-
|
|
316
|
+
addTeardown(onClose);
|
|
300
317
|
}
|
|
301
318
|
close_on_target_focus: {
|
|
302
319
|
const onfocus = () => {
|
|
@@ -310,7 +327,7 @@ export const openValidationMessage = (
|
|
|
310
327
|
close("target_element_focus");
|
|
311
328
|
};
|
|
312
329
|
targetElement.addEventListener("focus", onfocus);
|
|
313
|
-
|
|
330
|
+
addTeardown(() => {
|
|
314
331
|
targetElement.removeEventListener("focus", onfocus);
|
|
315
332
|
});
|
|
316
333
|
}
|
|
@@ -337,7 +354,7 @@ export const openValidationMessage = (
|
|
|
337
354
|
close("click_outside");
|
|
338
355
|
};
|
|
339
356
|
document.addEventListener("click", handleClickOutside, true);
|
|
340
|
-
|
|
357
|
+
addTeardown(() => {
|
|
341
358
|
document.removeEventListener("click", handleClickOutside, true);
|
|
342
359
|
});
|
|
343
360
|
}
|
|
@@ -349,19 +366,12 @@ export const openValidationMessage = (
|
|
|
349
366
|
updatePosition: positionFollower.updatePosition,
|
|
350
367
|
};
|
|
351
368
|
targetElement.jsenvValidationMessage = validationMessage;
|
|
352
|
-
|
|
369
|
+
addTeardown(() => {
|
|
353
370
|
delete targetElement.jsenvValidationMessage;
|
|
354
371
|
});
|
|
355
372
|
return validationMessage;
|
|
356
373
|
};
|
|
357
374
|
|
|
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
375
|
/**
|
|
366
376
|
* Generates SVG path for validation message with arrow on top
|
|
367
377
|
* @param {number} width - Validation message width
|
|
@@ -567,6 +577,8 @@ const stickValidationMessageToTarget = (validationMessage, targetElement) => {
|
|
|
567
577
|
spaceBelowTarget,
|
|
568
578
|
} = pickPositionRelativeTo(validationMessageClone, targetElement, {
|
|
569
579
|
alignToViewportEdgeWhenTargetNearEdge: 20,
|
|
580
|
+
// when fully to the left, the border color is collé to the browser window making it hard to see
|
|
581
|
+
minLeft: 1,
|
|
570
582
|
});
|
|
571
583
|
|
|
572
584
|
// Get element padding and border to properly position arrow
|
|
@@ -581,7 +593,7 @@ const stickValidationMessageToTarget = (validationMessage, targetElement) => {
|
|
|
581
593
|
let arrowTargetLeft;
|
|
582
594
|
if (arrowPositionAttribute === "center") {
|
|
583
595
|
// Target the center of the element
|
|
584
|
-
arrowTargetLeft = targetRight / 2;
|
|
596
|
+
arrowTargetLeft = (targetLeft + targetRight) / 2;
|
|
585
597
|
} else {
|
|
586
598
|
// Default behavior: target the left edge of the element (after borders)
|
|
587
599
|
arrowTargetLeft = targetLeft + targetBorderSizes.left;
|
|
@@ -626,14 +638,6 @@ const stickValidationMessageToTarget = (validationMessage, targetElement) => {
|
|
|
626
638
|
contentHeight -= 16; // padding * 2
|
|
627
639
|
const spaceRemainingAfterContent =
|
|
628
640
|
spaceAvailableForContent - contentHeight;
|
|
629
|
-
console.log({
|
|
630
|
-
position,
|
|
631
|
-
spaceBelowTarget,
|
|
632
|
-
validationMessageHeight,
|
|
633
|
-
spaceAvailableForContent,
|
|
634
|
-
contentHeight,
|
|
635
|
-
spaceRemainingAfterContent,
|
|
636
|
-
});
|
|
637
641
|
if (spaceRemainingAfterContent < 2) {
|
|
638
642
|
const maxHeight = spaceAvailableForContent;
|
|
639
643
|
validationMessageContent.style.maxHeight = `${maxHeight}px`;
|
|
@@ -667,10 +671,14 @@ const stickValidationMessageToTarget = (validationMessage, targetElement) => {
|
|
|
667
671
|
);
|
|
668
672
|
}
|
|
669
673
|
|
|
670
|
-
validationMessage.style.opacity = visibilityRatio ? "1" : "0";
|
|
671
674
|
validationMessage.setAttribute("data-position", position);
|
|
672
|
-
|
|
673
|
-
|
|
675
|
+
validationMessageStyleController.set(validationMessage, {
|
|
676
|
+
opacity: visibilityRatio ? 1 : 0,
|
|
677
|
+
transform: {
|
|
678
|
+
translateX: validationMessageLeft,
|
|
679
|
+
translateY: validationMessageTop,
|
|
680
|
+
},
|
|
681
|
+
});
|
|
674
682
|
validationMessageClone.remove();
|
|
675
683
|
},
|
|
676
684
|
);
|