/
ComponentField.tsx
251 lines (231 loc) · 7.57 KB
/
ComponentField.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
import * as React from "react";
import { Fragment } from "react";
import { useState, useEffect } from "react";
import { makeStyles } from "../../../styles";
import Box from "@mui/material/Box";
import makeField from "../components/makeField";
import { useOnePayload } from "../../../components/One/context/PayloadProvider";
import { useOneState } from "../../../components/One/context/StateProvider";
import { DEFAULT_VALUE, useOneContext } from "../context/OneContextProvider";
import { useOneFeatures } from "../context/FeatureProvider";
import IField from "../../../model/IField";
import IAnything from "../../../model/IAnything";
import IManaged, { PickProp } from "../../../model/IManaged";
import type { ComponentFieldInstanceProps } from "../../../model/ComponentFieldInstance";
import classNames from "../../../utils/classNames";
type FieldIgnoreParam = keyof Omit<IManaged, keyof IField> | "readonly";
const FIELD_NEVER_MARGIN = "0";
const FIELD_INTERNAL_PARAMS: FieldIgnoreParam[] = [
"dirty",
"fallback",
"readonly",
"invalid",
"loading",
"object",
"onChange",
"prefix",
"value",
];
/**
* Props interface for the IComponentField component.
*
* @template Data - The type of data for the field.
* @template Payload - The type of payload for the field.
*/
export interface IComponentFieldProps<Data = IAnything, Payload = IAnything> {
/**
* Type definition for the 'name' property of an object.
* The 'name' property is extracted from a specific type and is used to pick a specific property from that type.
*
* @template Data The data type to extract the property from.
* @template Payload The type of the property to pick.
*
* @typedef name
*/
name?: PickProp<IField<Data, Payload>, "name">;
/**
* Type definition for the `placeholder` property of a field.
*
* @template Data - The type of data associated with the field.
* @template Payload - The type of payload used for updating the field.
*/
placeholder?: PickProp<IField<Data, Payload>, "placeholder">;
/**
* Retrieves the "element" property from the given object
*
* @param element - The object to extract the "element" property from
* @returns - The value of the "element" property
*/
element?: PickProp<IField<Data, Payload>, "element">;
/**
* Represents a property of an object, where the key is "groupRef" and the value is of type PickProp<IField<Data, Payload>, "groupRef">.
*
* @property groupRef - The 'groupRef' property of the object.
*/
groupRef?: PickProp<IField<Data, Payload>, "groupRef">;
/**
* Retrieves the "className" property from an object of type PickProp<IField<Data, Payload>, "className">.
*
* @param obj - The input object containing the "className" property.
* @returns - The value of the "className" property if present, otherwise undefined.
* @throws If the input object is not of the expected type.
*/
className?: PickProp<IField<Data, Payload>, "className">;
/**
* Represents the contextual information for watching a single field in a data structure.
* @typedef watchOneContext?
*/
watchOneContext?: PickProp<IField<Data, Payload>, "watchOneContext">;
/**
* Returns the style property of the given variable.
*
* @template T - The type of the variable.
* @template K - The type of the property to be picked.
* @param obj - The variable from which to pick the property.
* @param prop - The property to pick from the variable.
* @returns - The picked property value.
*/
style?: PickProp<IField<Data, Payload>, "style">;
/**
* Represents the 'sx' property of a PickProp.
*
* @template IField - The input field type.
* @template Data - The data type.
* @template Payload - The payload type.
*
* @param - The input field.
*
* @returns - The value of the 'sx' property.
*/
sx?: PickProp<IField<Data, Payload>, "sx">;
}
/**
* @interface
* @template Data - The type of data for the component field
* @description Represents the private interface for a component field
*/
interface IComponentFieldPrivate<Data = IAnything> {
object: PickProp<IManaged<Data>, "object">;
disabled: PickProp<IManaged<Data>, "disabled">;
invalid: PickProp<IManaged<Data>, "invalid">;
incorrect: PickProp<IManaged<Data>, "incorrect">;
readonly: PickProp<IManaged<Data>, "readonly">;
onChange: PickProp<IManaged<Data>, "onChange">;
outlinePaper?: PickProp<IField<Data>, "outlinePaper">;
transparentPaper?: PickProp<IField<Data>, "transparentPaper">;
}
/**
* A function that returns a style object based on the given configuration
*
* @returns The style object containing the defined CSS properties
*/
const useStyles = makeStyles()({
root: {
display: "flex",
alignItems: "stretch",
justifyContent: "stretch",
"& > *": {
flex: 1,
},
},
disabled: {
pointerEvents: "none",
opacity: 0.5,
},
readonly: {
pointerEvents: "none",
},
});
const ComponentInstance = ({
Element,
...props
}: ComponentFieldInstanceProps) => {
const context = useOneContext();
return (
<Element
{...props}
context={context}
/>
);
};
/**
* Represents a component field.
* @param props - The component props.
* @param props.disabled - Indicates if the field is disabled.
* @param props.readonly - Indicates if the field is readonly.
* @param props.watchOneContext - Indicates if the field should watch the One context.
* @param props.element - The element to render, default is Fragment.
* @param props.outlinePaper - Indicates if the field should have an outline paper.
* @param props.transparentPaper - Indicates if the field should have a transparent paper.
* @param props.object - A generic object to pass to the field.
* @param props.otherProps - Other props to pass to the field.
* @param - The rendered field component.
*/
export const ComponentField = ({
disabled,
invalid,
incorrect,
readonly,
watchOneContext,
element: Element = () => <Fragment />,
outlinePaper,
transparentPaper,
object,
onChange: onValueChange,
...otherProps
}: IComponentFieldProps & IComponentFieldPrivate) => {
const { classes } = useStyles();
const [node, setNode] = useState<JSX.Element | null>(null);
const { changeObject: handleChange } = useOneState();
const payload = useOnePayload();
const features = useOneFeatures();
useEffect(() => {
const _fieldParams = Object.entries(otherProps as IField)
.filter(
([key]) => !FIELD_INTERNAL_PARAMS.includes(key as FieldIgnoreParam)
)
.reduce((acm, [key, value]) => ({ ...acm, [key]: value }), {}) as IField;
const props = {
...object,
onChange: handleChange,
onValueChange,
_fieldParams,
_fieldData: object,
outlinePaper,
transparentPaper,
invalid,
incorrect,
payload,
disabled,
readonly,
features,
};
if (watchOneContext) {
setNode(() => <ComponentInstance {...props} Element={Element} />);
return;
}
setNode(() => <Element {...props} context={DEFAULT_VALUE} />);
}, [object, disabled, invalid, incorrect, readonly]);
return (
<Box
className={classNames(classes.root, {
[classes.disabled]: disabled,
[classes.readonly]: readonly,
})}
>
{node}
</Box>
);
};
ComponentField.displayName = "ComponentField";
export default makeField(ComponentField, {
defaultProps: {
fieldRightMargin: FIELD_NEVER_MARGIN,
fieldBottomMargin: FIELD_NEVER_MARGIN,
},
withApplyQueue: true,
skipDirtyClickListener: true,
skipFocusReadonly: true,
skipFocusBlurCall: true,
skipDebounce: true,
});