@seed-design/react-field 1.0.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/lib/Field-12s-C77so-rw.js +196 -0
- package/lib/Field-12s-DIDZP1t4.cjs +200 -0
- package/lib/index.cjs +16 -0
- package/lib/index.d.ts +2381 -0
- package/lib/index.js +12 -0
- package/package.json +46 -0
- package/src/Field.namespace.ts +10 -0
- package/src/Field.tsx +68 -0
- package/src/dom.ts +4 -0
- package/src/index.ts +14 -0
- package/src/useField.test.tsx +637 -0
- package/src/useField.ts +183 -0
- package/src/useFieldContext.tsx +21 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { composeRefs } from '@radix-ui/react-compose-refs';
|
|
4
|
+
import { elementProps, dataAttr, inputProps, labelProps, ariaAttr, mergeProps } from '@seed-design/dom-utils';
|
|
5
|
+
import { Primitive } from '@seed-design/react-primitive';
|
|
6
|
+
import { useId, useState, useCallback, createContext, useContext, forwardRef } from 'react';
|
|
7
|
+
|
|
8
|
+
const getLabelId = (id)=>`field:${id}:label`;
|
|
9
|
+
const getInputId = (id)=>`field:${id}:input`;
|
|
10
|
+
const getDescriptionId = (id)=>`field:${id}:description`;
|
|
11
|
+
const getErrorMessageId = (id)=>`field:${id}:error-message`;
|
|
12
|
+
|
|
13
|
+
function useFieldState() {
|
|
14
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
15
|
+
const [isActive, setIsActive] = useState(false);
|
|
16
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
17
|
+
const [isFocusVisible, setIsFocusVisible] = useState(false);
|
|
18
|
+
const [isLabelRendered, setIsLabelRendered] = useState(false);
|
|
19
|
+
const labelRef = useCallback((node)=>{
|
|
20
|
+
setIsLabelRendered(!!node);
|
|
21
|
+
}, []);
|
|
22
|
+
const [isDescriptionRendered, setIsDescriptionRendered] = useState(false);
|
|
23
|
+
const descriptionRef = useCallback((node)=>{
|
|
24
|
+
setIsDescriptionRendered(!!node);
|
|
25
|
+
}, []);
|
|
26
|
+
const [isErrorMessageRendered, setIsErrorMessageRendered] = useState(false);
|
|
27
|
+
const errorMessageRef = useCallback((node)=>{
|
|
28
|
+
setIsErrorMessageRendered(!!node);
|
|
29
|
+
}, []);
|
|
30
|
+
return {
|
|
31
|
+
refs: {
|
|
32
|
+
label: labelRef,
|
|
33
|
+
description: descriptionRef,
|
|
34
|
+
errorMessage: errorMessageRef
|
|
35
|
+
},
|
|
36
|
+
isHovered,
|
|
37
|
+
isActive,
|
|
38
|
+
isFocused,
|
|
39
|
+
isFocusVisible,
|
|
40
|
+
renderedElements: {
|
|
41
|
+
label: isLabelRendered,
|
|
42
|
+
description: isDescriptionRendered,
|
|
43
|
+
errorMessage: isErrorMessageRendered
|
|
44
|
+
},
|
|
45
|
+
setIsHovered,
|
|
46
|
+
setIsActive,
|
|
47
|
+
setIsFocused,
|
|
48
|
+
setIsFocusVisible
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function useField(props) {
|
|
52
|
+
const id = useId();
|
|
53
|
+
const { disabled = false, invalid = false, readOnly = false, required = false } = props;
|
|
54
|
+
const { refs, renderedElements, isHovered, isActive, isFocused, isFocusVisible, setIsHovered, setIsActive, setIsFocused, setIsFocusVisible } = useFieldState();
|
|
55
|
+
const ariaDescribedBy = [
|
|
56
|
+
renderedElements.description ? getDescriptionId(id) : false,
|
|
57
|
+
renderedElements.errorMessage ? getErrorMessageId(id) : false
|
|
58
|
+
].filter(Boolean).join(" ") || undefined;
|
|
59
|
+
const stateProps = elementProps({
|
|
60
|
+
"data-hover": dataAttr(isHovered),
|
|
61
|
+
"data-active": dataAttr(isActive),
|
|
62
|
+
"data-focus": dataAttr(isFocused),
|
|
63
|
+
"data-focus-visible": dataAttr(isFocusVisible),
|
|
64
|
+
"data-readonly": dataAttr(readOnly),
|
|
65
|
+
"data-disabled": dataAttr(disabled),
|
|
66
|
+
"data-invalid": dataAttr(invalid)
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
refs,
|
|
70
|
+
active: isActive,
|
|
71
|
+
focused: isFocused,
|
|
72
|
+
invalid,
|
|
73
|
+
required,
|
|
74
|
+
setIsFocused,
|
|
75
|
+
setIsFocusVisible,
|
|
76
|
+
stateProps,
|
|
77
|
+
rootProps: elementProps({
|
|
78
|
+
...stateProps,
|
|
79
|
+
onPointerMove () {
|
|
80
|
+
setIsHovered(true);
|
|
81
|
+
},
|
|
82
|
+
onPointerDown () {
|
|
83
|
+
setIsActive(true);
|
|
84
|
+
},
|
|
85
|
+
onPointerUp () {
|
|
86
|
+
setIsActive(false);
|
|
87
|
+
},
|
|
88
|
+
onPointerLeave () {
|
|
89
|
+
setIsHovered(false);
|
|
90
|
+
setIsActive(false);
|
|
91
|
+
}
|
|
92
|
+
}),
|
|
93
|
+
labelProps: labelProps({
|
|
94
|
+
...stateProps,
|
|
95
|
+
id: getLabelId(id),
|
|
96
|
+
htmlFor: getInputId(id)
|
|
97
|
+
}),
|
|
98
|
+
inputProps: inputProps({
|
|
99
|
+
disabled,
|
|
100
|
+
readOnly,
|
|
101
|
+
name: props.name || id,
|
|
102
|
+
id: getInputId(id)
|
|
103
|
+
}),
|
|
104
|
+
inputAriaAttributes: elementProps({
|
|
105
|
+
...renderedElements.label && {
|
|
106
|
+
"aria-labelledby": getLabelId(id)
|
|
107
|
+
},
|
|
108
|
+
"aria-describedby": ariaDescribedBy,
|
|
109
|
+
"aria-required": ariaAttr(required),
|
|
110
|
+
"aria-invalid": ariaAttr(invalid),
|
|
111
|
+
"aria-readonly": ariaAttr(readOnly),
|
|
112
|
+
"aria-disabled": ariaAttr(disabled)
|
|
113
|
+
}),
|
|
114
|
+
inputHandlers: inputProps({
|
|
115
|
+
onChange: (event)=>{
|
|
116
|
+
setIsFocusVisible(event.target.matches(":focus-visible"));
|
|
117
|
+
},
|
|
118
|
+
onBlur () {
|
|
119
|
+
setIsFocused(false);
|
|
120
|
+
setIsFocusVisible(false);
|
|
121
|
+
},
|
|
122
|
+
onFocus (event) {
|
|
123
|
+
setIsFocused(true);
|
|
124
|
+
setIsFocusVisible(event.target.matches(":focus-visible"));
|
|
125
|
+
}
|
|
126
|
+
}),
|
|
127
|
+
descriptionProps: elementProps({
|
|
128
|
+
...stateProps,
|
|
129
|
+
id: getDescriptionId(id)
|
|
130
|
+
}),
|
|
131
|
+
errorMessageProps: elementProps({
|
|
132
|
+
...stateProps,
|
|
133
|
+
id: getErrorMessageId(id),
|
|
134
|
+
"aria-live": "polite"
|
|
135
|
+
})
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const FieldContext = /*#__PURE__*/ createContext(null);
|
|
140
|
+
const FieldProvider = FieldContext.Provider;
|
|
141
|
+
function useFieldContext({ strict = true } = {}) {
|
|
142
|
+
const context = useContext(FieldContext);
|
|
143
|
+
if (!context && strict) {
|
|
144
|
+
throw new Error("useFieldContext must be used within a Field");
|
|
145
|
+
}
|
|
146
|
+
return context;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const FieldRoot = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
150
|
+
const { readOnly, disabled, invalid, required, name, ...otherProps } = props;
|
|
151
|
+
const api = useField({
|
|
152
|
+
disabled,
|
|
153
|
+
invalid,
|
|
154
|
+
required,
|
|
155
|
+
readOnly,
|
|
156
|
+
name
|
|
157
|
+
});
|
|
158
|
+
const mergedProps = mergeProps(api.rootProps, otherProps);
|
|
159
|
+
return /*#__PURE__*/ jsx(FieldProvider, {
|
|
160
|
+
value: api,
|
|
161
|
+
children: /*#__PURE__*/ jsx(Primitive.div, {
|
|
162
|
+
ref: ref,
|
|
163
|
+
...mergedProps
|
|
164
|
+
})
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
FieldRoot.displayName = "FieldRoot";
|
|
168
|
+
const FieldLabel = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
169
|
+
const { refs, labelProps } = useFieldContext();
|
|
170
|
+
const mergedProps = mergeProps(labelProps, props);
|
|
171
|
+
return /*#__PURE__*/ jsx(Primitive.label, {
|
|
172
|
+
ref: composeRefs(refs.label, ref),
|
|
173
|
+
...mergedProps
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
FieldLabel.displayName = "FieldLabel";
|
|
177
|
+
const FieldDescription = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
178
|
+
const { refs, descriptionProps } = useFieldContext();
|
|
179
|
+
const mergedProps = mergeProps(descriptionProps, props);
|
|
180
|
+
return /*#__PURE__*/ jsx(Primitive.span, {
|
|
181
|
+
ref: composeRefs(refs.description, ref),
|
|
182
|
+
...mergedProps
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
FieldDescription.displayName = "FieldDescription";
|
|
186
|
+
const FieldErrorMessage = /*#__PURE__*/ forwardRef((props, ref)=>{
|
|
187
|
+
const { refs, errorMessageProps } = useFieldContext();
|
|
188
|
+
const mergedProps = mergeProps(errorMessageProps, props);
|
|
189
|
+
return /*#__PURE__*/ jsx(Primitive.div, {
|
|
190
|
+
ref: composeRefs(refs.errorMessage, ref),
|
|
191
|
+
...mergedProps
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
FieldErrorMessage.displayName = "FieldErrorMessage";
|
|
195
|
+
|
|
196
|
+
export { FieldDescription as F, FieldErrorMessage as a, FieldLabel as b, FieldRoot as c, useFieldContext as u };
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
3
|
+
var reactComposeRefs = require('@radix-ui/react-compose-refs');
|
|
4
|
+
var domUtils = require('@seed-design/dom-utils');
|
|
5
|
+
var reactPrimitive = require('@seed-design/react-primitive');
|
|
6
|
+
var react = require('react');
|
|
7
|
+
|
|
8
|
+
const getLabelId = (id)=>`field:${id}:label`;
|
|
9
|
+
const getInputId = (id)=>`field:${id}:input`;
|
|
10
|
+
const getDescriptionId = (id)=>`field:${id}:description`;
|
|
11
|
+
const getErrorMessageId = (id)=>`field:${id}:error-message`;
|
|
12
|
+
|
|
13
|
+
function useFieldState() {
|
|
14
|
+
const [isHovered, setIsHovered] = react.useState(false);
|
|
15
|
+
const [isActive, setIsActive] = react.useState(false);
|
|
16
|
+
const [isFocused, setIsFocused] = react.useState(false);
|
|
17
|
+
const [isFocusVisible, setIsFocusVisible] = react.useState(false);
|
|
18
|
+
const [isLabelRendered, setIsLabelRendered] = react.useState(false);
|
|
19
|
+
const labelRef = react.useCallback((node)=>{
|
|
20
|
+
setIsLabelRendered(!!node);
|
|
21
|
+
}, []);
|
|
22
|
+
const [isDescriptionRendered, setIsDescriptionRendered] = react.useState(false);
|
|
23
|
+
const descriptionRef = react.useCallback((node)=>{
|
|
24
|
+
setIsDescriptionRendered(!!node);
|
|
25
|
+
}, []);
|
|
26
|
+
const [isErrorMessageRendered, setIsErrorMessageRendered] = react.useState(false);
|
|
27
|
+
const errorMessageRef = react.useCallback((node)=>{
|
|
28
|
+
setIsErrorMessageRendered(!!node);
|
|
29
|
+
}, []);
|
|
30
|
+
return {
|
|
31
|
+
refs: {
|
|
32
|
+
label: labelRef,
|
|
33
|
+
description: descriptionRef,
|
|
34
|
+
errorMessage: errorMessageRef
|
|
35
|
+
},
|
|
36
|
+
isHovered,
|
|
37
|
+
isActive,
|
|
38
|
+
isFocused,
|
|
39
|
+
isFocusVisible,
|
|
40
|
+
renderedElements: {
|
|
41
|
+
label: isLabelRendered,
|
|
42
|
+
description: isDescriptionRendered,
|
|
43
|
+
errorMessage: isErrorMessageRendered
|
|
44
|
+
},
|
|
45
|
+
setIsHovered,
|
|
46
|
+
setIsActive,
|
|
47
|
+
setIsFocused,
|
|
48
|
+
setIsFocusVisible
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function useField(props) {
|
|
52
|
+
const id = react.useId();
|
|
53
|
+
const { disabled = false, invalid = false, readOnly = false, required = false } = props;
|
|
54
|
+
const { refs, renderedElements, isHovered, isActive, isFocused, isFocusVisible, setIsHovered, setIsActive, setIsFocused, setIsFocusVisible } = useFieldState();
|
|
55
|
+
const ariaDescribedBy = [
|
|
56
|
+
renderedElements.description ? getDescriptionId(id) : false,
|
|
57
|
+
renderedElements.errorMessage ? getErrorMessageId(id) : false
|
|
58
|
+
].filter(Boolean).join(" ") || undefined;
|
|
59
|
+
const stateProps = domUtils.elementProps({
|
|
60
|
+
"data-hover": domUtils.dataAttr(isHovered),
|
|
61
|
+
"data-active": domUtils.dataAttr(isActive),
|
|
62
|
+
"data-focus": domUtils.dataAttr(isFocused),
|
|
63
|
+
"data-focus-visible": domUtils.dataAttr(isFocusVisible),
|
|
64
|
+
"data-readonly": domUtils.dataAttr(readOnly),
|
|
65
|
+
"data-disabled": domUtils.dataAttr(disabled),
|
|
66
|
+
"data-invalid": domUtils.dataAttr(invalid)
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
refs,
|
|
70
|
+
active: isActive,
|
|
71
|
+
focused: isFocused,
|
|
72
|
+
invalid,
|
|
73
|
+
required,
|
|
74
|
+
setIsFocused,
|
|
75
|
+
setIsFocusVisible,
|
|
76
|
+
stateProps,
|
|
77
|
+
rootProps: domUtils.elementProps({
|
|
78
|
+
...stateProps,
|
|
79
|
+
onPointerMove () {
|
|
80
|
+
setIsHovered(true);
|
|
81
|
+
},
|
|
82
|
+
onPointerDown () {
|
|
83
|
+
setIsActive(true);
|
|
84
|
+
},
|
|
85
|
+
onPointerUp () {
|
|
86
|
+
setIsActive(false);
|
|
87
|
+
},
|
|
88
|
+
onPointerLeave () {
|
|
89
|
+
setIsHovered(false);
|
|
90
|
+
setIsActive(false);
|
|
91
|
+
}
|
|
92
|
+
}),
|
|
93
|
+
labelProps: domUtils.labelProps({
|
|
94
|
+
...stateProps,
|
|
95
|
+
id: getLabelId(id),
|
|
96
|
+
htmlFor: getInputId(id)
|
|
97
|
+
}),
|
|
98
|
+
inputProps: domUtils.inputProps({
|
|
99
|
+
disabled,
|
|
100
|
+
readOnly,
|
|
101
|
+
name: props.name || id,
|
|
102
|
+
id: getInputId(id)
|
|
103
|
+
}),
|
|
104
|
+
inputAriaAttributes: domUtils.elementProps({
|
|
105
|
+
...renderedElements.label && {
|
|
106
|
+
"aria-labelledby": getLabelId(id)
|
|
107
|
+
},
|
|
108
|
+
"aria-describedby": ariaDescribedBy,
|
|
109
|
+
"aria-required": domUtils.ariaAttr(required),
|
|
110
|
+
"aria-invalid": domUtils.ariaAttr(invalid),
|
|
111
|
+
"aria-readonly": domUtils.ariaAttr(readOnly),
|
|
112
|
+
"aria-disabled": domUtils.ariaAttr(disabled)
|
|
113
|
+
}),
|
|
114
|
+
inputHandlers: domUtils.inputProps({
|
|
115
|
+
onChange: (event)=>{
|
|
116
|
+
setIsFocusVisible(event.target.matches(":focus-visible"));
|
|
117
|
+
},
|
|
118
|
+
onBlur () {
|
|
119
|
+
setIsFocused(false);
|
|
120
|
+
setIsFocusVisible(false);
|
|
121
|
+
},
|
|
122
|
+
onFocus (event) {
|
|
123
|
+
setIsFocused(true);
|
|
124
|
+
setIsFocusVisible(event.target.matches(":focus-visible"));
|
|
125
|
+
}
|
|
126
|
+
}),
|
|
127
|
+
descriptionProps: domUtils.elementProps({
|
|
128
|
+
...stateProps,
|
|
129
|
+
id: getDescriptionId(id)
|
|
130
|
+
}),
|
|
131
|
+
errorMessageProps: domUtils.elementProps({
|
|
132
|
+
...stateProps,
|
|
133
|
+
id: getErrorMessageId(id),
|
|
134
|
+
"aria-live": "polite"
|
|
135
|
+
})
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const FieldContext = /*#__PURE__*/ react.createContext(null);
|
|
140
|
+
const FieldProvider = FieldContext.Provider;
|
|
141
|
+
function useFieldContext({ strict = true } = {}) {
|
|
142
|
+
const context = react.useContext(FieldContext);
|
|
143
|
+
if (!context && strict) {
|
|
144
|
+
throw new Error("useFieldContext must be used within a Field");
|
|
145
|
+
}
|
|
146
|
+
return context;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const FieldRoot = /*#__PURE__*/ react.forwardRef((props, ref)=>{
|
|
150
|
+
const { readOnly, disabled, invalid, required, name, ...otherProps } = props;
|
|
151
|
+
const api = useField({
|
|
152
|
+
disabled,
|
|
153
|
+
invalid,
|
|
154
|
+
required,
|
|
155
|
+
readOnly,
|
|
156
|
+
name
|
|
157
|
+
});
|
|
158
|
+
const mergedProps = domUtils.mergeProps(api.rootProps, otherProps);
|
|
159
|
+
return /*#__PURE__*/ jsxRuntime.jsx(FieldProvider, {
|
|
160
|
+
value: api,
|
|
161
|
+
children: /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.div, {
|
|
162
|
+
ref: ref,
|
|
163
|
+
...mergedProps
|
|
164
|
+
})
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
FieldRoot.displayName = "FieldRoot";
|
|
168
|
+
const FieldLabel = /*#__PURE__*/ react.forwardRef((props, ref)=>{
|
|
169
|
+
const { refs, labelProps } = useFieldContext();
|
|
170
|
+
const mergedProps = domUtils.mergeProps(labelProps, props);
|
|
171
|
+
return /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.label, {
|
|
172
|
+
ref: reactComposeRefs.composeRefs(refs.label, ref),
|
|
173
|
+
...mergedProps
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
FieldLabel.displayName = "FieldLabel";
|
|
177
|
+
const FieldDescription = /*#__PURE__*/ react.forwardRef((props, ref)=>{
|
|
178
|
+
const { refs, descriptionProps } = useFieldContext();
|
|
179
|
+
const mergedProps = domUtils.mergeProps(descriptionProps, props);
|
|
180
|
+
return /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.span, {
|
|
181
|
+
ref: reactComposeRefs.composeRefs(refs.description, ref),
|
|
182
|
+
...mergedProps
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
FieldDescription.displayName = "FieldDescription";
|
|
186
|
+
const FieldErrorMessage = /*#__PURE__*/ react.forwardRef((props, ref)=>{
|
|
187
|
+
const { refs, errorMessageProps } = useFieldContext();
|
|
188
|
+
const mergedProps = domUtils.mergeProps(errorMessageProps, props);
|
|
189
|
+
return /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.div, {
|
|
190
|
+
ref: reactComposeRefs.composeRefs(refs.errorMessage, ref),
|
|
191
|
+
...mergedProps
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
FieldErrorMessage.displayName = "FieldErrorMessage";
|
|
195
|
+
|
|
196
|
+
exports.FieldDescription = FieldDescription;
|
|
197
|
+
exports.FieldErrorMessage = FieldErrorMessage;
|
|
198
|
+
exports.FieldLabel = FieldLabel;
|
|
199
|
+
exports.FieldRoot = FieldRoot;
|
|
200
|
+
exports.useFieldContext = useFieldContext;
|
package/lib/index.cjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
var Field12s = require('./Field-12s-DIDZP1t4.cjs');
|
|
2
|
+
|
|
3
|
+
var Field_namespace = {
|
|
4
|
+
__proto__: null,
|
|
5
|
+
Description: Field12s.FieldDescription,
|
|
6
|
+
ErrorMessage: Field12s.FieldErrorMessage,
|
|
7
|
+
Label: Field12s.FieldLabel,
|
|
8
|
+
Root: Field12s.FieldRoot
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
exports.FieldDescription = Field12s.FieldDescription;
|
|
12
|
+
exports.FieldErrorMessage = Field12s.FieldErrorMessage;
|
|
13
|
+
exports.FieldLabel = Field12s.FieldLabel;
|
|
14
|
+
exports.FieldRoot = Field12s.FieldRoot;
|
|
15
|
+
exports.useFieldContext = Field12s.useFieldContext;
|
|
16
|
+
exports.Field = Field_namespace;
|