Skip to content

Commit 3aad044

Browse files
authored
fix(Form): optimize field creation (#527)
1 parent 6050a48 commit 3aad044

File tree

11 files changed

+99
-28
lines changed

11 files changed

+99
-28
lines changed

.changeset/brave-brooms-smile.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cube-dev/ui-kit': patch
3+
---
4+
5+
Add input trimming and keyboard interaction for TextInputMapper.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cube-dev/ui-kit': patch
3+
---
4+
5+
Add support for all html attributes in basic components.

.changeset/quick-badgers-guess.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cube-dev/ui-kit': minor
3+
---
4+
5+
Do not create field instance for non-exist fields in Form. Use default values from Form when creating new fields.

src/components/fields/TextInputMapper/TextInputMapper.stories.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,22 @@ const Template: StoryFn<CubeTextInputMapperProps> = ({ ...props }) => (
2828
);
2929

3030
const FormTemplate: StoryFn<CubeTextInputMapperProps> = ({ ...props }) => (
31+
<Form
32+
defaultValues={{ field: { name: 'value' } }}
33+
labelPosition="top"
34+
onSubmit={(data) => console.log('! onSubmit', data)}
35+
>
36+
<TextInputMapper
37+
name="field"
38+
label="Field Mapper"
39+
{...props}
40+
onChange={(value) => console.log('! onChange', value)}
41+
/>
42+
<Submit>Submit</Submit>
43+
</Form>
44+
);
45+
46+
const FormTemplateSync: StoryFn<CubeTextInputMapperProps> = ({ ...props }) => (
3147
<Form
3248
defaultValues={{ field: { name: 'value' } }}
3349
labelPosition="top"
@@ -66,3 +82,6 @@ WithValueAndNewMapping.play = async ({ canvasElement }) => {
6682

6783
export const WithinForm = FormTemplate.bind({});
6884
WithinForm.args = {};
85+
86+
export const WithinFormInputSync = FormTemplateSync.bind({});
87+
WithinFormInputSync.args = {};

src/components/fields/TextInputMapper/TextInputMapper.tsx

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import {
22
ComponentType,
3+
ForwardedRef,
34
forwardRef,
5+
KeyboardEvent,
46
useEffect,
57
useMemo,
68
useRef,
@@ -9,7 +11,7 @@ import {
911

1012
import { useEvent } from '../../../_internal/hooks';
1113
import { FieldBaseProps } from '../../../shared';
12-
import { mergeProps } from '../../../utils/react/index';
14+
import { mergeProps, useCombinedRefs } from '../../../utils/react/index';
1315
import { useFieldProps, useFormProps, wrapWithField } from '../../form';
1416
import { CloseIcon, PlusIcon } from '../../../icons';
1517
import { Button } from '../../actions';
@@ -63,7 +65,12 @@ function removeDuplicates(mappings: Mapping[]) {
6365
});
6466
}
6567

66-
function TextInputMapper(props: CubeTextInputMapperProps, ref: any) {
68+
function TextInputMapper(
69+
props: CubeTextInputMapperProps,
70+
ref: ForwardedRef<HTMLDivElement>,
71+
) {
72+
ref = useCombinedRefs(ref);
73+
6774
props = useFormProps(props);
6875
props = useFieldProps(props, {
6976
defaultValidationTrigger: 'onChange',
@@ -122,7 +129,7 @@ function TextInputMapper(props: CubeTextInputMapperProps, ref: any) {
122129
const onMappingsChange = useEvent((newMappings: Mapping[]) => {
123130
const newValue = newMappings.reduce(
124131
(acc, { key, value }) => {
125-
acc[key] = value;
132+
acc[key.trim()] = value.trim();
126133

127134
return acc;
128135
},
@@ -136,8 +143,25 @@ function TextInputMapper(props: CubeTextInputMapperProps, ref: any) {
136143
} else {
137144
onChange?.(newValue);
138145
}
146+
147+
const updatedMappings = extractLocalValues(newValue ?? {}, newMappings);
148+
149+
if (JSON.stringify(updatedMappings) !== JSON.stringify(mappings)) {
150+
setMappings(updatedMappings);
151+
}
139152
});
140153

154+
// useEffect(() => {
155+
// // focus on the last non-disabled input
156+
// setTimeout(() => {
157+
// (
158+
// ref?.current?.querySelector(
159+
// '[data-qa="Mapping"]:last-child input:not([disabled])',
160+
// ) as HTMLInputElement
161+
// )?.focus();
162+
// }, 100);
163+
// }, [mappings.length]);
164+
141165
const addNewMapping = useEvent(() => {
142166
setMappings((prev) => {
143167
return [...prev, { key: '', value: '', id: counterRef.current++ }];
@@ -176,17 +200,26 @@ function TextInputMapper(props: CubeTextInputMapperProps, ref: any) {
176200
onMappingsChange(mappings);
177201
});
178202

203+
const onKeyDown = useEvent((e: KeyboardEvent<HTMLDivElement>) => {
204+
// if Ctrl+Enter or Cmd+Enter is pressed then add new mapping if that's enabled
205+
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter' && showNewButton) {
206+
addNewMapping();
207+
}
208+
});
209+
179210
const renderedMappings = useMemo(() => {
180-
return mappings.map((mapping) => {
211+
return mappings.map((mapping, index) => {
181212
const { key, value, id } = mapping;
182213

183214
return (
184215
<Grid
185216
key={id}
217+
qa="Mapping"
186218
columns="minmax(0, 1fr) minmax(0, 1fr) min-content"
187219
gap="1x"
188220
>
189221
<TextInputMapperInput
222+
autoFocus={index === mappings.length - 1}
190223
id={id}
191224
isDisabled={isDisabled}
192225
type="name"
@@ -222,7 +255,7 @@ function TextInputMapper(props: CubeTextInputMapperProps, ref: any) {
222255
}, [JSON.stringify(mappings)]);
223256

224257
const element = (
225-
<Flow gap="1x">
258+
<Flow ref={ref} gap="1x" onKeyDown={onKeyDown}>
226259
{[...renderedMappings]}
227260
{showNewButton ? (
228261
<Space gap={0}>
@@ -251,6 +284,7 @@ export interface CubeTextInputMapperInputProps {
251284
onChange?: (id: number, newValue: string) => void;
252285
onSubmit?: (id: number) => void;
253286
isDisabled?: boolean;
287+
autoFocus?: boolean;
254288
}
255289

256290
function TextInputMapperInput(props: CubeTextInputMapperInputProps) {

src/components/form/Form/use-form.tsx

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export class CubeFormInstance<
3434
TFormData extends CubeFormData<T> = CubeFormData<T>,
3535
> {
3636
public forceReRender: () => void = () => {};
37-
private initialFields: PartialString<T> = {};
37+
private defaultValues: PartialString<T> = {};
3838
private fields: TFormData = {} as TFormData;
3939
public ref = {};
4040
public isSubmitting = false;
@@ -70,17 +70,17 @@ export class CubeFormInstance<
7070
newData: PartialString<T>,
7171
touched?: boolean,
7272
skipRender?: boolean,
73-
createFields = false,
7473
inputOnly = false,
7574
) => {
7675
let flag = false;
7776

77+
newData = { ...newData, ...dotize.convert(newData) };
78+
7879
Object.keys(newData).forEach((name: keyof T & string) => {
7980
let field = this.fields[name];
8081

81-
if (!field && createFields) {
82-
this.createField(name, skipRender);
83-
field = this.fields[name];
82+
if (!field) {
83+
return;
8484
}
8585

8686
if (!field || isEqual(field.value, newData[name])) {
@@ -205,35 +205,35 @@ export class CubeFormInstance<
205205
}
206206

207207
setInitialFieldsValue(values: PartialString<T>): void {
208-
this.initialFields = { ...values, ...dotize.convert(values) };
208+
this.defaultValues = { ...values, ...dotize.convert(values) };
209209
}
210210

211211
updateInitialFieldsValue(values: FieldTypes): void {
212-
this.initialFields = {
213-
...this.initialFields,
212+
this.defaultValues = {
213+
...this.defaultValues,
214214
...values,
215215
...dotize.convert(values),
216216
};
217217
}
218218

219219
resetFields(names?: (keyof T & string)[], skipRender?: boolean): void {
220220
const fieldsValue = this.getFieldsValue();
221-
const fieldNames = Object.keys({ ...fieldsValue, ...this.initialFields });
221+
const fieldNames = Object.keys({ ...fieldsValue, ...this.defaultValues });
222222
const filteredFieldNames = names
223223
? fieldNames.filter((name) => names.includes(name))
224224
: fieldNames;
225225

226226
const values = filteredFieldNames.reduce((map, name) => {
227-
if (name in this.initialFields) {
228-
map[name] = this.initialFields[name];
227+
if (name in this.defaultValues) {
228+
map[name] = this.defaultValues[name];
229229
} else {
230230
map[name] = undefined;
231231
}
232232

233233
return map;
234234
}, {});
235235

236-
this.setFieldsValue(values, false, skipRender, true);
236+
this.setFieldsValue(values, false, skipRender);
237237
}
238238

239239
async validateField<Name extends keyof T & string>(name: Name): Promise<any> {
@@ -404,6 +404,7 @@ export class CubeFormInstance<
404404
touched: false,
405405
errors: [],
406406
validationId: 0,
407+
value: this.defaultValues[name],
407408
...data,
408409
// it should be impossible to define or override status value
409410
status: data?.errors?.length ? 'invalid' : undefined,

src/components/layout/Flex.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { forwardRef } from 'react';
22

33
import {
4-
BaseProps,
4+
AllBaseProps,
55
CONTAINER_STYLES,
66
ContainerStyleProps,
77
extractStyles,
@@ -16,7 +16,7 @@ const FlexElement = tasty({
1616
},
1717
});
1818

19-
export interface CubeFlexProps extends BaseProps, ContainerStyleProps {}
19+
export interface CubeFlexProps extends AllBaseProps, ContainerStyleProps {}
2020

2121
export const Flex = forwardRef(function Flex(props: CubeFlexProps, ref) {
2222
const styles = extractStyles(props, CONTAINER_STYLES);

src/components/layout/Flow.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { forwardRef } from 'react';
22

33
import {
4-
BaseProps,
4+
AllBaseProps,
55
CONTAINER_STYLES,
66
ContainerStyleProps,
77
extractStyles,
@@ -18,7 +18,7 @@ const FlowElement = tasty({
1818

1919
const STYLE_PROPS = CONTAINER_STYLES;
2020

21-
export interface CubeFlowProps extends BaseProps, ContainerStyleProps {}
21+
export interface CubeFlowProps extends AllBaseProps, ContainerStyleProps {}
2222

2323
export const Flow = forwardRef(function Flow(props: CubeFlowProps, ref) {
2424
const styles = extractStyles(props, STYLE_PROPS);

src/components/layout/Grid.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { forwardRef } from 'react';
22

33
import {
4-
BaseProps,
4+
AllBaseProps,
55
CONTAINER_STYLES,
66
ContainerStyleProps,
77
extractStyles,
@@ -18,7 +18,7 @@ const GridElement = tasty({
1818
});
1919

2020
export interface CubeGridProps
21-
extends BaseProps,
21+
extends Omit<AllBaseProps, 'rows'>,
2222
ContainerStyleProps,
2323
ShortGridStyles {}
2424

src/components/layout/Space.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { forwardRef } from 'react';
22

33
import {
4-
BaseProps,
4+
AllBaseProps,
55
CONTAINER_STYLES,
66
ContainerStyleProps,
77
extractStyles,
@@ -24,7 +24,7 @@ const SpaceElement = tasty({
2424
},
2525
});
2626

27-
export interface CubeSpaceProps extends BaseProps, ContainerStyleProps {
27+
export interface CubeSpaceProps extends AllBaseProps, ContainerStyleProps {
2828
direction?: 'vertical' | 'horizontal';
2929
}
3030

0 commit comments

Comments
 (0)