@crisp-ui-kit/crisp 0.30.0 → 0.30.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.
|
@@ -37,14 +37,71 @@ exports.FormField = FormField;
|
|
|
37
37
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
38
38
|
const React = __importStar(require("react"));
|
|
39
39
|
const formfield_recipe_1 = require("./formfield.recipe");
|
|
40
|
+
/**
|
|
41
|
+
* Tell the control what the field knows about it.
|
|
42
|
+
*
|
|
43
|
+
* FormField renders the label, the description, the error and the required
|
|
44
|
+
* marker; the control is an opaque child, so the three facts that belong ON
|
|
45
|
+
* the control have to be put there by id — the same id the label already
|
|
46
|
+
* points at with `htmlFor`.
|
|
47
|
+
*
|
|
48
|
+
* Until now none of them were. The message got an id and nobody referenced it,
|
|
49
|
+
* `aria-invalid` was never set, and the required asterisk was `aria-hidden`
|
|
50
|
+
* decoration with no `aria-required` behind it: a screen-reader user heard the
|
|
51
|
+
* label and nothing else (#251).
|
|
52
|
+
*
|
|
53
|
+
* Only what this put there is taken back, so a consumer's own
|
|
54
|
+
* `aria-describedby` survives both ways.
|
|
55
|
+
*/
|
|
56
|
+
function useFieldWiring({ htmlFor, messageId, invalid, required, }) {
|
|
57
|
+
React.useEffect(() => {
|
|
58
|
+
if (!htmlFor)
|
|
59
|
+
return;
|
|
60
|
+
const el = document.getElementById(htmlFor);
|
|
61
|
+
if (!el)
|
|
62
|
+
return;
|
|
63
|
+
const previous = el.getAttribute("aria-describedby");
|
|
64
|
+
if (messageId) {
|
|
65
|
+
const ids = previous ? previous.split(/\s+/) : [];
|
|
66
|
+
if (!ids.includes(messageId))
|
|
67
|
+
el.setAttribute("aria-describedby", [...ids, messageId].join(" "));
|
|
68
|
+
}
|
|
69
|
+
if (invalid)
|
|
70
|
+
el.setAttribute("aria-invalid", "true");
|
|
71
|
+
if (required)
|
|
72
|
+
el.setAttribute("aria-required", "true");
|
|
73
|
+
return () => {
|
|
74
|
+
if (messageId) {
|
|
75
|
+
const now = (el.getAttribute("aria-describedby") ?? "")
|
|
76
|
+
.split(/\s+/)
|
|
77
|
+
.filter((id) => id && id !== messageId);
|
|
78
|
+
if (now.length)
|
|
79
|
+
el.setAttribute("aria-describedby", now.join(" "));
|
|
80
|
+
else
|
|
81
|
+
el.removeAttribute("aria-describedby");
|
|
82
|
+
}
|
|
83
|
+
if (invalid)
|
|
84
|
+
el.removeAttribute("aria-invalid");
|
|
85
|
+
if (required)
|
|
86
|
+
el.removeAttribute("aria-required");
|
|
87
|
+
};
|
|
88
|
+
}, [htmlFor, messageId, invalid, required]);
|
|
89
|
+
}
|
|
40
90
|
/**
|
|
41
91
|
* A labelled form field — measured from Attio's Settings forms. A muted label
|
|
42
92
|
* above any control, with an optional description or error line below. When
|
|
43
|
-
* `error` is set
|
|
93
|
+
* `error` is set it replaces the description; the control is given
|
|
94
|
+
* `aria-describedby`, plus `aria-invalid` and `aria-required` as they apply.
|
|
44
95
|
*/
|
|
45
96
|
function FormField({ label, htmlFor, required, optional, description, error, children, className, ...rest }) {
|
|
46
97
|
const descId = React.useId();
|
|
47
98
|
const hasMsg = error != null || description != null;
|
|
99
|
+
useFieldWiring({
|
|
100
|
+
htmlFor,
|
|
101
|
+
messageId: hasMsg ? descId : undefined,
|
|
102
|
+
invalid: error != null,
|
|
103
|
+
required: !!required,
|
|
104
|
+
});
|
|
48
105
|
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, formfield_recipe_1.formfieldRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("label", { className: "formfield-label", htmlFor: htmlFor, children: [label, required && ((0, jsx_runtime_1.jsx)("span", { className: "formfield-required", "aria-hidden": "true", children: "*" })), optional && !required && ((0, jsx_runtime_1.jsx)("span", { className: "formfield-optional", children: optional === true ? "(optional)" : optional }))] }), children, error != null ? ((0, jsx_runtime_1.jsx)("span", { className: "formfield-error", id: hasMsg ? descId : undefined, children: error })) : description != null ? ((0, jsx_runtime_1.jsx)("span", { className: "formfield-desc", id: hasMsg ? descId : undefined, children: description })) : null] }));
|
|
49
106
|
}
|
|
50
107
|
FormField.displayName = "FormField";
|
|
@@ -1,14 +1,71 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { formfieldRecipe } from "./formfield.recipe.js";
|
|
4
|
+
/**
|
|
5
|
+
* Tell the control what the field knows about it.
|
|
6
|
+
*
|
|
7
|
+
* FormField renders the label, the description, the error and the required
|
|
8
|
+
* marker; the control is an opaque child, so the three facts that belong ON
|
|
9
|
+
* the control have to be put there by id — the same id the label already
|
|
10
|
+
* points at with `htmlFor`.
|
|
11
|
+
*
|
|
12
|
+
* Until now none of them were. The message got an id and nobody referenced it,
|
|
13
|
+
* `aria-invalid` was never set, and the required asterisk was `aria-hidden`
|
|
14
|
+
* decoration with no `aria-required` behind it: a screen-reader user heard the
|
|
15
|
+
* label and nothing else (#251).
|
|
16
|
+
*
|
|
17
|
+
* Only what this put there is taken back, so a consumer's own
|
|
18
|
+
* `aria-describedby` survives both ways.
|
|
19
|
+
*/
|
|
20
|
+
function useFieldWiring({ htmlFor, messageId, invalid, required, }) {
|
|
21
|
+
React.useEffect(() => {
|
|
22
|
+
if (!htmlFor)
|
|
23
|
+
return;
|
|
24
|
+
const el = document.getElementById(htmlFor);
|
|
25
|
+
if (!el)
|
|
26
|
+
return;
|
|
27
|
+
const previous = el.getAttribute("aria-describedby");
|
|
28
|
+
if (messageId) {
|
|
29
|
+
const ids = previous ? previous.split(/\s+/) : [];
|
|
30
|
+
if (!ids.includes(messageId))
|
|
31
|
+
el.setAttribute("aria-describedby", [...ids, messageId].join(" "));
|
|
32
|
+
}
|
|
33
|
+
if (invalid)
|
|
34
|
+
el.setAttribute("aria-invalid", "true");
|
|
35
|
+
if (required)
|
|
36
|
+
el.setAttribute("aria-required", "true");
|
|
37
|
+
return () => {
|
|
38
|
+
if (messageId) {
|
|
39
|
+
const now = (el.getAttribute("aria-describedby") ?? "")
|
|
40
|
+
.split(/\s+/)
|
|
41
|
+
.filter((id) => id && id !== messageId);
|
|
42
|
+
if (now.length)
|
|
43
|
+
el.setAttribute("aria-describedby", now.join(" "));
|
|
44
|
+
else
|
|
45
|
+
el.removeAttribute("aria-describedby");
|
|
46
|
+
}
|
|
47
|
+
if (invalid)
|
|
48
|
+
el.removeAttribute("aria-invalid");
|
|
49
|
+
if (required)
|
|
50
|
+
el.removeAttribute("aria-required");
|
|
51
|
+
};
|
|
52
|
+
}, [htmlFor, messageId, invalid, required]);
|
|
53
|
+
}
|
|
4
54
|
/**
|
|
5
55
|
* A labelled form field — measured from Attio's Settings forms. A muted label
|
|
6
56
|
* above any control, with an optional description or error line below. When
|
|
7
|
-
* `error` is set
|
|
57
|
+
* `error` is set it replaces the description; the control is given
|
|
58
|
+
* `aria-describedby`, plus `aria-invalid` and `aria-required` as they apply.
|
|
8
59
|
*/
|
|
9
60
|
export function FormField({ label, htmlFor, required, optional, description, error, children, className, ...rest }) {
|
|
10
61
|
const descId = React.useId();
|
|
11
62
|
const hasMsg = error != null || description != null;
|
|
63
|
+
useFieldWiring({
|
|
64
|
+
htmlFor,
|
|
65
|
+
messageId: hasMsg ? descId : undefined,
|
|
66
|
+
invalid: error != null,
|
|
67
|
+
required: !!required,
|
|
68
|
+
});
|
|
12
69
|
return (_jsxs("div", { className: formfieldRecipe({ className }), ...rest, children: [_jsxs("label", { className: "formfield-label", htmlFor: htmlFor, children: [label, required && (_jsx("span", { className: "formfield-required", "aria-hidden": "true", children: "*" })), optional && !required && (_jsx("span", { className: "formfield-optional", children: optional === true ? "(optional)" : optional }))] }), children, error != null ? (_jsx("span", { className: "formfield-error", id: hasMsg ? descId : undefined, children: error })) : description != null ? (_jsx("span", { className: "formfield-desc", id: hasMsg ? descId : undefined, children: description })) : null] }));
|
|
13
70
|
}
|
|
14
71
|
FormField.displayName = "FormField";
|
|
@@ -21,7 +21,8 @@ export type FormFieldProps = React.ComponentPropsWithoutRef<"div"> & {
|
|
|
21
21
|
/**
|
|
22
22
|
* A labelled form field — measured from Attio's Settings forms. A muted label
|
|
23
23
|
* above any control, with an optional description or error line below. When
|
|
24
|
-
* `error` is set
|
|
24
|
+
* `error` is set it replaces the description; the control is given
|
|
25
|
+
* `aria-describedby`, plus `aria-invalid` and `aria-required` as they apply.
|
|
25
26
|
*/
|
|
26
27
|
export declare function FormField({ label, htmlFor, required, optional, description, error, children, className, ...rest }: FormFieldProps): React.JSX.Element;
|
|
27
28
|
export declare namespace FormField {
|