From c46e24230327a11460ab075bb396c9a206103cfb Mon Sep 17 00:00:00 2001 From: saint3347 Date: Mon, 25 Nov 2024 19:57:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=201.=20=E5=A2=9E=E5=BC=BA=20`Form`=20?= =?UTF-8?q?=E7=9A=84=20`formRef`=EF=BC=8C=E5=A2=9E=E5=8A=A0=20`scrollToFie?= =?UTF-8?q?ld`=20=E6=96=B9=E6=B3=95=EF=BC=8C=E6=94=AF=E6=8C=81=E6=A0=B9?= =?UTF-8?q?=E6=8D=AE=20name=20=E6=BB=9A=E5=8A=A8=E8=87=B3=E6=8C=87?= =?UTF-8?q?=E5=AE=9A=E8=A1=A8=E5=8D=95=E9=A1=B9;=202.=20=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=20`Form`=20=E7=9A=84=20`validate`=20=E6=96=B9=E6=B3=95?= =?UTF-8?q?=EF=BC=8C=E6=A0=A1=E9=AA=8C=E6=88=90=E5=8A=9F=E6=88=96=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E5=90=8E=E8=BF=94=E5=9B=9E=E6=9B=B4=E5=A4=9A=E7=9A=84?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E4=BF=A1=E6=81=AF;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- packages/base/src/button/button-group.tsx | 2 +- packages/base/src/button/button.type.ts | 6 + packages/base/src/cascader/cascader.tsx | 5 +- packages/base/src/checkbox/checkbox-group.tsx | 8 +- .../base/src/checkbox/simple-checkbox.tsx | 5 +- packages/base/src/date-picker/result.tsx | 9 +- .../base/src/editable-area/editable-area.tsx | 6 +- packages/base/src/form/form-field-context.ts | 12 + packages/base/src/form/form-field.tsx | 30 +- packages/base/src/form/form-field.type.ts | 1 - packages/base/src/form/form.type.ts | 8 +- packages/base/src/input/simple-input.tsx | 6 +- packages/base/src/radio/radio-group.tsx | 8 +- packages/base/src/radio/simple-radio.tsx | 6 +- packages/base/src/rate/rate.tsx | 5 +- packages/base/src/select/result.tsx | 1 - packages/base/src/select/select.tsx | 5 +- packages/base/src/slider/slider.tsx | 5 +- packages/base/src/switch/switch.tsx | 6 +- .../base/src/textarea/simple-textarea.tsx | 5 +- packages/base/src/transfer/transfer.tsx | 7 +- packages/base/src/tree-select/tree-select.tsx | 5 +- packages/base/src/tree/tree.tsx | 6 +- packages/base/src/upload/upload.tsx | 5 +- .../use-form/use-form-context.type.ts | 2 +- .../hooks/src/components/use-form/use-form.ts | 47 +- .../shineout/src/form/__doc__/changelog.cn.md | 4 +- .../src/form/__example__/001-base.tsx | 103 ++-- .../006-validate-05-multi-form.tsx | 142 ------ .../006-validate-05-scrolltoerror.tsx | 448 ++++++++++-------- .../test-002-validate-scrolltoerror.tsx | 241 ++++++++++ 32 files changed, 703 insertions(+), 448 deletions(-) create mode 100644 packages/base/src/form/form-field-context.ts delete mode 100644 packages/shineout/src/form/__example__/006-validate-05-multi-form.tsx create mode 100644 packages/shineout/src/form/__example__/test-002-validate-scrolltoerror.tsx diff --git a/package.json b/package.json index 630207c64..3714c375b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sheinx", "private": true, - "version": "3.5.2-beta.6", + "version": "3.5.3-beta.1", "description": "A react library developed with sheinx", "module": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/base/src/button/button-group.tsx b/packages/base/src/button/button-group.tsx index 048723c5f..fba81e3d6 100644 --- a/packages/base/src/button/button-group.tsx +++ b/packages/base/src/button/button-group.tsx @@ -26,7 +26,7 @@ const Group = (props: ButtonGroupProps) => { const shapeSetted = shape === 'round' ? 'round' : undefined; return ( -
+
{Children.toArray(children).map((child) => { const Child = child as React.ReactElement; return cloneElement(Child, { diff --git a/packages/base/src/button/button.type.ts b/packages/base/src/button/button.type.ts index d17798ca8..351e1909e 100644 --- a/packages/base/src/button/button.type.ts +++ b/packages/base/src/button/button.type.ts @@ -135,6 +135,12 @@ export interface ButtonGroupProps extends Pick( props0: CascaderProps, ) => { const props = useWithFormConfig(props0); + const { fieldId } = useContext(FormFieldContext); const defaultHeight = 250; const { jssStyle, @@ -711,6 +713,7 @@ const Cascader = ( return (
d; const defaultRenderItem = (d: any) => d; @@ -13,6 +14,7 @@ const defaultRenderItem = (d: any) => d; const Group = (props0: CheckboxGroupProps) => { const {format = defaultFormat, renderItem = defaultRenderItem} = props0 const props = useWithFormConfig(props0); + const { fieldId } = useContext(FormFieldContext); const { children, className, block, keygen, jssStyle, size, style, disabled } = props; const checkboxStyle = jssStyle?.checkbox?.(); @@ -89,13 +91,13 @@ const Group = (props0: CheckboxGroupProps +
{children}
); } else { return ( -
+
{props.data.map((d, i) => ( { const { jssStyle, className, style, children, renderFooter, size, theme, ...rest } = props; + const { fieldId } = useContext(FormFieldContext); const checkboxStyle = jssStyle?.checkbox?.(); const { getRootProps, getIndicatorProps, getInputProps, disabled, checked } = useCheck({ ...rest, @@ -33,6 +35,7 @@ const Checkbox = (props: SimpleCheckboxProps) => { return (
{ } = props; const { locale } = useConfig(); + const { fieldId } = useContext(FormFieldContext); + const styles = jssStyle?.datePicker?.(); const { current: context } = useRef<{ inputRefs: Array; clickIndex: number }>({ inputRefs: [], @@ -162,6 +167,7 @@ const Result = (props: ResultProps) => { const placeholderArr = getPlaceHolderArr(); + const formFieldId = fieldId.split('__separator__'); const renderItem = (info: { inputable?: boolean; target: string | undefined; @@ -211,6 +217,7 @@ const Result = (props: ResultProps) => { props.onFocus?.(e); }} onClick={onClickProps} + id={formFieldId[info.index]} />
diff --git a/packages/base/src/editable-area/editable-area.tsx b/packages/base/src/editable-area/editable-area.tsx index a67a5c025..ccc0f0fe4 100644 --- a/packages/base/src/editable-area/editable-area.tsx +++ b/packages/base/src/editable-area/editable-area.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { Textarea } from '../textarea'; import { EditableAreaProps } from './editable-area.type'; import AbsoluteList from '../absolute-list'; @@ -6,6 +6,7 @@ import { useInputAble, usePersistFn } from '@sheinx/hooks'; import classNames from 'classnames'; import Icons from '../icons'; import useInnerTitle from '../common/use-inner-title'; +import { FormFieldContext } from '../form/form-field-context'; function formatShowValue(value: unknown) { if (!value && value !== 0) return ''; @@ -37,6 +38,8 @@ const EditableArea = (props: EditableAreaProps) => { bordered = false, } = props; + const { fieldId } = useContext(FormFieldContext); + const editableAreaStyle = jssStyle?.editableArea?.(); const status = error ? 'error' : props.status; @@ -166,6 +169,7 @@ const EditableArea = (props: EditableAreaProps) => { return (
({ + fieldId: '', + separator: '__separator__', +}); diff --git a/packages/base/src/form/form-field.tsx b/packages/base/src/form/form-field.tsx index 1c5302097..f7ee2acfc 100644 --- a/packages/base/src/form/form-field.tsx +++ b/packages/base/src/form/form-field.tsx @@ -1,6 +1,7 @@ -import React from 'react'; +import React, { useContext, useMemo } from 'react'; import { useFormControl, usePersistFn, util } from '@sheinx/hooks'; import { FieldControlProps, FormFieldProps } from './form-field.type'; +import { FormFieldContext } from './form-field-context'; const FormField = (props: FormFieldProps) => { const { children } = props; @@ -37,11 +38,21 @@ const FormField = (props: FormFieldProps) => { const status = childrenProps.status ?? (formControl.error ? 'error' : undefined); + const { separator } = useContext(FormFieldContext); + const formFieldId = useMemo(() => { + if(childrenProps.id) return childrenProps.id + + if(Array.isArray(formControl.name)) { + return formControl.name.map(name => util.getFieldId(name, props.formName)).join(separator) + } + + return util.getFieldId(formControl.name, props.formName) + }, [formControl.name, props.formName, childrenProps.id]) + const cloneProps: FieldControlProps = { onChange: handleChange, status, error, - id: childrenProps.id || util.getFieldId(formControl.name, props.formName), }; if (formControl.inForm) { @@ -51,12 +62,21 @@ const FormField = (props: FormFieldProps) => { if (formControl.disabled) { cloneProps.disabled = true; } + + let finalChildren if (util.isFunc(children)) { - return children(cloneProps); + finalChildren = children(cloneProps) + } else if (React.isValidElement(children)) { + finalChildren = React.cloneElement(children, cloneProps) + } else { + finalChildren = children } - if (React.isValidElement(children)) return React.cloneElement(children, cloneProps); - return children; + return ( + + {finalChildren} + + ); }; export default FormField; diff --git a/packages/base/src/form/form-field.type.ts b/packages/base/src/form/form-field.type.ts index d9ac3e93e..3dcf0befc 100644 --- a/packages/base/src/form/form-field.type.ts +++ b/packages/base/src/form/form-field.type.ts @@ -7,7 +7,6 @@ export interface FieldControlProps { status?: 'error'; disabled?: boolean; error?: { message?: string } | string; - id?: string; } export type FormFieldChildrenFunc = (props: FieldControlProps) => React.ReactElement; diff --git a/packages/base/src/form/form.type.ts b/packages/base/src/form/form.type.ts index 003b45077..c671d9de1 100644 --- a/packages/base/src/form/form.type.ts +++ b/packages/base/src/form/form.type.ts @@ -11,14 +11,14 @@ export interface FormClasses { export interface FormValidateFn< FormValue, - FieldKey extends KeyType = keyof FormValue, - FieldsType = FieldKey | FieldKey[] + // FieldKey extends KeyType = keyof FormValue, + // FieldsType = FieldKey | FieldKey[] > { /** * 验证所有表单的值,并且返回报错和表单数据 * @param fields 需要校验的表单字段 */ - (fields?: FieldsType): Promise>; + (fields?: string | string[]): Promise; } @@ -67,7 +67,7 @@ export interface FormRef { * @en Scroll to the position of the specified field * @cn 滚动到指定字段的位置 */ - scrollToField: (name: string) => void; + scrollToField: (name: string, scrollIntoViewOptions?: ScrollIntoViewOptions) => void; } export interface FormProps extends Partial>, diff --git a/packages/base/src/input/simple-input.tsx b/packages/base/src/input/simple-input.tsx index bec16bf41..79d91dacb 100644 --- a/packages/base/src/input/simple-input.tsx +++ b/packages/base/src/input/simple-input.tsx @@ -1,10 +1,10 @@ import { useInput, useKeyEvent, usePersistFn, util } from '@sheinx/hooks'; import classNames from 'classnames'; -import React, { KeyboardEvent } from 'react'; +import React, { KeyboardEvent, useContext } from 'react'; import { SimpleInputProps } from './input.type'; import Icons from '../icons'; import { useConfig } from '../config'; - +import { FormFieldContext } from '../form/form-field-context'; const Input = (props: SimpleInputProps) => { const { jssStyle, @@ -27,6 +27,7 @@ const Input = (props: SimpleInputProps) => { const inputStyle = jssStyle?.input?.(); const config = useConfig(); + const { fieldId } = useContext(FormFieldContext); const { getRootProps, getClearProps, getInputProps, showClear, focused, disabled } = useInput({ ...rest, onFocusedChange, @@ -69,6 +70,7 @@ const Input = (props: SimpleInputProps) => { return (
d; const defaultRenderItem = (d: any) => d; @@ -13,6 +14,7 @@ const defaultRenderItem = (d: any) => d; const Group = (props0: RadioGroupProps) => { const {format = defaultFormat, renderItem = defaultRenderItem} = props0 const props = useWithFormConfig(props0); + const { fieldId } = useContext(FormFieldContext); const { children, className, button, block, keygen, jssStyle, style, size, disabled } = props; const radioClasses = jssStyle?.radio?.(); @@ -125,13 +127,13 @@ const Group = (props0: RadioGroupProps) => { ); if (button) return ( - + {Radios} ); return ( -
+
{Radios}
); diff --git a/packages/base/src/radio/simple-radio.tsx b/packages/base/src/radio/simple-radio.tsx index 0b5f710d7..007e42c6d 100644 --- a/packages/base/src/radio/simple-radio.tsx +++ b/packages/base/src/radio/simple-radio.tsx @@ -1,10 +1,12 @@ import { useCheck } from '@sheinx/hooks'; import classNames from 'classnames'; -import React from 'react'; +import React, { useContext } from 'react'; import { SimpleRadioProps } from './radio.type'; +import { FormFieldContext } from '../form/form-field-context'; const Radio = (props: SimpleRadioProps) => { const { jssStyle, className, style, children, renderRadio, size, theme, ...rest } = props; + const { fieldId } = useContext(FormFieldContext); const radioClasses = jssStyle?.radio?.(); const { getRootProps, getIndicatorProps, getInputProps, disabled, checked } = useCheck({ ...rest, @@ -31,7 +33,7 @@ const Radio = (props: SimpleRadioProps) => { const indicatorProps = getIndicatorProps(); const simpleRadio = ( -
+
diff --git a/packages/base/src/rate/rate.tsx b/packages/base/src/rate/rate.tsx index 8d04b4d2b..4df16b1a8 100644 --- a/packages/base/src/rate/rate.tsx +++ b/packages/base/src/rate/rate.tsx @@ -1,14 +1,16 @@ import classNames from 'classnames'; -import React, { useState } from 'react'; +import React, { useContext, useState } from 'react'; import { RateProps } from './rate.type'; import Icons from '../icons'; import { useInputAble } from '@sheinx/hooks'; import useWithFormConfig from '../common/use-with-form-config'; import { useConfig } from '../config'; +import { FormFieldContext } from '../form/form-field-context'; const Rate = (props0: RateProps) => { const props = useWithFormConfig(props0); const config = useConfig(); + const { fieldId } = useContext(FormFieldContext); const { size } = props0; const { max = 5, repeat = true, clearable = false } = props; const [hoverValue, setHoverValue] = useState(null); @@ -115,6 +117,7 @@ const Rate = (props0: RateProps) => { const text = Array.isArray(props.text) && props.text[Math.ceil(value - 1)]; return (
{ diff --git a/packages/base/src/select/result.tsx b/packages/base/src/select/result.tsx index 0c9261b62..0a187fe31 100644 --- a/packages/base/src/select/result.tsx +++ b/packages/base/src/select/result.tsx @@ -60,7 +60,6 @@ const Result = (props: ResultProps) => { const [more, setMore] = useState(-1); const [shouldResetMore, setShouldResetMore] = useState(false); const render = useRender(); - const resultRef = useRef(null); const prevMore = useRef(more); const showInput = allowOnFilter; diff --git a/packages/base/src/select/select.tsx b/packages/base/src/select/select.tsx index 8d1e8bd1d..7ee219b45 100644 --- a/packages/base/src/select/select.tsx +++ b/packages/base/src/select/select.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode, useRef, useState } from 'react'; +import React, { ReactNode, useContext, useRef, useState } from 'react'; import classNames from 'classnames'; import { util, @@ -26,6 +26,7 @@ import ColumnsList from './list-columns'; import useWithFormConfig from '../common/use-with-form-config'; import useTip from '../common/use-tip'; import { getLocale, useConfig } from '../config'; +import { FormFieldContext } from '../form/form-field-context'; function Select(props0: SelectPropsBase) { const props = useWithFormConfig(props0); @@ -538,6 +539,7 @@ function Select(props0: SelectPropsBase) { ); }; + const { fieldId } = useContext(FormFieldContext); const renderResult = () => { const result = (
@@ -585,6 +587,7 @@ function Select(props0: SelectPropsBase) { return (
(props0: SliderProps) => { const props = useWithFormConfig(props0); const sliderClasses = props.jssStyle?.slider?.(); const config = useConfig() + const { fieldId } = useContext(FormFieldContext); const { scale = defaultScale, step = 1, height = 200, valueTipType: tipType = 'always' } = props; @@ -86,6 +88,7 @@ const Slider = (props0: SliderProps) => return (
{ const props = useWithFormConfig(props0); const { jssStyle, content, size, loading, className, style } = props; const config = useConfig(); + const { fieldId } = useContext(FormFieldContext); const switchClasses = jssStyle?.switch?.(); const disabled = props.disabled || props.loading; @@ -50,7 +52,7 @@ const Switch = (props0: SwitchProps) => { const inputProps = getInputProps(); return ( -
{ console.log('form submit', v); }} - onChange={setForm} + onChange={(v) => { + console.log('form change', v); + }} onReset={() => { console.log('form reset'); }} > + + + + + + + + + + + + + + + + + + + + + + + { + return data; + }} + /> + - i} columns={columns}>
+