[]
+
+ private context: Context
+
+ private subscribers: FormHeartSubscriber[]
+
+ constructor({
+ lifecycles,
+ context
+ }: {
+ lifecycles?: FormLifeCycle[]
+ context?: Context
+ }) {
+ this.lifecycles = this.buildLifeCycles(lifecycles || [])
+ this.subscribers = []
+ this.context = context
+ }
+
+ buildLifeCycles(lifecycles: FormLifeCycle[]) {
+ return lifecycles.reduce((buf, item) => {
+ if (item instanceof FormLifeCycle) {
+ return buf.concat(item)
+ } else {
+ if (typeof item === 'object') {
+ this.context = item
+ return buf
+ } else if (isArr(item)) {
+ return this.buildLifeCycles(item)
+ }
+ return buf
+ }
+ }, [])
+ }
+
+ unsubscribe = (callback?: FormHeartSubscriber) => {
+ if (isFn(callback)) {
+ this.subscribers = this.subscribers.filter(
+ fn => fn.toString() !== callback.toString()
+ )
+ } else {
+ this.subscribers = []
+ }
+ }
+
+ subscribe = (callback?: FormHeartSubscriber) => {
+ if (
+ isFn(callback) &&
+ !this.subscribers.some(fn => fn.toString() === callback.toString())
+ ) {
+ this.subscribers.push(callback)
+ }
+ }
+
+ notify = (type: any, payload: P, context?: C) => {
+ if (isStr(type)) {
+ this.lifecycles.forEach(lifecycle => {
+ lifecycle.notify(type, payload, context || this.context)
+ })
+ this.subscribers.forEach(callback => {
+ callback({ type, payload })
+ })
+ }
+ }
+}
diff --git a/packages/core/src/shared/model.ts b/packages/core/src/shared/model.ts
new file mode 100644
index 00000000000..532fd5d8f90
--- /dev/null
+++ b/packages/core/src/shared/model.ts
@@ -0,0 +1,180 @@
+import { clone, isEqual, isFn, each, globalThisPolyfill } from '@uform/shared'
+import produce, { Draft } from 'immer'
+import { Subscrible } from './subscrible'
+import { IStateModelFactory, StateDirtyMap, IModel, StateModel } from '../types'
+const hasProxy = !!globalThisPolyfill.Proxy
+
+export const createStateModel = (
+ Factory: IStateModelFactory
+) => {
+ return class Model extends Subscrible
+ implements IModel {
+ public state: State & { displayName?: string }
+ public props: Props & DefaultProps & { useDirty?: boolean }
+ public displayName?: string
+ public dirtyNum: number
+ public dirtyMap: StateDirtyMap
+ public batching: boolean
+ public controller: StateModel
+ constructor(defaultProps: DefaultProps) {
+ super()
+ this.state = { ...Factory.defaultState }
+ this.props = {
+ ...Factory.defaultProps,
+ ...defaultProps
+ }
+ this.dirtyMap = {}
+ this.dirtyNum = 0
+ this.batching = false
+ this.controller = new Factory(this.state, this.props)
+ this.displayName = Factory.displayName
+ this.state.displayName = this.displayName
+ }
+
+ batch = (callback?: () => void) => {
+ this.batching = true
+ if (isFn(callback)) {
+ callback()
+ }
+ if (this.dirtyNum > 0) {
+ this.notify(this.getState())
+ }
+ this.dirtyMap = {}
+ this.dirtyNum = 0
+ this.batching = false
+ }
+
+ getState = (callback?: (state: State) => any) => {
+ if (isFn(callback)) {
+ return callback(this.getState())
+ } else {
+ if (!hasProxy || this.props.useDirty) {
+ if (isFn(this.controller.publishState)) {
+ return this.controller.publishState(this.state)
+ }
+ return clone(this.state)
+ } else {
+ return produce(this.state, () => {})
+ }
+ }
+ }
+
+ unsafe_getSourceState = (callback?: (state: State) => any) => {
+ if (isFn(callback)) {
+ return callback(this.state)
+ } else {
+ return this.state
+ }
+ }
+
+ unsafe_setSourceState = (callback: (state: State) => void) => {
+ if (isFn(callback)) {
+ if (!hasProxy || this.props.useDirty) {
+ callback(this.state)
+ } else {
+ this.state = produce(this.state, callback)
+ }
+ }
+ }
+
+ setState = (
+ callback: (state: State | Draft) => State | void,
+ silent = false
+ ) => {
+ if (isFn(callback)) {
+ if (!hasProxy || this.props.useDirty) {
+ const draft = this.getState()
+ this.dirtyNum = 0
+ this.dirtyMap = {}
+ callback(draft)
+ if (isFn(this.controller.computeState)) {
+ this.controller.computeState(draft, this.state)
+ }
+ const draftKeys = Object.keys(draft || {})
+ const stateKeys = Object.keys(this.state || {})
+ each(
+ draftKeys.length > stateKeys.length ? draft : this.state,
+ (value, key) => {
+ if (!isEqual(value, draft[key])) {
+ this.state[key] = draft[key]
+ this.dirtyMap[key] = true
+ this.dirtyNum++
+ }
+ }
+ )
+ if (isFn(this.controller.dirtyCheck)) {
+ const result = this.controller.dirtyCheck(this.dirtyMap)
+ if (result !== undefined) {
+ Object.assign(this.dirtyMap, result)
+ }
+ }
+ if (this.dirtyNum > 0 && !silent) {
+ if (this.batching) return
+ this.notify(this.getState())
+ this.dirtyMap = {}
+ this.dirtyNum = 0
+ }
+ } else {
+ this.dirtyNum = 0
+ this.dirtyMap = {}
+ //用proxy解决脏检查计算属性问题
+ this.state = produce(
+ this.state,
+ draft => {
+ callback(draft)
+ if (isFn(this.controller.computeState)) {
+ this.controller.computeState(draft, this.state)
+ }
+ },
+ patches => {
+ patches.forEach(({ path, op, value }) => {
+ if (!this.dirtyMap[path[0]]) {
+ if (op === 'replace') {
+ if (!isEqual(this.state[path[0]], value)) {
+ this.dirtyMap[path[0]] = true
+ this.dirtyNum++
+ }
+ } else {
+ this.dirtyMap[path[0]] = true
+ this.dirtyNum++
+ }
+ }
+ })
+ }
+ )
+ if (isFn(this.controller.dirtyCheck)) {
+ const result = this.controller.dirtyCheck(this.dirtyMap)
+ if (result !== undefined) {
+ Object.assign(this.dirtyMap, result)
+ }
+ }
+ if (this.dirtyNum > 0 && !silent) {
+ if (this.batching) return
+ this.notify(this.getState())
+ this.dirtyMap = {}
+ this.dirtyNum = 0
+ }
+ }
+ }
+ }
+
+ hasChanged = (key?: string) =>
+ key ? this.dirtyMap[key] === true : this.dirtyNum > 0
+
+ getChanged = () => {
+ if (!hasProxy || this.props.useDirty) {
+ return clone(this.dirtyMap)
+ } else {
+ return this.dirtyMap
+ }
+ }
+
+ watch = (key: string, callback?: (dirtys: StateDirtyMap) => any) => {
+ if (this.hasChanged(key)) {
+ if (isFn(callback)) {
+ callback(this.getChanged())
+ }
+ }
+ }
+ }
+}
diff --git a/packages/core/src/shared/subscrible.ts b/packages/core/src/shared/subscrible.ts
new file mode 100644
index 00000000000..d1acdde036a
--- /dev/null
+++ b/packages/core/src/shared/subscrible.ts
@@ -0,0 +1,29 @@
+import { isFn, each } from '@uform/shared'
+import { Subscriber } from '../types'
+
+export class Subscrible {
+ subscribers: Subscriber[] = []
+
+ subscribe = (callback?: Subscriber) => {
+ if (
+ isFn(callback) &&
+ !this.subscribers.some(fn => fn.toString() === callback.toString())
+ ) {
+ this.subscribers.push(callback)
+ }
+ }
+
+ unsubscribe = (callback?: Subscriber) => {
+ if (isFn(callback)) {
+ this.subscribers = this.subscribers.filter(fn => {
+ return fn.toString() !== callback.toString()
+ })
+ } else {
+ this.subscribers.length = 0
+ }
+ }
+
+ notify = (payload?: Payload) => {
+ each(this.subscribers, callback => callback(payload))
+ }
+}
diff --git a/packages/core/src/state/field.ts b/packages/core/src/state/field.ts
new file mode 100644
index 00000000000..2e4f73ccf64
--- /dev/null
+++ b/packages/core/src/state/field.ts
@@ -0,0 +1,187 @@
+import { createStateModel } from '../shared/model'
+import { clone, toArr, isValid, isEqual, FormPath, isFn } from '@uform/shared'
+import { IFieldState, IFieldStateProps } from '../types'
+/**
+ * 核心数据结构,描述表单字段的所有状态
+ */
+export const FieldState = createStateModel(
+ class FieldState {
+ static displayName = 'FieldState'
+ static defaultState = {
+ name: '',
+ path: '',
+ initialized: false,
+ pristine: true,
+ valid: true,
+ modified: false,
+ touched: false,
+ active: false,
+ visited: false,
+ invalid: false,
+ visible: true,
+ display: true,
+ loading: false,
+ validating: false,
+ errors: [],
+ values: [],
+ ruleErrors: [],
+ ruleWarnings: [],
+ effectErrors: [],
+ warnings: [],
+ effectWarnings: [],
+ editable: true,
+ selfEditable: undefined,
+ formEditable: undefined,
+ value: undefined,
+ initialValue: undefined,
+ rules: [],
+ required: false,
+ mounted: false,
+ unmounted: false,
+ props: {}
+ }
+
+ static defaultProps = {
+ path: ''
+ }
+
+ private state: IFieldState
+
+ private nodePath: FormPath
+
+ private dataPath: FormPath
+
+ constructor(state: IFieldState, props: IFieldStateProps) {
+ this.state = state
+ this.nodePath = FormPath.getPath(props.nodePath)
+ this.dataPath = FormPath.getPath(props.dataPath)
+ this.state.name = this.dataPath.entire
+ this.state.path = this.nodePath.entire
+ }
+
+ readValues({ value, values }: IFieldStateProps) {
+ if (isValid(values)) {
+ values = toArr(values)
+ values[0] = value
+ } else {
+ if (isValid(value)) {
+ values = toArr(value)
+ }
+ }
+ return {
+ value,
+ values: toArr(values)
+ }
+ }
+
+ readRules({ rules, required }: IFieldStateProps) {
+ let newRules = isValid(rules) ? clone(toArr(rules)) : this.state.rules
+ if (isValid(required)) {
+ if (required) {
+ if (!newRules.some(rule => rule && rule.required)) {
+ newRules.push({ required: true })
+ }
+ } else {
+ newRules = newRules.filter(rule => rule && !rule.required)
+ }
+ } else {
+ required = newRules.some(rule => rule && rule.required)
+ }
+ return {
+ rules: newRules,
+ required
+ }
+ }
+
+ computeState(draft: IFieldState, prevState: IFieldState) {
+ //如果是隐藏状态,则禁止修改值
+ if (!draft.visible || draft.unmounted) {
+ draft.value = prevState.value
+ draft.initialValue = prevState.initialValue
+ }
+ //操作重定向
+ if (!isEqual(draft.errors, prevState.errors)) {
+ draft.effectErrors = draft.errors
+ }
+ if (!isEqual(draft.warnings, prevState.warnings)) {
+ draft.effectWarnings = draft.warnings
+ }
+ //容错逻辑
+ draft.rules = toArr(draft.rules).filter(v => !!v)
+ draft.effectWarnings = toArr(draft.effectWarnings).filter(v => !!v)
+ draft.effectErrors = toArr(draft.effectErrors).filter(v => !!v)
+ draft.ruleWarnings = toArr(draft.ruleWarnings).filter(v => !!v)
+ draft.ruleErrors = toArr(draft.ruleErrors).filter(v => !!v)
+
+ draft.errors = draft.ruleErrors.concat(draft.effectErrors)
+ draft.warnings = draft.ruleWarnings.concat(draft.effectWarnings)
+
+ if (!isEqual(draft.editable, prevState.editable)) {
+ draft.selfEditable = draft.editable
+ }
+ draft.editable = isValid(draft.selfEditable)
+ ? draft.selfEditable
+ : isValid(draft.formEditable)
+ ? isFn(draft.formEditable)
+ ? draft.formEditable(draft.name)
+ : draft.formEditable
+ : true
+
+ const { value, values } = this.readValues(draft)
+ draft.value = value
+ draft.values = values
+ if (
+ draft.initialized &&
+ prevState.initialized &&
+ !isEqual(draft.value, prevState.value)
+ ) {
+ draft.modified = true
+ }
+ if (isEqual(draft.value, draft.initialValue)) {
+ draft.pristine = true
+ } else {
+ draft.pristine = false
+ }
+ if (!isValid(draft.props)) {
+ draft.props = prevState.props
+ }
+
+ if (draft.validating === true) {
+ draft.loading = true
+ } else if (draft.validating === false) {
+ draft.loading = false
+ }
+ // 以下几种情况清理错误和警告信息
+ // 1. 字段设置为不可编辑
+ // 2. 字段隐藏
+ // 3. 字段被卸载
+ if (
+ (draft.selfEditable !== prevState.selfEditable &&
+ !draft.selfEditable) ||
+ draft.visible === false ||
+ draft.unmounted === true
+ ) {
+ draft.errors = []
+ draft.effectErrors = []
+ draft.warnings = []
+ draft.effectWarnings = []
+ }
+ if (draft.mounted === true) {
+ draft.unmounted = false
+ }
+ if (draft.unmounted === true) {
+ draft.mounted = false
+ }
+ if (draft.errors.length) {
+ draft.invalid = true
+ draft.valid = false
+ } else {
+ draft.invalid = false
+ draft.valid = true
+ }
+ const { rules, required } = this.readRules(draft)
+ draft.rules = rules
+ draft.required = required
+ }
+ }
+)
diff --git a/packages/core/src/state/form.ts b/packages/core/src/state/form.ts
new file mode 100644
index 00000000000..cfc05ade446
--- /dev/null
+++ b/packages/core/src/state/form.ts
@@ -0,0 +1,68 @@
+import { createStateModel } from '../shared/model'
+import { toArr, clone, isEqual } from '@uform/shared'
+import { IFormState, IFormStateProps } from '../types'
+/**
+ * 核心数据结构,描述Form级别状态
+ */
+export const FormState = createStateModel(
+ class FormState {
+ static displayName = 'FormState'
+ static defaultState = {
+ pristine: true,
+ valid: true,
+ invalid: false,
+ loading: false,
+ validating: false,
+ initialized: false,
+ submitting: false,
+ editable: true,
+ errors: [],
+ warnings: [],
+ values: {},
+ initialValues: {},
+ mounted: false,
+ unmounted: false,
+ props: {}
+ }
+
+ static defaultProps = {
+ lifecycles: []
+ }
+
+ private state: IFormState
+ constructor(state: IFormState, props: IFormStateProps) {
+ this.state = state
+ this.state.initialValues = clone(props.initialValues || {})
+ this.state.values = clone(props.values || props.initialValues || {})
+ this.state.editable = props.editable
+ }
+
+ computeState(draft: IFormState, prevState: IFormState) {
+ draft.errors = toArr(draft.errors).filter(v => !!v)
+ draft.warnings = toArr(draft.warnings).filter(v => !!v)
+ if (draft.errors.length) {
+ draft.invalid = true
+ draft.valid = false
+ } else {
+ draft.invalid = false
+ draft.valid = true
+ }
+ if (isEqual(draft.values, draft.initialValues)) {
+ draft.pristine = true
+ } else {
+ draft.pristine = false
+ }
+ if (draft.validating === true) {
+ draft.loading = true
+ } else if (draft.validating === false) {
+ draft.loading = false
+ }
+ if (draft.mounted === true) {
+ draft.unmounted = false
+ }
+ if (draft.unmounted === true) {
+ draft.mounted = false
+ }
+ }
+ }
+)
diff --git a/packages/core/src/state/virtual-field.ts b/packages/core/src/state/virtual-field.ts
new file mode 100644
index 00000000000..0f7028bc52b
--- /dev/null
+++ b/packages/core/src/state/virtual-field.ts
@@ -0,0 +1,56 @@
+import { createStateModel } from '../shared/model'
+import { FormPath, isValid } from '@uform/shared'
+import { IVirtualFieldState, IVirtualFieldStateProps } from '../types'
+
+/**
+ * UForm特有,描述一个虚拟字段,
+ * 它不占用数据空间,但是它拥有状态,
+ * 可以联动控制Field或者VirtualField的状态
+ * 类似于现在UForm的Card之类的容器布局组件
+ */
+export const VirtualFieldState = createStateModel<
+ IVirtualFieldState,
+ IVirtualFieldStateProps
+>(
+ class VirtualFieldState {
+ static displayName = 'VirtualFieldState'
+ static defaultState = {
+ name: '',
+ path: '',
+ initialized: false,
+ visible: true,
+ display: true,
+ mounted: false,
+ unmounted: false,
+ props: {}
+ }
+
+ static defaultProps = {
+ path: '',
+ props: {}
+ }
+
+ private state: IVirtualFieldState
+
+ private path: FormPath
+
+ constructor(state: IVirtualFieldState, props: IVirtualFieldStateProps) {
+ this.state = state
+ this.path = FormPath.getPath(props.nodePath)
+ this.state.name = this.path.entire
+ this.state.path = this.path.entire
+ }
+
+ computeState(draft: IVirtualFieldState, prevState: IVirtualFieldState) {
+ if (draft.mounted === true) {
+ draft.unmounted = false
+ }
+ if (!isValid(draft.props)) {
+ draft.props = prevState.props
+ }
+ if (draft.unmounted === true) {
+ draft.mounted = false
+ }
+ }
+ }
+)
diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts
new file mode 100644
index 00000000000..c1d00804800
--- /dev/null
+++ b/packages/core/src/types.ts
@@ -0,0 +1,319 @@
+import { FormPath, FormPathPattern, isFn } from '@uform/shared'
+import { ValidatePatternRules, ValidateNodeResult } from '@uform/validator'
+import { FormLifeCycle } from './shared/lifecycle'
+import { Draft } from 'immer'
+import { Subscrible } from './shared/subscrible'
+
+export type FormLifeCycleHandler = (payload: T, context: any) => void
+
+export type FormHeartSubscriber = ({
+ type,
+ payload
+}: {
+ type: string
+ payload: any
+}) => void
+
+export enum LifeCycleTypes {
+ /**
+ * Form LifeCycle
+ **/
+
+ ON_FORM_WILL_INIT = 'onFormWillInit',
+ ON_FORM_INIT = 'onFormInit',
+ ON_FORM_CHANGE = 'onFormChange', //ChangeType精准控制响应
+ ON_FORM_MOUNT = 'onFormMount',
+ ON_FORM_UNMOUNT = 'onFormUnmount',
+ ON_FORM_SUBMIT = 'onFormSubmit',
+ ON_FORM_RESET = 'onFormReset',
+ ON_FORM_SUBMIT_START = 'onFormSubmitStart',
+ ON_FORM_SUBMIT_END = 'onFormSubmitEnd',
+ ON_FORM_VALUES_CHANGE = 'onFormValuesChange',
+ ON_FORM_INITIAL_VALUES_CHANGE = 'onFormInitialValueChange',
+ ON_FORM_VALIDATE_START = 'onFormValidateStart',
+ ON_FORM_VALIDATE_END = 'onFormValidateEnd',
+ ON_FORM_INPUT_CHANGE = 'onFormInputChange',
+ /**
+ * FormGraph LifeCycle
+ **/
+ ON_FORM_GRAPH_CHANGE = 'onFormGraphChange',
+
+ /**
+ * Field LifeCycle
+ **/
+
+ ON_FIELD_WILL_INIT = 'onFieldWillInit',
+ ON_FIELD_INIT = 'onFieldInit',
+ ON_FIELD_CHANGE = 'onFieldChange',
+ ON_FIELD_INPUT_CHANGE = 'onFieldInputChange',
+ ON_FIELD_VALUE_CHANGE = 'onFieldValueChange',
+ ON_FIELD_INITIAL_VALUE_CHANGE = 'onFieldInitialValueChange',
+ ON_FIELD_MOUNT = 'onFieldMount',
+ ON_FIELD_UNMOUNT = 'onFieldUnmount'
+}
+
+export type FormGraphNodeMap = {
+ [key in string]: T
+}
+
+export interface FormGraphVisitorOptions {
+ path: FormPath
+ exsist: boolean
+ append: (node: T) => void
+}
+
+export type FormGraph = (
+ node: T,
+ options: FormGraphVisitorOptions
+) => void
+
+export interface FormGraphNodeRef {
+ parent?: FormGraphNodeRef
+ path: FormPath
+ children: FormPath[]
+}
+
+export type FormGraphMatcher = (node: T, path: FormPath) => void | boolean
+
+export type FormGraphEacher = (node: T, path: FormPath) => void
+
+export type FormLifeCyclePayload = (
+ params: {
+ type: string
+ payload: T
+ },
+ context: any
+) => void
+
+export type StateDirtyMap = {
+ [key in keyof P]?: boolean
+}
+
+export interface StateModel
{
+ publishState?: (state: P) => P
+ dirtyCheck?: (dirtys: StateDirtyMap
) => StateDirtyMap
| void
+ computeState?: (state: Draft
, preState?: P) => Draft
| void
+}
+
+export interface IStateModelFactory {
+ new (state: S, props: P): StateModel
+ defaultState?: S
+ defaultProps?: P
+ displayName?: string
+}
+
+export interface IFieldState {
+ displayName?: string
+ name: string
+ path: string
+ initialized: boolean
+ pristine: boolean
+ valid: boolean
+ touched: boolean
+ invalid: boolean
+ visible: boolean
+ display: boolean
+ editable: boolean
+ selfEditable: boolean
+ formEditable: boolean | ((name: string) => boolean)
+ loading: boolean
+ modified: boolean
+ active: boolean
+ visited: boolean
+ validating: boolean
+ values: any[]
+ errors: string[]
+ effectErrors: string[]
+ ruleErrors: string[]
+ warnings: string[]
+ effectWarnings: string[]
+ ruleWarnings: string[]
+ value: any
+ initialValue: any
+ rules: ValidatePatternRules[]
+ required: boolean
+ mounted: boolean
+ unmounted: boolean
+ props: {}
+}
+export type FieldStateDirtyMap = StateDirtyMap
+
+export interface IFieldStateProps {
+ path?: FormPathPattern
+ nodePath?: FormPathPattern
+ dataPath?: FormPathPattern
+ name?: string
+ value?: any
+ values?: any[]
+ initialValue?: any
+ props?: {}
+ rules?: ValidatePatternRules[]
+ required?: boolean
+ editable?: boolean
+ onChange?: (fieldState: IField) => void
+}
+
+export const isField = (target: any): target is IField =>
+ target && target.displayName === 'FieldState'
+
+export const isFieldState = (target: any): target is IFieldState =>
+ target && target.displayName === 'FieldState'
+
+export const isVirtualField = (target: any): target is IVirtualField =>
+ target && target.displayName === 'VirtualFieldState'
+
+export const isVirtualFieldState = (
+ target: any
+): target is IVirtualFieldState =>
+ target && target.displayName === 'VirtualFieldState'
+
+export const isStateModel = (target: any): target is IModel =>
+ target && isFn(target.getState)
+
+export interface IFormState {
+ pristine: boolean
+ valid: boolean
+ invalid: boolean
+ loading: boolean
+ validating: boolean
+ submitting: boolean
+ initialized: boolean
+ editable: boolean | ((name: string) => boolean)
+ errors: string[]
+ warnings: string[]
+ values: {}
+ initialValues: {}
+ mounted: boolean
+ unmounted: boolean
+ props: {}
+}
+
+export type FormStateDirtyMap = StateDirtyMap
+
+export interface IFormStateProps {
+ initialValues?: {}
+ values?: {}
+ lifecycles?: FormLifeCycle[]
+ editable?: boolean | ((name: string) => boolean)
+}
+
+export interface IFormCreatorOptions extends IFormStateProps {
+ useDirty?: boolean
+ validateFirst?: boolean
+ editable?: boolean
+ onSubmit?: (values: IFormState['values']) => any | Promise
+ onReset?: () => void
+ onValidateFailed?: (validated: IFormValidateResult) => void
+}
+
+export interface IVirtualFieldState {
+ name: string
+ path: string
+ displayName?: string
+ initialized: boolean
+ visible: boolean
+ display: boolean
+ mounted: boolean
+ unmounted: boolean
+ props: {}
+}
+export type VirtualFieldStateDirtyMap = StateDirtyMap
+
+export interface IVirtualFieldStateProps {
+ path?: FormPathPattern
+ nodePath?: FormPathPattern
+ name?: string
+ props?: {}
+ onChange?: (fieldState: IVirtualField) => void
+}
+
+export type IFormValidateResult = ValidateNodeResult
+
+export interface IFormSubmitResult {
+ validated: IFormValidateResult
+ payload: any
+}
+
+export interface IFormResetOptions {
+ forceClear?: boolean
+ validate?: boolean
+}
+
+export interface IFormGraph {
+ [path: string]: IFormState | IFieldState | IVirtualFieldState
+}
+
+export interface IMutators {
+ change(...values: any[]): any
+ focus(): void
+ blur(): void
+ push(value: any): any[]
+ pop(): any[]
+ insert(index: number, value: any): any[]
+ remove(index: number | string): any
+ unshift(value: any): any[]
+ shift(): any[]
+ move($from: number, $to: number): any
+ moveDown(index: number): any
+ moveUp(index: number): any
+ validate(): Promise
+ exist(index?: number | string): boolean
+}
+
+export type Subscriber = (payload: S) => void
+
+export interface IModel extends Subscrible {
+ state: S
+ props: P
+ displayName?: string
+ dirtyNum: number
+ dirtyMap: StateDirtyMap
+ subscribers: Subscriber[]
+ batching: boolean
+ controller: StateModel
+ batch: (callback?: () => void) => void
+ getState: (callback?: (state: S) => any) => any
+ setState: (callback?: (state: S | Draft) => void, silent?: boolean) => void
+ unsafe_getSourceState: (callback?: (state: S) => any) => any
+ unsafe_setSourceState: (callback?: (state: S) => void) => void
+ hasChanged: (key?: string) => boolean
+ getChanged: () => StateDirtyMap
+}
+
+export type IField = IModel
+
+export type IVirtualField = IModel
+
+export type IFormInternal = IModel
+
+export interface IForm {
+ submit(
+ onSubmit?: (values: IFormState['values']) => any | Promise
+ ): Promise
+ clearErrors: (pattern?: FormPathPattern) => void
+ reset(options?: IFormResetOptions): Promise
+ validate(path?: FormPathPattern, options?: {}): Promise
+ setFormState(callback?: (state: IFormState) => any): void
+ getFormState(callback?: (state: IFormState) => any): any
+ setFieldState(
+ path: FormPathPattern,
+ callback?: (state: IFieldState) => void
+ ): void
+ getFieldState(
+ path: FormPathPattern,
+ callback?: (state: IFieldState) => any
+ ): any
+ unsafe_do_not_use_transform_data_path(path: FormPathPattern): FormPathPattern //eslint-disable-line
+ registerField(props: IFieldStateProps): IField
+ registerVirtualField(props: IVirtualFieldStateProps): IVirtualField
+ createMutators(field: IField): IMutators
+ getFormGraph(): IFormGraph
+ setFormGraph(graph: IFormGraph): void
+ subscribe(callback?: FormHeartSubscriber): void
+ unsubscribe(callback?: FormHeartSubscriber): void
+ notify: (type: string, payload?: T) => void
+ setFieldValue(path?: FormPathPattern, value?: any): void
+ getFieldValue(path?: FormPathPattern): any
+ setFieldInitialValue(path?: FormPathPattern, value?: any): void
+ getFieldInitialValue(path?: FormPathPattern): any
+}
diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts
deleted file mode 100644
index 837927628d7..00000000000
--- a/packages/core/src/utils.ts
+++ /dev/null
@@ -1,164 +0,0 @@
-import { Path, IFormPathMatcher } from '@uform/types'
-import {
- isArr,
- isStr,
- getPathSegments,
- isEqual,
- toArr,
- clone,
- isFn,
- globalThisPolyfill
-} from '@uform/utils'
-
-export * from '@uform/utils'
-
-const self = globalThisPolyfill
-
-const compactScheduler = ([raf, caf, priority], fresh: boolean) => {
- return [fresh ? callback => raf(priority, callback) : raf, caf]
-}
-
-const getScheduler = () => {
- if (!self.requestAnimationFrame) {
- return [self.setTimeout, self.clearTimeout]
- }
- try {
- // eslint-disable-next-line @typescript-eslint/no-var-requires
- const scheduler = require('scheduler') as any
- return compactScheduler(
- [
- scheduler.scheduleCallback || scheduler.unstable_scheduleCallback,
- scheduler.cancelCallback || scheduler.unstable_cancelCallback,
- scheduler.NormalPriority || scheduler.unstable_NormalPriority
- ],
- !!scheduler.unstable_requestPaint
- )
- } catch (err) {
- return [self.requestAnimationFrame, self.cancelAnimationFrame]
- }
-}
-
-export const [raf, caf] = getScheduler()
-
-export const resolveFieldPath = (path: Path | IFormPathMatcher): string[] => {
- if (!isArr(path)) {
- return isStr(path) ? resolveFieldPath(getPathSegments(path)) : undefined
- }
- return path.reduce((buf, key) => {
- return buf.concat(getPathSegments(key))
- }, [])
-}
-
-export const isChildField = (field, parent) => {
- if (field && parent && field.path && parent.path) {
- for (let i = 0; i < parent.path.length; i++) {
- if (field.path[i] !== parent.path[i]) {
- return false
- }
- }
- return parent.path.length < field.path.length
- }
- return false
-}
-
-export const hasRequired = rules => {
- return toArr(rules).some(rule => {
- return rule && rule.required
- })
-}
-
-export const publishFormState = state => {
- const {
- values,
- valid,
- invalid,
- initialValues,
- errors,
- pristine,
- dirty
- } = state
- return {
- values: clone(values),
- valid,
- invalid,
- errors,
- pristine,
- dirty,
- initialValues: clone(initialValues)
- }
-}
-
-export const publishFieldState = state => {
- const {
- value,
- valid,
- invalid,
- errors,
- visible,
- display,
- editable,
- initialValue,
- name,
- path,
- props,
- effectErrors,
- loading,
- pristine,
- required,
- rules
- } = state
- return {
- value: clone(value),
- valid,
- invalid,
- editable,
- visible,
- display,
- loading,
- errors: errors.concat(effectErrors),
- pristine,
- initialValue: clone(initialValue),
- name,
- path,
- props,
- required,
- rules
- }
-}
-
-export class BufferList {
- public data = []
- public indexes = {}
- public push(key: string, value: any, extra: any) {
- if (!this.indexes[key]) {
- const index = this.data.length
- this.data.push({ ...extra, key, values: [value] })
- this.indexes[key] = index
- } else {
- const item = this.data[this.indexes[key]]
- if (!item.values.some(callback => isEqual(callback, value))) {
- item.values.push(value)
- }
- }
- }
-
- public forEach(callback) {
- for (let i = 0; i < this.data.length; i++) {
- if (isFn(callback)) {
- callback(this.data[i], this.data[i].key)
- }
- }
- }
-
- public remove(key: string, value?: any) {
- this.data = this.data.reduce((buf, item, index) => {
- if (item.key === key) {
- delete this.indexes[key]
- return buf
- } else {
- this.indexes[key] = buf.length
- return buf.concat(item)
- }
- }, [])
- }
-}
diff --git a/packages/next/README.md b/packages/next/README.md
index 3eb267bdc52..f7b7d78e396 100644
--- a/packages/next/README.md
+++ b/packages/next/README.md
@@ -1,2 +1,77 @@
# @uform/next
-> UForm Fusion Next组件插件包
\ No newline at end of file
+
+> UForm Fusion Next 组件插件包
+
+```jsx
+import {
+ SchemaForm,
+ Field,
+ FormButtonGroup,
+ Submit,
+ FormEffectHooks,
+ createFormActions,
+ FormGridRow,
+ FormItemGrid,
+ FormGridCol,
+ FormPath,
+ FormLayout,
+ FormBlock,
+ FormCard,
+ FormTextBox,
+ FormStep
+} from './src/index'
+import { Button } from '@alifd/next'
+import '@alifd/next/dist/next.css'
+
+const { onFormInit$ } = FormEffectHooks
+
+const actions = createFormActions()
+
+export default () => (
+ {
+ console.log('提交')
+ console.log(values)
+ }}
+ actions={actions}
+ labelCol={{ span: 8 }}
+ wrapperCol={{ span: 6 }}
+ validateFirst
+ effects={({ setFieldState, getFormGraph }) => {
+ onFormInit$().subscribe(() => {
+ setFieldState('col1', state => {
+ state.visible = false
+ })
+ })
+ FormStep.effects(['step-1', 'step-2', 'step-3'])
+ }}
+ >
+
+
+
+
+
+
+
+
+
+
+
+ 提交
+
+
+
+
+)
+```
diff --git a/packages/next/build.ts b/packages/next/build.ts
index 9ee3d0d8514..4fa9dc8329f 100644
--- a/packages/next/build.ts
+++ b/packages/next/build.ts
@@ -1,19 +1,19 @@
import { compile, getCompileConfig } from '../../scripts/build'
import ts from 'typescript'
-import tsImportPluginFactory from 'ts-import-plugin'
+//import tsImportPluginFactory from 'ts-import-plugin'
import glob from 'glob'
-const transformer = tsImportPluginFactory({
- libraryName: '@alifd/next',
- //style: importPath => `${importPath}/style`,
-})
+// const transformer = tsImportPluginFactory({
+// libraryName: '@alifd/next',
+// style: importPath => `${importPath}/style`,
+// })
function buildESM() {
const { fileNames, options } = getCompileConfig(require.resolve('./tsconfig.json'), {
outDir: './esm',
module: ts.ModuleKind.ESNext
})
- compile(fileNames, options, { before: [transformer] })
+ compile(fileNames, options)
console.log('esm build successfully')
}
diff --git a/packages/next/package.json b/packages/next/package.json
index 1309dc647e8..e40e33414ae 100644
--- a/packages/next/package.json
+++ b/packages/next/package.json
@@ -1,6 +1,6 @@
{
"name": "@uform/next",
- "version": "0.4.3",
+ "version": "0.4.0",
"license": "MIT",
"main": "lib",
"module": "esm",
@@ -21,24 +21,31 @@
},
"peerDependencies": {
"@alifd/next": "^1.13.1",
+ "@types/classnames": "^2.2.9",
+ "@types/styled-components": "^4.1.19",
"@babel/runtime": "^7.4.4",
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
},
"dependencies": {
- "@uform/react": "^0.4.3",
- "@uform/types": "^0.4.3",
- "@uform/utils": "^0.4.3",
+ "@uform/react-schema-renderer": "^0.4.0",
+ "@uform/react-shared-components":"^0.4.0",
+ "@uform/types": "^0.4.0",
+ "@uform/shared": "^0.4.0",
"classnames": "^2.2.6",
"moveto": "^1.7.4",
"react-stikky": "^0.1.15",
- "styled-components": "^4.1.1"
+ "styled-components": "^4.1.1",
+ "react-eva": "^1.0.0",
+ "rxjs": "^6.5.1"
},
"devDependencies": {
- "@alifd/next": "^1.13.1"
+ "@alifd/next": "^1.13.1",
+ "@types/classnames": "^2.2.9",
+ "@types/styled-components": "^4.1.19"
},
"publishConfig": {
"access": "public"
},
"gitHead": "4d068dad6183e8da294a4c899a158326c0b0b050"
-}
+}
\ No newline at end of file
diff --git a/packages/next/src/compat/Form.tsx b/packages/next/src/compat/Form.tsx
new file mode 100644
index 00000000000..3ea7255f778
--- /dev/null
+++ b/packages/next/src/compat/Form.tsx
@@ -0,0 +1,21 @@
+import React from 'react'
+import { Form } from '@alifd/next'
+import { FormProps } from '@alifd/next/types/form'
+import { IFormItemTopProps } from '../types'
+import { FormItemProvider } from './context'
+import { normalizeCol } from '../shared'
+
+export const CompatNextForm: React.FC<
+ FormProps & IFormItemTopProps
+> = props => {
+ return (
+
+
+
+ )
+}
diff --git a/packages/next/src/compat/FormItem.tsx b/packages/next/src/compat/FormItem.tsx
new file mode 100644
index 00000000000..db71dc8d82a
--- /dev/null
+++ b/packages/next/src/compat/FormItem.tsx
@@ -0,0 +1,109 @@
+import React, { createContext } from 'react'
+import { Form } from '@alifd/next'
+import { useFormItem } from './context'
+import { IFormItemTopProps, ICompatItemProps } from '../types'
+import { normalizeCol } from '../shared'
+import { useContext } from 'react'
+
+const computeStatus = (props: ICompatItemProps) => {
+ if (props.loading) {
+ return 'loading'
+ }
+ if (props.invalid) {
+ return 'error'
+ }
+ //todo:暂时不支持
+ // if (props.warnings.length) {
+ // return 'warning'
+ // }
+}
+
+const computeHelp = (props: ICompatItemProps) => {
+ if (props.help) return props.help
+ const messages = [].concat(props.errors || [], props.warnings || [])
+ return messages.length ? messages : props.schema && props.schema.description
+}
+
+const computeLabel = (props: ICompatItemProps) => {
+ if (props.label) return props.label
+ if (props.schema && props.schema.title) {
+ return props.schema.title
+ }
+}
+
+const computeExtra = (props: ICompatItemProps) => {
+ if (props.extra) return props.extra
+}
+
+function pickProps(obj: T, ...keys: (keyof T)[]): Pick {
+ const result: Pick = {} as any
+ for (let i = 0; i < keys.length; i++) {
+ if (obj[keys[i]] !== undefined) {
+ result[keys[i]] = obj[keys[i]]
+ }
+ }
+ return result
+}
+
+const computeSchemaExtendProps = (
+ props: ICompatItemProps
+): IFormItemTopProps => {
+ if (props.schema) {
+ return pickProps(
+ {
+ ...props.schema.getExtendsItemProps(),
+ ...props.schema.getExtendsProps()
+ },
+ 'prefix',
+ 'labelAlign',
+ 'labelTextAlign',
+ 'size',
+ 'labelCol',
+ 'wrapperCol'
+ )
+ }
+}
+
+const FormItemPropsContext = createContext({})
+
+export const FormItemProps = ({ children, ...props }) => (
+
+ {children}
+
+)
+
+export const CompatNextFormItem: React.FC = props => {
+ const {
+ prefix,
+ labelAlign,
+ labelCol,
+ labelTextAlign,
+ wrapperCol,
+ size
+ } = useFormItem()
+ const formItemProps = useContext(FormItemPropsContext)
+ const help = computeHelp(props)
+ const label = computeLabel(props)
+ const status = computeStatus(props)
+ const extra = computeExtra(props)
+ const itemProps = computeSchemaExtendProps(props)
+ return (
+ {extra}
}
+ {...itemProps}
+ {...formItemProps}
+ >
+ {props.children}
+
+ )
+}
diff --git a/packages/next/src/compat/context.tsx b/packages/next/src/compat/context.tsx
new file mode 100644
index 00000000000..e3f38383c73
--- /dev/null
+++ b/packages/next/src/compat/context.tsx
@@ -0,0 +1,35 @@
+import React, { createContext, useContext } from 'react'
+import { IFormItemTopProps } from '../types'
+
+const FormItemContext = createContext({})
+
+export const FormItemProvider: React.FC = ({
+ children,
+ prefix,
+ size,
+ labelAlign,
+ labelCol,
+ inline,
+ labelTextAlign,
+ wrapperCol
+}) => (
+
+ {children}
+
+)
+
+FormItemProvider.displayName = 'FormItemProvider'
+
+export const useFormItem = () => {
+ return useContext(FormItemContext)
+}
diff --git a/packages/next/src/compat/index.ts b/packages/next/src/compat/index.ts
new file mode 100644
index 00000000000..1eca2fb65ed
--- /dev/null
+++ b/packages/next/src/compat/index.ts
@@ -0,0 +1,10 @@
+import {
+ registerFormComponent,
+ registerFormItemComponent
+} from '@uform/react-schema-renderer'
+import { CompatNextForm } from './Form'
+import { CompatNextFormItem } from './FormItem'
+
+registerFormComponent(CompatNextForm)
+
+registerFormItemComponent(CompatNextFormItem)
diff --git a/packages/next/src/components/FormBlock.tsx b/packages/next/src/components/FormBlock.tsx
new file mode 100644
index 00000000000..6468e5b03c6
--- /dev/null
+++ b/packages/next/src/components/FormBlock.tsx
@@ -0,0 +1,26 @@
+import React from 'react'
+import { createVirtualBox } from '@uform/react-schema-renderer'
+import { Card } from '@alifd/next'
+import { CardProps } from '@alifd/next/types/card'
+import styled from 'styled-components'
+
+export const FormBlock = createVirtualBox(
+ 'block',
+ styled(({ children, className, ...props }) => {
+ return (
+
+ {children}
+
+ )
+ })`
+ margin-bottom: 0px;
+ .next-card-body {
+ padding-top: 20px;
+ padding-bottom: 0 !important;
+ }
+ &.next-card {
+ border: none;
+ padding-bottom: 15px;
+ }
+ `
+)
diff --git a/packages/next/src/components/FormCard.tsx b/packages/next/src/components/FormCard.tsx
new file mode 100644
index 00000000000..71cb0fbe68d
--- /dev/null
+++ b/packages/next/src/components/FormCard.tsx
@@ -0,0 +1,22 @@
+import React from 'react'
+import { createVirtualBox } from '@uform/react-schema-renderer'
+import { Card } from '@alifd/next'
+import { CardProps } from '@alifd/next/types/card'
+import styled from 'styled-components'
+
+export const FormCard = createVirtualBox(
+ 'card',
+ styled(({ children, className, ...props }) => {
+ return (
+
+ {children}
+
+ )
+ })`
+ margin-bottom: 30px;
+ .next-card-body {
+ padding-top: 30px;
+ padding-bottom: 0 !important;
+ }
+ `
+)
diff --git a/packages/next/src/components/FormItemGrid.tsx b/packages/next/src/components/FormItemGrid.tsx
new file mode 100644
index 00000000000..fbe30387150
--- /dev/null
+++ b/packages/next/src/components/FormItemGrid.tsx
@@ -0,0 +1,89 @@
+import React, { Fragment } from 'react'
+import { CompatNextFormItem } from '../compat/FormItem'
+import { createVirtualBox } from '@uform/react-schema-renderer'
+import { toArr } from '@uform/shared'
+import { Grid } from '@alifd/next'
+import { RowProps, ColProps } from '@alifd/next/types/grid'
+import { ItemProps } from '@alifd/next/types/form'
+import { IFormItemGridProps, IItemProps } from '../types'
+import { normalizeCol } from '../shared'
+const { Row, Col } = Grid
+
+export const FormItemGrid = createVirtualBox(
+ 'grid',
+ props => {
+ const {
+ cols: rawCols,
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ title,
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ description,
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ help,
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ extra,
+ ...selfProps
+ } = props
+ const children = toArr(props.children)
+ const cols = toArr(rawCols).map(col => normalizeCol(col))
+ const childNum = children.length
+
+ if (cols.length < childNum) {
+ let offset: number = childNum - cols.length
+ let lastSpan: number =
+ 24 -
+ cols.reduce((buf, col) => {
+ return (
+ buf +
+ Number(col.span ? col.span : 0) +
+ Number(col.offset ? col.offset : 0)
+ )
+ }, 0)
+ for (let i = 0; i < offset; i++) {
+ cols.push({ span: Math.floor(lastSpan / offset) })
+ }
+ }
+ const grids = (
+
+ {children.reduce((buf, child, key) => {
+ return child
+ ? buf.concat(
+
+ {child}
+
+ )
+ : buf
+ }, [])}
+
+ )
+
+ if (title) {
+ return (
+
+ {grids}
+
+ )
+ }
+ return {grids}
+ }
+)
+
+export const FormGridRow = createVirtualBox(
+ 'grid-row',
+ props => {
+ const { title, description, extra } = props
+ const grids = {props.children}
+ if (title) {
+ return (
+
+ {grids}
+
+ )
+ }
+ return grids
+ }
+)
+
+export const FormGridCol = createVirtualBox('grid-col', props => {
+ return {props.children}
+})
diff --git a/packages/next/src/components/FormLayout.tsx b/packages/next/src/components/FormLayout.tsx
new file mode 100644
index 00000000000..eb89eab6c43
--- /dev/null
+++ b/packages/next/src/components/FormLayout.tsx
@@ -0,0 +1,27 @@
+import React from 'react'
+import { FormItemProvider, useFormItem } from '../compat/context'
+import { createVirtualBox } from '@uform/react-schema-renderer'
+import cls from 'classnames'
+import { IFormItemTopProps } from '../types'
+
+export const FormLayout = createVirtualBox(
+ 'layout',
+ props => {
+ const { inline } = useFormItem()
+ const isInline = props.inline || inline
+ const children =
+ isInline || props.className || props.style ? (
+
+ {props.children}
+
+ ) : (
+ props.children
+ )
+ return {children}
+ }
+)
diff --git a/packages/next/src/components/FormStep.tsx b/packages/next/src/components/FormStep.tsx
new file mode 100644
index 00000000000..1de88681f35
--- /dev/null
+++ b/packages/next/src/components/FormStep.tsx
@@ -0,0 +1,98 @@
+import React, { useState, useMemo, useRef } from 'react'
+import {
+ createControllerBox,
+ ISchemaVirtualFieldComponentProps,
+ FormPathPattern,
+ createEffectHook,
+ createFormActions
+} from '@uform/react-schema-renderer'
+import { toArr } from '@uform/shared'
+import { Observable } from 'rxjs/internal/Observable'
+import { Step } from '@alifd/next'
+import { IFormStep } from '../types'
+
+enum StateMap {
+ ON_FORM_STEP_NEXT = 'onFormStepNext',
+ ON_FORM_STEP_PREVIOUS = 'onFormStepPrevious',
+ ON_FORM_STEP_GO_TO = 'onFormStepGoto',
+ ON_FORM_STEP_CURRENT_CHANGE = 'onFormStepCurrentChange'
+}
+const EffectHooks = {
+ onStepNext$: createEffectHook(StateMap.ON_FORM_STEP_NEXT),
+ onStepPrevious$: createEffectHook(StateMap.ON_FORM_STEP_PREVIOUS),
+ onStepGoto$: createEffectHook(StateMap.ON_FORM_STEP_GO_TO),
+ onStepCurrentChange$: createEffectHook<{
+ value: number
+ preValue: number
+ }>(StateMap.ON_FORM_STEP_CURRENT_CHANGE)
+}
+
+const effects = (relations: FormPathPattern[]) => {
+ const actions = createFormActions()
+ return EffectHooks.onStepCurrentChange$().subscribe(({ value }) => {
+ relations.forEach((pattern, index) => {
+ actions.setFieldState(pattern, (state: any) => {
+ state.display = index === value
+ })
+ })
+ })
+}
+
+type StepComponentExtendsProps = StateMap & {
+ getEffects: (
+ relations: FormPathPattern[]
+ ) => Observable<{
+ value: number
+ preValue: number
+ }>
+}
+
+export const FormStep: React.FC &
+ StepComponentExtendsProps = createControllerBox(
+ 'step',
+ ({ props, form }: ISchemaVirtualFieldComponentProps) => {
+ const [current, setCurrent] = useState(0)
+ const ref = useRef(current)
+ const { dataSource, ...stepProps } = props['x-component-props'] || {}
+ const items = toArr(dataSource)
+ const update = (cur: number) => {
+ form.notify(StateMap.ON_FORM_STEP_CURRENT_CHANGE, {
+ value: cur,
+ preValue: current
+ })
+ setCurrent(cur)
+ }
+ useMemo(() => {
+ update(ref.current)
+ form.subscribe(({ type, payload }) => {
+ switch (type) {
+ case StateMap.ON_FORM_STEP_NEXT:
+ update(
+ ref.current + 1 > items.length - 1 ? ref.current : ref.current + 1
+ )
+ break
+ case StateMap.ON_FORM_STEP_PREVIOUS:
+ update(ref.current - 1 < 0 ? ref.current : ref.current - 1)
+ break
+ case StateMap.ON_FORM_STEP_GO_TO:
+ if (!(payload < 0 || payload > items.length)) {
+ update(payload)
+ }
+ break
+ }
+ })
+ }, [])
+ ref.current = current
+ return (
+
+ {items.map((props, key) => {
+ return
+ })}
+
+ )
+ }
+) as any
+
+Object.assign(FormStep, StateMap, EffectHooks, {
+ effects
+})
diff --git a/packages/next/src/components/FormTextBox.tsx b/packages/next/src/components/FormTextBox.tsx
new file mode 100644
index 00000000000..45cf18233ee
--- /dev/null
+++ b/packages/next/src/components/FormTextBox.tsx
@@ -0,0 +1,104 @@
+import React, { useRef, useEffect } from 'react'
+import { createControllerBox } from '@uform/react-schema-renderer'
+import { IFormTextBox } from '../types'
+import { toArr } from '@uform/shared'
+import { CompatNextFormItem } from '../compat/FormItem'
+import styled from 'styled-components'
+
+export const FormTextBox = createControllerBox(
+ 'text-box',
+ styled(({ props, className, children }) => {
+ const {
+ title,
+ help,
+ text,
+ name,
+ extra,
+ gutter,
+ style,
+ ...componentProps
+ } = Object.assign(
+ {
+ gutter: 5
+ },
+ props['x-component-props']
+ )
+ const ref: React.RefObject = useRef()
+ const arrChildren = toArr(children)
+ const split = text.split('%s')
+ let index = 0
+ useEffect(() => {
+ if (ref.current) {
+ const eles = ref.current.querySelectorAll('.text-box-field')
+ eles.forEach((el: HTMLElement) => {
+ const ctrl = el.querySelector('.next-form-item-control:first-child')
+ if (ctrl) {
+ el.style.width = getComputedStyle(ctrl).width
+ }
+ })
+ }
+ }, [])
+ const newChildren = split.reduce((buf, item, key) => {
+ return buf.concat(
+ item ? (
+
+ {item}
+
+ ) : null,
+ arrChildren[key] ? (
+
+ {arrChildren[key]}
+
+ ) : null
+ )
+ }, [])
+
+ const textChildren = (
+
+ {newChildren}
+
+ )
+
+ if (!title) return textChildren
+
+ return (
+
+ {textChildren}
+
+ )
+ })`
+ display: flex;
+ .text-box-words:nth-child(1) {
+ margin-left: 0;
+ }
+ .text-box-field {
+ display: inline-block;
+ }
+ .next-form-item {
+ margin-bottom: 0 !important;
+ }
+ .preview-text {
+ text-align: center !important;
+ }
+ `
+)
diff --git a/packages/next/src/components/button.tsx b/packages/next/src/components/button.tsx
index 7cbc4eb1d69..9a3b67750b4 100644
--- a/packages/next/src/components/button.tsx
+++ b/packages/next/src/components/button.tsx
@@ -1,25 +1,77 @@
import React from 'react'
-import { FormConsumer } from '@uform/react'
+import { FormSpy, LifeCycleTypes } from '@uform/react-schema-renderer'
import { Button } from '@alifd/next'
-import { ISubmitProps } from '../type'
+import { ButtonProps } from '@alifd/next/types/button'
+import { ISubmitProps, IResetProps } from '../types'
+import styled from 'styled-components'
-export const Submit = ({ showLoading, ...props }: ISubmitProps) => {
+export const TextButton: React.FC = props => (
+
+)
+
+export const CircleButton = styled(props => {
+ return (
+
+ )
+})`
+ border-radius: 50% !important;
+ padding: 0 !important;
+ min-width: 28px;
+ &.next-large {
+ min-width: 40px;
+ }
+ &.next-small {
+ min-width: 20px;
+ }
+ &.has-text {
+ .next-icon {
+ margin-right: 5px;
+ }
+ background: none !important;
+ border: none !important;
+ }
+`
+
+export const Submit = ({ showLoading, onSubmit, ...props }: ISubmitProps) => {
return (
-
- {({ status }) => {
+ {
+ switch (action.type) {
+ case LifeCycleTypes.ON_FORM_SUBMIT_START:
+ return {
+ ...state,
+ submitting: true
+ }
+ case LifeCycleTypes.ON_FORM_SUBMIT_END:
+ return {
+ ...state,
+ submitting: false
+ }
+ default:
+ return state
+ }
+ }}
+ >
+ {({ state, form }) => {
return (
)
}}
-
+
)
}
@@ -27,16 +79,24 @@ Submit.defaultProps = {
showLoading: true
}
-export const Reset: React.FC> = props => {
+export const Reset: React.FC = ({
+ children,
+ forceClear,
+ validate,
+ ...props
+}) => {
return (
-
- {({ reset }) => {
+
+ {({ form }) => {
return (
-
+
)
}
diff --git a/packages/next/src/components/formButtonGroup.tsx b/packages/next/src/components/formButtonGroup.tsx
index 0af76eead94..2faea71c2a4 100644
--- a/packages/next/src/components/formButtonGroup.tsx
+++ b/packages/next/src/components/formButtonGroup.tsx
@@ -1,12 +1,10 @@
-import React, { Component } from 'react'
-import ReactDOM from 'react-dom'
+import React, { useRef } from 'react'
import { Grid } from '@alifd/next'
import Sticky from 'react-stikky'
import cls from 'classnames'
import styled from 'styled-components'
-
-import { FormLayoutConsumer } from '../form'
-import { IFormButtonGroupProps } from '../type'
+import { useFormItem } from '../compat/context'
+import { IFormButtonGroupProps } from '../types'
const { Row, Col } = Grid
@@ -59,17 +57,22 @@ const isElementInViewport = (
)
}
-export const FormButtonGroup: React.FC = styled(
- class FormButtonGroup extends Component {
- static defaultProps = {
- span: 24,
- zIndex: 100
- }
-
- private formNode: HTMLElement
-
- private renderChildren() {
- const { children, itemStyle, offset, span } = this.props
+export const FormButtonGroup = styled(
+ (props: React.PropsWithChildren) => {
+ const {
+ span,
+ zIndex,
+ sticky,
+ style,
+ offset,
+ className,
+ children,
+ triggerDistance,
+ itemStyle
+ } = props
+ const { inline } = useFormItem()
+ const selfRef = useRef()
+ const renderChildren = () => {
return (
@@ -84,69 +87,53 @@ export const FormButtonGroup: React.FC = styled(
)
}
-
- getStickyBoundaryHandler(ref) {
+ const getStickyBoundaryHandler = () => {
return () => {
- this.formNode = this.formNode || ReactDOM.findDOMNode(ref.current)
- if (this.formNode) {
- return isElementInViewport(this.formNode.getBoundingClientRect())
+ if (selfRef.current && selfRef.current.parentElement) {
+ const container = selfRef.current.parentElement
+ return isElementInViewport(container.getBoundingClientRect())
}
return true
}
}
- render() {
- const { sticky, style, className } = this.props
+ const content = (
+
+ {renderChildren()}
+
+ )
- const content = (
-
- {({ inline } = {}) => (
-
- {this.renderChildren()}
+ if (sticky) {
+ return (
+
+
+
+ {content}
- )}
-
+
+
)
-
- if (sticky) {
- return (
-
-
- {({ FormRef } = {}) => {
- if (!FormRef) return
- return (
-
-
- {content}
-
-
- )
- }}
-
-
- )
- }
-
- return content
}
+
+ return content
}
-)`
- ${props =>
+)
`
+ ${(props: IFormButtonGroupProps) =>
props.align ? `display:flex;justify-content: ${getAlign(props.align)}` : ''}
&.is-inline {
display: inline-block;
diff --git a/packages/next/src/components/index.ts b/packages/next/src/components/index.ts
new file mode 100644
index 00000000000..1015143d9c7
--- /dev/null
+++ b/packages/next/src/components/index.ts
@@ -0,0 +1,8 @@
+export * from './Button'
+export * from './FormButtonGroup'
+export * from './FormLayout'
+export * from './FormItemGrid'
+export * from './FormCard'
+export * from './FormBlock'
+export * from './FormTextBox'
+export * from './FormStep'
diff --git a/packages/next/src/components/layout.tsx b/packages/next/src/components/layout.tsx
deleted file mode 100644
index 08c19608d79..00000000000
--- a/packages/next/src/components/layout.tsx
+++ /dev/null
@@ -1,312 +0,0 @@
-import React, { Component, useEffect, useRef } from 'react'
-import { createVirtualBox, createControllerBox } from '@uform/react'
-import { toArr } from '@uform/utils'
-import { Grid } from '@alifd/next'
-import Card from '@alifd/next/lib/card'
-import styled from 'styled-components'
-import cls from 'classnames'
-import { IFormItemGridProps, IFormItemProps } from '@uform/types'
-
-import { FormLayoutConsumer, FormItem, FormLayoutProvider } from '../form'
-import {
- IFormTextBox,
- IFormCardProps,
- IFormBlockProps,
- IFormLayoutProps,
- TFormCardOrFormBlockProps,
- IFormItemGridProps as IFormItemGridPropsAlias
-} from '../type'
-
-const { Row, Col } = Grid
-
-const normalizeCol = (
- col: { span: number; offset?: number } | number,
- defaultValue: { span: number } = { span: 0 }
-): { span: number; offset?: number } => {
- if (!col) {
- return defaultValue
- } else {
- return typeof col === 'object' ? col : { span: col }
- }
-}
-
-export const FormLayout = createVirtualBox(
- 'layout',
- ({ children, ...props }) => {
- return (
-
- {value => {
- let newValue = { ...value, ...props }
- let child =
- newValue.inline || newValue.className || newValue.style ? (
-
- {children}
-
- ) : (
- children
- )
- return (
- {child}
- )
- }}
-
- )
- }
-)
-
-export const FormLayoutItem: React.FC = props =>
- React.createElement(
- FormLayoutConsumer,
- {},
- ({
- labelAlign,
- labelTextAlign,
- labelCol,
- wrapperCol,
- size,
- autoAddColon
- }) => {
- return React.createElement(
- FormItem,
- {
- labelAlign,
- labelTextAlign,
- labelCol,
- wrapperCol,
- autoAddColon,
- size,
- ...props
- },
- props.children
- )
- }
- )
-
-export const FormItemGrid = createVirtualBox(
- 'grid',
- class extends Component {
- renderFormItem(children) {
- const { title, help, name, extra, ...props } = this.props
- return React.createElement(
- FormLayoutItem,
- {
- label: title,
- noMinHeight: true,
- id: name,
- extra,
- help,
- ...props
- } as IFormItemGridProps,
- children
- )
- }
-
- renderGrid() {
- const {
- children: rawChildren,
- cols: rawCols,
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- title,
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- description,
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- help,
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- extra,
- ...props
- } = this.props
-
- const children = toArr(rawChildren)
- const cols = toArr(rawCols).map(col => normalizeCol(col))
- const childNum = children.length
-
- if (cols.length < childNum) {
- let offset: number = childNum - cols.length
- let lastSpan: number =
- 24 -
- cols.reduce((buf, col) => {
- return (
- buf +
- Number(col.span ? col.span : 0) +
- Number(col.offset ? col.offset : 0)
- )
- }, 0)
- for (let i = 0; i < offset; i++) {
- cols.push({ span: Math.floor(lastSpan / offset) })
- }
- }
-
- return (
-
- {children.reduce((buf, child, key) => {
- return child
- ? buf.concat(
-
- {child}
-
- )
- : buf
- }, [])}
-
- )
- }
-
- render() {
- const { title } = this.props
- if (title) {
- return this.renderFormItem(this.renderGrid())
- } else {
- return this.renderGrid()
- }
- }
- }
-)
-
-export const FormCard = createVirtualBox(
- 'card',
- styled(
- class extends Component {
- static defaultProps = {
- contentHeight: 'auto'
- }
- render() {
- const { children, className, ...props } = this.props
- return (
-
- {children}
-
- )
- }
- }
- )`
- margin-bottom: 30px;
- .next-card-body {
- padding-top: 30px;
- padding-bottom: 0 !important;
- }
- `
-)
-
-export const FormBlock = createVirtualBox(
- 'block',
- styled(
- class extends Component {
- static defaultProps = {
- contentHeight: 'auto'
- }
- render() {
- const { children, className, ...props } = this.props
- return (
-
- {children}
-
- )
- }
- }
- )`
- margin-bottom: 0px;
- .next-card-body {
- padding-top: 20px;
- padding-bottom: 0 !important;
- }
- &.next-card {
- border: none;
- padding: 0 15px;
- padding-bottom: 15px;
- }
- `
-)
-
-export const FormTextBox = createControllerBox(
- 'text-box',
- styled(({ children, schema, className }) => {
- const { title, help, text, name, extra, ...props } = schema['x-props']
- const ref: React.RefObject = useRef()
- const arrChildren = toArr(children)
- const split = String(text).split('%s')
- let index = 0
- useEffect(() => {
- if (ref.current) {
- const eles = ref.current.querySelectorAll('.text-box-field')
- eles.forEach((el: HTMLElement) => {
- const ctrl = el.querySelector(
- '.next-form-item-control>*:not(.next-form-item-space)'
- )
- if (ctrl) {
- el.style.width = getComputedStyle(ctrl).width
- }
- })
- }
- }, [])
- const newChildren = split.reduce((buf, item, key) => {
- return buf.concat(
- item ? (
-
- {item}
-
- ) : (
- undefined
- ),
- arrChildren[key] ? (
-
- {arrChildren[key]}
-
- ) : (
- undefined
- )
- )
- }, [])
-
- if (!title)
- return (
-
- {newChildren}
-
- )
-
- return React.createElement(
- FormLayoutItem,
- {
- label: title,
- noMinHeight: true,
- id: name,
- extra,
- help,
- ...props
- },
-
- {newChildren}
-
- )
- })`
- display: flex;
- .text-box-words {
- font-size: 12px;
- line-height: 28px;
- color: #333;
- ${props => {
- const { editable, schema } = props
- const { gutter } = schema['x-props']
- if (!editable) {
- return {
- margin: 0
- }
- }
- return {
- margin: `0 ${gutter === 0 || gutter ? gutter : 10}px`
- }
- }}
- }
- .text-box-words:nth-child(1) {
- margin-left: 0;
- }
- .text-box-field {
- display: inline-block;
- }
- `
-)
diff --git a/packages/next/src/fields/array.tsx b/packages/next/src/fields/array.tsx
deleted file mode 100644
index 3221814b335..00000000000
--- a/packages/next/src/fields/array.tsx
+++ /dev/null
@@ -1,180 +0,0 @@
-import React from 'react'
-import { registerFormField, createArrayField } from '@uform/react'
-import { Button, Icon } from '@alifd/next'
-import styled from 'styled-components'
-
-export const CircleButton = styled['div'].attrs({ className: 'cricle-btn' })`
- ${props =>
- !props.hasText
- ? `width:30px;
- height:30px;`
- : ''}
- margin-right:10px;
- border-radius: ${props => (!props.hasText ? '100px' : 'none')};
- border: ${props => (!props.hasText ? '1px solid #eee' : 'none')};
- margin-bottom:20px;
- cursor:pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- line-height: 1.3;
- ${props =>
- !props.hasText
- ? `&:hover{
- background:#f7f4f4;
- }`
- : ''}
- .next-icon{
- display:flex;
- align-items:'center'
- }
- .op-name{
- margin-left:3px;
- }
-}
-`
-
-export const ArrayField = createArrayField({
- CircleButton,
- TextButton: props => (
-
- {props.children}
-
- ),
- AddIcon: () => ,
- RemoveIcon: () => (
-
- ),
- MoveDownIcon: () => (
-
- ),
- MoveUpIcon: () => (
-
- )
-})
-
-registerFormField(
- 'array',
- styled(
- class extends ArrayField {
- render() {
- const { className, name, value, renderField } = this.props
- const cls = this.getProps('className')
- const style = this.getProps('style')
- return (
-
- {value.map((item, index) => {
- return (
-
-
- {index + 1}
-
-
{renderField(index)}
-
- {this.renderRemove(index, item)}
- {this.renderMoveDown(index, item)}
- {this.renderMoveUp(index)}
-
-
- )
- })}
- {this.renderEmpty()}
- {value.length > 0 && this.renderAddition()}
-
- )
- }
- }
- )`
- border: 1px solid #eee;
- min-width: 400px;
- .array-item {
- padding: 20px;
- padding-bottom: 0;
- padding-top: 30px;
- border-bottom: 1px solid #eee;
- position: relative;
- &:nth-child(even) {
- background: #fafafa;
- }
- .array-index {
- position: absolute;
- top: 0;
- left: 0;
- display: block;
- span {
- position: absolute;
- color: #fff;
- z-index: 1;
- font-size: 12px;
- top: 3px;
- left: 3px;
- }
- &::after {
- content: '';
- display: block;
- border-top: 20px solid transparent;
- border-left: 20px solid transparent;
- border-bottom: 20px solid transparent;
- border-right: 20px solid #888;
- transform: rotate(45deg);
- position: absolute;
- z-index: 0;
- top: -20px;
- left: -20px;
- }
- }
- .array-item-operator {
- display: flex;
- border-top: 1px solid #eee;
- padding-top: 20px;
- }
- }
- .array-empty-wrapper {
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- &.disabled {
- cursor: default;
- }
- .array-empty {
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- margin: 20px;
- img {
- display: block;
- height: 80px;
- }
- .next-btn-text {
- color: #999;
- .next-icon:before {
- width: 16px !important;
- font-size: 16px !important;
- margin-right: 3px;
- }
- }
- }
- }
- .array-item-wrapper {
- margin: 0 -20px;
- }
- .array-item-addition {
- padding: 10px 20px;
- background: #fbfbfb;
- .next-btn-text {
- color: #888;
- .next-icon:before {
- width: 16px !important;
- font-size: 16px !important;
- margin-right: 3px;
- }
- }
- }
- `
-)
diff --git a/packages/next/src/fields/boolean.ts b/packages/next/src/fields/boolean.ts
index c742488cbbb..db32fcda84d 100644
--- a/packages/next/src/fields/boolean.ts
+++ b/packages/next/src/fields/boolean.ts
@@ -1,5 +1,5 @@
-import { connect, registerFormField } from '@uform/react'
-import { acceptEnum, mapStyledProps } from '../utils'
+import { connect, registerFormField } from '@uform/react-schema-renderer'
+import { acceptEnum, mapStyledProps } from '../shared'
import { Switch } from '@alifd/next'
registerFormField(
diff --git a/packages/next/src/fields/cards.tsx b/packages/next/src/fields/cards.tsx
index 00b886f03e4..dc62ae1d27d 100644
--- a/packages/next/src/fields/cards.tsx
+++ b/packages/next/src/fields/cards.tsx
@@ -1,86 +1,131 @@
import React, { Fragment } from 'react'
-import { registerFormField } from '@uform/react'
-import { toArr } from '@uform/utils'
-import { ArrayField } from './array'
+import { Icon } from '@alifd/next'
+import {
+ registerFormField,
+ ISchemaFieldComponentProps,
+ SchemaField
+} from '@uform/react-schema-renderer'
+import { toArr, isFn, FormPath } from '@uform/shared'
+import { ArrayList } from '@uform/react-shared-components'
+import { CircleButton, TextButton } from '../components/Button'
import { Card } from '@alifd/next'
import styled from 'styled-components'
-const FormCardsField = styled(
- class extends ArrayField {
- renderOperations(item, index) {
- return (
-
- {this.renderRemove(index, item)}
- {this.renderMoveDown(index, item)}
- {this.renderMoveUp(index)}
- {this.renderExtraOperations(index)}
-
- )
- }
+const ArrayComponents = {
+ CircleButton,
+ TextButton,
+ AdditionIcon: () => ,
+ RemoveIcon: () => (
+
+ ),
+ MoveDownIcon: () => (
+
+ ),
+ MoveUpIcon: () => (
+
+ )
+}
- renderCardEmpty(title) {
- return (
-
- {this.renderEmpty()}
-
- )
+const FormCardsField = styled(
+ (props: ISchemaFieldComponentProps & { className: string }) => {
+ const { value, schema, className, editable, path, mutators } = props
+ const {
+ renderAddition,
+ renderRemove,
+ renderMoveDown,
+ renderMoveUp,
+ renderEmpty,
+ renderExtraOperations,
+ ...componentProps
+ } = schema.getExtendsComponentProps() || {}
+ const onAdd = () => {
+ const items = Array.isArray(schema.items)
+ ? schema.items[schema.items.length - 1]
+ : schema.items
+ mutators.push(items.getEmptyValue())
}
-
- render() {
- const { value, className, schema, renderField } = this.props
- /* eslint-disable @typescript-eslint/no-unused-vars */
- const {
- title,
- style,
- className: cls,
- renderAddition,
- renderRemove,
- renderEmpty,
- renderMoveDown,
- renderMoveUp,
- renderOperations,
- ...others
- } = this.getProps() || ({} as any)
-
- /* eslint-enable @typescript-eslint/no-unused-vars */
- return (
-
+
{toArr(value).map((item, index) => {
return (
- {index + 1}. {title || schema.title}
+ {index + 1}. {componentProps.title || schema.title}
}
- className="card-list"
- key={index}
- contentHeight="auto"
- extra={this.renderOperations(item, index)}
+ extra={
+
+ mutators.remove(index)}
+ />
+ mutators.moveDown(index)}
+ />
+ mutators.moveUp(index)}
+ />
+ {isFn(renderExtraOperations)
+ ? renderExtraOperations(index)
+ : renderExtraOperations}
+
+ }
>
- {renderField(index)}
+
)
})}
- {value.length === 0 && this.renderCardEmpty(title)}
-
- {value.length > 0 && this.renderAddition()}
-
-
- )
- }
+
+ {({ children }) => {
+ return (
+
+ {children}
+
+ )
+ }}
+
+
+ {({ children, isEmpty }) => {
+ if (!isEmpty) {
+ return (
+
+ {children}
+
+ )
+ }
+ }}
+
+
+
+ )
}
-)`
+)`
.next-card-body {
padding-top: 30px;
padding-bottom: 0 !important;
@@ -94,42 +139,15 @@ const FormCardsField = styled(
display: block;
margin-bottom: 0px;
background: #fff;
- .array-empty-wrapper {
- display: flex;
- justify-content: center;
- cursor: pointer;
- margin-bottom: 0px;
- &.disabled {
- cursor: default;
- }
- .array-empty {
- display: flex;
- flex-direction: column;
- margin-bottom: 20px;
- img {
- margin-bottom: 16px;
- height: 85px;
- }
- .next-btn-text {
- color: #888;
- }
- .next-icon:before {
- width: 16px !important;
- font-size: 16px !important;
- margin-right: 5px;
- }
- }
- }
.next-card {
box-shadow: none;
}
- .card-list {
+ .card-list-item {
box-shadow: none;
border: 1px solid #eee;
}
-
- .array-item-addition {
+ .array-cards-addition {
box-shadow: none;
border: 1px solid #eee;
transition: all 0.35s ease-in-out;
@@ -137,22 +155,41 @@ const FormCardsField = styled(
border: 1px solid #ccc;
}
}
+ .empty-wrapper {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin-bottom: 20px;
+ img {
+ margin-bottom: 16px;
+ height: 85px;
+ }
+ .next-btn-text {
+ color: #888;
+ }
+ .next-icon:before {
+ width: 16px !important;
+ font-size: 16px !important;
+ margin-right: 5px;
+ }
+ }
}
- .next-card.card-list {
- margin-top: 20px;
+ .card-list-empty.card-list-item {
+ cursor: pointer;
}
-
- .addition-wrapper .array-item-addition {
+ .next-card.card-list-item {
margin-top: 20px;
- margin-bottom: 3px;
- }
- .cricle-btn {
- margin-bottom: 0;
}
+
.next-card-extra {
display: flex;
+ button {
+ margin-right: 8px;
+ }
}
- .array-item-addition {
+ .array-cards-addition {
+ margin-top: 20px;
+ margin-bottom: 3px;
background: #fff;
display: flex;
cursor: pointer;
@@ -168,9 +205,10 @@ const FormCardsField = styled(
margin-right: 5px;
}
}
- .card-list:first-child {
+ .card-list-item:first-child {
margin-top: 0 !important;
}
`
registerFormField('cards', FormCardsField)
+registerFormField('array', FormCardsField)
diff --git a/packages/next/src/fields/checkbox.ts b/packages/next/src/fields/checkbox.ts
index bdacbe002d6..0a449eca07c 100644
--- a/packages/next/src/fields/checkbox.ts
+++ b/packages/next/src/fields/checkbox.ts
@@ -1,6 +1,6 @@
-import { connect, registerFormField } from '@uform/react'
+import { connect, registerFormField } from '@uform/react-schema-renderer'
import { Checkbox } from '@alifd/next'
-import { mapStyledProps, mapTextComponent } from '../utils'
+import { mapStyledProps, mapTextComponent } from '../shared'
const { Group: CheckboxGroup } = Checkbox
diff --git a/packages/next/src/fields/date.tsx b/packages/next/src/fields/date.ts
similarity index 90%
rename from packages/next/src/fields/date.tsx
rename to packages/next/src/fields/date.ts
index 823ea9796ac..b0f75d94cf8 100644
--- a/packages/next/src/fields/date.tsx
+++ b/packages/next/src/fields/date.ts
@@ -1,6 +1,6 @@
-import { connect, registerFormField } from '@uform/react'
+import { connect, registerFormField } from '@uform/react-schema-renderer'
import { DatePicker } from '@alifd/next'
-import { mapStyledProps, mapTextComponent } from '../utils'
+import { mapStyledProps, mapTextComponent } from '../shared'
const { RangePicker, MonthPicker, YearPicker } = DatePicker
diff --git a/packages/next/src/fields/index.ts b/packages/next/src/fields/index.ts
new file mode 100644
index 00000000000..59c23266c82
--- /dev/null
+++ b/packages/next/src/fields/index.ts
@@ -0,0 +1,15 @@
+import './string'
+import './number'
+import './boolean'
+import './date'
+import './time'
+import './range'
+import './upload'
+import './checkbox'
+import './radio'
+import './rating'
+import './transfer'
+import './cards'
+import './table'
+import './textarea'
+import './password'
\ No newline at end of file
diff --git a/packages/next/src/fields/number.ts b/packages/next/src/fields/number.ts
index a346a1cf4cc..ab126e4e095 100644
--- a/packages/next/src/fields/number.ts
+++ b/packages/next/src/fields/number.ts
@@ -1,6 +1,6 @@
-import { connect, registerFormField } from '@uform/react'
+import { connect, registerFormField } from '@uform/react-schema-renderer'
import { NumberPicker } from '@alifd/next'
-import { acceptEnum, mapStyledProps, mapTextComponent } from '../utils'
+import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared'
registerFormField(
'number',
diff --git a/packages/next/src/fields/password.tsx b/packages/next/src/fields/password.tsx
index 5664e29b93d..018287bd43b 100644
--- a/packages/next/src/fields/password.tsx
+++ b/packages/next/src/fields/password.tsx
@@ -1,154 +1,10 @@
import React from 'react'
-import { connect, registerFormField } from '@uform/react'
+import { connect, registerFormField } from '@uform/react-schema-renderer'
import { Input } from '@alifd/next'
import { InputProps } from '@alifd/next/types/input'
+import { PasswordStrength } from '@uform/react-shared-components'
import styled from 'styled-components'
-import { mapStyledProps } from '../utils'
-
-var isNum = function(c) {
- return c >= 48 && c <= 57
-}
-var isLower = function(c) {
- return c >= 97 && c <= 122
-}
-var isUpper = function(c) {
- return c >= 65 && c <= 90
-}
-var isSymbol = function(c) {
- return !(isLower(c) || isUpper(c) || isNum(c))
-}
-var isLetter = function(c) {
- return isLower(c) || isUpper(c)
-}
-
-const getStrength = val => {
- if (!val) return 0
- let num = 0
- let lower = 0
- let upper = 0
- let symbol = 0
- let MNS = 0
- let rep = 0
- let repC = 0
- let consecutive = 0
- let sequential = 0
- const len = () => num + lower + upper + symbol
- const callme = () => {
- var re = num > 0 ? 1 : 0
- re += lower > 0 ? 1 : 0
- re += upper > 0 ? 1 : 0
- re += symbol > 0 ? 1 : 0
- if (re > 2 && len() >= 8) {
- return re + 1
- } else {
- return 0
- }
- }
- for (var i = 0; i < val.length; i++) {
- var c = val.charCodeAt(i)
- if (isNum(c)) {
- num++
- if (i !== 0 && i !== val.length - 1) {
- MNS++
- }
- if (i > 0 && isNum(val.charCodeAt(i - 1))) {
- consecutive++
- }
- } else if (isLower(c)) {
- lower++
- if (i > 0 && isLower(val.charCodeAt(i - 1))) {
- consecutive++
- }
- } else if (isUpper(c)) {
- upper++
- if (i > 0 && isUpper(val.charCodeAt(i - 1))) {
- consecutive++
- }
- } else {
- symbol++
- if (i !== 0 && i !== val.length - 1) {
- MNS++
- }
- }
- var exists = false
- for (var j = 0; j < val.length; j++) {
- if (val[i] === val[j] && i !== j) {
- exists = true
- repC += Math.abs(val.length / (j - i))
- }
- }
- if (exists) {
- rep++
- var unique = val.length - rep
- repC = unique ? Math.ceil(repC / unique) : Math.ceil(repC)
- }
- if (i > 1) {
- var last1 = val.charCodeAt(i - 1)
- var last2 = val.charCodeAt(i - 2)
- if (isLetter(c)) {
- if (isLetter(last1) && isLetter(last2)) {
- var v = val.toLowerCase()
- var vi = v.charCodeAt(i)
- var vi1 = v.charCodeAt(i - 1)
- var vi2 = v.charCodeAt(i - 2)
- if (vi - vi1 === vi1 - vi2 && Math.abs(vi - vi1) === 1) {
- sequential++
- }
- }
- } else if (isNum(c)) {
- if (isNum(last1) && isNum(last2)) {
- if (c - last1 === last1 - last2 && Math.abs(c - last1) === 1) {
- sequential++
- }
- }
- } else {
- if (isSymbol(last1) && isSymbol(last2)) {
- if (c - last1 === last1 - last2 && Math.abs(c - last1) === 1) {
- sequential++
- }
- }
- }
- }
- }
- let sum = 0
- let length = len()
- sum += 4 * length
- if (lower > 0) {
- sum += 2 * (length - lower)
- }
- if (upper > 0) {
- sum += 2 * (length - upper)
- }
- if (num !== length) {
- sum += 4 * num
- }
- sum += 6 * symbol
- sum += 2 * MNS
- sum += 2 * callme()
- if (length === lower + upper) {
- sum -= length
- }
- if (length === num) {
- sum -= num
- }
- sum -= repC
- sum -= 2 * consecutive
- sum -= 3 * sequential
- sum = sum < 0 ? 0 : sum
- sum = sum > 100 ? 100 : sum
-
- if (sum >= 80) {
- return 100
- } else if (sum >= 60) {
- return 80
- } else if (sum >= 40) {
- return 60
- } else if (sum >= 20) {
- return 40
- } else {
- return 20
- }
-}
+import { mapStyledProps } from '../shared'
export interface IPasswordProps extends InputProps {
checkStrength: boolean
@@ -158,7 +14,6 @@ const Password = styled(
class Password extends React.Component {
state = {
value: this.props.value || this.props.defaultValue,
- strength: 0,
eye: false
}
@@ -168,8 +23,7 @@ const Password = styled(
this.props.value !== this.state.value
) {
this.setState({
- value: this.props.value,
- strength: getStrength(this.props.value)
+ value: this.props.value
})
}
}
@@ -177,8 +31,7 @@ const Password = styled(
onChangeHandler = (value, e) => {
this.setState(
{
- value,
- strength: getStrength(value)
+ value
},
() => {
if (this.props.onChange) {
@@ -189,20 +42,25 @@ const Password = styled(
}
renderStrength() {
- const { strength } = this.state
return (
-
+
+ {score => {
+ return (
+
+ )
+ }}
+
)
}
diff --git a/packages/next/src/fields/radio.ts b/packages/next/src/fields/radio.ts
index 5057f842427..319a718f1f7 100644
--- a/packages/next/src/fields/radio.ts
+++ b/packages/next/src/fields/radio.ts
@@ -1,6 +1,6 @@
-import { connect, registerFormField } from '@uform/react'
+import { connect, registerFormField } from '@uform/react-schema-renderer'
import { Radio } from '@alifd/next'
-import { mapStyledProps, mapTextComponent } from '../utils'
+import { mapStyledProps, mapTextComponent } from '../shared'
const { Group: RadioGroup } = Radio
diff --git a/packages/next/src/fields/range.ts b/packages/next/src/fields/range.ts
index e22a7780209..5398fa152ba 100644
--- a/packages/next/src/fields/range.ts
+++ b/packages/next/src/fields/range.ts
@@ -1,6 +1,6 @@
-import { connect, registerFormField } from '@uform/react'
+import { connect, registerFormField } from '@uform/react-schema-renderer'
import { Range } from '@alifd/next'
-import { mapStyledProps } from '../utils'
+import { mapStyledProps } from '../shared'
registerFormField(
'range',
diff --git a/packages/next/src/fields/rating.ts b/packages/next/src/fields/rating.ts
index bfc8be1771d..6b0473a63a3 100644
--- a/packages/next/src/fields/rating.ts
+++ b/packages/next/src/fields/rating.ts
@@ -1,6 +1,6 @@
-import { connect, registerFormField } from '@uform/react'
+import { connect, registerFormField } from '@uform/react-schema-renderer'
import { Rating } from '@alifd/next'
-import { mapStyledProps } from '../utils'
+import { mapStyledProps } from '../shared'
registerFormField(
'rating',
diff --git a/packages/next/src/fields/string.ts b/packages/next/src/fields/string.ts
index ab3e0bde5f6..e0139740ed1 100644
--- a/packages/next/src/fields/string.ts
+++ b/packages/next/src/fields/string.ts
@@ -1,6 +1,6 @@
-import { connect, registerFormField } from '@uform/react'
+import { connect, registerFormField } from '@uform/react-schema-renderer'
import { Input } from '@alifd/next'
-import { acceptEnum, mapStyledProps, mapTextComponent } from '../utils'
+import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared'
registerFormField(
'string',
diff --git a/packages/next/src/fields/table.tsx b/packages/next/src/fields/table.tsx
index d090ce7adc4..131a09bfaa8 100644
--- a/packages/next/src/fields/table.tsx
+++ b/packages/next/src/fields/table.tsx
@@ -1,358 +1,181 @@
-import React, { Component } from 'react'
-import { registerFormField } from '@uform/react'
-import { isFn, toArr } from '@uform/utils'
-import { ArrayField } from './array'
+import React from 'react'
+import { Icon } from '@alifd/next'
+import {
+ registerFormField,
+ ISchemaFieldComponentProps,
+ SchemaField,
+ Schema
+} from '@uform/react-schema-renderer'
+import { toArr, isFn, isArr, FormPath } from '@uform/shared'
+import { ArrayList } from '@uform/react-shared-components'
+import { CircleButton, TextButton } from '../components/Button'
+import { Table, Form } from '@alifd/next'
import styled from 'styled-components'
-
-/**
- * 轻量级Table,用next table实在太重了
- **/
-
-export interface IColumnProps {
- title?: string
- dataIndex?: string
- width?: string | number
- cell: (item?: any, index?: number) => React.ReactElement
+import { FormItemProps } from '../compat/FormItem'
+const ArrayComponents = {
+ CircleButton,
+ TextButton,
+ AdditionIcon: () => ,
+ RemoveIcon: () => (
+
+ ),
+ MoveDownIcon: () => (
+
+ ),
+ MoveUpIcon: () => (
+
+ )
}
-export interface ITableProps {
- className?: string
- dataSource: any[]
-}
-
-class Column extends Component {
- static displayName = '@schema-table-column'
- render() {
- return this.props.children
- }
-}
-
-const Table = styled(
- class Table extends Component {
- renderCell({ record, col, rowIndex }) {
- return (
-
- {isFn(col.cell)
- ? col.cell(
- record ? record[col.dataIndex] : undefined,
- rowIndex,
- record
+const FormTableField = styled(
+ (props: ISchemaFieldComponentProps & { className: string }) => {
+ const { value, schema, className, editable, path, mutators } = props
+ const {
+ renderAddition,
+ renderRemove,
+ renderMoveDown,
+ renderMoveUp,
+ renderEmpty,
+ renderExtraOperations,
+ operations,
+ ...componentProps
+ } = schema.getExtendsComponentProps() || {}
+ const onAdd = () => {
+ const items = Array.isArray(schema.items)
+ ? schema.items[schema.items.length - 1]
+ : schema.items
+ mutators.push(items.getEmptyValue())
+ }
+ const renderColumns = (items: Schema) => {
+ return items.mapProperties((props, key) => {
+ const itemProps = {
+ ...props.getExtendsItemProps(),
+ ...props.getExtendsProps()
+ }
+ return (
+
{
+ return (
+
+
+
)
- : record
- ? record[col.dataIndex]
- : undefined}
-
- )
+ }}
+ />
+ )
+ })
+ return []
}
-
- renderTable(columns, dataSource) {
- return (
-
-
-
-
- {columns.map((col, index) => {
- return (
-
- {col.title}
- |
- )
- })}
-
-
-
- {dataSource.map((record, rowIndex) => {
+ return (
+
+
+
+ {isArr(schema.items)
+ ? schema.items.reduce((buf, items) => {
+ return buf.concat(renderColumns(items))
+ }, [])
+ : renderColumns(schema.items)}
+ {
return (
-
- {columns.map((col, colIndex) => {
- return (
-
- {this.renderCell({
- record,
- col,
- rowIndex
- })}
- |
- )
- })}
-
+
+
+
mutators.remove(index)}
+ />
+ mutators.moveDown(index)}
+ />
+ mutators.moveUp(index)}
+ />
+ {isFn(renderExtraOperations)
+ ? renderExtraOperations(index)
+ : renderExtraOperations}
+
+
)
- })}
- {this.renderPlacehodler(dataSource, columns)}
-
-
-
- )
- }
-
- renderPlacehodler(dataSource, columns) {
- if (dataSource.length === 0) {
- return (
-
-
-
-
-
- |
-
- )
+ }}
+ />
+
+
+ {({ children }) => {
+ return (
+
+ {children}
+
+ )
+ }}
+
+
+
+ )
+ }
+)`
+ display: inline-block;
+ min-width: 600px;
+ max-width: 100%;
+ overflow: scroll;
+ table {
+ margin-bottom: 0 !important;
+ th,
+ td {
+ padding: 0 !important;
+ vertical-align: top;
+ .next-form-item {
+ margin-bottom: 0 !important;
}
}
-
- getColumns(children) {
- const columns: IColumnProps[] = []
- React.Children.forEach>(
- children,
- child => {
- if (React.isValidElement(child)) {
- if (
- child.type === Column ||
- child.type.displayName === '@schema-table-column'
- ) {
- columns.push(child.props)
- }
- }
- }
- )
-
- return columns
+ }
+ .array-table-addition {
+ padding: 10px;
+ background: #fbfbfb;
+ border-left: 1px solid #dcdee3;
+ border-right: 1px solid #dcdee3;
+ border-bottom: 1px solid #dcdee3;
+ .next-btn-text {
+ color: #888;
}
-
- render() {
- const columns = this.getColumns(this.props.children)
- const dataSource = toArr(this.props.dataSource)
- return (
-
-
-
- {this.renderTable(columns, dataSource)}
-
-
-
- )
+ .next-icon:before {
+ width: 16px !important;
+ font-size: 16px !important;
+ margin-right: 5px;
}
- }
-)`
- .next-table {
- position: relative;
+ margin-bottom: 10px;
}
- .next-table,
- .next-table *,
- .next-table :after,
- .next-table :before {
- -webkit-box-sizing: border-box;
- box-sizing: border-box;
- }
-
- .next-table table {
- border-collapse: collapse;
- border-spacing: 0;
- width: 100%;
- background: #fff;
- display: table !important;
- margin: 0 !important;
- }
-
- .next-table table tr:first-child td {
- border-top-width: 0;
- }
-
- .next-table th {
- padding: 0;
- background: #ebecf0;
- color: #333;
- text-align: left;
- font-weight: 400;
- min-width: 200px;
- border: 1px solid #dcdee3;
- }
-
- .next-table th .next-table-cell-wrapper {
- padding: 12px 16px;
- overflow: hidden;
- text-overflow: ellipsis;
- word-break: break-all;
- }
-
- .next-table td {
- padding: 0;
- border: 1px solid #dcdee3;
- }
-
- .next-table td .next-table-cell-wrapper {
- padding: 12px 16px;
- overflow: hidden;
- text-overflow: ellipsis;
- word-break: break-all;
+ .array-item-operator {
display: flex;
- }
-
- .next-table.zebra tr:nth-child(odd) td {
- background: #fff;
- }
-
- .next-table.zebra tr:nth-child(2n) td {
- background: #f7f8fa;
- }
-
- .next-table-empty {
- color: #a0a2ad;
- padding: 32px 0;
- text-align: center;
- }
-
- .next-table-row {
- -webkit-transition: all 0.3s ease;
- transition: all 0.3s ease;
- background: #fff;
- color: #333;
- border: none !important;
- }
-
- .next-table-row.hidden {
- display: none;
- }
-
- .next-table-row.hovered,
- .next-table-row.selected {
- background: #f2f3f7;
- color: #333;
- }
-
- .next-table-body,
- .next-table-header {
- overflow: auto;
- font-size: 12px;
- }
-
- .next-table-body {
- font-size: 12px;
+ align-items: center;
+ button {
+ margin-right: 8px;
+ }
}
`
-registerFormField(
- 'table',
- styled(
- class extends ArrayField {
- createFilter(key, payload) {
- const { schema } = this.props
- const columnFilter: (key: string, payload: any) => boolean =
- schema['x-props'] && schema['x-props'].columnFilter
-
- return (render, otherwise) => {
- if (isFn(columnFilter)) {
- return columnFilter(key, payload)
- ? isFn(render)
- ? render()
- : render
- : isFn(otherwise)
- ? otherwise()
- : otherwise
- } else {
- return render()
- }
- }
- }
-
- render() {
- const {
- value,
- schema,
- locale,
- className,
- renderField,
- getOrderProperties
- } = this.props
- const cls = this.getProps('className')
- const style = this.getProps('style')
- const operationsWidth = this.getProps('operationsWidth')
- return (
-
-
-
- {getOrderProperties(schema.items).reduce(
- (buf, { key, schema }) => {
- const filter = this.createFilter(key, schema)
- const res = filter(
- () => {
- return buf.concat(
- {
- return renderField([index, key])
- }}
- />
- )
- },
- () => {
- return buf
- }
- )
- return res
- },
- []
- )}
- {
- return (
-
- {this.renderRemove(index, item)}
- {this.renderMoveDown(index, item)}
- {this.renderMoveUp(index)}
- {this.renderExtraOperations(index)}
-
- )
- }}
- />
-
- {this.renderAddition()}
-
-
- )
- }
- }
- )`
- display: inline-block;
- .array-item-addition {
- padding: 10px;
- background: #fbfbfb;
- border-left: 1px solid #dcdee3;
- border-right: 1px solid #dcdee3;
- border-bottom: 1px solid #dcdee3;
- .next-btn-text {
- color: #888;
- }
- .next-icon:before {
- width: 16px !important;
- font-size: 16px !important;
- margin-right: 5px;
- }
- }
-
- .next-table-cell-wrapper > .next-form-item {
- margin-bottom: 0;
- }
- .array-item-operator {
- display: flex;
- }
- `
-)
+registerFormField('table', FormTableField)
diff --git a/packages/next/src/fields/textarea.ts b/packages/next/src/fields/textarea.ts
index c65ce5616f2..ed33de7a648 100644
--- a/packages/next/src/fields/textarea.ts
+++ b/packages/next/src/fields/textarea.ts
@@ -1,6 +1,6 @@
-import { connect, registerFormField } from '@uform/react'
+import { connect, registerFormField } from '@uform/react-schema-renderer'
import { Input } from '@alifd/next'
-import { acceptEnum, mapStyledProps, mapTextComponent } from '../utils'
+import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared'
const { TextArea } = Input
diff --git a/packages/next/src/fields/time.ts b/packages/next/src/fields/time.ts
index 6a046c54a39..dd15c25b647 100644
--- a/packages/next/src/fields/time.ts
+++ b/packages/next/src/fields/time.ts
@@ -1,6 +1,6 @@
-import { connect, registerFormField } from '@uform/react'
+import { connect, registerFormField } from '@uform/react-schema-renderer'
import { TimePicker } from '@alifd/next'
-import { mapStyledProps, mapTextComponent } from '../utils'
+import { mapStyledProps, mapTextComponent } from '../shared'
const transformMoment = (value, format = 'YYYY-MM-DD HH:mm:ss') => {
return value && value.format ? value.format(format) : value
diff --git a/packages/next/src/fields/transfer.ts b/packages/next/src/fields/transfer.ts
index 006f5473cab..b38c0a9842e 100644
--- a/packages/next/src/fields/transfer.ts
+++ b/packages/next/src/fields/transfer.ts
@@ -1,6 +1,6 @@
-import { connect, registerFormField } from '@uform/react'
+import { connect, registerFormField } from '@uform/react-schema-renderer'
import { Transfer } from '@alifd/next'
-import { mapStyledProps } from '../utils'
+import { mapStyledProps } from '../shared'
registerFormField(
'transfer',
diff --git a/packages/next/src/fields/upload.tsx b/packages/next/src/fields/upload.tsx
index 887c816a759..cb9f9905cb9 100644
--- a/packages/next/src/fields/upload.tsx
+++ b/packages/next/src/fields/upload.tsx
@@ -1,6 +1,6 @@
import React from 'react'
-import { connect, registerFormField } from '@uform/react'
-import { toArr, isArr, isEqual, mapStyledProps } from '../utils'
+import { connect, registerFormField } from '@uform/react-schema-renderer'
+import { toArr, isArr, isEqual, mapStyledProps } from '../shared'
import { Button, Upload } from '@alifd/next'
import { UploadProps, CardProps } from '@alifd/next/types/upload'
const { Card: UploadCard, Dragger: UploadDragger } = Upload
diff --git a/packages/next/src/form.tsx b/packages/next/src/form.tsx
deleted file mode 100644
index 50cc06be6dc..00000000000
--- a/packages/next/src/form.tsx
+++ /dev/null
@@ -1,434 +0,0 @@
-import React from 'react'
-import classNames from 'classnames'
-import styled from 'styled-components'
-import { ConfigProvider, Balloon, Icon, Grid } from '@alifd/next'
-import { registerFormWrapper, registerFieldMiddleware } from '@uform/react'
-import { IFormItemProps, IFormProps } from '@uform/types'
-
-import LOCALE from './locale'
-import { isFn, moveTo, isStr, stringLength } from './utils'
-
-/**
- * 轻量级Next Form,不包含任何数据管理能力
- */
-
-const { Row, Col } = Grid
-
-export const {
- Provider: FormLayoutProvider,
- Consumer: FormLayoutConsumer
-} = React.createContext(undefined)
-
-const normalizeCol = col => {
- return typeof col === 'object' ? col : { span: col }
-}
-
-const getParentNode = (node, selector) => {
- if (!node || (node && !node.matches)) return
- if (node.matches(selector)) return node
- else {
- return getParentNode(node.parentNode || node.parentElement, selector)
- }
-}
-
-const isPopDescription = (description, maxTipsNum = 30) => {
- if (isStr(description)) {
- return stringLength(description) > maxTipsNum
- } else {
- return React.isValidElement(description)
- }
-}
-
-export const FormItem = styled(
- class FormItem extends React.Component {
- static defaultProps = {
- prefix: 'next-'
- }
-
- private getItemLabel() {
- const {
- id,
- required,
- label,
- labelCol,
- wrapperCol,
- prefix,
- extra,
- labelAlign,
- labelTextAlign,
- autoAddColon,
- isTableColItem,
- maxTipsNum
- } = this.props
-
- if (!label || isTableColItem) {
- return null
- }
-
- const ele = (
- // @ts-ignore
-
- )
-
- const cls = classNames({
- [`${prefix}form-item-label`]: true,
- [`${prefix}left`]: labelTextAlign === 'left'
- })
-
- if ((wrapperCol || labelCol) && labelAlign !== 'top') {
- return (
-
- {ele}
- {isPopDescription(extra, maxTipsNum) && this.renderHelper()}
-
- )
- }
-
- return (
-
- {ele}
- {isPopDescription(extra, maxTipsNum) && this.renderHelper()}
-
- )
- }
-
- private getItemWrapper() {
- const {
- labelCol,
- wrapperCol,
- children,
- extra,
- label,
- labelAlign,
- help,
- size,
- prefix,
- noMinHeight,
- isTableColItem,
- maxTipsNum
- } = this.props
-
- const message = (
-
- {help &&
{help}
}
- {!help && !isPopDescription(extra, maxTipsNum) && (
-
{extra}
- )}
-
- )
- if (
- (wrapperCol || labelCol) &&
- labelAlign !== 'top' &&
- !isTableColItem &&
- label
- ) {
- return (
-
- {React.cloneElement(children, { size })}
- {message}
-
- )
- }
-
- return (
-
- {React.cloneElement(children, { size })}
- {message}
-
- )
- }
-
- private renderHelper() {
- return (
- }
- >
- {this.props.extra}
-
- )
- }
-
- public render() {
- /* eslint-disable @typescript-eslint/no-unused-vars */
- const {
- className,
- labelAlign,
- labelTextAlign,
- style,
- prefix,
- wrapperCol,
- labelCol,
- size,
- help,
- extra,
- noMinHeight,
- isTableColItem,
- validateState,
- autoAddColon,
- required,
- maxTipsNum,
- type,
- schema,
- ...others
- } = this.props
-
- /* eslint-enable @typescript-eslint/no-unused-vars */
- const itemClassName = classNames({
- [`${prefix}form-item`]: true,
- [`${prefix}${labelAlign}`]: labelAlign,
- [`has-${validateState}`]: !!validateState,
- [`${prefix}${size}`]: !!size,
- [`${className}`]: !!className,
- [`field-${type}`]: !!type
- })
-
- // 垂直模式并且左对齐才用到
- const Tag = (wrapperCol || labelCol) && labelAlign !== 'top' ? Row : 'div'
- const label = labelAlign === 'inset' ? null : this.getItemLabel()
-
- return (
-
- {label}
- {this.getItemWrapper()}
-
- )
- }
- }
-)`
- margin-bottom: 4px !important;
- &.field-table {
- .next-form-item-control {
- overflow: auto;
- }
- }
- .next-form-item-msg {
- &.next-form-item-space {
- min-height: 20px;
- .next-form-item-help,
- .next-form-item-extra {
- margin-top: 0;
- }
- }
- }
- .next-form-item-extra {
- color: #888;
- font-size: 12px;
- line-height: 1.7;
- }
-`
-
-const toArr = val => (Array.isArray(val) ? val : val ? [val] : [])
-
-registerFormWrapper(OriginForm => {
- OriginForm = styled(OriginForm)`
- &.next-inline {
- display: flex;
- .rs-uform-content {
- margin-right: 15px;
- }
- }
- .next-radio-group,
- .next-checkbox-group {
- line-height: 28px;
- & > label {
- margin-right: 8px;
- }
- }
- .next-small {
- .next-radio-group,
- .next-checkbox-group {
- line-height: 20px;
- }
- }
- .next-small {
- .next-radio-group,
- .next-checkbox-group {
- line-height: 40px;
- }
- }
- .next-card-head {
- background: none;
- }
- .next-rating-medium {
- min-height: 28px;
- line-height: 28px;
- }
- .next-rating-small {
- min-height: 20px;
- line-height: 20px;
- }
- .next-rating-large {
- min-height: 40px;
- line-height: 40px;
- }
- `
-
- return ConfigProvider.config(
- class Form extends React.Component {
- static defaultProps = {
- component: 'form',
- prefix: 'next-',
- size: 'medium',
- labelAlign: 'left',
- locale: LOCALE,
- autoAddColon: true
- }
-
- static displayName = 'SchemaForm'
-
- FormRef = React.createRef()
-
- validateFailedHandler(onValidateFailed) {
- return (...args) => {
- if (isFn(onValidateFailed)) {
- onValidateFailed(...args)
- }
- const container = this.FormRef.current as HTMLElement
- if (container) {
- const errors = container.querySelectorAll('.next-form-item-help')
- if (errors && errors.length) {
- const node = getParentNode(errors[0], '.next-form-item')
- if (node) {
- moveTo(node)
- }
- }
- }
- }
- }
-
- render() {
- const {
- className,
- inline,
- size,
- labelAlign,
- labelTextAlign,
- autoAddColon,
- children,
- labelCol,
- wrapperCol,
- style,
- prefix,
- maxTipsNum,
- ...others
- } = this.props
-
- const formClassName = classNames({
- [`${prefix}form`]: true,
- [`${prefix}inline`]: inline, // 内联
- [`${prefix}${size}`]: size,
- [className]: !!className
- })
- return (
-
-
- {children}
-
-
- )
- }
- },
- {}
- )
-})
-
-const isTableColItem = (path, getSchema) => {
- const schema = getSchema(path)
- return schema && schema.type === 'array' && schema['x-component'] === 'table'
-}
-
-registerFieldMiddleware(Field => {
- return props => {
- const {
- name,
- editable,
- errors,
- path,
- schemaPath,
- schema,
- getSchema,
- required
- } = props
-
- if (path.length === 0) {
- // 根节点是不需要包FormItem的
- return React.createElement(Field, props)
- }
-
- return React.createElement(
- FormLayoutConsumer,
- {},
- ({
- labelAlign,
- labelTextAlign,
- labelCol,
- wrapperCol,
- maxTipsNum,
- size,
- autoAddColon
- }) => {
- return React.createElement(
- FormItem,
- {
- labelAlign,
- labelTextAlign,
- labelCol,
- wrapperCol,
- autoAddColon,
- maxTipsNum,
- size,
- ...schema['x-item-props'],
- label: schema.title,
- noMinHeight: schema.type === 'object' && !schema['x-component'],
- isTableColItem: isTableColItem(
- schemaPath.slice(0, schemaPath.length - 2),
- getSchema
- ),
- type: schema['x-component'] || schema['type'],
- id: name,
- validateState: toArr(errors).length ? 'error' : undefined,
- required: editable === false ? false : required,
- extra: schema.description,
- help:
- toArr(errors).join(' , ') ||
- (schema['x-item-props'] && schema['x-item-props'].help)
- },
- React.createElement(Field, props)
- )
- }
- )
- }
-})
diff --git a/packages/next/src/index.tsx b/packages/next/src/index.tsx
index b47f2e0a123..85d9569b902 100644
--- a/packages/next/src/index.tsx
+++ b/packages/next/src/index.tsx
@@ -1,45 +1,17 @@
-import './form'
-import './fields/string'
-import './fields/number'
-import './fields/boolean'
-import './fields/date'
-import './fields/time'
-import './fields/range'
-import './fields/upload'
-import './fields/checkbox'
-import './fields/radio'
-import './fields/rating'
-import './fields/transfer'
-import './fields/array'
-import './fields/cards'
-import './fields/table'
-import './fields/textarea'
-import './fields/password'
-
-export * from '@uform/react'
-export * from './components/formButtonGroup'
-export * from './components/button'
-export * from './components/layout'
-
import React from 'react'
import {
- SchemaForm as InternalSchemaForm,
- Field as InternalField
-} from '@uform/react'
-import { SchemaFormProps, FieldProps } from './type'
-
-export { mapStyledProps, mapTextComponent } from './utils'
-
-export default class SchemaForm extends React.Component> {
- render() {
- return
- }
-}
-
-export class Field extends React.Component<
- FieldProps
-> {
- render() {
- return
- }
-}
+ SchemaMarkupForm,
+ SchemaMarkupField
+} from '@uform/react-schema-renderer'
+import { INextSchemaFormProps, INextSchemaFieldProps } from './types'
+import './fields'
+import './compat'
+export * from '@uform/react-schema-renderer'
+export * from './components'
+export * from './types'
+export { mapStyledProps, mapTextComponent } from './shared'
+export const SchemaForm: React.FC<
+ INextSchemaFormProps
+> = SchemaMarkupForm as any
+export const Field: React.FC = SchemaMarkupField
+export default SchemaForm
diff --git a/packages/next/src/locale.ts b/packages/next/src/locale.ts
deleted file mode 100644
index aa28ff56c6e..00000000000
--- a/packages/next/src/locale.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-/* eslint-disable @typescript-eslint/camelcase */
-export default {
- addItem: '添加',
- array_invalid_minItems: '条目数不允许小于%s条',
- array_invalid_maxItems: '条目数不允许大于%s条',
- operations: '操作'
-}
diff --git a/packages/next/src/shared.ts b/packages/next/src/shared.ts
new file mode 100644
index 00000000000..5d7761ae55a
--- /dev/null
+++ b/packages/next/src/shared.ts
@@ -0,0 +1,66 @@
+import React from 'react'
+import { Select } from '@alifd/next'
+import { PreviewText } from '@uform/react-shared-components'
+import {
+ MergedFieldComponentProps,
+ IConnectProps
+} from '@uform/react-schema-renderer'
+export * from '@uform/shared'
+
+export const mapTextComponent = (
+ Target: React.JSXElementConstructor,
+ props: any = {},
+ fieldProps: any = {}
+): React.JSXElementConstructor => {
+ const { editable } = fieldProps
+ if (editable !== undefined) {
+ if (editable === false) {
+ return PreviewText
+ }
+ }
+ if (Array.isArray(props.dataSource)) {
+ return Select
+ }
+ return Target
+}
+
+export const acceptEnum = (component: React.JSXElementConstructor) => {
+ return ({ dataSource, ...others }) => {
+ if (dataSource) {
+ return React.createElement(Select, { dataSource, ...others })
+ } else {
+ return React.createElement(component, others)
+ }
+ }
+}
+
+export const normalizeCol = (
+ col: { span: number; offset?: number } | number,
+ defaultValue: { span: number }
+): { span: number; offset?: number } => {
+ if (!col) {
+ return defaultValue
+ } else {
+ return typeof col === 'object' ? col : { span: Number(col) }
+ }
+}
+
+export const mapStyledProps = (
+ props: IConnectProps,
+ fieldProps: MergedFieldComponentProps
+) => {
+ const { loading, errors } = fieldProps
+ if (loading) {
+ props.state = props.state || 'loading'
+ } else if (errors && errors.length) {
+ props.state = 'error'
+ }
+}
+
+export const compose = (...args: any[]) => {
+ return (payload: any, ...extra: any[]) => {
+ return args.reduce((buf, fn) => {
+ return buf !== undefined ? fn(buf, ...extra) : fn(payload, ...extra)
+ }, payload)
+ }
+}
diff --git a/packages/next/src/type.tsx b/packages/next/src/type.tsx
deleted file mode 100644
index 24bae70f004..00000000000
--- a/packages/next/src/type.tsx
+++ /dev/null
@@ -1,160 +0,0 @@
-import { ButtonProps } from '@alifd/next/types/button'
-import { CardProps } from '@alifd/next/types/card'
-import { RowProps, ColProps } from '@alifd/next/types/grid'
-import {
- IFormActions,
- ISchema,
- IEffects,
- IFieldError,
- Size,
- TextAlign,
- Layout,
- TextEl,
- LabelAlign
-} from '@uform/types'
-import { SwitchProps } from '@alifd/next/types/switch'
-import { GroupProps as CheckboxGroupProps } from '@alifd/next/types/checkbox'
-import { GroupProps as RadioGroupProps } from '@alifd/next/types/radio'
-import {
- DatePickerProps,
- RangePickerProps,
- MonthPickerProps,
- YearPickerProps
-} from '@alifd/next/types/date-picker'
-import { NumberPickerProps } from '@alifd/next/types/number-picker'
-import { IPasswordProps } from './fields/password'
-import { RangeProps } from '@alifd/next/types/range'
-import { RatingProps } from '@alifd/next/types/rating'
-import { InputProps, TextAreaProps } from '@alifd/next/types/input'
-import { TimePickerProps } from '@alifd/next/types/time-picker'
-import { TransferProps } from '@alifd/next/types/transfer'
-import { IUploaderProps } from './fields/upload'
-import { SelectProps } from '@alifd/next/types/select'
-
-type ColSpanType = number | string
-
-export interface ColSize {
- span?: ColSpanType
- offset?: ColSpanType
-}
-
-export interface ILocaleMessages {
- [key: string]: string | ILocaleMessages
-}
-
-export interface IFormLayoutProps {
- className?: string
- inline?: boolean
- labelAlign?: LabelAlign
- wrapperCol?: IColProps | number
- labelCol?: IColProps | number
- labelTextAlign?: TextAlign
- size?: Size
- style?: React.CSSProperties
-}
-
-export interface IFormItemGridProps {
- cols?: Array
- description?: TextEl
- gutter?: number
- title?: TextEl
-}
-
-export type TFormCardOrFormBlockProps = Omit
-
-export interface IFormTextBox {
- text?: string
- title?: TextEl
- description?: TextEl
- gutter?: number
-}
-
-export interface IRowProps extends RowProps {
- prefix?: string
- pure?: boolean
- className?: string
- style?: object
-}
-
-export interface IColProps extends ColProps {
- prefix?: string
- pure?: boolean
- className?: string
-}
-
-export interface IFormCardProps extends CardProps {
- className?: string
-}
-
-export interface IFormBlockProps extends CardProps {
- className?: string
-}
-
-export interface ISubmitProps extends Omit {
- showLoading?: boolean
-}
-
-export interface IFormButtonGroupProps {
- sticky?: boolean
- style?: React.CSSProperties
- itemStyle?: React.CSSProperties
- className?: string
- align?: 'left' | 'right' | 'start' | 'end' | 'top' | 'bottom' | 'center'
- triggerDistance?: number
- zIndex?: number
- span?: ColSpanType
- offset?: ColSpanType
-}
-
-export interface SchemaFormProps {
- actions?: IFormActions
- initialValues?: V
- defaultValue?: V
- value?: V
- editable?: boolean | ((name: string) => boolean)
- effects?: IEffects
- locale?: ILocaleMessages
- schema?: ISchema
- onChange?: (values: V) => void
- onReset?: (values: V) => void
- onSubmit?: (values: V) => void
- onValidateFailed?: (fieldErrors: IFieldError[]) => void
- autoAddColon?: boolean
- className?: string
- inline?: boolean
- layout?: Layout
- maxTipsNum?: number
- labelAlign?: LabelAlign
- labelTextAlign?: TextAlign
- labelCol?: ColSize | number
- wrapperCol?: ColSize | number
- size?: Size
- style?: React.CSSProperties
- prefix?: string
-}
-
-interface InternalFieldTypes {
- boolean: SwitchProps | SelectProps
- checkbox: CheckboxGroupProps
- date: DatePickerProps
- daterange: RangePickerProps
- month: MonthPickerProps
- // week: WeekPickerProps
- year: YearPickerProps
- number: NumberPickerProps | SelectProps
- password: IPasswordProps
- radio: RadioGroupProps
- range: RangeProps
- rating: RatingProps
- string: InputProps | SelectProps
- textarea: TextAreaProps | SelectProps
- time: TimePickerProps
- transfer: TransferProps
- upload: IUploaderProps
-}
-export interface FieldProps extends ISchema {
- type?: T
- name?: string
- editable?: boolean
- ['x-props']?: T extends keyof InternalFieldTypes ? InternalFieldTypes[T] : any
-}
diff --git a/packages/next/src/types.ts b/packages/next/src/types.ts
new file mode 100644
index 00000000000..b93217d9e24
--- /dev/null
+++ b/packages/next/src/types.ts
@@ -0,0 +1,95 @@
+import { ButtonProps } from '@alifd/next/types/button'
+import { FormProps, ItemProps } from '@alifd/next/types/form'
+import { StepProps, ItemProps as StepItemProps } from '@alifd/next/types/step'
+import {
+ ISchemaFormProps,
+ IMarkupSchemaFieldProps,
+ ISchemaFieldComponentProps
+} from '@uform/react-schema-renderer'
+import { StyledComponent } from 'styled-components'
+
+type ColSpanType = number | string
+
+export type INextSchemaFormProps = ISchemaFormProps &
+ FormProps &
+ IFormItemTopProps
+
+export type INextSchemaFieldProps = IMarkupSchemaFieldProps
+
+export interface ISubmitProps extends ButtonProps {
+ onSubmit?: ISchemaFormProps['onSubmit']
+ showLoading?: boolean
+}
+
+export interface IResetProps extends ButtonProps {
+ forceClear?: boolean
+ validate?: boolean
+}
+
+export type IFormItemTopProps = React.PropsWithChildren<
+ Exclude<
+ Pick<
+ ItemProps,
+ | 'prefix'
+ | 'labelCol'
+ | 'wrapperCol'
+ | 'labelAlign'
+ | 'labelTextAlign'
+ | 'size'
+ >,
+ 'labelCol' | 'wrapperCol'
+ > & {
+ inline?: boolean
+ className?: string
+ style?: React.CSSProperties
+ labelCol?: number | { span: number; offset?: number }
+ wrapperCol?: number | { span: number; offset?: number }
+ }
+>
+
+export interface ICompatItemProps
+ extends Exclude,
+ Partial {
+ labelCol?: number | { span: number; offset?: number }
+ wrapperCol?: number | { span: number; offset?: number }
+}
+
+export type StyledCP = StyledComponent<
+ (props: React.PropsWithChildren
) => React.ReactElement,
+ any,
+ {},
+ never
+>
+
+export type StyledCC = StyledCP & Statics
+
+export interface IFormButtonGroupProps {
+ sticky?: boolean
+ style?: React.CSSProperties
+ itemStyle?: React.CSSProperties
+ className?: string
+ align?: 'left' | 'right' | 'start' | 'end' | 'top' | 'bottom' | 'center'
+ triggerDistance?: number
+ zIndex?: number
+ span?: ColSpanType
+ offset?: ColSpanType
+}
+
+export interface IItemProps {
+ title?: React.ReactText
+ description?: React.ReactText
+}
+
+export interface IFormItemGridProps extends IItemProps {
+ cols?: Array
+ gutter?: number
+}
+
+export interface IFormTextBox extends IItemProps {
+ text?: string
+ gutter?: number
+}
+
+export interface IFormStep extends StepProps {
+ dataSource: StepItemProps[]
+}
diff --git a/packages/next/src/utils.tsx b/packages/next/src/utils.tsx
deleted file mode 100644
index 1da41174248..00000000000
--- a/packages/next/src/utils.tsx
+++ /dev/null
@@ -1,113 +0,0 @@
-import React from 'react'
-import { Select } from '@alifd/next'
-import styled from 'styled-components'
-import { isFn } from '@uform/utils'
-import { IConnectProps, IFieldProps } from '@uform/react'
-
-export * from '@uform/utils'
-
-const MoveTo = typeof window !== 'undefined' ? require('moveto') : null
-const Text = styled(props => {
- let value
- if (props.dataSource && props.dataSource.length) {
- let find = props.dataSource.filter(({ value }) =>
- Array.isArray(props.value)
- ? props.value.some(val => val == value)
- : props.value == value
- )
- value = find.map(item => item.label).join(' , ')
- } else {
- value = Array.isArray(props.value)
- ? props.value.join(' ~ ')
- : String(
- props.value === undefined || props.value === null ? '' : props.value
- )
- }
- return (
-
- {!value ? 'N/A' : value}
- {props.innerAfter ? ' ' + props.innerAfter : ''}
- {props.addonTextAfter ? ' ' + props.addonTextAfter : ''}
- {props.addonAfter ? ' ' + props.addonAfter : ''}
-
- )
-})`
- height: 28px;
- line-height: 28px;
- vertical-align: middle;
- font-size: 13px;
- color: #333;
- &.small {
- height: 20px;
- line-height: 20px;
- }
- &.large {
- height: 40px;
- line-height: 40px;
- }
-`
-
-export const acceptEnum = component => {
- return ({ dataSource, ...others }) => {
- if (dataSource) {
- return React.createElement(Select, { dataSource, ...others })
- } else {
- return React.createElement(component, others)
- }
- }
-}
-
-export const mapStyledProps = (
- props: IConnectProps,
- { loading, size, errors }: IFieldProps
-) => {
- if (loading) {
- props.state = props.state || 'loading'
- } else if (errors && errors.length) {
- props.state = 'error'
- }
- if (size) {
- props.size = size
- }
-}
-
-export const mapTextComponent = (
- Target: React.ComponentClass,
- props,
- {
- editable,
- name
- }: { editable: boolean | ((name: string) => boolean); name: string }
-): React.ComponentClass => {
- if (editable !== undefined) {
- if (isFn(editable)) {
- if (!editable(name)) {
- return Text
- }
- } else if (editable === false) {
- return Text
- }
- }
- return Target
-}
-
-export const compose = (...args) => {
- return (payload, ...extra) => {
- return args.reduce((buf, fn) => {
- return buf !== undefined ? fn(buf, ...extra) : fn(payload, ...extra)
- }, payload)
- }
-}
-
-export const moveTo = element => {
- if (!element || !MoveTo) return
- if (element.scrollIntoView) {
- element.scrollIntoView({
- behavior: 'smooth',
- inline: 'start',
- block: 'start'
- })
- } else {
- new MoveTo().move(element.getBoundingClientRect().top)
- }
-}
diff --git a/packages/next/tsconfig.json b/packages/next/tsconfig.json
index 1d669c29c46..7d101da7c66 100644
--- a/packages/next/tsconfig.json
+++ b/packages/next/tsconfig.json
@@ -3,6 +3,6 @@
"compilerOptions": {
"outDir": "./lib"
},
- "include": ["./src/**/*.ts", "./src/**/*.tsx"],
+ "include": ["./src/**/*.js", "./src/**/*.ts", "./src/**/*.tsx"],
"exclude": ["./src/__tests__/*"]
}
diff --git a/packages/printer/package.json b/packages/printer/package.json
index 33135913ec6..920ca202bed 100644
--- a/packages/printer/package.json
+++ b/packages/printer/package.json
@@ -1,6 +1,6 @@
{
"name": "@uform/printer",
- "version": "0.4.3",
+ "version": "0.4.0",
"license": "MIT",
"main": "lib",
"repository": {
@@ -26,7 +26,7 @@
"typescript": "^3.5.2"
},
"dependencies": {
- "@uform/react": "^0.4.3",
+ "@uform/react-schema-renderer": "^0.4.0",
"react-modal": "^3.8.1",
"styled-components": "^4.1.1"
},
diff --git a/packages/printer/src/index.js b/packages/printer/src/index.js
index 577a44ab55d..a0b9a71ce6a 100644
--- a/packages/printer/src/index.js
+++ b/packages/printer/src/index.js
@@ -1,6 +1,6 @@
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
-import { createFormActions } from '@uform/react'
+import { createFormActions } from '@uform/react-schema-renderer'
import styled from 'styled-components'
import Modal from 'react-modal'
diff --git a/packages/builder-next/.npmignore b/packages/react-schema-renderer/.npmignore
similarity index 100%
rename from packages/builder-next/.npmignore
rename to packages/react-schema-renderer/.npmignore
diff --git a/packages/react/LESENCE.md b/packages/react-schema-renderer/LESENCE.md
similarity index 100%
rename from packages/react/LESENCE.md
rename to packages/react-schema-renderer/LESENCE.md
diff --git a/packages/react-schema-renderer/README.md b/packages/react-schema-renderer/README.md
new file mode 100644
index 00000000000..0f89496d1c6
--- /dev/null
+++ b/packages/react-schema-renderer/README.md
@@ -0,0 +1,2 @@
+# @uform/react-schema-renderer
+> UForm React实现
\ No newline at end of file
diff --git a/packages/react/jest.config.js b/packages/react-schema-renderer/jest.config.js
similarity index 100%
rename from packages/react/jest.config.js
rename to packages/react-schema-renderer/jest.config.js
diff --git a/packages/builder-next/package.json b/packages/react-schema-renderer/package.json
similarity index 59%
rename from packages/builder-next/package.json
rename to packages/react-schema-renderer/package.json
index a3e15207104..d14d82987e5 100644
--- a/packages/builder-next/package.json
+++ b/packages/react-schema-renderer/package.json
@@ -1,8 +1,8 @@
{
- "name": "@uform/builder-next",
- "version": "0.4.3",
+ "name": "@uform/react-schema-renderer",
+ "version": "0.4.0",
"license": "MIT",
- "main": "lib/index.js",
+ "main": "lib",
"repository": {
"type": "git",
"url": "git+https://github.com/alibaba/uform.git"
@@ -14,23 +14,26 @@
"engines": {
"npm": ">=3.0.0"
},
+ "types": "lib/index.d.ts",
"scripts": {
- "build": "tsc"
+ "build": "tsc --declaration"
},
- "resolutions": {
- "@types/react": "16.8.23"
+ "devDependencies": {
+ "typescript": "^3.5.2"
},
"peerDependencies": {
- "@alifd/next": "^1.13.1",
"@babel/runtime": "^7.4.4",
+ "@types/json-schema": "^7.0.3",
+ "@types/react": "^16.8.23",
"react": ">=16.8.0",
- "react-dom": ">=16.8.0"
+ "react-dom": ">=16.8.0",
+ "react-eva": "^1.1.7"
},
"dependencies": {
- "@uform/builder": "^0.4.3"
- },
- "devDependencies": {
- "typescript": "^3.5.2"
+ "@uform/react": "^0.4.0",
+ "@uform/shared": "^0.4.0",
+ "@uform/validator": "^0.4.0",
+ "pascal-case": "^2.0.1"
},
"publishConfig": {
"access": "public"
diff --git a/packages/react/src/__tests__/actions.spec.js b/packages/react-schema-renderer/src/__old_tests__/actions.spec.js
similarity index 100%
rename from packages/react/src/__tests__/actions.spec.js
rename to packages/react-schema-renderer/src/__old_tests__/actions.spec.js
diff --git a/packages/react/src/__tests__/context.spec.js b/packages/react-schema-renderer/src/__old_tests__/context.spec.js
similarity index 100%
rename from packages/react/src/__tests__/context.spec.js
rename to packages/react-schema-renderer/src/__old_tests__/context.spec.js
diff --git a/packages/react/src/__tests__/destruct.spec.js b/packages/react-schema-renderer/src/__old_tests__/destruct.spec.js
similarity index 97%
rename from packages/react/src/__tests__/destruct.spec.js
rename to packages/react-schema-renderer/src/__old_tests__/destruct.spec.js
index 37a3ec6726f..871b83bef73 100644
--- a/packages/react/src/__tests__/destruct.spec.js
+++ b/packages/react-schema-renderer/src/__old_tests__/destruct.spec.js
@@ -1,6 +1,6 @@
import React, { Fragment } from 'react'
import SchemaForm, { Field, registerFormField, connect } from '../index'
-import { toArr } from '@uform/utils'
+import { toArr } from '@uform/shared'
import { render } from '@testing-library/react'
registerFormField('string', connect()(props => {props.value}
))
diff --git a/packages/react/src/__tests__/display.spec.js b/packages/react-schema-renderer/src/__old_tests__/display.spec.js
similarity index 100%
rename from packages/react/src/__tests__/display.spec.js
rename to packages/react-schema-renderer/src/__old_tests__/display.spec.js
diff --git a/packages/react/src/__tests__/dynamic.spec.js b/packages/react-schema-renderer/src/__old_tests__/dynamic.spec.js
similarity index 99%
rename from packages/react/src/__tests__/dynamic.spec.js
rename to packages/react-schema-renderer/src/__old_tests__/dynamic.spec.js
index 9379aa7d1bb..00b98d15129 100644
--- a/packages/react/src/__tests__/dynamic.spec.js
+++ b/packages/react-schema-renderer/src/__old_tests__/dynamic.spec.js
@@ -7,7 +7,7 @@ import SchemaForm, {
createFormActions,
createVirtualBox
} from '../index'
-import { toArr } from '@uform/utils'
+import { toArr } from '@uform/shared'
import { render, fireEvent, act } from '@testing-library/react'
let FormCard
diff --git a/packages/react/src/__tests__/editable.spec.js b/packages/react-schema-renderer/src/__old_tests__/editable.spec.js
similarity index 89%
rename from packages/react/src/__tests__/editable.spec.js
rename to packages/react-schema-renderer/src/__old_tests__/editable.spec.js
index 70c8c71033b..6b29fa53977 100644
--- a/packages/react/src/__tests__/editable.spec.js
+++ b/packages/react-schema-renderer/src/__old_tests__/editable.spec.js
@@ -8,7 +8,7 @@ import SchemaForm, {
FormPath
} from '../index'
import { render, act, fireEvent } from '@testing-library/react'
-import { toArr } from '@uform/utils'
+import { toArr } from '@uform/shared'
registerFieldMiddleware(Field => {
return props => {
@@ -375,53 +375,3 @@ test('editable conflicts that x-props editable props with setFieldState', async
await sleep(33)
expect(queryByTestId('this is bbb')).toBeVisible()
})
-
-test('fix:#300', async () => {
- const beforeValue = {
- name: 'completeOrderParam',
- protocol: 'dubbo',
- rpcGroup: 'HSF',
- rpcMethod: 'completeCommodity',
- rpcService: 'com.aliyun.lx.spi.babeldemo.TradeService:1.0.1',
- status: 'pre'
- }
-
- const afterValue = {
- name: 'completeCommodity',
- protocol: 'pop',
- rpcGroup: 'popGroup',
- rpcMethod: 'popMethod',
- rpcService: 'popService',
- status: 'product'
- }
-
- const TestComponent = () => {
- const [values, setValues] = useState({})
- function getValue() {
- setValues(afterValue)
- }
- return (
-
-
- GetValue
-
- )
- }
-
- const { queryByText } = render()
- await sleep(33)
- fireEvent.click(queryByText('GetValue'))
- await sleep(33)
- expect(queryByText('empty')).toBeVisible()
-})
diff --git a/packages/react/src/__tests__/effects.spec.js b/packages/react-schema-renderer/src/__old_tests__/effects.spec.js
similarity index 100%
rename from packages/react/src/__tests__/effects.spec.js
rename to packages/react-schema-renderer/src/__old_tests__/effects.spec.js
diff --git a/packages/react/src/__tests__/mutators.spec.js b/packages/react-schema-renderer/src/__old_tests__/mutators.spec.js
similarity index 100%
rename from packages/react/src/__tests__/mutators.spec.js
rename to packages/react-schema-renderer/src/__old_tests__/mutators.spec.js
diff --git a/packages/react/src/__tests__/schema_form.spec.js b/packages/react-schema-renderer/src/__old_tests__/schema_form.spec.js
similarity index 100%
rename from packages/react/src/__tests__/schema_form.spec.js
rename to packages/react-schema-renderer/src/__old_tests__/schema_form.spec.js
diff --git a/packages/react/src/__tests__/traverse.spec.js b/packages/react-schema-renderer/src/__old_tests__/traverse.spec.js
similarity index 100%
rename from packages/react/src/__tests__/traverse.spec.js
rename to packages/react-schema-renderer/src/__old_tests__/traverse.spec.js
diff --git a/packages/react/src/__tests__/utils.spec.js b/packages/react-schema-renderer/src/__old_tests__/utils.spec.js
similarity index 100%
rename from packages/react/src/__tests__/utils.spec.js
rename to packages/react-schema-renderer/src/__old_tests__/utils.spec.js
diff --git a/packages/react/src/__tests__/validate.spec.js b/packages/react-schema-renderer/src/__old_tests__/validate.spec.js
similarity index 95%
rename from packages/react/src/__tests__/validate.spec.js
rename to packages/react-schema-renderer/src/__old_tests__/validate.spec.js
index 2bd27fcbe7d..b5b57119123 100644
--- a/packages/react/src/__tests__/validate.spec.js
+++ b/packages/react-schema-renderer/src/__old_tests__/validate.spec.js
@@ -437,26 +437,3 @@ test('async validate side effect', async () => {
expect(queryByText('aa is required')).toBeVisible()
expect(queryByText('bb is required')).toBeNull()
})
-
-test('async validate side effect', async () => {
- const actions = createFormActions()
- const TestComponent = () => {
- return (
-
-
-
- Submit
-
- )
- }
- const { queryByText } = render()
- await sleep(33)
-
- fireEvent.click(queryByText('Submit'))
- await sleep(33)
- actions.setFieldState(FormPath.match('*'), state => {
- state.editable = false
- })
- await sleep(33)
- expect(queryByText('aa is required')).toBeNull()
-})
diff --git a/packages/react/src/__tests__/validate_relations.spec.js b/packages/react-schema-renderer/src/__old_tests__/validate_relations.spec.js
similarity index 100%
rename from packages/react/src/__tests__/validate_relations.spec.js
rename to packages/react-schema-renderer/src/__old_tests__/validate_relations.spec.js
diff --git a/packages/react/src/__tests__/value.spec.js b/packages/react-schema-renderer/src/__old_tests__/value.spec.js
similarity index 97%
rename from packages/react/src/__tests__/value.spec.js
rename to packages/react-schema-renderer/src/__old_tests__/value.spec.js
index 84ee7b50919..d3ef1224b45 100644
--- a/packages/react/src/__tests__/value.spec.js
+++ b/packages/react-schema-renderer/src/__old_tests__/value.spec.js
@@ -147,9 +147,9 @@ test('controlled with hooks by initalValues', async () => {
await actions.reset()
await sleep(33)
expect(queryByTestId('test-input').value).toEqual('123')
- expect(queryByTestId('outer-result').textContent).toEqual('Total is:123')
- expect(queryByTestId('inner-result').textContent).toEqual('Total is:123')
- expect(onChangeHandler).toHaveBeenCalledTimes(4)
+ expect(queryByTestId('outer-result').textContent).toEqual('Total is:456')
+ expect(queryByTestId('inner-result').textContent).toEqual('Total is:456')
+ expect(onChangeHandler).toHaveBeenCalledTimes(3)
})
test('controlled with hooks by static value', async () => {
@@ -192,9 +192,9 @@ test('controlled with hooks by static value', async () => {
expect(onChangeHandler).toHaveBeenCalledTimes(3)
actions.reset()
await sleep(33)
- expect(queryByTestId('outer-result').textContent).toEqual('Total is:')
- expect(queryByTestId('inner-result').textContent).toEqual('Total is:')
- expect(onChangeHandler).toHaveBeenCalledTimes(4)
+ expect(queryByTestId('outer-result').textContent).toEqual('Total is:123')
+ expect(queryByTestId('inner-result').textContent).toEqual('Total is:123')
+ expect(onChangeHandler).toHaveBeenCalledTimes(3)
await actions.setFieldState('a3', state => {
state.value = '456'
})
@@ -202,13 +202,13 @@ test('controlled with hooks by static value', async () => {
expect(queryByTestId('test-input').value).toEqual('123')
expect(queryByTestId('outer-result').textContent).toEqual('Total is:123')
expect(queryByTestId('inner-result').textContent).toEqual('Total is:123')
- expect(onChangeHandler).toHaveBeenCalledTimes(6)
+ expect(onChangeHandler).toHaveBeenCalledTimes(5)
await actions.reset()
await sleep(33)
expect(queryByTestId('test-input').value).toEqual('')
- expect(queryByTestId('outer-result').textContent).toEqual('Total is:')
- expect(queryByTestId('inner-result').textContent).toEqual('Total is:')
- expect(onChangeHandler).toHaveBeenCalledTimes(7)
+ expect(queryByTestId('outer-result').textContent).toEqual('Total is:123')
+ expect(queryByTestId('inner-result').textContent).toEqual('Total is:123')
+ expect(onChangeHandler).toHaveBeenCalledTimes(5)
})
test('controlled with hooks by dynamic value', async () => {
@@ -252,9 +252,9 @@ test('controlled with hooks by dynamic value', async () => {
actions.reset()
await sleep(33)
expect(queryByTestId('test-input').value).toEqual('')
- expect(queryByTestId('outer-result').textContent).toEqual('Total is:')
- expect(queryByTestId('inner-result').textContent).toEqual('Total is:')
- expect(onChangeHandler).toHaveBeenCalledTimes(3)
+ expect(queryByTestId('outer-result').textContent).toEqual('Total is:333')
+ expect(queryByTestId('inner-result').textContent).toEqual('Total is:333')
+ expect(onChangeHandler).toHaveBeenCalledTimes(2)
await actions.setFieldState('a3', state => {
state.value = '456'
})
@@ -262,13 +262,13 @@ test('controlled with hooks by dynamic value', async () => {
expect(queryByTestId('test-input').value).toEqual('456')
expect(queryByTestId('outer-result').textContent).toEqual('Total is:456')
expect(queryByTestId('inner-result').textContent).toEqual('Total is:456')
- expect(onChangeHandler).toHaveBeenCalledTimes(4)
+ expect(onChangeHandler).toHaveBeenCalledTimes(3)
await actions.reset()
await sleep(33)
expect(queryByTestId('test-input').value).toEqual('')
- expect(queryByTestId('outer-result').textContent).toEqual('Total is:')
- expect(queryByTestId('inner-result').textContent).toEqual('Total is:')
- expect(onChangeHandler).toHaveBeenCalledTimes(5)
+ expect(queryByTestId('outer-result').textContent).toEqual('Total is:456')
+ expect(queryByTestId('inner-result').textContent).toEqual('Total is:456')
+ expect(onChangeHandler).toHaveBeenCalledTimes(3)
})
test('invariant initialValues will not be changed when form rerender', async () => {
diff --git a/packages/react/src/__tests__/virtualbox.spec.js b/packages/react-schema-renderer/src/__old_tests__/virtualbox.spec.js
similarity index 100%
rename from packages/react/src/__tests__/virtualbox.spec.js
rename to packages/react-schema-renderer/src/__old_tests__/virtualbox.spec.js
diff --git a/packages/react/src/__tests__/visible.spec.js b/packages/react-schema-renderer/src/__old_tests__/visible.spec.js
similarity index 100%
rename from packages/react/src/__tests__/visible.spec.js
rename to packages/react-schema-renderer/src/__old_tests__/visible.spec.js
diff --git a/packages/react/src/__tests__/x-component.spec.js b/packages/react-schema-renderer/src/__old_tests__/x-component.spec.js
similarity index 100%
rename from packages/react/src/__tests__/x-component.spec.js
rename to packages/react-schema-renderer/src/__old_tests__/x-component.spec.js
diff --git a/packages/react-schema-renderer/src/__tests__/__snapshots__/markup.spec.tsx.snap b/packages/react-schema-renderer/src/__tests__/__snapshots__/markup.spec.tsx.snap
new file mode 100644
index 00000000000..8f8529a73aa
--- /dev/null
+++ b/packages/react-schema-renderer/src/__tests__/__snapshots__/markup.spec.tsx.snap
@@ -0,0 +1,218 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`test all apis markup string 1`] = `
+Object {
+ "": Object {
+ "displayName": "FormState",
+ "editable": undefined,
+ "errors": Array [],
+ "initialValues": Object {
+ "aa": "123",
+ },
+ "initialized": true,
+ "invalid": false,
+ "loading": false,
+ "mounted": true,
+ "pristine": false,
+ "props": Object {},
+ "submitting": false,
+ "unmounted": false,
+ "valid": true,
+ "validating": false,
+ "values": Object {
+ "aa": "aa change",
+ },
+ "warnings": Array [],
+ },
+ "aa": Object {
+ "active": false,
+ "display": true,
+ "displayName": "FieldState",
+ "editable": true,
+ "effectErrors": Array [],
+ "effectWarnings": Array [],
+ "errors": Array [],
+ "formEditable": undefined,
+ "initialValue": "123",
+ "initialized": true,
+ "invalid": false,
+ "loading": false,
+ "modified": true,
+ "mounted": true,
+ "name": "aa",
+ "pristine": false,
+ "props": Object {
+ "default": "123",
+ "type": "string",
+ "x-props": Object {
+ "data-testid": "input",
+ },
+ },
+ "required": false,
+ "ruleErrors": Array [],
+ "ruleWarnings": Array [],
+ "rules": Array [],
+ "selfEditable": undefined,
+ "touched": false,
+ "unmounted": false,
+ "valid": true,
+ "validating": false,
+ "value": "aa change",
+ "values": Array [
+ "aa change",
+ ],
+ "visible": true,
+ "visited": false,
+ "warnings": Array [],
+ },
+}
+`;
+
+exports[`test all apis markup virtualbox 1`] = `
+Object {
+ "": Object {
+ "displayName": "FormState",
+ "editable": undefined,
+ "errors": Array [],
+ "initialValues": Object {
+ "aa": "123",
+ },
+ "initialized": true,
+ "invalid": false,
+ "loading": false,
+ "mounted": true,
+ "pristine": false,
+ "props": Object {},
+ "submitting": false,
+ "unmounted": false,
+ "valid": true,
+ "validating": false,
+ "values": Object {
+ "aa": "aa change",
+ },
+ "warnings": Array [],
+ },
+ "NO_NAME_FIELD_$0": Object {
+ "display": true,
+ "displayName": "VirtualFieldState",
+ "initialized": true,
+ "mounted": false,
+ "name": "NO_NAME_FIELD_$0",
+ "props": Object {
+ "type": "object",
+ "x-component": "card",
+ "x-props": Object {
+ "title": "this is card",
+ },
+ },
+ "unmounted": false,
+ "visible": true,
+ },
+ "NO_NAME_FIELD_$0.aa": Object {
+ "active": false,
+ "display": true,
+ "displayName": "FieldState",
+ "editable": true,
+ "effectErrors": Array [],
+ "effectWarnings": Array [],
+ "errors": Array [],
+ "formEditable": undefined,
+ "initialValue": "123",
+ "initialized": true,
+ "invalid": false,
+ "loading": false,
+ "modified": true,
+ "mounted": true,
+ "name": "NO_NAME_FIELD_$0.aa",
+ "pristine": false,
+ "props": Object {
+ "default": "123",
+ "type": "string",
+ "x-props": Object {
+ "data-testid": "input",
+ },
+ },
+ "required": false,
+ "ruleErrors": Array [],
+ "ruleWarnings": Array [],
+ "rules": Array [],
+ "selfEditable": undefined,
+ "touched": false,
+ "unmounted": false,
+ "valid": true,
+ "validating": false,
+ "value": "aa change",
+ "values": Array [
+ "aa change",
+ ],
+ "visible": true,
+ "visited": false,
+ "warnings": Array [],
+ },
+ "aa": Object {
+ "active": false,
+ "display": true,
+ "displayName": "FieldState",
+ "editable": true,
+ "effectErrors": Array [],
+ "effectWarnings": Array [],
+ "errors": Array [],
+ "formEditable": undefined,
+ "initialValue": "123",
+ "initialized": true,
+ "invalid": false,
+ "loading": false,
+ "modified": true,
+ "mounted": true,
+ "name": "NO_NAME_FIELD_$0.aa",
+ "pristine": false,
+ "props": Object {
+ "default": "123",
+ "type": "string",
+ "x-props": Object {
+ "data-testid": "input",
+ },
+ },
+ "required": false,
+ "ruleErrors": Array [],
+ "ruleWarnings": Array [],
+ "rules": Array [],
+ "selfEditable": undefined,
+ "touched": false,
+ "unmounted": false,
+ "valid": true,
+ "validating": false,
+ "value": "aa change",
+ "values": Array [
+ "aa change",
+ ],
+ "visible": true,
+ "visited": false,
+ "warnings": Array [],
+ },
+}
+`;
+
+exports[`test all apis markup virtualbox 2`] = `
+Object {
+ "properties": Object {
+ "NO_NAME_FIELD_$0": Object {
+ "properties": Object {
+ "aa": Object {
+ "default": "123",
+ "type": "string",
+ "x-props": Object {
+ "data-testid": "input",
+ },
+ },
+ },
+ "type": "object",
+ "x-component": "card",
+ "x-props": Object {
+ "title": "this is card",
+ },
+ },
+ },
+ "type": "object",
+}
+`;
diff --git a/packages/react-schema-renderer/src/__tests__/__snapshots__/register.spec.tsx.snap b/packages/react-schema-renderer/src/__tests__/__snapshots__/register.spec.tsx.snap
new file mode 100644
index 00000000000..52f8a58efbc
--- /dev/null
+++ b/packages/react-schema-renderer/src/__tests__/__snapshots__/register.spec.tsx.snap
@@ -0,0 +1,259 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`test all apis registerFieldMiddleware 1`] = `
+Object {
+ "": Object {
+ "displayName": "FormState",
+ "editable": undefined,
+ "errors": Array [],
+ "initialValues": Object {
+ "aa": "123",
+ },
+ "initialized": true,
+ "invalid": false,
+ "loading": false,
+ "mounted": true,
+ "pristine": false,
+ "props": Object {},
+ "submitting": false,
+ "unmounted": false,
+ "valid": true,
+ "validating": false,
+ "values": Object {
+ "aa": "aa change",
+ },
+ "warnings": Array [],
+ },
+ "aa": Object {
+ "active": false,
+ "display": true,
+ "displayName": "FieldState",
+ "editable": true,
+ "effectErrors": Array [],
+ "effectWarnings": Array [],
+ "errors": Array [],
+ "formEditable": undefined,
+ "initialValue": "123",
+ "initialized": true,
+ "invalid": false,
+ "loading": false,
+ "modified": true,
+ "mounted": true,
+ "name": "aa",
+ "pristine": false,
+ "props": Object {
+ "default": "123",
+ "type": "string",
+ "x-props": Object {
+ "data-testid": "input",
+ },
+ },
+ "required": false,
+ "ruleErrors": Array [],
+ "ruleWarnings": Array [],
+ "rules": Array [],
+ "selfEditable": undefined,
+ "touched": false,
+ "unmounted": false,
+ "valid": true,
+ "validating": false,
+ "value": "aa change",
+ "values": Array [
+ "aa change",
+ ],
+ "visible": true,
+ "visited": false,
+ "warnings": Array [],
+ },
+}
+`;
+
+exports[`test all apis registerFormField 1`] = `
+Object {
+ "": Object {
+ "displayName": "FormState",
+ "editable": undefined,
+ "errors": Array [],
+ "initialValues": Object {
+ "aa": "123",
+ },
+ "initialized": true,
+ "invalid": false,
+ "loading": false,
+ "mounted": true,
+ "pristine": false,
+ "props": Object {},
+ "submitting": false,
+ "unmounted": false,
+ "valid": true,
+ "validating": false,
+ "values": Object {
+ "aa": "aa change",
+ },
+ "warnings": Array [],
+ },
+ "aa": Object {
+ "active": false,
+ "display": true,
+ "displayName": "FieldState",
+ "editable": true,
+ "effectErrors": Array [],
+ "effectWarnings": Array [],
+ "errors": Array [],
+ "formEditable": undefined,
+ "initialValue": "123",
+ "initialized": true,
+ "invalid": false,
+ "loading": false,
+ "modified": true,
+ "mounted": true,
+ "name": "aa",
+ "pristine": false,
+ "props": Object {
+ "default": "123",
+ "type": "string",
+ "x-props": Object {
+ "data-testid": "input",
+ },
+ },
+ "required": false,
+ "ruleErrors": Array [],
+ "ruleWarnings": Array [],
+ "rules": Array [],
+ "selfEditable": undefined,
+ "touched": false,
+ "unmounted": false,
+ "valid": true,
+ "validating": false,
+ "value": "aa change",
+ "values": Array [
+ "aa change",
+ ],
+ "visible": true,
+ "visited": false,
+ "warnings": Array [],
+ },
+}
+`;
+
+exports[`test all apis registerVirtualBox 1`] = `
+Object {
+ "": Object {
+ "displayName": "FormState",
+ "editable": undefined,
+ "errors": Array [],
+ "initialValues": Object {
+ "aa": "123",
+ },
+ "initialized": true,
+ "invalid": false,
+ "loading": false,
+ "mounted": true,
+ "pristine": false,
+ "props": Object {},
+ "submitting": false,
+ "unmounted": false,
+ "valid": true,
+ "validating": false,
+ "values": Object {
+ "aa": "aa change",
+ },
+ "warnings": Array [],
+ },
+ "aa": Object {
+ "active": false,
+ "display": true,
+ "displayName": "FieldState",
+ "editable": true,
+ "effectErrors": Array [],
+ "effectWarnings": Array [],
+ "errors": Array [],
+ "formEditable": undefined,
+ "initialValue": "123",
+ "initialized": true,
+ "invalid": false,
+ "loading": false,
+ "modified": true,
+ "mounted": true,
+ "name": "cc.aa",
+ "pristine": false,
+ "props": Object {
+ "default": "123",
+ "type": "string",
+ "x-props": Object {
+ "data-testid": "input",
+ },
+ },
+ "required": false,
+ "ruleErrors": Array [],
+ "ruleWarnings": Array [],
+ "rules": Array [],
+ "selfEditable": undefined,
+ "touched": false,
+ "unmounted": false,
+ "valid": true,
+ "validating": false,
+ "value": "aa change",
+ "values": Array [
+ "aa change",
+ ],
+ "visible": true,
+ "visited": false,
+ "warnings": Array [],
+ },
+ "cc": Object {
+ "display": true,
+ "displayName": "VirtualFieldState",
+ "initialized": true,
+ "mounted": false,
+ "name": "cc",
+ "props": Object {
+ "type": "object",
+ "x-component": "box",
+ },
+ "unmounted": false,
+ "visible": true,
+ },
+ "cc.aa": Object {
+ "active": false,
+ "display": true,
+ "displayName": "FieldState",
+ "editable": true,
+ "effectErrors": Array [],
+ "effectWarnings": Array [],
+ "errors": Array [],
+ "formEditable": undefined,
+ "initialValue": "123",
+ "initialized": true,
+ "invalid": false,
+ "loading": false,
+ "modified": true,
+ "mounted": true,
+ "name": "cc.aa",
+ "pristine": false,
+ "props": Object {
+ "default": "123",
+ "type": "string",
+ "x-props": Object {
+ "data-testid": "input",
+ },
+ },
+ "required": false,
+ "ruleErrors": Array [],
+ "ruleWarnings": Array [],
+ "rules": Array [],
+ "selfEditable": undefined,
+ "touched": false,
+ "unmounted": false,
+ "valid": true,
+ "validating": false,
+ "value": "aa change",
+ "values": Array [
+ "aa change",
+ ],
+ "visible": true,
+ "visited": false,
+ "warnings": Array [],
+ },
+}
+`;
diff --git a/packages/react-schema-renderer/src/__tests__/field.spec.tsx b/packages/react-schema-renderer/src/__tests__/field.spec.tsx
new file mode 100644
index 00000000000..3764eab2358
--- /dev/null
+++ b/packages/react-schema-renderer/src/__tests__/field.spec.tsx
@@ -0,0 +1,12 @@
+describe('test all apis',()=>{
+ //todo
+})
+
+describe('major scenes',()=>{
+ //todo
+})
+
+
+describe('bugfix',()=>{
+ //todo
+})
\ No newline at end of file
diff --git a/packages/react-schema-renderer/src/__tests__/form.spec.tsx b/packages/react-schema-renderer/src/__tests__/form.spec.tsx
new file mode 100644
index 00000000000..3764eab2358
--- /dev/null
+++ b/packages/react-schema-renderer/src/__tests__/form.spec.tsx
@@ -0,0 +1,12 @@
+describe('test all apis',()=>{
+ //todo
+})
+
+describe('major scenes',()=>{
+ //todo
+})
+
+
+describe('bugfix',()=>{
+ //todo
+})
\ No newline at end of file
diff --git a/packages/react-schema-renderer/src/__tests__/json-schema.spec.tsx b/packages/react-schema-renderer/src/__tests__/json-schema.spec.tsx
new file mode 100644
index 00000000000..3764eab2358
--- /dev/null
+++ b/packages/react-schema-renderer/src/__tests__/json-schema.spec.tsx
@@ -0,0 +1,12 @@
+describe('test all apis',()=>{
+ //todo
+})
+
+describe('major scenes',()=>{
+ //todo
+})
+
+
+describe('bugfix',()=>{
+ //todo
+})
\ No newline at end of file
diff --git a/packages/react-schema-renderer/src/__tests__/markup.spec.tsx b/packages/react-schema-renderer/src/__tests__/markup.spec.tsx
new file mode 100644
index 00000000000..9013675dd70
--- /dev/null
+++ b/packages/react-schema-renderer/src/__tests__/markup.spec.tsx
@@ -0,0 +1,105 @@
+import React from 'react'
+import {
+ registerFormField,
+ createVirtualBox,
+ connect,
+ SchemaMarkupForm as SchemaForm,
+ SchemaMarkupField as Field,
+ createFormActions,
+ cleanRegistry
+} from '../index'
+import { render, fireEvent, wait } from '@testing-library/react'
+
+describe('test all apis', () => {
+ beforeEach(() => {
+ registerFormField(
+ 'string',
+ connect()(props => {
+ return
+ })
+ )
+ })
+
+ afterEach(() => {
+ cleanRegistry()
+ })
+
+ test('markup string', async () => {
+ const actions = createFormActions()
+
+ const { queryByTestId } = render(
+
+
+
+ )
+ expect(queryByTestId('input').getAttribute('value')).toEqual('123')
+ fireEvent.change(queryByTestId('input'), {
+ target: {
+ value: 'aa change'
+ }
+ })
+ await wait(() => {
+ expect(queryByTestId('input').getAttribute('value')).toEqual('aa change')
+ expect(actions.getFormGraph()).toMatchSnapshot()
+ expect(actions.getFormState(state => state.values)).toEqual({
+ aa: 'aa change'
+ })
+ })
+ })
+
+ test('markup virtualbox', async () => {
+ const Card = createVirtualBox<{ title?: string | React.ReactElement }>(
+ 'card',
+ props => {
+ return (
+
+
Title:{props.title}
+
{props.children}
+
+ )
+ }
+ )
+
+ const actions = createFormActions()
+
+ const { queryByTestId } = render(
+
+
+
+
+
+ )
+ expect(queryByTestId('input').getAttribute('value')).toEqual('123')
+ fireEvent.change(queryByTestId('input'), {
+ target: {
+ value: 'aa change'
+ }
+ })
+ await wait(() => {
+ expect(queryByTestId('input').getAttribute('value')).toEqual('aa change')
+ expect(actions.getFormGraph()).toMatchSnapshot()
+ expect(actions.getFormState(state => state.values)).toEqual({
+ aa: 'aa change'
+ })
+ expect(actions.getFormSchema()).toMatchSnapshot()
+ })
+ })
+})
+
+describe('major scenes',()=>{
+ //todo,核心场景回归
+})
+
+describe('bugfix',()=>{
+ //todo,问题修复回归
+})
\ No newline at end of file
diff --git a/packages/react-schema-renderer/src/__tests__/register.spec.tsx b/packages/react-schema-renderer/src/__tests__/register.spec.tsx
new file mode 100644
index 00000000000..a72934ce55a
--- /dev/null
+++ b/packages/react-schema-renderer/src/__tests__/register.spec.tsx
@@ -0,0 +1,166 @@
+import React from 'react'
+import {
+ registerFormField,
+ registerFieldMiddleware,
+ registerVirtualBox,
+ connect,
+ SchemaForm,
+ createFormActions,
+ cleanRegistry
+} from '../index'
+import { render, fireEvent, wait } from '@testing-library/react'
+
+describe('test all apis', () => {
+ afterEach(() => {
+ cleanRegistry()
+ })
+ test('registerFormField', async () => {
+ registerFormField(
+ 'string',
+ connect()(props => {
+ return
+ })
+ )
+
+ const actions = createFormActions()
+
+ const { queryByTestId } = render(
+
+ )
+ expect(queryByTestId('input').getAttribute('value')).toEqual('123')
+ fireEvent.change(queryByTestId('input'), {
+ target: {
+ value: 'aa change'
+ }
+ })
+ await wait(() => {
+ expect(queryByTestId('input').getAttribute('value')).toEqual('aa change')
+ expect(actions.getFormGraph()).toMatchSnapshot()
+ })
+ })
+
+ test('registerFieldMiddleware', async () => {
+ registerFormField(
+ 'string',
+ connect()(props => {
+ return
+ })
+ )
+
+ registerFieldMiddleware(FieldComponent => props => {
+ return (
+
+ this is wrapper
+
+
+ )
+ })
+
+ const actions = createFormActions()
+
+ const { queryByTestId, queryByText } = render(
+
+ )
+ expect(queryByTestId('input').getAttribute('value')).toEqual('123')
+ fireEvent.change(queryByTestId('input'), {
+ target: {
+ value: 'aa change'
+ }
+ })
+ await wait(() => {
+ expect(queryByTestId('input').getAttribute('value')).toEqual('aa change')
+ expect(actions.getFormGraph()).toMatchSnapshot()
+ expect(queryByText('this is wrapper')).toBeTruthy()
+ })
+ })
+
+ test('registerVirtualBox', async () => {
+ registerFormField(
+ 'string',
+ connect()(props => {
+ return
+ })
+ )
+
+ registerVirtualBox('box', props => {
+ return this is VirtualBox.{props.children}
+ })
+
+ const actions = createFormActions()
+
+ const { queryByTestId, queryByText } = render(
+
+ )
+ expect(queryByTestId('input').getAttribute('value')).toEqual('123')
+ fireEvent.change(queryByTestId('input'), {
+ target: {
+ value: 'aa change'
+ }
+ })
+ await wait(() => {
+ expect(queryByTestId('input').getAttribute('value')).toEqual('aa change')
+ expect(actions.getFormGraph()).toMatchSnapshot()
+ expect(actions.getFormState(state => state.values)).toEqual({
+ aa: 'aa change'
+ })
+ expect(queryByText('this is VirtualBox.')).toBeTruthy()
+ })
+ })
+})
+
+describe('major scenes',()=>{
+ //todo
+})
+
+
+describe('bugfix',()=>{
+ //todo
+})
\ No newline at end of file
diff --git a/packages/react-schema-renderer/src/components/SchemaField.tsx b/packages/react-schema-renderer/src/components/SchemaField.tsx
new file mode 100644
index 00000000000..ae1b6110bde
--- /dev/null
+++ b/packages/react-schema-renderer/src/components/SchemaField.tsx
@@ -0,0 +1,144 @@
+import React, { useContext, Fragment } from 'react'
+import { Field, VirtualField } from '@uform/react'
+import { FormPath, isFn, isStr } from '@uform/shared'
+import {
+ ISchemaFieldProps,
+ ISchemaFieldComponentProps,
+ ISchemaVirtualFieldComponentProps
+} from '../types'
+import { Schema } from '../shared/schema'
+import SchemaContext, { FormComponentsContext } from '../shared/context'
+
+export const SchemaField: React.FunctionComponent = (
+ props: ISchemaFieldProps
+) => {
+ const path = FormPath.parse(props.path)
+ const formSchema = useContext(SchemaContext)
+ const fieldSchema = formSchema.get(path)
+ const formRegistry = useContext(FormComponentsContext)
+ if (!fieldSchema) {
+ throw new Error(`Can not found schema node by ${path.toString()}.`)
+ }
+ if (!formRegistry) {
+ throw new Error(`Can not found any form components.`)
+ }
+ const schemaType = fieldSchema.type
+ const schemaComponent = fieldSchema.getExtendsComponent()
+ const schemaRenderer = fieldSchema.getExtendsRenderer()
+ const finalComponentName = schemaComponent || schemaType
+ const renderField = (
+ addtionKey: string | number,
+ reactKey?: string | number
+ ) => {
+ return
+ }
+ const renderChildren = (
+ callback: (props: ISchemaFieldComponentProps) => React.ReactElement
+ ) => {
+ return (
+
+ {({ state, mutators, form }) => {
+ const props: ISchemaFieldComponentProps = {
+ ...state,
+ schema: fieldSchema,
+ form,
+ mutators,
+ renderField
+ }
+ return callback(props)
+ }}
+
+ )
+ }
+ if (fieldSchema.isObject() && !schemaComponent) {
+ const properties = fieldSchema.mapProperties(
+ (schema: Schema, key: string) => {
+ const childPath = path.concat(key)
+ return
+ }
+ )
+ if (path.length == 0) {
+ return {properties}
+ }
+ return renderChildren(props => {
+ return React.createElement(
+ formRegistry.formItemComponent,
+ props,
+ properties
+ )
+ })
+ } else {
+ if (isFn(finalComponentName)) {
+ return renderChildren(props => {
+ return React.createElement(
+ formRegistry.formItemComponent,
+ props,
+ React.createElement(finalComponentName, props)
+ )
+ })
+ } else if (isStr(finalComponentName)) {
+ if (formRegistry.fields[finalComponentName]) {
+ return renderChildren(props => {
+ const renderComponent = (): React.ReactElement =>
+ React.createElement(
+ formRegistry.formItemComponent,
+ props,
+ React.createElement(
+ formRegistry.fields[finalComponentName],
+ props
+ )
+ )
+ if (isFn(schemaRenderer)) {
+ return schemaRenderer({ ...props, renderComponent })
+ }
+ return renderComponent()
+ })
+ } else if (formRegistry.virtualFields[finalComponentName]) {
+ return (
+
+ {({ state, form }) => {
+ const props: ISchemaVirtualFieldComponentProps = {
+ ...state,
+ schema: fieldSchema,
+ form,
+ renderField,
+ children: fieldSchema.mapProperties(
+ (schema: Schema, key: string) => {
+ const childPath = path.concat(key)
+ return (
+
+ )
+ }
+ )
+ }
+ const renderComponent = () =>
+ React.createElement(
+ formRegistry.virtualFields[finalComponentName],
+ props
+ )
+ if (isFn(schemaRenderer)) {
+ return schemaRenderer({ ...props, renderComponent })
+ }
+ return renderComponent()
+ }}
+
+ )
+ } else {
+ throw new Error(
+ `Can not found any custom component in ${path.toString()}.`
+ )
+ }
+ }
+ }
+}
diff --git a/packages/react-schema-renderer/src/components/SchemaForm.tsx b/packages/react-schema-renderer/src/components/SchemaForm.tsx
new file mode 100644
index 00000000000..c31870b6e10
--- /dev/null
+++ b/packages/react-schema-renderer/src/components/SchemaForm.tsx
@@ -0,0 +1,49 @@
+import React from 'react'
+import { ISchemaFormProps } from '../types'
+import { Form } from '@uform/react'
+import { SchemaField } from './SchemaField'
+import { useSchemaForm } from '../hooks/useSchemaForm'
+import SchemaContext, { FormComponentsContext } from '../shared/context'
+
+export const SchemaForm: React.FC = props => {
+ const {
+ fields,
+ virtualFields,
+ formComponent,
+ formItemComponent,
+ formComponentProps,
+ schema,
+ form,
+ children
+ } = useSchemaForm(props)
+ return (
+
+
+
+
+
+ )
+}
+
+SchemaForm.defaultProps = {
+ schema: {}
+}
+
+export default SchemaForm
diff --git a/packages/react-schema-renderer/src/components/SchemaMarkup.tsx b/packages/react-schema-renderer/src/components/SchemaMarkup.tsx
new file mode 100644
index 00000000000..684988bb099
--- /dev/null
+++ b/packages/react-schema-renderer/src/components/SchemaMarkup.tsx
@@ -0,0 +1,131 @@
+import React, { Fragment, createContext, useContext } from 'react'
+import { registerVirtualBox } from '../shared/registry'
+import { SchemaForm } from './SchemaForm'
+import { Schema } from '../shared/schema'
+import { render } from '../shared/virtual-render'
+import {
+ ISchemaFormProps,
+ IMarkupSchemaFieldProps,
+ ISchemaVirtualFieldComponentProps
+} from '../types'
+
+const env = {
+ nonameId: 0
+}
+
+const MarkupContext = createContext(null)
+
+const getRadomName = () => {
+ return `NO_NAME_FIELD_$${env.nonameId++}`
+}
+
+export const SchemaMarkupField: React.FC = ({
+ name,
+ children,
+ ...props
+}) => {
+ const parentSchema = useContext(MarkupContext)
+ if (!parentSchema) return
+ if (parentSchema.isObject()) {
+ const propName = name || getRadomName()
+ const schema = parentSchema.setProperty(propName, props)
+ return (
+ {children}
+ )
+ } else if (parentSchema.isArray()) {
+ const schema = parentSchema.setArrayItems(props)
+ return (
+ {children}
+ )
+ } else {
+ return (children as React.ReactElement) ||
+ }
+}
+
+SchemaMarkupField.displayName = 'SchemaMarkupField'
+
+export const SchemaMarkupForm: React.FC = props => {
+ let alreadyHasSchema = false
+ let finalSchema: Schema
+ if (props.schema) {
+ alreadyHasSchema = true
+ finalSchema = new Schema(props.schema)
+ } else {
+ finalSchema = new Schema({ type: 'object' })
+ }
+ env.nonameId = 0
+ return (
+
+ {!alreadyHasSchema &&
+ render(
+
+ {props.children}
+
+ )}
+
+
+ )
+}
+
+SchemaMarkupForm.displayName = 'SchemaMarkupForm'
+
+export function createVirtualBox(
+ key: string,
+ component?: React.JSXElementConstructor
+) {
+ registerVirtualBox(
+ key,
+ component
+ ? ({ props, children }) => {
+ return React.createElement(component, {
+ ...props['x-props'],
+ ...props['x-component-props'],
+ children
+ })
+ }
+ : () =>
+ )
+ const VirtualBox: React.FC = ({
+ children,
+ name,
+ ...props
+ }) => {
+ return (
+
+ {children}
+
+ )
+ }
+ return VirtualBox
+}
+
+export function createControllerBox(
+ key: string,
+ component?: React.JSXElementConstructor
+) {
+ registerVirtualBox(key, component ? component : () => )
+ const VirtualBox: React.FC = ({
+ children,
+ name,
+ ...props
+ }) => {
+ return (
+
+ {children}
+
+ )
+ }
+ return VirtualBox
+}
diff --git a/packages/react-schema-renderer/src/hooks/useSchemaForm.ts b/packages/react-schema-renderer/src/hooks/useSchemaForm.ts
new file mode 100644
index 00000000000..1bd1c998523
--- /dev/null
+++ b/packages/react-schema-renderer/src/hooks/useSchemaForm.ts
@@ -0,0 +1,71 @@
+import { useMemo, useRef } from 'react'
+import { useForm } from '@uform/react'
+import { Schema } from '../shared/schema'
+import { deprecate } from '@uform/shared'
+import { useEva } from 'react-eva'
+import { ISchemaFormProps } from '../types'
+import { createSchemaFormActions } from '../shared/actions'
+import { getRegistry } from '../shared/registry'
+
+const useInternalSchemaForm = (props: ISchemaFormProps) => {
+ const {
+ fields,
+ virtualFields,
+ formComponent,
+ formItemComponent,
+ component,
+ schema,
+ value,
+ initialValues,
+ actions,
+ effects,
+ onChange,
+ onSubmit,
+ onReset,
+ onValidateFailed,
+ useDirty,
+ children,
+ editable,
+ validateFirst,
+ ...formComponentProps
+ } = props
+ const { implementActions } = useEva({
+ actions
+ })
+ const registry = getRegistry()
+ return {
+ form: useForm(props),
+ formComponentProps,
+ fields: {
+ ...registry.fields,
+ ...fields
+ },
+ virtualFields: {
+ ...registry.virtualFields,
+ ...virtualFields
+ },
+ formComponent: formComponent ? formComponent : registry.formComponent,
+ formItemComponent: formItemComponent
+ ? formItemComponent
+ : registry.formItemComponent,
+ schema: useMemo(() => {
+ const result = new Schema(schema)
+ implementActions({
+ getSchema: deprecate(() => result, 'Please use the getFormSchema.'),
+ getFormSchema: () => result
+ })
+ return result
+ }, [schema]),
+ children
+ }
+}
+
+export const useSchemaForm = (props: ISchemaFormProps) => {
+ const actionsRef = useRef(null)
+ actionsRef.current =
+ actionsRef.current || props.actions || createSchemaFormActions()
+ return useInternalSchemaForm({
+ ...props,
+ actions: actionsRef.current
+ })
+}
diff --git a/packages/react-schema-renderer/src/index.tsx b/packages/react-schema-renderer/src/index.tsx
new file mode 100644
index 00000000000..118b190a965
--- /dev/null
+++ b/packages/react-schema-renderer/src/index.tsx
@@ -0,0 +1,16 @@
+import {
+ createAsyncSchemaFormActions,
+ createSchemaFormActions
+} from './shared/actions'
+export * from '@uform/react'
+export * from './components/SchemaField'
+export * from './components/SchemaForm'
+export * from './components/SchemaMarkup'
+export * from './hooks/useSchemaForm'
+export * from './shared/connect'
+export * from './shared/registry'
+export * from './shared/schema'
+export * from './types'
+
+export const createFormActions = createSchemaFormActions
+export const createAsyncFormActions = createAsyncSchemaFormActions
diff --git a/packages/react-schema-renderer/src/shared/actions.ts b/packages/react-schema-renderer/src/shared/actions.ts
new file mode 100644
index 00000000000..7f3fff6d0a0
--- /dev/null
+++ b/packages/react-schema-renderer/src/shared/actions.ts
@@ -0,0 +1,15 @@
+import { createFormActions, createAsyncFormActions } from '@uform/react'
+import { mergeActions, createActions, createAsyncActions } from 'react-eva'
+import { ISchemaFormActions, ISchemaFormAsyncActions } from '../types'
+
+export const createSchemaFormActions = (): ISchemaFormActions =>
+ mergeActions(
+ createFormActions(),
+ createActions('getSchema', 'getFormSchema')
+ ) as ISchemaFormActions
+
+export const createAsyncSchemaFormActions = (): ISchemaFormAsyncActions =>
+ mergeActions(
+ createAsyncFormActions(),
+ createAsyncActions('getSchema', 'getFormSchema')
+ ) as ISchemaFormAsyncActions
diff --git a/packages/react-schema-renderer/src/shared/connect.ts b/packages/react-schema-renderer/src/shared/connect.ts
new file mode 100644
index 00000000000..159fcac127c
--- /dev/null
+++ b/packages/react-schema-renderer/src/shared/connect.ts
@@ -0,0 +1,123 @@
+import React from 'react'
+import { isArr, each, isFn } from '@uform/shared'
+import {
+ ISchema,
+ IConnectOptions,
+ ISchemaFieldComponentProps,
+ IConnectProps
+} from '../types'
+
+const createEnum = (enums: any) => {
+ if (isArr(enums)) {
+ return enums.map(item => {
+ if (typeof item === 'object') {
+ return {
+ ...item
+ }
+ } else {
+ return {
+ ...item,
+ label: item,
+ value: item
+ }
+ }
+ })
+ }
+
+ return []
+}
+
+const bindEffects = (
+ props: {},
+ effect: ISchema['x-effect'],
+ notify: (type: string, payload?: any) => void
+): any => {
+ each(effect(notify, { ...props }), (event, key) => {
+ const prevEvent = key === 'onChange' ? props[key] : undefined
+ props[key] = (...args: any[]) => {
+ if (isFn(prevEvent)) {
+ prevEvent(...args)
+ }
+ if (isFn(event)) {
+ return event(...args)
+ }
+ }
+ })
+ return props
+}
+
+export const connect = (options?: IConnectOptions) => {
+ options = {
+ valueName: 'value',
+ eventName: 'onChange',
+ ...options
+ }
+ return (Component: React.JSXElementConstructor) => {
+ return (fieldProps: ISchemaFieldComponentProps) => {
+ const {
+ value,
+ name,
+ mutators,
+ form,
+ schema,
+ editable,
+ props
+ } = fieldProps
+ let componentProps: IConnectProps = {
+ ...options.defaultProps,
+ ...props['x-props'],
+ ...props['x-component-props'],
+ [options.valueName]: value,
+ [options.eventName]: (event: any, ...args: any[]) => {
+ mutators.change(
+ options.getValueFromEvent
+ ? options.getValueFromEvent.call(schema, event, ...args)
+ : event,
+ ...args
+ )
+ },
+ onBlur: () => mutators.blur(),
+ onFocus: () => mutators.focus()
+ }
+ if (editable !== undefined) {
+ if (isFn(editable)) {
+ if (!editable(name)) {
+ componentProps.disabled = true
+ componentProps.readOnly = true
+ }
+ } else if (editable === false) {
+ componentProps.disabled = true
+ componentProps.readOnly = true
+ }
+ }
+
+ const extendsEffect = schema.getExtendsEffect()
+
+ if (isFn(extendsEffect)) {
+ componentProps = bindEffects(componentProps, extendsEffect, form.notify)
+ }
+
+ if (isFn(options.getProps)) {
+ const newProps = options.getProps(componentProps, fieldProps)
+ if (newProps !== undefined) {
+ componentProps = newProps as any
+ }
+ }
+
+ if (isArr(schema.enum) && !componentProps.dataSource) {
+ componentProps.dataSource = createEnum(schema.enum)
+ }
+
+ if (componentProps.editable !== undefined) {
+ delete componentProps.editable
+ }
+
+ return React.createElement(
+ isFn(options.getComponent)
+ ? options.getComponent(Component, props, fieldProps)
+ : Component,
+ componentProps
+ )
+ }
+ }
+}
diff --git a/packages/react-schema-renderer/src/shared/context.ts b/packages/react-schema-renderer/src/shared/context.ts
new file mode 100644
index 00000000000..29b52885b26
--- /dev/null
+++ b/packages/react-schema-renderer/src/shared/context.ts
@@ -0,0 +1,7 @@
+import { createContext } from 'react'
+import { Schema } from './schema'
+import { ISchemaFormRegistry } from '../types'
+
+export const FormComponentsContext = createContext(null)
+
+export default createContext(null)
diff --git a/packages/react-schema-renderer/src/shared/registry.ts b/packages/react-schema-renderer/src/shared/registry.ts
new file mode 100644
index 00000000000..5ffcadc5749
--- /dev/null
+++ b/packages/react-schema-renderer/src/shared/registry.ts
@@ -0,0 +1,134 @@
+import { isFn, lowercase, reduce, each, deprecate } from '@uform/shared'
+import {
+ ComponentWithStyleComponent,
+ ISchemaFieldWrapper,
+ ISchemaFormRegistry,
+ ISchemaFieldComponent,
+ ISchemaFieldComponentProps,
+ ISchemaVirtualFieldComponentProps
+} from '../types'
+import pascalCase from 'pascal-case'
+
+const registry: ISchemaFormRegistry = {
+ fields: {},
+ virtualFields: {},
+ wrappers: [],
+ formItemComponent: ({ children }) => children,
+ formComponent: 'form'
+}
+
+export const getRegistry = () => {
+ return {
+ fields: registry.fields,
+ virtualFields: registry.virtualFields,
+ formItemComponent: registry.formItemComponent,
+ formComponent: registry.formComponent
+ }
+}
+
+export const cleanRegistry = () => {
+ registry.fields = {}
+ registry.virtualFields = {}
+ registry.wrappers = []
+}
+
+export function registerFormComponent(
+ component: React.JSXElementConstructor
+) {
+ if (isFn(component)) {
+ registry.formComponent = component
+ }
+}
+
+function compose(payload: T, args: P[], revert: boolean) {
+ return reduce(
+ args,
+ (buf: T, fn: P) => {
+ return isFn(fn) ? fn(buf) : buf
+ },
+ payload,
+ revert
+ )
+}
+
+export function registerFormField(
+ name: string,
+ component: ComponentWithStyleComponent,
+ noWrapper: boolean = false
+) {
+ if (
+ name &&
+ (isFn(component) || typeof component.styledComponentId === 'string')
+ ) {
+ name = lowercase(name)
+ if (noWrapper) {
+ registry.fields[name] = component
+ registry.fields[name].__WRAPPERS__ = []
+ } else {
+ registry.fields[name] = compose(
+ component,
+ registry.wrappers,
+ true
+ )
+ registry.fields[name].__WRAPPERS__ = registry.wrappers
+ }
+ registry.fields[name].displayName = pascalCase(name)
+ }
+}
+
+export function registerFormFields(object: ISchemaFormRegistry['fields']) {
+ each(
+ object,
+ (component, key) => {
+ registerFormField(key, component)
+ }
+ )
+}
+
+export function registerVirtualBox(
+ name: string,
+ component: ComponentWithStyleComponent
+) {
+ if (
+ name &&
+ (isFn(component) || typeof component.styledComponentId === 'string')
+ ) {
+ name = lowercase(name)
+ registry.virtualFields[name] = component
+ registry.virtualFields[name].displayName = pascalCase(name)
+ }
+}
+
+export function registerFormItemComponent(
+ component: React.JSXElementConstructor
+) {
+ if (isFn(component)) {
+ registry.formItemComponent = component
+ }
+}
+
+type FieldMiddleware = ISchemaFieldWrapper
+
+export const registerFieldMiddleware = deprecate<
+ FieldMiddleware,
+ FieldMiddleware,
+ FieldMiddleware
+>(function registerFieldMiddleware(
+ ...wrappers: ISchemaFieldWrapper[]
+) {
+ each(
+ registry.fields,
+ (component, key) => {
+ if (
+ !component.__WRAPPERS__.some(wrapper => wrappers.indexOf(wrapper) > -1)
+ ) {
+ registry.fields[key] = compose(
+ registry.fields[key],
+ wrappers,
+ true
+ )
+ registry.fields[key].__WRAPPERS__ = wrappers
+ }
+ }
+ )
+})
diff --git a/packages/react-schema-renderer/src/shared/schema.ts b/packages/react-schema-renderer/src/shared/schema.ts
new file mode 100644
index 00000000000..facc840ba7e
--- /dev/null
+++ b/packages/react-schema-renderer/src/shared/schema.ts
@@ -0,0 +1,392 @@
+import React from 'react'
+import {
+ ValidatePatternRules,
+ ValidateDescription,
+ CustomValidator,
+ getMessage
+} from '@uform/validator'
+import {
+ lowercase,
+ map,
+ each,
+ isEmpty,
+ isEqual,
+ isArr,
+ toArr,
+ isBool,
+ isValid,
+ FormPathPattern,
+ FormPath
+} from '@uform/shared'
+import { ISchemaFieldComponentProps, SchemaMessage, ISchema } from '../types'
+
+const numberRE = /^\d+$/
+
+type SchemaProperties = {
+ [key: string]: Schema
+}
+
+export class Schema implements ISchema {
+ /** base json schema spec**/
+ public title?: SchemaMessage
+ public description?: SchemaMessage
+ public default?: any
+ public readOnly?: boolean
+ public writeOnly?: boolean
+ public type?: 'string' | 'object' | 'array' | 'number' | string
+ public enum?: Array
+ public const?: any
+ public multipleOf?: number
+ public maximum?: number
+ public exclusiveMaximum?: number
+ public minimum?: number
+ public exclusiveMinimum?: number
+ public maxLength?: number
+ public minLength?: number
+ public pattern?: string | RegExp
+ public maxItems?: number
+ public minItems?: number
+ public uniqueItems?: boolean
+ public maxProperties?: number
+ public minProperties?: number
+ public required?: string[] | boolean
+ public format?: string
+ /** nested json schema spec **/
+ public properties?: SchemaProperties
+ public items?: Schema | Schema[]
+ public additionalItems?: Schema
+ public patternProperties?: {
+ [key: string]: Schema
+ }
+ public additionalProperties?: Schema
+ /** extend json schema specs */
+ public editable?: boolean
+ public ['x-props']?: { [name: string]: any }
+ public ['x-index']?: number
+ public ['x-rules']?: ValidatePatternRules
+ public ['x-component']?: string | React.JSXElementConstructor
+ public ['x-component-props']?: { [name: string]: any }
+ public ['x-render']?: (
+ props: T & {
+ renderComponent: () => React.ReactElement
+ }
+ ) => React.ReactElement
+ public ['x-effect']?: (
+ dispatch: (type: string, payload: any) => void,
+ option?: object
+ ) => { [key: string]: any }
+ /** schema class self specs**/
+
+ public parent?: Schema
+
+ public _isJSONSchemaObject = true
+
+ constructor(json: ISchema, parent?: Schema) {
+ if (parent) {
+ this.parent = parent
+ }
+ return this.fromJSON(json) as any
+ }
+ /**
+ * getters
+ */
+ get(path?: FormPathPattern) {
+ if (!path) {
+ return this
+ }
+ let res: Schema = this
+ let suc = 0
+ path = FormPath.parse(path)
+ path.forEach(key => {
+ if (res && !isEmpty(res.properties)) {
+ res = res.properties[key]
+ suc++
+ } else if (res && !isEmpty(res.items) && numberRE.test(key as string)) {
+ res = isArr(res.items) ? res.items[key] : res.items
+ suc++
+ }
+ })
+ return suc === path.length ? res : undefined
+ }
+
+ getEmptyValue() {
+ if (this.type === 'string') {
+ return ''
+ }
+ if (this.type === 'array') {
+ return []
+ }
+ if (this.type === 'object') {
+ return {}
+ }
+ if (this.type === 'number') {
+ return 0
+ }
+ }
+
+ getSelfProps() {
+ const {
+ _isJSONSchemaObject,
+ properties,
+ additionalProperties,
+ additionalItems,
+ patternProperties,
+ items,
+ parent,
+ ...props
+ } = this
+ return props
+ }
+ getExtendsRules() {
+ let rules: Array = []
+ if (this.format) {
+ rules.push({ format: this.format })
+ }
+ if (isValid(this.maxItems)) {
+ rules.push({ max: this.maxItems })
+ }
+ if (isValid(this.maxLength)) {
+ rules.push({ max: this.maxLength })
+ }
+ if (isValid(this.maximum)) {
+ rules.push({ maximum: this.maximum })
+ }
+ if (isValid(this.minimum)) {
+ rules.push({ minimum: this.minimum })
+ }
+ if (isValid(this.exclusiveMaximum)) {
+ rules.push({ exclusiveMaximum: this.exclusiveMaximum })
+ }
+ if (isValid(this.exclusiveMinimum)) {
+ rules.push({ exclusiveMinimum: this.exclusiveMinimum })
+ }
+ if (isValid(this.pattern)) {
+ rules.push({ pattern: this.pattern })
+ }
+ if (isValid(this.const)) {
+ rules.push({
+ validator: value => {
+ return value === this.const ? '' : getMessage('schema.const')
+ }
+ })
+ }
+ if (isValid(this.multipleOf)) {
+ rules.push({
+ validator: value => {
+ return value % this.multipleOf === 0
+ ? ''
+ : getMessage('schema.multipleOf')
+ }
+ })
+ }
+ if (isValid(this.maxProperties)) {
+ rules.push({
+ validator: value => {
+ return Object.keys(value || {}).length <= this.maxProperties
+ ? ''
+ : getMessage('schema.maxProperties')
+ }
+ })
+ }
+ if (isValid(this.minProperties)) {
+ rules.push({
+ validator: value => {
+ return Object.keys(value || {}).length >= this.minProperties
+ ? ''
+ : getMessage('schema.minProperties')
+ }
+ })
+ }
+ if (isValid(this.uniqueItems) && this.uniqueItems) {
+ rules.push({
+ validator: value => {
+ value = toArr(value)
+ return value.some((item: any, index: number) => {
+ for (let start = index; start < value.length; start++) {
+ if (isEqual(value[start], item)) {
+ return false
+ }
+ }
+ })
+ ? getMessage('schema.uniqueItems')
+ : ''
+ }
+ })
+ }
+ /**剩余校验的都是关联型复杂校验,不抹平,让用户自己处理 */
+ if (isValid(this['x-rules'])) {
+ rules = rules.concat(this['x-rules'])
+ }
+
+ return rules
+ }
+ getExtendsRequired() {
+ if (isBool(this.required)) {
+ return this.required
+ }
+ }
+ getExtendsEditable() {
+ if (isValid(this.editable)) {
+ return this.editable
+ } else if (isValid(this['x-props'] && this['x-props'].editable)) {
+ return this['x-props'].editable
+ } else if (isValid(this.readOnly)) {
+ return !this.readOnly
+ }
+ }
+ getExtendsTriggerType() {
+ const itemProps = this.getExtendsItemProps()
+ const props = this.getExtendsProps()
+ const componentProps = this.getExtendsComponentProps()
+ if (itemProps.triggerType) {
+ return itemProps.triggerType
+ } else if (props.triggerType) {
+ return props.triggerType
+ } else if (componentProps.triggerType) {
+ return componentProps.triggerType
+ }
+ }
+ getExtendsItemProps() {
+ return this['x-item-props'] || {}
+ }
+ getExtendsComponent() {
+ return this['x-component']
+ }
+ getExtendsRenderer() {
+ return this['x-render']
+ }
+ getExtendsEffect() {
+ return this['x-effect']
+ }
+ getExtendsProps() {
+ return this['x-props'] || {}
+ }
+ getExtendsComponentProps() {
+ return { ...this['x-props'], ...this['x-component-props'] }
+ }
+ /**
+ * getters
+ */
+ setProperty(key: string, schema: ISchema) {
+ this.properties = this.properties || {}
+ this.properties[key] = new Schema(schema, this)
+ return this.properties[key]
+ }
+ setProperties(properties: SchemaProperties) {
+ each, ISchema>(properties, (schema, key) => {
+ this.setProperty(key, schema)
+ })
+ return this.properties
+ }
+ setArrayItems(schema: ISchema) {
+ this.items = new Schema(schema, this)
+ return this.items
+ }
+ toJSON() {
+ const result: ISchema = this.getSelfProps()
+ if (isValid(this.properties)) {
+ result.properties = map(this.properties, schema => {
+ return schema.toJSON()
+ })
+ }
+ if (isValid(this.items)) {
+ result.items = isArr(this.items)
+ ? this.items.map(schema => schema.toJSON())
+ : this.items.toJSON()
+ }
+ if (isValid(this.additionalItems)) {
+ result.additionalItems = this.additionalItems.toJSON()
+ }
+ if (isValid(this.additionalProperties)) {
+ result.additionalProperties = this.additionalProperties.toJSON()
+ }
+ if (isValid(this.patternProperties)) {
+ result.patternProperties = map(this.patternProperties, schema => {
+ return schema.toJSON()
+ })
+ }
+ return result
+ }
+
+ fromJSON(json: ISchema = {}) {
+ if (typeof json === 'boolean') return json
+ if (json instanceof Schema) return json
+ Object.assign(this, json)
+ if (isValid(json.type)) {
+ this.type = lowercase(String(json.type))
+ }
+ if (isValid(json['x-component'])) {
+ this['x-component'] = lowercase(json['x-component'])
+ }
+ if (!isEmpty(json.properties)) {
+ this.properties = map(json.properties, item => {
+ return new Schema(item, this)
+ })
+ if (isValid(json.additionalProperties)) {
+ this.additionalProperties = new Schema(json.additionalProperties, this)
+ }
+ if (isValid(json.patternProperties)) {
+ this.patternProperties = map(json.patternProperties, item => {
+ return new Schema(item, this)
+ })
+ }
+ } else if (!isEmpty(json.items)) {
+ this.items = isArr(json.items)
+ ? map(json.items, item => new Schema(item, this))
+ : new Schema(json.items)
+ if (isValid(json.additionalItems)) {
+ this.additionalItems = new Schema(json.additionalItems, this)
+ }
+ }
+ return this
+ }
+ /**
+ * tools
+ */
+ isObject() {
+ return this.type === 'object'
+ }
+ isArray() {
+ return this.type === 'array'
+ }
+
+ mapProperties(callback?: (schema: Schema, key: string) => any) {
+ return this.getOrderProperties().map(({ schema, key }) => {
+ return callback(schema, key)
+ })
+ }
+
+ getOrderProperties() {
+ return Schema.getOrderProperties(this)
+ }
+
+ getOrderPatternProperties() {
+ return Schema.getOrderProperties(this, 'patternProperties')
+ }
+
+ mapPatternProperties(callback?: (schema: Schema, key: string) => any) {
+ return this.getOrderPatternProperties().map(({ schema, key }) => {
+ return callback(schema, key)
+ })
+ }
+
+ static getOrderProperties = (
+ schema: ISchema = {},
+ propertiesName: string = 'properties'
+ ) => {
+ const newSchema = new Schema(schema)
+ const properties = []
+ each(newSchema[propertiesName], (item, key) => {
+ const index = item['x-index']
+ if (typeof index === 'number') {
+ properties[index] = {
+ schema: item,
+ key
+ }
+ } else {
+ properties.push({ schema: item, key })
+ }
+ })
+ return properties
+ }
+}
diff --git a/packages/react-schema-renderer/src/shared/virtual-render.tsx b/packages/react-schema-renderer/src/shared/virtual-render.tsx
new file mode 100644
index 00000000000..671550f921b
--- /dev/null
+++ b/packages/react-schema-renderer/src/shared/virtual-render.tsx
@@ -0,0 +1,16 @@
+import React from 'react'
+import { globalThisPolyfill } from '@uform/shared'
+
+const env = {
+ portalDOM: null
+}
+
+export const render = (element: React.ReactElement) => {
+ if (globalThisPolyfill['document']) {
+ env.portalDOM =
+ env.portalDOM || globalThisPolyfill['document'].createElement('div')
+ return require('react-dom').createPortal(element, env.portalDOM)
+ } else {
+ return {element}
+ }
+}
diff --git a/packages/react-schema-renderer/src/types.ts b/packages/react-schema-renderer/src/types.ts
new file mode 100644
index 00000000000..6ffedd1265a
--- /dev/null
+++ b/packages/react-schema-renderer/src/types.ts
@@ -0,0 +1,180 @@
+import React from 'react'
+import { FormPathPattern } from '@uform/shared'
+import {
+ IFieldState,
+ IVirtualFieldState,
+ IMutators,
+ IFormProps,
+ IForm,
+ IFormActions,
+ IFormAsyncActions
+} from '@uform/react'
+import { ValidatePatternRules } from '@uform/validator'
+import { Schema } from './shared/schema'
+export interface ISchemaFieldProps {
+ path?: FormPathPattern
+}
+
+export type ComponentWithStyleComponent<
+ ComponentProps
+> = React.JSXElementConstructor & {
+ styledComponentId?: string
+ displayName?: string
+}
+
+export interface ISchemaFieldComponentProps extends IFieldState {
+ schema: Schema
+ mutators: IMutators
+ form: IForm
+ renderField: (
+ addtionKey: string | number,
+ reactKey?: string | number
+ ) => React.ReactElement
+}
+export interface ISchemaVirtualFieldComponentProps extends IVirtualFieldState {
+ schema: Schema
+ form: IForm
+ children: React.ReactElement[]
+ renderField: (
+ addtionKey: string | number,
+ reactKey?: string | number
+ ) => React.ReactElement
+}
+
+export interface ISchemaFieldWrapper {
+ (Traget: ISchemaFieldComponent):
+ | React.FC
+ | React.ClassicComponent
+}
+
+export type ISchemaFieldComponent = ComponentWithStyleComponent<
+ ISchemaFieldComponentProps
+> & {
+ __WRAPPERS__?: ISchemaFieldWrapper[]
+}
+
+export type ISchemaVirtualFieldComponent = ComponentWithStyleComponent<
+ ISchemaVirtualFieldComponentProps
+> & {
+ __WRAPPERS__?: ISchemaFieldWrapper[]
+}
+
+export interface ISchemaFormRegistry {
+ fields: {
+ [key: string]: ISchemaFieldComponent
+ }
+ virtualFields: {
+ [key: string]: ISchemaVirtualFieldComponent
+ }
+ wrappers?: ISchemaFieldWrapper[]
+ formItemComponent: React.JSXElementConstructor
+ formComponent: string | React.JSXElementConstructor
+}
+
+export type SchemaMessage = React.ReactNode
+
+export interface ISchema {
+ /** base json schema spec**/
+ title?: SchemaMessage
+ description?: SchemaMessage
+ default?: any
+ readOnly?: boolean
+ writeOnly?: boolean
+ type?: 'string' | 'object' | 'array' | 'number' | string
+ enum?: Array
+ const?: any
+ multipleOf?: number
+ maximum?: number
+ exclusiveMaximum?: number
+ minimum?: number
+ exclusiveMinimum?: number
+ maxLength?: number
+ minLength?: number
+ pattern?: string | RegExp
+ maxItems?: number
+ minItems?: number
+ uniqueItems?: boolean
+ maxProperties?: number
+ minProperties?: number
+ required?: string[] | boolean
+ format?: string
+ /** nested json schema spec **/
+ properties?: {
+ [key: string]: ISchema
+ }
+ items?: ISchema | ISchema[]
+ additionalItems?: ISchema
+ patternProperties?: {
+ [key: string]: ISchema
+ }
+ additionalProperties?: ISchema
+ /** extend json schema specs */
+ editable?: boolean
+ ['x-props']?: { [name: string]: any }
+ ['x-index']?: number
+ ['x-rules']?: ValidatePatternRules
+ ['x-component']?: string | React.JSXElementConstructor
+ ['x-component-props']?: { [name: string]: any }
+ ['x-render']?: (
+ props: T & {
+ renderComponent: () => React.ReactElement
+ }
+ ) => React.ReactElement
+ ['x-effect']?: (
+ dispatch: (type: string, payload: any) => void,
+ option?: object
+ ) => { [key: string]: any }
+}
+
+export interface ISchemaFormProps
+ extends IFormProps<
+ any,
+ any,
+ any,
+ ISchemaFormActions | ISchemaFormAsyncActions
+ > {
+ schema?: ISchema
+ component?: string | React.JSXElementConstructor
+ fields?: ISchemaFormRegistry['fields']
+ virtualFields?: ISchemaFormRegistry['virtualFields']
+ formComponent?: ISchemaFormRegistry['formComponent']
+ formItemComponent?: ISchemaFormRegistry['formItemComponent']
+}
+
+export interface IMarkupSchemaFieldProps extends ISchema {
+ name?: string
+}
+
+export type MergedFieldComponentProps = Partial<
+ ISchemaFieldComponentProps & ISchemaVirtualFieldComponentProps
+>
+
+export interface IConnectOptions {
+ valueName?: string
+ eventName?: string
+ defaultProps?: {}
+ getValueFromEvent?: (event?: any, value?: any) => any
+ getProps?: (
+ componentProps: {},
+ fieldProps: MergedFieldComponentProps
+ ) => {} | void
+ getComponent?: (
+ Target: any,
+ componentProps: {},
+ fieldProps: MergedFieldComponentProps
+ ) => React.JSXElementConstructor
+}
+
+export interface IConnectProps {
+ [key: string]: any
+}
+
+export interface ISchemaFormActions extends IFormActions {
+ getSchema(): Schema
+ getFormSchema(): Schema
+}
+
+export interface ISchemaFormAsyncActions extends IFormAsyncActions {
+ getSchema(): Promise
+ getFormSchema(): Promise
+}
diff --git a/packages/react-schema-renderer/tsconfig.json b/packages/react-schema-renderer/tsconfig.json
new file mode 100644
index 00000000000..1d669c29c46
--- /dev/null
+++ b/packages/react-schema-renderer/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./lib"
+ },
+ "include": ["./src/**/*.ts", "./src/**/*.tsx"],
+ "exclude": ["./src/__tests__/*"]
+}
diff --git a/packages/types/.npmignore b/packages/react-shared-components/.npmignore
similarity index 100%
rename from packages/types/.npmignore
rename to packages/react-shared-components/.npmignore
diff --git a/packages/builder-next/LICENSE.md b/packages/react-shared-components/LICENSE.md
similarity index 100%
rename from packages/builder-next/LICENSE.md
rename to packages/react-shared-components/LICENSE.md
diff --git a/packages/react-shared-components/README.md b/packages/react-shared-components/README.md
new file mode 100644
index 00000000000..0e8a2b774af
--- /dev/null
+++ b/packages/react-shared-components/README.md
@@ -0,0 +1,2 @@
+# @uform/react-shared
+> UForm React通用库
\ No newline at end of file
diff --git a/packages/utils/jest.config.js b/packages/react-shared-components/jest.config.js
similarity index 100%
rename from packages/utils/jest.config.js
rename to packages/react-shared-components/jest.config.js
diff --git a/packages/utils/package.json b/packages/react-shared-components/package.json
similarity index 83%
rename from packages/utils/package.json
rename to packages/react-shared-components/package.json
index c2bb19778ca..a479c36aa14 100644
--- a/packages/utils/package.json
+++ b/packages/react-shared-components/package.json
@@ -1,6 +1,6 @@
{
- "name": "@uform/utils",
- "version": "0.4.3",
+ "name": "@uform/react-shared-components",
+ "version": "0.4.0",
"license": "MIT",
"main": "lib",
"types": "lib/index.d.ts",
@@ -29,7 +29,7 @@
"@babel/runtime": "^7.4.4"
},
"dependencies": {
- "@uform/types": "^0.4.3",
- "camel-case": "^3.0.0"
+ "@uform/types": "^0.4.0",
+ "@uform/shared": "^0.4.0"
}
}
diff --git a/packages/react-shared-components/src/ArrayList.tsx b/packages/react-shared-components/src/ArrayList.tsx
new file mode 100644
index 00000000000..abf09c3f0b2
--- /dev/null
+++ b/packages/react-shared-components/src/ArrayList.tsx
@@ -0,0 +1,232 @@
+import React, { createContext, useContext, Fragment, useMemo } from 'react'
+import { isNum, isFn, toArr } from '@uform/shared'
+import { IArrayList, IArrayListProps } from './types'
+
+const ArrayContext = createContext({})
+
+export const ArrayList: IArrayList = props => {
+ return (
+
+ {props.children}
+
+ )
+}
+
+const useArrayList = (index: number = 0) => {
+ const {
+ value,
+ disabled,
+ editable,
+ minItems,
+ maxItems,
+ renders,
+ ...props
+ } = useContext(ArrayContext)
+
+ const renderWith = (
+ name: string,
+ render: (node: any) => React.ReactElement,
+ wrapper: any
+ ) => {
+ let children: any
+ if (renders && renders[name]) {
+ if (isFn(renders[name]) || renders[name].styledComponentId) {
+ children = renders[name](context.currentIndex)
+ } else {
+ children = render(renders[name])
+ }
+ } else {
+ children = render(renders[name])
+ }
+ if (isFn(wrapper)) {
+ return wrapper({ ...context, children }) ||
+ }
+ return children ||
+ }
+
+ const newValue = toArr(value)
+
+ const isEmpty = !newValue || (newValue && newValue.length <= 0)
+ const isDisable = disabled || editable === false
+ const allowMoveUp = newValue && newValue.length > 1 && !isDisable
+ const allowMoveDown = newValue && newValue.length > 1 && !isDisable
+ const allowRemove = isNum(minItems) ? newValue.length > minItems : !isDisable
+ const allowAddition = isNum(maxItems)
+ ? newValue.length <= maxItems
+ : !isDisable
+
+ const context = {
+ ...props,
+ currentIndex: index,
+ isEmpty,
+ isDisable,
+ allowRemove,
+ allowAddition,
+ allowMoveDown,
+ allowMoveUp,
+ renderWith
+ }
+
+ return context
+}
+
+const useComponent = (name: string) => {
+ const { components } = useContext(ArrayContext)
+ return useMemo(() => {
+ if (isFn(components[name]) || components[name].styledComponentId)
+ return components[name]
+ return (props: {}) => {
+ return React.isValidElement(components[name]) ? (
+ React.cloneElement(components[name], props)
+ ) : (
+
+ )
+ }
+ }, [])
+}
+
+const createButtonCls = (props: any = {}, hasText: any) => {
+ return {
+ className: `${hasText ? 'has-text' : ''} ${props.className || ''}`
+ }
+}
+
+ArrayList.useArrayList = useArrayList
+ArrayList.useComponent = useComponent
+
+ArrayList.Remove = ({ children, component, index, ...props }) => {
+ const { allowRemove, renderWith } = ArrayList.useArrayList(index)
+ const Button = ArrayList.useComponent(component)
+ const RemoveIcon = ArrayList.useComponent('RemoveIcon')
+ if (allowRemove) {
+ return renderWith(
+ 'renderRemove',
+ text => (
+
+
+ {text}
+
+ ),
+ children
+ )
+ }
+
+ return React.createElement(React.Fragment)
+}
+
+ArrayList.Remove.defaultProps = {
+ component: 'CircleButton'
+}
+
+ArrayList.Addition = ({ children, component, ...props }) => {
+ const { allowAddition, renderWith } = ArrayList.useArrayList()
+ const Button = ArrayList.useComponent(component)
+ const AdditionIcon = ArrayList.useComponent('AdditionIcon')
+
+ if (allowAddition) {
+ return renderWith(
+ 'renderAddition',
+ text => (
+
+
+ {text}
+
+ ),
+ children
+ )
+ }
+ return React.createElement(React.Fragment)
+}
+
+ArrayList.Addition.defaultProps = {
+ component: 'TextButton'
+}
+
+ArrayList.MoveUp = ({ children, component, index, ...props }) => {
+ const { allowMoveUp, renderWith } = ArrayList.useArrayList(index)
+ const Button = ArrayList.useComponent(component)
+ const MoveUpIcon = ArrayList.useComponent('MoveUpIcon')
+
+ if (allowMoveUp) {
+ return renderWith(
+ 'renderMoveUp',
+ text => (
+
+
+ {text}
+
+ ),
+ children
+ )
+ }
+ return React.createElement(React.Fragment)
+}
+
+ArrayList.MoveUp.defaultProps = {
+ component: 'CircleButton'
+}
+
+ArrayList.MoveDown = ({ children, component, index, ...props }) => {
+ const { allowMoveDown, renderWith } = ArrayList.useArrayList(index)
+ const Button = ArrayList.useComponent(component)
+ const MoveUpIcon = ArrayList.useComponent('MoveDownIcon')
+
+ if (allowMoveDown) {
+ return renderWith(
+ 'renderMoveDown',
+ text => (
+
+
+ {text}
+
+ ),
+ children
+ )
+ }
+ return React.createElement(React.Fragment)
+}
+
+ArrayList.MoveDown.defaultProps = {
+ component: 'CircleButton'
+}
+
+ArrayList.Empty = ({ children, component, ...props }) => {
+ const { allowAddition, isEmpty, renderWith } = ArrayList.useArrayList()
+ const Button = ArrayList.useComponent(component)
+ const AdditionIcon = ArrayList.useComponent('AdditionIcon')
+ let addtion: any
+ if (allowAddition) {
+ addtion = renderWith('renderAddition', text => (
+
+
+ {text}
+
+ ))
+ }
+
+ if (isEmpty) {
+ return renderWith(
+ 'renderEmpty',
+ text => {
+ return (
+
+
+ {text}
+ {addtion}
+
+ )
+ },
+ children
+ )
+ }
+ return React.createElement(React.Fragment)
+}
+
+ArrayList.Empty.defaultProps = {
+ component: 'TextButton'
+}
diff --git a/packages/react-shared-components/src/PasswordStrength.tsx b/packages/react-shared-components/src/PasswordStrength.tsx
new file mode 100644
index 00000000000..4495f10c30f
--- /dev/null
+++ b/packages/react-shared-components/src/PasswordStrength.tsx
@@ -0,0 +1,156 @@
+import React, { Fragment } from 'react'
+import { IPasswordStrengthProps } from './types'
+import { isFn } from '@uform/shared'
+
+var isNum = function(c) {
+ return c >= 48 && c <= 57
+}
+var isLower = function(c) {
+ return c >= 97 && c <= 122
+}
+var isUpper = function(c) {
+ return c >= 65 && c <= 90
+}
+var isSymbol = function(c) {
+ return !(isLower(c) || isUpper(c) || isNum(c))
+}
+var isLetter = function(c) {
+ return isLower(c) || isUpper(c)
+}
+
+const getStrength = val => {
+ if (!val) return 0
+ let num = 0
+ let lower = 0
+ let upper = 0
+ let symbol = 0
+ let MNS = 0
+ let rep = 0
+ let repC = 0
+ let consecutive = 0
+ let sequential = 0
+ const len = () => num + lower + upper + symbol
+ const callme = () => {
+ var re = num > 0 ? 1 : 0
+ re += lower > 0 ? 1 : 0
+ re += upper > 0 ? 1 : 0
+ re += symbol > 0 ? 1 : 0
+ if (re > 2 && len() >= 8) {
+ return re + 1
+ } else {
+ return 0
+ }
+ }
+ for (var i = 0; i < val.length; i++) {
+ var c = val.charCodeAt(i)
+ if (isNum(c)) {
+ num++
+ if (i !== 0 && i !== val.length - 1) {
+ MNS++
+ }
+ if (i > 0 && isNum(val.charCodeAt(i - 1))) {
+ consecutive++
+ }
+ } else if (isLower(c)) {
+ lower++
+ if (i > 0 && isLower(val.charCodeAt(i - 1))) {
+ consecutive++
+ }
+ } else if (isUpper(c)) {
+ upper++
+ if (i > 0 && isUpper(val.charCodeAt(i - 1))) {
+ consecutive++
+ }
+ } else {
+ symbol++
+ if (i !== 0 && i !== val.length - 1) {
+ MNS++
+ }
+ }
+ var exists = false
+ for (var j = 0; j < val.length; j++) {
+ if (val[i] === val[j] && i !== j) {
+ exists = true
+ repC += Math.abs(val.length / (j - i))
+ }
+ }
+ if (exists) {
+ rep++
+ var unique = val.length - rep
+ repC = unique ? Math.ceil(repC / unique) : Math.ceil(repC)
+ }
+ if (i > 1) {
+ var last1 = val.charCodeAt(i - 1)
+ var last2 = val.charCodeAt(i - 2)
+ if (isLetter(c)) {
+ if (isLetter(last1) && isLetter(last2)) {
+ var v = val.toLowerCase()
+ var vi = v.charCodeAt(i)
+ var vi1 = v.charCodeAt(i - 1)
+ var vi2 = v.charCodeAt(i - 2)
+ if (vi - vi1 === vi1 - vi2 && Math.abs(vi - vi1) === 1) {
+ sequential++
+ }
+ }
+ } else if (isNum(c)) {
+ if (isNum(last1) && isNum(last2)) {
+ if (c - last1 === last1 - last2 && Math.abs(c - last1) === 1) {
+ sequential++
+ }
+ }
+ } else {
+ if (isSymbol(last1) && isSymbol(last2)) {
+ if (c - last1 === last1 - last2 && Math.abs(c - last1) === 1) {
+ sequential++
+ }
+ }
+ }
+ }
+ }
+ let sum = 0
+ let length = len()
+ sum += 4 * length
+ if (lower > 0) {
+ sum += 2 * (length - lower)
+ }
+ if (upper > 0) {
+ sum += 2 * (length - upper)
+ }
+ if (num !== length) {
+ sum += 4 * num
+ }
+ sum += 6 * symbol
+ sum += 2 * MNS
+ sum += 2 * callme()
+ if (length === lower + upper) {
+ sum -= length
+ }
+ if (length === num) {
+ sum -= num
+ }
+ sum -= repC
+ sum -= 2 * consecutive
+ sum -= 3 * sequential
+ sum = sum < 0 ? 0 : sum
+ sum = sum > 100 ? 100 : sum
+
+ if (sum >= 80) {
+ return 100
+ } else if (sum >= 60) {
+ return 80
+ } else if (sum >= 40) {
+ return 60
+ } else if (sum >= 20) {
+ return 40
+ } else {
+ return 20
+ }
+}
+
+export const PasswordStrength: React.FC = props => {
+ if (isFn(props.children)) {
+ return props.children(getStrength(String(props.value)))
+ } else {
+ return {props.children}
+ }
+}
diff --git a/packages/react-shared-components/src/PreviewText.tsx b/packages/react-shared-components/src/PreviewText.tsx
new file mode 100644
index 00000000000..b2ddca4e642
--- /dev/null
+++ b/packages/react-shared-components/src/PreviewText.tsx
@@ -0,0 +1,31 @@
+import React from 'react'
+import { IPreviewTextProps } from './types'
+
+export const PreviewText: React.FC = props => {
+ let value: any
+ if (props.dataSource && props.dataSource.length) {
+ let find = props.dataSource.filter(({ value }) =>
+ Array.isArray(props.value)
+ ? props.value.some(val => val == value)
+ : props.value == value
+ )
+ value = find.map(item => item.label).join(' , ')
+ } else {
+ value = Array.isArray(props.value)
+ ? props.value.join(' ~ ')
+ : String(
+ props.value === undefined || props.value === null ? '' : props.value
+ )
+ }
+ return (
+
+ {props.addonBefore ? ' ' + props.addonBefore : ''}
+ {props.innerBefore ? ' ' + props.innerBefore : ''}
+ {props.addonTextBefore ? ' ' + props.addonTextBefore : ''}
+ {!value ? 'N/A' : value}
+ {props.addonTextAfter ? ' ' + props.addonTextAfter : ''}
+ {props.innerAfter ? ' ' + props.innerAfter : ''}
+ {props.addonAfter ? ' ' + props.addonAfter : ''}
+
+ )
+}
diff --git a/packages/react-shared-components/src/index.ts b/packages/react-shared-components/src/index.ts
new file mode 100644
index 00000000000..c7a46558638
--- /dev/null
+++ b/packages/react-shared-components/src/index.ts
@@ -0,0 +1,4 @@
+export * from './ArrayList'
+export * from './PreviewText'
+export * from './PasswordStrength'
+export * from './types'
diff --git a/packages/react-shared-components/src/types.ts b/packages/react-shared-components/src/types.ts
new file mode 100644
index 00000000000..eb4949ed662
--- /dev/null
+++ b/packages/react-shared-components/src/types.ts
@@ -0,0 +1,88 @@
+export type IArrayList = React.FC & {
+ Remove: React.FC
+ Addition: React.FC
+ MoveUp: React.FC
+ MoveDown: React.FC
+ Empty: React.FC
+ useComponent: (name: string) => ReactComponent
+ useArrayList: (index?: number) => IArrayListProps & ArrayListInfo
+}
+
+interface ArrayListInfo {
+ currentIndex: number
+ isEmpty: boolean
+ isDisable: boolean
+ allowRemove: boolean
+ allowAddition: boolean
+ allowMoveDown: boolean
+ allowMoveUp: boolean
+ renderWith: (
+ name: string,
+ render: (node: any) => React.ReactElement,
+ wrapper?: any
+ ) => any
+}
+
+type ReactComponent = React.JSXElementConstructor
+
+type ReactRenderPropsChildren =
+ | React.ReactNode
+ | ((props: T) => React.ReactElement)
+
+export interface IArrayListProps {
+ value?: any[]
+ maxItems?: number
+ minItems?: number
+ editable?: boolean
+ disabled?: boolean
+ components?: {
+ CircleButton?: ReactComponent
+ TextButton?: ReactComponent
+ AdditionIcon?: ReactComponent
+ RemoveIcon?: ReactComponent
+ MoveDownIcon?: ReactComponent
+ MoveUpIcon?: ReactComponent
+ }
+ renders?: {
+ renderAddition?: ReactRenderPropsChildren
+ renderEmpty?: ReactRenderPropsChildren
+ renderMoveDown?: ReactRenderPropsChildren
+ renderMoveUp?: ReactRenderPropsChildren
+ renderRemove?: ReactRenderPropsChildren
+ }
+ context?: any
+}
+
+export interface IArrayListOperatorProps {
+ children?: React.ReactElement | ((method: T) => React.ReactElement)
+}
+
+export interface IPreviewTextProps {
+ className?: React.ReactText
+ dataSource?: any[]
+ value?: any
+ addonBefore?: React.ReactNode
+ innerBefore?: React.ReactNode
+ addonTextBefore?: React.ReactNode
+ addonTextAfter?: React.ReactNode
+ innerAfter?: React.ReactNode
+ addonAfter?: React.ReactNode
+}
+
+export interface OperatorButtonProps {
+ index?: number
+ children?: ReactRenderPropsChildren
+ component?: string
+ onClick?: (event: any) => void
+}
+
+export interface IPasswordStrengthProps {
+ value?: React.ReactText
+ children?: ReactRenderPropsChildren
+}
+
+export type IArrayListRemoveProps = OperatorButtonProps
+export type IArrayListAdditionProps = OperatorButtonProps
+export type IArrayListMoveUpProps = OperatorButtonProps
+export type IArrayListMoveDownProps = OperatorButtonProps
+export type IArrayListEmptyProps = IArrayListAdditionProps
diff --git a/packages/react-shared-components/tsconfig.json b/packages/react-shared-components/tsconfig.json
new file mode 100644
index 00000000000..1d669c29c46
--- /dev/null
+++ b/packages/react-shared-components/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./lib"
+ },
+ "include": ["./src/**/*.ts", "./src/**/*.tsx"],
+ "exclude": ["./src/__tests__/*"]
+}
diff --git a/packages/types/LICENSE.md b/packages/react/LICENSE.md
similarity index 100%
rename from packages/types/LICENSE.md
rename to packages/react/LICENSE.md
diff --git a/packages/react/README.md b/packages/react/README.md
index 6162875a43d..749d5be34d4 100644
--- a/packages/react/README.md
+++ b/packages/react/README.md
@@ -1,2 +1,299 @@
# @uform/react
-> UForm React实现
\ No newline at end of file
+
+> UForm React Pure Package
+
+
+### ArrayStringList
+
+```jsx
+import React, { useState } from 'react'
+import {
+ Form,
+ Field,
+ FormPath,
+ createFormActions,
+ FormSpy,
+ FormProvider,
+ FormConsumer,
+ FormEffectHooks
+} from './src'
+
+const actions = createFormActions()
+
+const Input = props => (
+
+ {({ state, mutators }) => (
+
+
+ {state.errors}
+ {state.warnings}
+
+ )}
+
+)
+
+const { onFormInit$, onFormInputChange$, onFieldInputChange$ } = FormEffectHooks
+
+const App = () => {
+ const [values, setValues] = useState({})
+ const [editable, setEditable] = useState(true)
+ return (
+
+
+
+ )
+}
+
+ReactDOM.render(, document.getElementById('root'))
+```
+
+### ArrayObjectList
+
+```jsx
+import React, { useState } from 'react'
+import {
+ Form,
+ Field,
+ FormPath,
+ createFormActions,
+ FormSpy,
+ FormProvider,
+ FormConsumer,
+ FormEffectHooks
+} from './src'
+
+const actions = createFormActions()
+
+const Input = props => (
+
+ {({ state, mutators }) => (
+
+
+ {state.errors}
+ {state.warnings}
+
+ )}
+
+)
+
+const { onFormInit$, onFormInputChange$, onFieldInputChange$ } = FormEffectHooks
+
+const App = () => {
+ const [values, setValues] = useState({})
+ const [editable, setEditable] = useState(true)
+ return (
+
+
+
+ )
+}
+
+ReactDOM.render(, document.getElementById('root'))
+```
+
+### Dynamic Object
+
+```jsx
+import React, { useState } from 'react'
+import {
+ Form,
+ Field,
+ FormPath,
+ createFormActions,
+ FormSpy,
+ FormProvider,
+ FormConsumer,
+ FormEffectHooks
+} from './src'
+
+const actions = createFormActions()
+
+const Input = props => (
+
+ {({ state, mutators }) => (
+
+
+ {state.errors}
+ {state.warnings}
+
+ )}
+
+)
+
+const { onFormInit$, onFormInputChange$, onFieldInputChange$ } = FormEffectHooks
+
+const App = () => {
+ const [values, setValues] = useState({})
+ const [editable, setEditable] = useState(true)
+ return (
+
+
+
+ )
+}
+
+ReactDOM.render(, document.getElementById('root'))
+```
diff --git a/packages/react/package.json b/packages/react/package.json
index 1c9aa1b1338..47a8480d8a8 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -1,12 +1,13 @@
{
"name": "@uform/react",
- "version": "0.4.3",
+ "version": "0.4.0",
"license": "MIT",
"main": "lib",
"repository": {
"type": "git",
"url": "git+https://github.com/alibaba/uform.git"
},
+ "types": "lib/index.d.ts",
"bugs": {
"url": "https://github.com/alibaba/uform/issues"
},
@@ -14,30 +15,27 @@
"engines": {
"npm": ">=3.0.0"
},
- "types": "lib/index.d.ts",
"scripts": {
- "build": "tsc --declaration"
+ "build": "rm -rf lib && tsc --declaration"
},
"devDependencies": {
"typescript": "^3.5.2"
},
"peerDependencies": {
"@babel/runtime": "^7.4.4",
+ "scheduler": ">=0.11.2",
"@types/react": "^16.8.23",
"react": ">=16.8.0",
- "react-dom": ">=16.8.0"
+ "react-dom": ">=16.8.0",
+ "react-eva": "^1.0.0",
+ "rxjs": "^6.5.1"
},
"dependencies": {
- "@uform/core": "^0.4.3",
- "@uform/types": "^0.4.3",
- "@uform/utils": "^0.4.3",
- "@uform/validator": "^0.4.3",
- "pascal-case": "^2.0.1",
- "react-eva": "^1.0.0",
- "rxjs-compat": "^6.5.1"
+ "@uform/core": "^0.4.0",
+ "@uform/shared": "^0.4.0"
},
"publishConfig": {
"access": "public"
},
"gitHead": "4d068dad6183e8da294a4c899a158326c0b0b050"
-}
+}
\ No newline at end of file
diff --git a/packages/react/src/__tests__/actions.spec.tsx b/packages/react/src/__tests__/actions.spec.tsx
new file mode 100644
index 00000000000..3764eab2358
--- /dev/null
+++ b/packages/react/src/__tests__/actions.spec.tsx
@@ -0,0 +1,12 @@
+describe('test all apis',()=>{
+ //todo
+})
+
+describe('major scenes',()=>{
+ //todo
+})
+
+
+describe('bugfix',()=>{
+ //todo
+})
\ No newline at end of file
diff --git a/packages/react/src/__tests__/consumer.spec.tsx b/packages/react/src/__tests__/consumer.spec.tsx
new file mode 100644
index 00000000000..3764eab2358
--- /dev/null
+++ b/packages/react/src/__tests__/consumer.spec.tsx
@@ -0,0 +1,12 @@
+describe('test all apis',()=>{
+ //todo
+})
+
+describe('major scenes',()=>{
+ //todo
+})
+
+
+describe('bugfix',()=>{
+ //todo
+})
\ No newline at end of file
diff --git a/packages/react/src/__tests__/effects.spec.tsx b/packages/react/src/__tests__/effects.spec.tsx
new file mode 100644
index 00000000000..3764eab2358
--- /dev/null
+++ b/packages/react/src/__tests__/effects.spec.tsx
@@ -0,0 +1,12 @@
+describe('test all apis',()=>{
+ //todo
+})
+
+describe('major scenes',()=>{
+ //todo
+})
+
+
+describe('bugfix',()=>{
+ //todo
+})
\ No newline at end of file
diff --git a/packages/react/src/__tests__/field.spec.tsx b/packages/react/src/__tests__/field.spec.tsx
new file mode 100644
index 00000000000..3764eab2358
--- /dev/null
+++ b/packages/react/src/__tests__/field.spec.tsx
@@ -0,0 +1,12 @@
+describe('test all apis',()=>{
+ //todo
+})
+
+describe('major scenes',()=>{
+ //todo
+})
+
+
+describe('bugfix',()=>{
+ //todo
+})
\ No newline at end of file
diff --git a/packages/react/src/__tests__/form.spec.tsx b/packages/react/src/__tests__/form.spec.tsx
new file mode 100644
index 00000000000..041f97923ce
--- /dev/null
+++ b/packages/react/src/__tests__/form.spec.tsx
@@ -0,0 +1,71 @@
+import React from 'react'
+import {
+ Form,
+ Field,
+ createFormActions,
+ FormEffectHooks,
+ IFieldProps
+} from '../index'
+import { render } from '@testing-library/react'
+
+const Input: React.FC = props => (
+
+ {({ state, mutators }) => (
+
+
+ {state.errors}
+ {state.warnings}
+
+ )}
+
+)
+
+const { onFieldValueChange$ } = FormEffectHooks
+
+describe('test all apis', () => {
+ test('Form', async () => {
+ const actions = createFormActions()
+ const onSubmitHandler = jest.fn()
+ const onValidateFailedHandler = jest.fn()
+ render(
+
+ )
+ try {
+ await actions.submit()
+ } catch (e) {
+ expect(e).toEqual([{ path: 'aaa', messages: ['This field is required'] }])
+ }
+ actions.setFieldState('aaa', state => {
+ state.value = '123'
+ })
+ await actions.submit()
+ expect(onSubmitHandler).toBeCalledWith({ aaa: 'hello world' })
+ })
+})
+
+describe('major scenes', () => {
+ //todo
+})
+
+describe('bugfix', () => {
+ //todo
+})
diff --git a/packages/react/src/__tests__/provider.spec.tsx b/packages/react/src/__tests__/provider.spec.tsx
new file mode 100644
index 00000000000..3764eab2358
--- /dev/null
+++ b/packages/react/src/__tests__/provider.spec.tsx
@@ -0,0 +1,12 @@
+describe('test all apis',()=>{
+ //todo
+})
+
+describe('major scenes',()=>{
+ //todo
+})
+
+
+describe('bugfix',()=>{
+ //todo
+})
\ No newline at end of file
diff --git a/packages/react/src/__tests__/spy.spec.tsx b/packages/react/src/__tests__/spy.spec.tsx
new file mode 100644
index 00000000000..3764eab2358
--- /dev/null
+++ b/packages/react/src/__tests__/spy.spec.tsx
@@ -0,0 +1,12 @@
+describe('test all apis',()=>{
+ //todo
+})
+
+describe('major scenes',()=>{
+ //todo
+})
+
+
+describe('bugfix',()=>{
+ //todo
+})
\ No newline at end of file
diff --git a/packages/react/src/__tests__/useDirty.spec.tsx b/packages/react/src/__tests__/useDirty.spec.tsx
new file mode 100644
index 00000000000..3764eab2358
--- /dev/null
+++ b/packages/react/src/__tests__/useDirty.spec.tsx
@@ -0,0 +1,12 @@
+describe('test all apis',()=>{
+ //todo
+})
+
+describe('major scenes',()=>{
+ //todo
+})
+
+
+describe('bugfix',()=>{
+ //todo
+})
\ No newline at end of file
diff --git a/packages/react/src/__tests__/useField.spec.tsx b/packages/react/src/__tests__/useField.spec.tsx
new file mode 100644
index 00000000000..3764eab2358
--- /dev/null
+++ b/packages/react/src/__tests__/useField.spec.tsx
@@ -0,0 +1,12 @@
+describe('test all apis',()=>{
+ //todo
+})
+
+describe('major scenes',()=>{
+ //todo
+})
+
+
+describe('bugfix',()=>{
+ //todo
+})
\ No newline at end of file
diff --git a/packages/react/src/__tests__/useForceUpdate.spec.tsx b/packages/react/src/__tests__/useForceUpdate.spec.tsx
new file mode 100644
index 00000000000..3764eab2358
--- /dev/null
+++ b/packages/react/src/__tests__/useForceUpdate.spec.tsx
@@ -0,0 +1,12 @@
+describe('test all apis',()=>{
+ //todo
+})
+
+describe('major scenes',()=>{
+ //todo
+})
+
+
+describe('bugfix',()=>{
+ //todo
+})
\ No newline at end of file
diff --git a/packages/react/src/__tests__/useForm.spec.tsx b/packages/react/src/__tests__/useForm.spec.tsx
new file mode 100644
index 00000000000..3764eab2358
--- /dev/null
+++ b/packages/react/src/__tests__/useForm.spec.tsx
@@ -0,0 +1,12 @@
+describe('test all apis',()=>{
+ //todo
+})
+
+describe('major scenes',()=>{
+ //todo
+})
+
+
+describe('bugfix',()=>{
+ //todo
+})
\ No newline at end of file
diff --git a/packages/react/src/__tests__/virtual.spec.tsx b/packages/react/src/__tests__/virtual.spec.tsx
new file mode 100644
index 00000000000..3764eab2358
--- /dev/null
+++ b/packages/react/src/__tests__/virtual.spec.tsx
@@ -0,0 +1,12 @@
+describe('test all apis',()=>{
+ //todo
+})
+
+describe('major scenes',()=>{
+ //todo
+})
+
+
+describe('bugfix',()=>{
+ //todo
+})
\ No newline at end of file
diff --git a/packages/react/src/components/Field.tsx b/packages/react/src/components/Field.tsx
new file mode 100644
index 00000000000..1011f64b6cb
--- /dev/null
+++ b/packages/react/src/components/Field.tsx
@@ -0,0 +1,53 @@
+import React from 'react'
+import { useField } from '../hooks/useField'
+import { isFn } from '@uform/shared'
+import { IMutators, IFieldState } from '@uform/core'
+import { IFieldProps } from '../types'
+import { getValueFromEvent } from '../shared'
+
+const createFieldMutators = (
+ mutators: IMutators,
+ props: IFieldProps,
+ state: IFieldState
+): IMutators => {
+ return {
+ ...mutators,
+ change: (...args) => {
+ args[0] = isFn(props.getValueFromEvent)
+ ? props.getValueFromEvent(...args)
+ : args[0]
+ mutators.change(...args.map(event => getValueFromEvent(event)))
+ if (props.triggerType === 'onChange') {
+ mutators.validate()
+ }
+ },
+ blur: () => {
+ mutators.blur()
+ if (props.triggerType === 'onBlur') {
+ mutators.validate()
+ }
+ }
+ }
+}
+
+export const Field: React.FunctionComponent = props => {
+ const { state, props: innerProps, mutators, form } = useField(props)
+ if (!state.visible || !state.display) return
+ if (isFn(props.children)) {
+ return props.children({
+ form,
+ state,
+ props: innerProps,
+ mutators: createFieldMutators(mutators, props, state)
+ })
+ } else {
+ return {props.children}
+ }
+}
+
+Field.displayName = 'ReactInternalField'
+
+Field.defaultProps = {
+ path: '',
+ triggerType: 'onChange'
+}
diff --git a/packages/react/src/components/Form.tsx b/packages/react/src/components/Form.tsx
new file mode 100644
index 00000000000..948feb95544
--- /dev/null
+++ b/packages/react/src/components/Form.tsx
@@ -0,0 +1,18 @@
+import React from 'react'
+import { isFn } from '@uform/shared'
+import { useForm } from '../hooks/useForm'
+import FormContext from '../context'
+import { IFormProps, IFormActions, IFormAsyncActions } from '../types'
+
+export const Form: React.FunctionComponent<
+ IFormProps
+> = (props = {}) => {
+ const form = useForm(props)
+ return (
+
+ {isFn(props.children) ? props.children(form) : props.children}
+
+ )
+}
+
+Form.displayName = 'ReactInternalForm'
diff --git a/packages/react/src/components/FormConsumer.tsx b/packages/react/src/components/FormConsumer.tsx
new file mode 100644
index 00000000000..49ac2963a19
--- /dev/null
+++ b/packages/react/src/components/FormConsumer.tsx
@@ -0,0 +1,46 @@
+import React from 'react'
+import { isFn, deprecate } from '@uform/shared'
+import { FormSpy } from './FormSpy'
+import { IForm, LifeCycleTypes } from '@uform/core'
+import { IFormConsumerProps, IFormConsumerAPI } from '../types'
+
+const transformStatus = (type: string) => {
+ switch (type) {
+ case LifeCycleTypes.ON_FORM_INIT:
+ return 'initialize'
+ case LifeCycleTypes.ON_FORM_SUBMIT_START:
+ return 'submitting'
+ case LifeCycleTypes.ON_FORM_SUBMIT_END:
+ return 'submitted'
+ case LifeCycleTypes.ON_FORM_VALIDATE_START:
+ return 'validating'
+ case LifeCycleTypes.ON_FORM_VALIDATE_END:
+ return 'validated'
+ default:
+ return type
+ }
+}
+
+const transformFormAPI = (api: IForm, type: string): IFormConsumerAPI => {
+ deprecate('FormConsumer', 'Please use FormSpy Component.')
+ return {
+ status: transformStatus(type),
+ state: api.getFormState(),
+ submit: api.submit,
+ reset: api.reset
+ }
+}
+
+export const FormConsumer: React.FunctionComponent<
+ IFormConsumerProps
+> = props => {
+ return (
+
+ {({ form, type }) => {
+ return isFn(props.children)
+ ? props.children(transformFormAPI(form, type))
+ : props.children
+ }}
+
+ )
+}
diff --git a/packages/react/src/components/FormProvider.tsx b/packages/react/src/components/FormProvider.tsx
new file mode 100644
index 00000000000..ac73cc3cbdd
--- /dev/null
+++ b/packages/react/src/components/FormProvider.tsx
@@ -0,0 +1,12 @@
+import React, { useMemo } from 'react'
+import { BroadcastContext } from '../context'
+import { Broadcast } from '@uform/shared'
+
+const { Provider } = BroadcastContext
+
+export const FormProvider: React.FunctionComponent = props => {
+ const broadcast = useMemo(() => {
+ return new Broadcast()
+ }, [])
+ return {props.children}
+}
diff --git a/packages/react/src/components/FormSpy.tsx b/packages/react/src/components/FormSpy.tsx
new file mode 100644
index 00000000000..444ac63cdbd
--- /dev/null
+++ b/packages/react/src/components/FormSpy.tsx
@@ -0,0 +1,73 @@
+import {
+ useContext,
+ useMemo,
+ useRef,
+ useEffect,
+ useCallback,
+ useState,
+ useReducer
+} from 'react'
+import { FormHeartSubscriber, LifeCycleTypes } from '@uform/core'
+import { isFn, isStr, FormPath, isArr } from '@uform/shared'
+import { IFormSpyProps } from '../types'
+import FormContext, { BroadcastContext } from '../context'
+
+export const FormSpy: React.FunctionComponent = props => {
+ const broadcast = useContext(BroadcastContext)
+ const form = useContext(FormContext)
+ const initializedRef = useRef(false)
+ const [type, setType] = useState(LifeCycleTypes.ON_FORM_INIT)
+ const [state, dispatch] = useReducer(
+ (state, action) => props.reducer(state, action, form),
+ {}
+ )
+ const subscriber = useCallback(({ type, payload }) => {
+ if (initializedRef.current) return
+ if (isStr(props.selector) && FormPath.parse(props.selector).match(type)) {
+ setType(type)
+ dispatch({
+ type,
+ payload
+ })
+ } else if (isArr(props.selector) && props.selector.indexOf(type) > -1) {
+ setType(type)
+ dispatch({
+ type,
+ payload
+ })
+ }
+ }, [])
+ useMemo(() => {
+ initializedRef.current = true
+ if (form) {
+ form.subscribe(subscriber)
+ } else if (broadcast) {
+ broadcast.subscribe(subscriber)
+ }
+ initializedRef.current = false
+ }, [])
+ useEffect(() => {
+ return () => {
+ if (form) {
+ form.unsubscribe(subscriber)
+ } else if (broadcast) {
+ broadcast.unsubscribe(subscriber)
+ }
+ }
+ }, [])
+ if (isFn(props.children)) {
+ const formApi = form ? form : broadcast && broadcast.getContext()
+ return props.children({ form: formApi, type, state })
+ } else {
+ return props.children
+ }
+}
+
+FormSpy.displayName = 'ReactInternalFormSpy'
+
+FormSpy.defaultProps = {
+ selector: `*`,
+ reducer: (state, action) => {
+ return state
+ }
+}
diff --git a/packages/react/src/components/VirtualField.tsx b/packages/react/src/components/VirtualField.tsx
new file mode 100644
index 00000000000..84bacbdb4fe
--- /dev/null
+++ b/packages/react/src/components/VirtualField.tsx
@@ -0,0 +1,26 @@
+import React from 'react'
+import { useVirtualField } from '../hooks/useVirtualField'
+import { isFn } from '@uform/shared'
+import { IVirtualFieldProps } from '../types'
+
+export const VirtualField: React.FunctionComponent<
+ IVirtualFieldProps
+> = props => {
+ const { state, props: innerProps, form } = useVirtualField(props)
+ if (!state.visible || !state.display) return
+ if (isFn(props.children)) {
+ return props.children({
+ form,
+ state,
+ props: innerProps
+ })
+ } else {
+ return props.children
+ }
+}
+
+VirtualField.displayName = 'ReactInternalVirtualField'
+
+VirtualField.defaultProps = {
+ path: ''
+}
diff --git a/packages/react/src/context.ts b/packages/react/src/context.ts
new file mode 100644
index 00000000000..4097188b652
--- /dev/null
+++ b/packages/react/src/context.ts
@@ -0,0 +1,7 @@
+import { createContext } from 'react'
+import { Broadcast } from '@uform/shared'
+import { IForm } from '@uform/core'
+
+export const BroadcastContext = createContext(null)
+
+export default createContext(null)
diff --git a/packages/react/src/decorators/connect.ts b/packages/react/src/decorators/connect.ts
deleted file mode 100644
index 040870a4a46..00000000000
--- a/packages/react/src/decorators/connect.ts
+++ /dev/null
@@ -1,203 +0,0 @@
-import React, { PureComponent } from 'react'
-import { ISchema, Dispatcher } from '@uform/types'
-import { isArr, isFn, each } from '../utils'
-import { IEventTargetOption, IFieldProps } from '../type'
-
-const isEvent = (candidate: React.SyntheticEvent): boolean =>
- !!(candidate && candidate.stopPropagation && candidate.preventDefault)
-
-const isReactNative =
- typeof window !== 'undefined' &&
- window.navigator &&
- window.navigator.product &&
- window.navigator.product === 'ReactNative'
-
-const getSelectedValues = (options?: IEventTargetOption[]) => {
- const result = []
- if (options) {
- for (let index = 0; index < options.length; index++) {
- const option = options[index]
- if (option.selected) {
- result.push(option.value)
- }
- }
- }
- return result
-}
-
-// TODO 需要 any ?
-const getValue = (
- event: React.SyntheticEvent | any,
- isReactNative: boolean
-) => {
- if (isEvent(event)) {
- if (
- !isReactNative &&
- event.nativeEvent &&
- event.nativeEvent.text !== undefined
- ) {
- return event.nativeEvent.text
- }
- if (isReactNative && event.nativeEvent !== undefined) {
- return event.nativeEvent.text
- }
-
- const detypedEvent = event
- const {
- target: { type, value, checked, files },
- dataTransfer
- } = detypedEvent
-
- if (type === 'checkbox') {
- return !!checked
- }
-
- if (type === 'file') {
- return files || (dataTransfer && dataTransfer.files)
- }
-
- if (type === 'select-multiple') {
- return getSelectedValues(event.target.options)
- }
- return value
- }
- return event
-}
-
-const createEnum = (enums: any, enumNames: string | any[]) => {
- if (isArr(enums)) {
- return enums.map((item, index) => {
- if (typeof item === 'object') {
- return {
- ...item
- }
- } else {
- return {
- ...item,
- label: isArr(enumNames) ? enumNames[index] || item : item,
- value: item
- }
- }
- })
- }
-
- return []
-}
-
-const bindEffects = (
- props: IConnectProps,
- effect: ISchema['x-effect'],
- dispatch: Dispatcher
-) => {
- each(effect(dispatch, { ...props }), (event, key) => {
- const prevEvent = key === 'onChange' ? props[key] : undefined
- props[key] = (...args) => {
- if (isFn(prevEvent)) {
- prevEvent(...args)
- }
- if (isFn(event)) {
- return event(...args)
- }
- }
- })
- return props
-}
-
-// 这个不枚举了,因为是 x-props 的
-export interface IConnectProps extends IFieldProps {
- disabled?: boolean
- readOnly?: boolean
- showTime?: boolean
- dataSource?: any[]
- [name: string]: any
-}
-
-export interface IConnectOptions {
- valueName?: string
- eventName?: string
- defaultProps?: object
- getValueFromEvent?: (event?: any, value?: any) => any
- getProps?: (
- props: IConnectProps,
- componentProps: IFieldProps
- ) => IConnectProps | void
- getComponent?: (
- Target: T,
- props,
- {
- editable,
- name
- }: { editable: boolean | ((name: string) => boolean); name: string }
- ) => T
-}
-
-export const connect = >(
- opts?: IConnectOptions
-) => (Target: T): React.ComponentClass => {
- opts = {
- valueName: 'value',
- eventName: 'onChange',
- ...opts
- }
- return class extends PureComponent {
- render() {
- const { value, name, mutators, schema, editable } = this.props
-
- let props = {
- ...opts.defaultProps,
- ...schema['x-props'],
- [opts.valueName]: value,
- [opts.eventName]: (event, ...args) => {
- mutators.change(
- opts.getValueFromEvent
- ? opts.getValueFromEvent.call(
- { props: schema['x-props'] || {} },
- event,
- ...args
- )
- : getValue(event, isReactNative)
- )
- }
- } as IConnectProps
-
- if (editable !== undefined) {
- if (isFn(editable)) {
- if (!editable(name)) {
- props.disabled = true
- props.readOnly = true
- }
- } else if (editable === false) {
- props.disabled = true
- props.readOnly = true
- }
- }
-
- if (isFn(schema['x-effect'])) {
- props = bindEffects(props, schema['x-effect'], mutators.dispatch)
- }
-
- if (isFn(opts.getProps)) {
- const newProps = opts.getProps(props, this.props)
- if (newProps !== undefined) {
- // @ts-ignore TODO
- props = newProps
- }
- }
-
- if (isArr(schema.enum) && !props.dataSource) {
- props.dataSource = createEnum(schema.enum, schema.enumNames)
- }
-
- if (props.editable !== undefined) {
- delete props.editable
- }
-
- return React.createElement(
- isFn(opts.getComponent)
- ? opts.getComponent(Target, props, this.props)
- : Target,
- props
- )
- }
- }
-}
diff --git a/packages/react/src/decorators/markup.tsx b/packages/react/src/decorators/markup.tsx
deleted file mode 100644
index 66e0350235c..00000000000
--- a/packages/react/src/decorators/markup.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import React, { Component, useContext } from 'react'
-import { createPortal } from 'react-dom'
-
-import { ISchemaFormProps } from '../type'
-import {
- createHOC,
- schemaIs,
- clone,
- filterSchemaPropertiesAndReactChildren
-} from '../utils'
-import { MarkupContext } from '../shared/context'
-
-let nonameId = 0
-
-const getRadomName = () => {
- return `UFORM_NO_NAME_FIELD_$${nonameId++}`
-}
-
-export const SchemaField = props => {
- const parent = useContext(MarkupContext)
- if (schemaIs(parent, 'object')) {
- const name = props.name || getRadomName()
- parent.properties = parent.properties || {}
- parent.properties[name] = clone(
- props,
- filterSchemaPropertiesAndReactChildren
- )
- return (
-
- {props.children}
-
- )
- } else if (schemaIs(parent, 'array')) {
- parent.items = clone(props, filterSchemaPropertiesAndReactChildren)
- return (
-
- {props.children}
-
- )
- } else {
- return props.children ||
- }
-}
-
-export const SchemaMarkup = createHOC((options, SchemaForm) => {
- return class extends Component {
- public static displayName = 'SchemaMarkupParser'
-
- public portalRoot = document.createElement('div')
-
- public render() {
- const {
- children,
- initialValues,
- defaultValue,
- value,
- schema,
- ...others
- } = this.props
-
- let alreadyHasSchema = false
- let finalSchema = {}
- if (schema) {
- alreadyHasSchema = true
- finalSchema = schema
- } else {
- finalSchema = { type: 'object' }
- }
-
- nonameId = 0
-
- return (
-
- {!alreadyHasSchema &&
- // 只是为了去收集 schema 数据
- createPortal(
-
- {children}
- ,
- this.portalRoot
- )}
-
- {children}
-
-
- )
- }
- }
-})
diff --git a/packages/react/src/hooks/useDirty.ts b/packages/react/src/hooks/useDirty.ts
new file mode 100644
index 00000000000..f49b3af82fb
--- /dev/null
+++ b/packages/react/src/hooks/useDirty.ts
@@ -0,0 +1,17 @@
+import React from 'react'
+import { isEqual } from '@uform/shared'
+
+export const useDirty = (input: any = {}, keys: string[] = []) => {
+ const ref = React.useRef({ data: {...input}, dirtys: {}, num: 0 })
+ ref.current.num = 0
+ keys.forEach(key => {
+ if (!isEqual(input[key], ref.current.data[key])) {
+ ref.current.data[key] = input[key]
+ ref.current.dirtys[key] = true
+ ref.current.num++
+ } else {
+ ref.current.dirtys[key] = false
+ }
+ })
+ return ref.current
+}
diff --git a/packages/react/src/hooks/useField.ts b/packages/react/src/hooks/useField.ts
new file mode 100644
index 00000000000..4d6b653c259
--- /dev/null
+++ b/packages/react/src/hooks/useField.ts
@@ -0,0 +1,76 @@
+import { useMemo, useEffect, useRef, useContext } from 'react'
+import { each } from '@uform/shared'
+import { IFieldStateProps, IFieldState, IForm, IField } from '@uform/core'
+import { raf } from '../shared'
+import { useDirty } from './useDirty'
+import { useForceUpdate } from './useForceUpdate'
+import { IFieldHook } from '../types'
+import FormContext from '../context'
+
+export const useField = (options: IFieldStateProps): IFieldHook => {
+ const forceUpdate = useForceUpdate()
+ const dirty = useDirty(options, ['props', 'rules', 'required', 'editable'])
+ const ref = useRef<{ field: IField; unmounted: boolean }>({
+ field: null,
+ unmounted: false
+ })
+ const form = useContext(FormContext)
+ if (!form) {
+ throw new Error('Form object cannot be found from context.')
+ }
+ useMemo(() => {
+ let initialized = false
+ ref.current.field = form.registerField({
+ ...options,
+ onChange() {
+ if (ref.current.unmounted) return
+ /**
+ * 同步Field状态只需要forceUpdate一下触发重新渲染,因为字段状态全部代理在uform core内部
+ */
+ if (initialized) {
+ raf(() => {
+ forceUpdate()
+ })
+ }
+ }
+ })
+ initialized = true
+ }, [])
+
+ useEffect(() => {
+ if (dirty.num > 0) {
+ ref.current.field.setState((state: IFieldState) => {
+ each(dirty.dirtys, (result, key) => {
+ if (result) {
+ state[key] = options[key]
+ }
+ })
+ })
+ }
+ })
+
+ useEffect(() => {
+ ref.current.field.unsafe_setSourceState(state => {
+ state.mounted = true
+ })
+ return () => {
+ ref.current.unmounted = true
+ ref.current.field.setState((state: IFieldState) => {
+ state.unmounted = true
+ })
+ }
+ }, [])
+
+ const state = ref.current.field.getState()
+ return {
+ form,
+ state: {
+ ...state,
+ errors: state.errors.join(', ')
+ },
+ mutators: form.createMutators(ref.current.field),
+ props: state.props
+ }
+}
+
+export default useField
diff --git a/packages/react/src/hooks/useForceUpdate.ts b/packages/react/src/hooks/useForceUpdate.ts
new file mode 100644
index 00000000000..193c8916af5
--- /dev/null
+++ b/packages/react/src/hooks/useForceUpdate.ts
@@ -0,0 +1,18 @@
+import { useCallback, useState } from 'react';
+
+// Returning a new object reference guarantees that a before-and-after
+// equivalence check will always be false, resulting in a re-render, even
+// when multiple calls to forceUpdate are batched.
+
+export function useForceUpdate(): () => void {
+ const [ , dispatch ] = useState<{}>(Object.create(null));
+
+ // Turn dispatch(required_parameter) into dispatch().
+ const memoizedDispatch = useCallback(
+ (): void => {
+ dispatch(Object.create(null));
+ },
+ [ dispatch ],
+ );
+ return memoizedDispatch;
+}
\ No newline at end of file
diff --git a/packages/react/src/hooks/useForm.ts b/packages/react/src/hooks/useForm.ts
new file mode 100644
index 00000000000..3958e2aea7e
--- /dev/null
+++ b/packages/react/src/hooks/useForm.ts
@@ -0,0 +1,128 @@
+import { useMemo, useEffect, useRef, useContext } from 'react'
+import {
+ createForm,
+ IFormCreatorOptions,
+ LifeCycleTypes,
+ FormLifeCycle,
+ IForm,
+ IModel,
+ isStateModel,
+ IFormState
+} from '@uform/core'
+import { useDirty } from './useDirty'
+import { useEva } from 'react-eva'
+import { IFormProps } from '../types'
+import { BroadcastContext } from '../context'
+import { createFormEffects, createFormActions } from '../shared'
+import { isValid } from '@uform/shared'
+const FormHookSymbol = Symbol('FORM_HOOK')
+
+const useInternalForm = (
+ options: IFormCreatorOptions & { form?: IForm } = {}
+) => {
+ const dirty = useDirty(options, ['initialValues', 'values', 'editable'])
+ const alreadyHaveForm = !!options.form
+ const alreadyHaveHookForm = options.form && options.form[FormHookSymbol]
+ const form = useMemo(() => {
+ return alreadyHaveForm ? options.form : createForm(options)
+ }, [])
+
+ useEffect(() => {
+ if (alreadyHaveHookForm) return
+ if (dirty.num > 0) {
+ form.setFormState(state => {
+ if (dirty.dirtys.values && isValid(options.values)) {
+ state.values = options.values
+ }
+ if (dirty.dirtys.initialValues && isValid(options.initialValues)) {
+ state.values = options.initialValues
+ state.initialValues = options.initialValues
+ }
+ if (dirty.dirtys.editable && isValid(options.editable)) {
+ state.editable = options.editable
+ }
+ })
+ }
+ })
+
+ useEffect(() => {
+ if (alreadyHaveHookForm) return
+ form.setFormState(state => {
+ state.mounted = true
+ })
+ return () => {
+ form.setFormState(state => {
+ state.unmounted = true
+ })
+ }
+ }, [])
+ ;(form as any)[FormHookSymbol] = true
+
+ return form
+}
+
+export const useForm = <
+ Value = any,
+ DefaultValue = any,
+ EffectPayload = any,
+ EffectAction = any
+>(
+ props: IFormProps
+) => {
+ const actionsRef = useRef(null)
+ actionsRef.current =
+ actionsRef.current || props.actions || createFormActions()
+ const broadcast = useContext(BroadcastContext)
+ const { implementActions, dispatch } = useEva({
+ actions: actionsRef.current,
+ effects: createFormEffects(props.effects, actionsRef.current)
+ })
+ const form = useInternalForm({
+ form: props.form,
+ values: props.value,
+ initialValues: props.initialValues,
+ useDirty: props.useDirty,
+ editable: props.editable,
+ validateFirst: props.validateFirst,
+ lifecycles: [
+ new FormLifeCycle(
+ ({ type, payload }: { type: string; payload: IModel }) => {
+ dispatch.lazy(type, () => {
+ return isStateModel(payload) ? payload.getState() : payload
+ })
+ if (type === LifeCycleTypes.ON_FORM_INPUT_CHANGE) {
+ if (props.onChange) {
+ props.onChange(
+ isStateModel(payload)
+ ? payload.getState((state: IFormState) => state.values)
+ : {}
+ )
+ }
+ }
+ if (broadcast) {
+ broadcast.notify({ type, payload })
+ }
+ }
+ ),
+ new FormLifeCycle(
+ LifeCycleTypes.ON_FORM_WILL_INIT,
+ (payload: IModel, form: IForm) => {
+ const actions = {
+ ...form,
+ dispatch: form.notify
+ }
+ if (broadcast) {
+ broadcast.setContext(actions)
+ }
+ implementActions(actions)
+ }
+ )
+ ],
+ onReset: props.onReset,
+ onSubmit: props.onSubmit,
+ onValidateFailed: props.onValidateFailed
+ })
+ return form
+}
+
+export default useForm
diff --git a/packages/react/src/hooks/useVirtualField.ts b/packages/react/src/hooks/useVirtualField.ts
new file mode 100644
index 00000000000..3f5f21645dc
--- /dev/null
+++ b/packages/react/src/hooks/useVirtualField.ts
@@ -0,0 +1,71 @@
+import { useMemo, useEffect, useRef, useContext } from 'react'
+import { each } from '@uform/shared'
+import { IVirtualFieldStateProps, IVirtualFieldState, IForm } from '@uform/core'
+import { useDirty } from './useDirty'
+import { useForceUpdate } from './useForceUpdate'
+import { raf } from '../shared'
+import { IVirtualFieldHook } from '../types'
+import FormContext from '../context'
+
+export const useVirtualField = (
+ options: IVirtualFieldStateProps
+): IVirtualFieldHook => {
+ const forceUpdate = useForceUpdate()
+ const dirty = useDirty(options, ['props'])
+ const ref = useRef({
+ field: null,
+ unmounted: false
+ })
+ const form = useContext(FormContext)
+ if (!form) {
+ throw new Error('Form object cannot be found from context.')
+ }
+ useMemo(() => {
+ let initialized = false
+ ref.current.field = form.registerVirtualField({
+ ...options,
+ onChange() {
+ if (ref.current.unmounted) return
+ /**
+ * 同步Field状态只需要forceUpdate一下触发重新渲染,因为字段状态全部代理在uform core内部
+ */
+ if (initialized) {
+ raf(() => {
+ forceUpdate()
+ })
+ }
+ }
+ })
+ initialized = true
+ }, [])
+
+ useEffect(() => {
+ if (dirty.num > 0) {
+ ref.current.field.setState((state: IVirtualFieldState) => {
+ each(dirty.dirtys, (result, key) => {
+ if (result) {
+ state[key] = options[key]
+ }
+ })
+ })
+ }
+ })
+
+ useEffect(() => {
+ return () => {
+ ref.current.unmounted = true
+ ref.current.field.setState((state: IVirtualFieldState) => {
+ state.unmounted = true
+ })
+ }
+ }, [])
+
+ const state = ref.current.field.getState()
+ return {
+ state,
+ form,
+ props: state.props
+ }
+}
+
+export default useVirtualField
diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts
new file mode 100644
index 00000000000..7427cdcb193
--- /dev/null
+++ b/packages/react/src/index.ts
@@ -0,0 +1,24 @@
+import {
+ FormEffectHooks,
+ createEffectHook,
+ createFormActions,
+ createAsyncFormActions
+} from './shared'
+export * from '@uform/core'
+export * from './components/Form'
+export * from './components/Field'
+export * from './components/VirtualField'
+export * from './components/FormSpy'
+export * from './components/FormProvider'
+export * from './components/FormConsumer'
+export * from './hooks/useForm'
+export * from './hooks/useField'
+export * from './hooks/useVirtualField'
+export * from './types'
+
+export {
+ FormEffectHooks,
+ createEffectHook,
+ createFormActions,
+ createAsyncFormActions
+}
diff --git a/packages/react/src/index.tsx b/packages/react/src/index.tsx
deleted file mode 100644
index 0523bb43043..00000000000
--- a/packages/react/src/index.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import * as React from 'react'
-import { setLocale, setLanguage } from '@uform/validator'
-import { FormPath } from '@uform/core'
-import { IFormActions, IAsyncFormActions } from '@uform/types'
-import { createActions, createAsyncActions } from 'react-eva'
-
-import {
- OriginForm,
- registerFieldMiddleware,
- registerFormFieldPropsTransformer,
- registerFormField,
- registerFormFields,
- registerFormWrapper
-} from './shared/core'
-import { FormField } from './state/field'
-import { calculateSchemaInitialValues } from './utils'
-import { SchemaField, SchemaMarkup } from './decorators/markup'
-import initialize from './initialize'
-import { ISchemaFormProps } from './type'
-
-export * from './type'
-export * from './shared/virtualbox'
-export * from './decorators/connect'
-export * from './shared/broadcast'
-export * from './shared/array'
-
-initialize()
-
-export const SchemaForm = SchemaMarkup()(
- React.forwardRef((props: ISchemaFormProps, ref: React.Ref) => {
- // 这个时候就有 schema 数据
- const { children, className, ...others } = props
- return (
-
-
-
-
- {children}
-
- )
- })
-)
-
-export const Field = SchemaField
-
-export const setValidationLocale = setLocale
-
-export const setValidationLanguage = setLanguage
-
-export const createFormActions = (): IFormActions =>
- createActions(
- 'getFormState',
- 'getFieldState',
- 'setFormState',
- 'setFieldState',
- 'getSchema',
- 'reset',
- 'submit',
- 'validate',
- 'dispatch'
- )
-
-export const createAsyncFormActions = (): IAsyncFormActions =>
- createAsyncActions(
- 'getFormState',
- 'getFieldState',
- 'setFormState',
- 'setFieldState',
- 'getSchema',
- 'reset',
- 'submit',
- 'validate',
- 'dispatch'
- )
-
-export {
- registerFormField,
- registerFormFields,
- registerFormWrapper,
- registerFieldMiddleware,
- registerFormFieldPropsTransformer,
- calculateSchemaInitialValues,
- FormPath
-}
-
-SchemaForm.displayName = 'SchemaForm'
-
-export default SchemaForm
diff --git a/packages/react/src/initialize/index.ts b/packages/react/src/initialize/index.ts
deleted file mode 100644
index 7d3fcdf1949..00000000000
--- a/packages/react/src/initialize/index.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import initialObject from './object'
-import initialRender from './render'
-import initialVirtualbox from './virtualbox'
-import { initialContainer } from '../shared/core'
-import intialState from '../state'
-
-export default () => {
- initialContainer()
- intialState()
- initialObject()
- initialRender()
- initialVirtualbox()
-}
diff --git a/packages/react/src/initialize/object.tsx b/packages/react/src/initialize/object.tsx
deleted file mode 100644
index b499e06588d..00000000000
--- a/packages/react/src/initialize/object.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import React from 'react'
-import { IFieldProps } from '../type'
-import { registerFormField } from '../shared/core'
-import { each } from '../utils'
-
-export default () =>
- registerFormField(
- 'object',
- class ObjectField extends React.Component {
- public render() {
- return this.renderProperties()
- }
- private renderProperties() {
- const { renderField, getOrderProperties } = this.props
- const properties = getOrderProperties()
- const children = []
- each(properties, ({ key }: { key?: string } = {}) => {
- if (key) {
- children.push(renderField(key, true))
- }
- })
- return children
- }
- }
- )
diff --git a/packages/react/src/initialize/render.tsx b/packages/react/src/initialize/render.tsx
deleted file mode 100644
index 4f2e99f7ed3..00000000000
--- a/packages/react/src/initialize/render.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react'
-import { IFieldProps } from '../type'
-import { registerFieldRenderer } from '../shared/core'
-import { isFn } from '../utils'
-
-export default () =>
- registerFieldRenderer(
- class extends React.Component {
- public static displayName = 'FieldXRenderer'
- public render() {
- if (isFn(this.props.schema['x-render'])) {
- return this.props.schema['x-render'](this.props)
- }
- return
- }
- }
- )
diff --git a/packages/react/src/initialize/virtualbox.tsx b/packages/react/src/initialize/virtualbox.tsx
deleted file mode 100644
index 3fba8214da2..00000000000
--- a/packages/react/src/initialize/virtualbox.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from 'react'
-import { IFieldProps } from '../type'
-import { registerFormField } from '../shared/core'
-import { registerVirtualboxFlag } from '../utils'
-
-export default () => {
- registerVirtualboxFlag('slot')
- registerFormField(
- 'slot',
- class extends React.Component {
- public static displayName = 'FormSlot'
- public render() {
- const { schema } = this.props
- return {schema.renderChildren}
- }
- }
- )
-}
diff --git a/packages/react/src/shared.ts b/packages/react/src/shared.ts
new file mode 100644
index 00000000000..3bc79a02713
--- /dev/null
+++ b/packages/react/src/shared.ts
@@ -0,0 +1,270 @@
+import { isFn, FormPath, globalThisPolyfill } from '@uform/shared'
+import { IFormEffect, IFormActions, IFormAsyncActions } from './types'
+import { Observable } from 'rxjs/internal/Observable'
+import { filter } from 'rxjs/internal/operators'
+import { createActions, createAsyncActions } from 'react-eva'
+import {
+ LifeCycleTypes,
+ IFormState,
+ FormGraph,
+ IFieldState,
+ IVirtualFieldState
+} from '@uform/core'
+
+export const createFormActions = (): IFormActions => {
+ if (env.currentActions) {
+ return env.currentActions
+ }
+ return createActions(
+ 'submit',
+ 'reset',
+ 'validate',
+ 'setFormState',
+ 'getFormState',
+ 'setFieldState',
+ 'getFieldState',
+ 'getFormGraph',
+ 'setFormGraph',
+ 'subscribe',
+ 'unsubscribe',
+ 'notify',
+ 'dispatch',
+ 'setFieldValue',
+ 'getFieldValue',
+ 'setFieldInitialValue',
+ 'getFieldInitialValue'
+ ) as IFormActions
+}
+
+export const createAsyncFormActions = (): IFormAsyncActions =>
+ createAsyncActions(
+ 'submit',
+ 'reset',
+ 'validate',
+ 'setFormState',
+ 'getFormState',
+ 'setFieldState',
+ 'getFieldState',
+ 'getFormGraph',
+ 'setFormGraph',
+ 'subscribe',
+ 'unsubscribe',
+ 'notify',
+ 'dispatch',
+ 'setFieldValue',
+ 'getFieldValue',
+ 'setFieldInitialValue',
+ 'getFieldInitialValue'
+ ) as IFormAsyncActions
+
+export interface IEventTargetOption {
+ selected: boolean
+ value: any
+}
+
+const isEvent = (candidate: any): boolean =>
+ candidate &&
+ (candidate.stopPropagation || candidate.preventDefault || candidate.bubbles)
+
+const isReactNative =
+ typeof window !== 'undefined' &&
+ window.navigator &&
+ window.navigator.product &&
+ window.navigator.product === 'ReactNative'
+
+const getSelectedValues = (options?: IEventTargetOption[]) => {
+ const result = []
+ if (options) {
+ for (let index = 0; index < options.length; index++) {
+ const option = options[index]
+ if (option.selected) {
+ result.push(option.value)
+ }
+ }
+ }
+ return result
+}
+
+export const getValueFromEvent = (event: any) => {
+ if (isEvent(event)) {
+ if (
+ !isReactNative &&
+ event.nativeEvent &&
+ event.nativeEvent.text !== undefined
+ ) {
+ return event.nativeEvent.text
+ }
+ if (isReactNative && event.nativeEvent !== undefined) {
+ return event.nativeEvent.text
+ }
+
+ const detypedEvent = event
+ const {
+ target: { type, value, checked, files },
+ dataTransfer
+ } = detypedEvent
+
+ if (type === 'checkbox') {
+ return !!checked
+ }
+
+ if (type === 'file') {
+ return files || (dataTransfer && dataTransfer.files)
+ }
+
+ if (type === 'select-multiple') {
+ return getSelectedValues(event.target.options)
+ }
+ return value
+ }
+ return event
+}
+
+const compactScheduler = ([raf, caf, priority], fresh: boolean) => {
+ return [fresh ? callback => raf(priority, callback) : raf, caf]
+}
+
+const getScheduler = () => {
+ if (!globalThisPolyfill.requestAnimationFrame) {
+ return [globalThisPolyfill.setTimeout, globalThisPolyfill.clearTimeout]
+ }
+ try {
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ const scheduler = require('scheduler') as any
+ return compactScheduler(
+ [
+ scheduler.scheduleCallback || scheduler.unstable_scheduleCallback,
+ scheduler.cancelCallback || scheduler.unstable_cancelCallback,
+ scheduler.NormalPriority || scheduler.unstable_NormalPriority
+ ],
+ !!scheduler.unstable_requestPaint
+ )
+ } catch (err) {
+ return [self.requestAnimationFrame, self.cancelAnimationFrame]
+ }
+}
+
+export const env = {
+ effectStart: false,
+ effectSelector: null,
+ effectEnd: false,
+ currentActions: null
+}
+
+export const [raf, caf] = getScheduler()
+
+export const createFormEffects = (
+ effects: IFormEffect | null,
+ actions: Actions
+) => {
+ if (isFn(effects)) {
+ return (selector: (type: string) => Observable) => {
+ env.effectEnd = false
+ env.effectStart = true
+ env.currentActions = actions
+ env.effectSelector = (
+ type: string,
+ matcher?: string | ((payload: T) => boolean)
+ ) => {
+ const observable$: Observable = selector(type)
+ if (matcher) {
+ return observable$.pipe(
+ filter(
+ isFn(matcher)
+ ? matcher
+ : (payload: T): boolean => {
+ return FormPath.parse(matcher).match(
+ payload && (payload as any).name
+ )
+ }
+ )
+ )
+ }
+ return observable$
+ }
+ Object.assign(env.effectSelector, actions)
+ effects(env.effectSelector, actions)
+ env.effectStart = false
+ env.effectEnd = true
+ env.currentActions = null
+ }
+ } else {
+ return () => {}
+ }
+}
+
+export const createEffectHook = = any[]>(
+ type: string
+) => (...args: Props): Observable => {
+ if (!env.effectStart || env.effectEnd) {
+ throw new Error(
+ 'EffectHook must be called synchronously within the effects callback function.'
+ )
+ }
+ if (!env.effectSelector) {
+ throw new Error('Can not found effect hook selector.')
+ }
+ return env.effectSelector(type, ...args)
+}
+
+type FieldMergeState = Partial & Partial
+
+export const FormEffectHooks = {
+ onFormWillInit$: createEffectHook(
+ LifeCycleTypes.ON_FORM_WILL_INIT
+ ),
+ onFormInit$: createEffectHook(LifeCycleTypes.ON_FORM_INIT),
+ onFormChange$: createEffectHook(LifeCycleTypes.ON_FORM_CHANGE),
+ onFormInputChange$: createEffectHook(
+ LifeCycleTypes.ON_FORM_INPUT_CHANGE
+ ),
+ onFormInitialValueChange$: createEffectHook(
+ LifeCycleTypes.ON_FORM_INITIAL_VALUES_CHANGE
+ ),
+ onFormReset$: createEffectHook(LifeCycleTypes.ON_FORM_RESET),
+ onFormSubmit$: createEffectHook(LifeCycleTypes.ON_FORM_SUBMIT),
+ onFormSubmitStart$: createEffectHook(
+ LifeCycleTypes.ON_FORM_SUBMIT_START
+ ),
+ onFormSubmitEnd$: createEffectHook(
+ LifeCycleTypes.ON_FORM_SUBMIT_END
+ ),
+ onFormMount$: createEffectHook(LifeCycleTypes.ON_FORM_MOUNT),
+ onFormUnmount$: createEffectHook(LifeCycleTypes.ON_FORM_UNMOUNT),
+ onFormValidateStart$: createEffectHook(
+ LifeCycleTypes.ON_FORM_VALIDATE_START
+ ),
+ onFormValidateEnd$: createEffectHook(
+ LifeCycleTypes.ON_FORM_VALIDATE_END
+ ),
+ onFormValuesChange$: createEffectHook(
+ LifeCycleTypes.ON_FORM_VALUES_CHANGE
+ ),
+
+ onFormGraphChange$: createEffectHook(
+ LifeCycleTypes.ON_FORM_GRAPH_CHANGE
+ ),
+
+ onFieldWillInit$: createEffectHook(
+ LifeCycleTypes.ON_FIELD_WILL_INIT
+ ),
+ onFieldInit$: createEffectHook(LifeCycleTypes.ON_FIELD_INIT),
+ onFieldChange$: createEffectHook(
+ LifeCycleTypes.ON_FIELD_CHANGE
+ ),
+ onFieldMount$: createEffectHook(
+ LifeCycleTypes.ON_FIELD_MOUNT
+ ),
+ onFieldUnmount$: createEffectHook(
+ LifeCycleTypes.ON_FIELD_UNMOUNT
+ ),
+ onFieldInputChange$: createEffectHook(
+ LifeCycleTypes.ON_FIELD_INPUT_CHANGE
+ ),
+ onFieldValueChange$: createEffectHook