@flowgram-vue/form 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/LICENSE +22 -0
- package/dist/index.cjs +0 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +0 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
- package/src/composables/index.ts +13 -0
- package/src/composables/use-current-field-state.ts +29 -0
- package/src/composables/use-current-field.ts +31 -0
- package/src/composables/use-field-validate.ts +25 -0
- package/src/composables/use-field.ts +47 -0
- package/src/composables/use-form-model.ts +17 -0
- package/src/composables/use-form-state.ts +25 -0
- package/src/composables/use-form.ts +16 -0
- package/src/composables/use-watch.ts +34 -0
- package/src/constants.ts +34 -0
- package/src/core/create-form.ts +58 -0
- package/src/core/field-array-model.ts +333 -0
- package/src/core/field-model.ts +396 -0
- package/src/core/form-model.ts +362 -0
- package/src/core/index.ts +14 -0
- package/src/core/path.ts +125 -0
- package/src/core/store.ts +33 -0
- package/src/core/to-field-array.ts +52 -0
- package/src/core/to-field.ts +80 -0
- package/src/core/to-form.ts +64 -0
- package/src/core/utils.ts +123 -0
- package/src/env.d.ts +10 -0
- package/src/index.ts +30 -0
- package/src/types/common.ts +6 -0
- package/src/types/field.ts +193 -0
- package/src/types/form.ts +145 -0
- package/src/types/index.ts +8 -0
- package/src/types/validate.ts +88 -0
- package/src/utils/dom.ts +31 -0
- package/src/utils/event.ts +32 -0
- package/src/utils/glob.ts +279 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/object.ts +107 -0
- package/src/utils/validate.ts +42 -0
- package/src/vue/context.ts +12 -0
- package/src/vue/field-array.vue +117 -0
- package/src/vue/field.vue +127 -0
- package/src/vue/form.vue +61 -0
- package/src/vue/index.ts +11 -0
- package/src/vue/types.ts +45 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
cloneVNode,
|
|
8
|
+
defineComponent,
|
|
9
|
+
isVNode,
|
|
10
|
+
onBeforeUnmount,
|
|
11
|
+
provide,
|
|
12
|
+
type PropType,
|
|
13
|
+
type VNode,
|
|
14
|
+
} from 'vue';
|
|
15
|
+
import { isFunction } from 'lodash-es';
|
|
16
|
+
import { DisposableCollection, useRefresh } from '@flowgram-vue/utils';
|
|
17
|
+
import { useReadonlyReactiveState } from '@flowgram-vue/reactive';
|
|
18
|
+
|
|
19
|
+
import { toField, toFieldState } from '../core/to-field';
|
|
20
|
+
import { FieldModelState, FieldName, FieldRenderProps } from '../types/field';
|
|
21
|
+
import { FormModelState } from '../types';
|
|
22
|
+
import { toFormState } from '../core/to-form';
|
|
23
|
+
import { useFormModel } from '../composables/use-form-model';
|
|
24
|
+
import { FieldModelKey } from './context';
|
|
25
|
+
|
|
26
|
+
export default defineComponent({
|
|
27
|
+
name: 'Field',
|
|
28
|
+
props: {
|
|
29
|
+
name: { type: String, required: true },
|
|
30
|
+
defaultValue: { default: undefined },
|
|
31
|
+
render: {
|
|
32
|
+
type: Function as PropType<(props: FieldRenderProps<any>) => VNode>,
|
|
33
|
+
default: undefined,
|
|
34
|
+
},
|
|
35
|
+
deps: { type: Array as PropType<FieldName[]>, default: undefined },
|
|
36
|
+
},
|
|
37
|
+
setup(props, { slots }) {
|
|
38
|
+
const formModel = useFormModel();
|
|
39
|
+
const fieldModel = formModel.getField(props.name) || formModel.createField(props.name);
|
|
40
|
+
const field = toField(fieldModel);
|
|
41
|
+
|
|
42
|
+
const fieldModelState = useReadonlyReactiveState<FieldModelState>(fieldModel.reactiveState);
|
|
43
|
+
const formModelState = useReadonlyReactiveState<FormModelState>(formModel.reactiveState);
|
|
44
|
+
const fieldState = toFieldState(fieldModelState);
|
|
45
|
+
const formState = toFormState(formModelState);
|
|
46
|
+
const refresh = useRefresh();
|
|
47
|
+
|
|
48
|
+
const bind = () => {
|
|
49
|
+
if (fieldModel.disposed) {
|
|
50
|
+
refresh();
|
|
51
|
+
return () => {};
|
|
52
|
+
}
|
|
53
|
+
fieldModel.renderCount = fieldModel.renderCount + 1;
|
|
54
|
+
|
|
55
|
+
if (!formModel.getValueIn(props.name) !== undefined && props.defaultValue !== undefined) {
|
|
56
|
+
formModel.setInitValueIn(props.name, props.defaultValue);
|
|
57
|
+
refresh();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const disposableCollection = new DisposableCollection();
|
|
61
|
+
|
|
62
|
+
disposableCollection.push(
|
|
63
|
+
fieldModel.onValueChange(() => {
|
|
64
|
+
refresh();
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
if (props.deps) {
|
|
69
|
+
props.deps.forEach((dep) => {
|
|
70
|
+
const disposable = formModel.getField(dep)?.onValueChange(() => {
|
|
71
|
+
refresh();
|
|
72
|
+
});
|
|
73
|
+
if (disposable) {
|
|
74
|
+
disposableCollection.push(disposable);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return () => {
|
|
80
|
+
disposableCollection.dispose();
|
|
81
|
+
|
|
82
|
+
if (fieldModel.renderCount > 1) {
|
|
83
|
+
fieldModel.renderCount = fieldModel.renderCount - 1;
|
|
84
|
+
} else {
|
|
85
|
+
const newFieldModel = formModel.getField(fieldModel.name);
|
|
86
|
+
if (newFieldModel === fieldModel) fieldModel.dispose();
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const unbind = bind();
|
|
92
|
+
onBeforeUnmount(unbind);
|
|
93
|
+
|
|
94
|
+
provide(FieldModelKey, fieldModel);
|
|
95
|
+
|
|
96
|
+
return () => {
|
|
97
|
+
if (fieldModel.disposed) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const slotProps: FieldRenderProps<any> = { field, fieldState, formState };
|
|
102
|
+
|
|
103
|
+
if (props.render) {
|
|
104
|
+
return props.render(slotProps);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const slot = slots.default;
|
|
108
|
+
if (isFunction(slot)) {
|
|
109
|
+
const children = slot(slotProps);
|
|
110
|
+
if (Array.isArray(children) && children.length === 1 && isVNode(children[0])) {
|
|
111
|
+
return cloneVNode(children[0], {
|
|
112
|
+
value: field.value,
|
|
113
|
+
onInput: field.onChange,
|
|
114
|
+
onChange: field.onChange,
|
|
115
|
+
onFocus: field.onFocus,
|
|
116
|
+
onBlur: field.onBlur,
|
|
117
|
+
name: field.name,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return children;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return null;
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
</script>
|
package/src/vue/form.vue
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
import { defineComponent, h, onBeforeUnmount, provide, type PropType, type VNode } from 'vue';
|
|
7
|
+
|
|
8
|
+
import { createForm } from '../core/create-form';
|
|
9
|
+
import { toForm } from '../core/to-form';
|
|
10
|
+
import type { FormControl, FormOptions, FormRenderProps } from '../types/form';
|
|
11
|
+
import { FormModelKey } from './context';
|
|
12
|
+
|
|
13
|
+
const FormContentRender = defineComponent({
|
|
14
|
+
name: 'FormContentRender',
|
|
15
|
+
props: {
|
|
16
|
+
form: { type: Object, required: true },
|
|
17
|
+
render: { type: Function as PropType<(props: FormRenderProps<any>) => any>, required: true },
|
|
18
|
+
},
|
|
19
|
+
setup(props) {
|
|
20
|
+
return () => props.render({ form: props.form });
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export default defineComponent({
|
|
25
|
+
name: 'Form',
|
|
26
|
+
props: {
|
|
27
|
+
keepModelOnUnMount: { type: Boolean, default: false },
|
|
28
|
+
control: { type: Object as PropType<FormControl<any>>, default: undefined },
|
|
29
|
+
initialValues: { type: Object, default: undefined },
|
|
30
|
+
validateTrigger: { type: String, default: undefined },
|
|
31
|
+
validate: { type: [Object, Function], default: undefined },
|
|
32
|
+
context: { type: Object, default: undefined },
|
|
33
|
+
},
|
|
34
|
+
setup(props, { slots }) {
|
|
35
|
+
const restOptions: FormOptions = {
|
|
36
|
+
initialValues: props.initialValues,
|
|
37
|
+
validateTrigger: props.validateTrigger as FormOptions['validateTrigger'],
|
|
38
|
+
validate: props.validate as FormOptions['validate'],
|
|
39
|
+
context: props.context,
|
|
40
|
+
};
|
|
41
|
+
const { _formModel: formModel } = props.control ? props.control : createForm(restOptions).control;
|
|
42
|
+
|
|
43
|
+
onBeforeUnmount(() => {
|
|
44
|
+
if (!props.keepModelOnUnMount) {
|
|
45
|
+
formModel.dispose();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const form = toForm(formModel);
|
|
50
|
+
provide(FormModelKey, formModel);
|
|
51
|
+
|
|
52
|
+
return () => {
|
|
53
|
+
const slot = slots.default;
|
|
54
|
+
if (!slot) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return h(FormContentRender, { form, render: slot as (p: FormRenderProps<any>) => VNode });
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
</script>
|
package/src/vue/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { default as Form } from './form.vue';
|
|
7
|
+
export { default as Field } from './field.vue';
|
|
8
|
+
export { default as FieldArray } from './field-array.vue';
|
|
9
|
+
export { FormModelKey, FieldModelKey } from './context';
|
|
10
|
+
export type { FormProps, FieldProps, FieldArrayProps } from './types';
|
|
11
|
+
export * from '../composables';
|
package/src/vue/types.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { VNode } from 'vue';
|
|
7
|
+
|
|
8
|
+
import { FieldArrayRenderProps, FieldName, FieldOptions, FieldRenderProps } from '../types/field';
|
|
9
|
+
import { FormControl, FormOptions, FormRenderProps } from '../types/form';
|
|
10
|
+
|
|
11
|
+
export type FormProps<TValues> = FormOptions & {
|
|
12
|
+
/**
|
|
13
|
+
* Vue default slot or child render prop
|
|
14
|
+
*/
|
|
15
|
+
children?: ((props: FormRenderProps<TValues>) => VNode) | VNode;
|
|
16
|
+
/**
|
|
17
|
+
* If this prop is set to true, Form instance will be kept even though <Form /> is destroyed.
|
|
18
|
+
* This means you can still use some form's api such as Form.validate and Form.setValueIn to handle pure data logic.
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
keepModelOnUnMount?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* provide form instance from outside. if control is given Form will use the form instance in the control instead of creating one.
|
|
24
|
+
*/
|
|
25
|
+
control?: FormControl<TValues>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type FieldProps<TValue> = FieldOptions<TValue> & {
|
|
29
|
+
/**
|
|
30
|
+
* A Vue VNode or a render prop
|
|
31
|
+
*/
|
|
32
|
+
children?: ((props: FieldRenderProps<TValue>) => VNode) | VNode;
|
|
33
|
+
/**
|
|
34
|
+
* Dependencies of the current field. If a field name is given in deps, current field will re-render if the given field name data is updated
|
|
35
|
+
*/
|
|
36
|
+
deps?: FieldName[];
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type FieldArrayProps<TValue> = {
|
|
40
|
+
name: FieldName;
|
|
41
|
+
defaultValue?: TValue[];
|
|
42
|
+
render?: (props: FieldArrayRenderProps<TValue>) => VNode;
|
|
43
|
+
children?: ((props: FieldArrayRenderProps<TValue>) => VNode) | VNode;
|
|
44
|
+
deps?: FieldName[];
|
|
45
|
+
};
|