diff --git a/.changeset/shiny-cats-crash.md b/.changeset/shiny-cats-crash.md new file mode 100644 index 00000000..c78cb84d --- /dev/null +++ b/.changeset/shiny-cats-crash.md @@ -0,0 +1,6 @@ +--- +'@modelscope-studio/antd': minor +'modelscope_studio': minor +--- + +feat: add the `form_action` property to manually trigger form actions diff --git a/backend/modelscope_studio/components/antd/form/__init__.py b/backend/modelscope_studio/components/antd/form/__init__.py index 94c6cc3d..dba3923e 100644 --- a/backend/modelscope_studio/components/antd/form/__init__.py +++ b/backend/modelscope_studio/components/antd/form/__init__.py @@ -45,6 +45,7 @@ def __init__( value: dict | None = None, props: dict | None = None, *, + form_action: Literal['reset', 'submit', 'validate'] | None = None, colon: bool = True, disabled: bool | None = None, component: str | False | None = None, @@ -84,6 +85,7 @@ def __init__( elem_style=elem_style, **kwargs) self.props = props + self.form_action = form_action self.colon = colon self.disabled = disabled self.component = component diff --git a/docs/components/antd/form/README-zh_CN.md b/docs/components/antd/form/README-zh_CN.md index 4def98ab..d3896570 100644 --- a/docs/components/antd/form/README-zh_CN.md +++ b/docs/components/antd/form/README-zh_CN.md @@ -5,4 +5,9 @@ High-performance form component with data domain management. Includes data entry ## Examples - + + + +通过修改`form_action`可以手动触发表单动作,每当`form_action`变化并触发对应表单动作时,都会自动重置`form_action`的值,以便后续多次调用。 + + diff --git a/docs/components/antd/form/README.md b/docs/components/antd/form/README.md index 4def98ab..c896d01b 100644 --- a/docs/components/antd/form/README.md +++ b/docs/components/antd/form/README.md @@ -6,3 +6,8 @@ High-performance form component with data domain management. Includes data entry + + +By modifying `form_action`, you can manually trigger a form action. Whenever `form_action` changes and triggers the corresponding form action, the value of `form_action` will be automatically reset for subsequent multiple calls. + + diff --git a/docs/components/antd/form/demos/dynamic_form.py b/docs/components/antd/form/demos/dynamic_form.py new file mode 100644 index 00000000..84f8d5b8 --- /dev/null +++ b/docs/components/antd/form/demos/dynamic_form.py @@ -0,0 +1,37 @@ +import gradio as gr +import modelscope_studio.components.antd as antd +import modelscope_studio.components.base as ms + + +def submit(form_value): + print(form_value) + + +def add(state_value): + count = len(state_value) + return state_value + [{ + "form_name": str(count), + "label": "Label " + str(count) + }] + + +with gr.Blocks() as demo, ms.Application(), antd.ConfigProvider(): + state = gr.State([{"form_name": "0", "label": "Label 0"}]) + with antd.Form() as form: + with antd.Form.Item(): + add_btn = antd.Button("Add List") + + @gr.render(inputs=[state]) + def render_inputs(state_data): + for item in state_data: + with antd.Form.Item(form_name=item["form_name"], + label=item["label"]): + antd.Input() + + with antd.Form.Item(): + antd.Button("Submit", type="primary", html_type="submit") + add_btn.click(fn=add, inputs=[state], outputs=[state]) + form.finish(fn=submit, inputs=[form]) + +if __name__ == "__main__": + demo.queue().launch() diff --git a/docs/components/antd/form/demos/form_action.py b/docs/components/antd/form/demos/form_action.py new file mode 100644 index 00000000..d0783391 --- /dev/null +++ b/docs/components/antd/form/demos/form_action.py @@ -0,0 +1,80 @@ +import gradio as gr +import modelscope_studio.components.antd as antd +import modelscope_studio.components.base as ms + + +def on_submit(_form): + print(_form) # the Form Component will automatically collect the form data + + +def bind_action_event(action): + + def on_action(): + return gr.update(form_action=action) + + return on_action + + +with gr.Blocks() as demo: + with ms.Application(): + with antd.ConfigProvider(): + with antd.Card(title="Out of the Form"): + submit_btn = antd.Button("Submit", type="primary") + reset_btn = antd.Button("Reset") + validate_btn = antd.Button("Validate") + + with antd.Form(label_col=dict(span=8), + wrapper_col=dict(span=16)) as form: + with antd.Form.Item( + form_name="username", + label="Username", + rules=[{ + "required": True, + "message": 'Please input your username!' + }, { + "pattern": + "^[a-zA-Z0-9]+$", + "message": + "Username can only contain letters and numbers!" + }, { + "min": + 6, + "message": + "Username must be at least 6 characters long!" + }, { + "max": + 20, + "message": + "Username must be at most 20 characters long!" + }]): + antd.Input() + with antd.Form.Item( + form_name="password", + label="Password", + rules=[ + { + "required": True, + "message": 'Please input your password!' + }, + { + # custom validator with javascript function + "validator": + """(rule, value, cb) => { + if (value !== '123') { + cb('Password must be "123"') + } + cb() + }""" + } + ]): + antd.Input.Password() + + with antd.Form.Item(wrapper_col=dict(offset=8, span=16)): + antd.Button("Submit", type="primary", html_type="submit") + form.finish(on_submit, inputs=[form]) + submit_btn.click(bind_action_event("submit"), outputs=[form]) + reset_btn.click(bind_action_event("reset"), outputs=[form]) + validate_btn.click(bind_action_event("validate"), outputs=[form]) + +if __name__ == "__main__": + demo.queue().launch() diff --git a/frontend/antd/form/Index.svelte b/frontend/antd/form/Index.svelte index bfe9a589..94345fda 100644 --- a/frontend/antd/form/Index.svelte +++ b/frontend/antd/form/Index.svelte @@ -15,9 +15,12 @@ import cls from 'classnames'; import { writable } from 'svelte/store'; + import type { FormProps } from './form'; + const AwaitedForm = importComponent(() => import('./form')); export let gradio: Gradio; export let value: Record; + export let form_action: FormProps['formAction'] | null = null; export let props: Record = {}; const updatedProps = writable(props); $: updatedProps.update((prev) => ({ ...prev, ...props })); @@ -43,6 +46,7 @@ elem_style, as_item, value, + form_action, restProps: $$restProps, }, { @@ -61,6 +65,7 @@ elem_style, as_item, value, + form_action, restProps: $$restProps, }); @@ -79,10 +84,14 @@ values_change: 'valuesChange', })} slots={$slots} + formAction={$mergedProps.form_action} value={$mergedProps.value} onValueChange={(v) => { value = v; }} + onResetFormAction={() => { + form_action = null; + }} {setSlotParams} > diff --git a/frontend/antd/form/form.tsx b/frontend/antd/form/form.tsx index b03dd843..da546642 100644 --- a/frontend/antd/form/form.tsx +++ b/frontend/antd/form/form.tsx @@ -2,30 +2,51 @@ import { sveltify } from '@svelte-preprocess-react'; import type { SetSlotParams } from '@svelte-preprocess-react/slot'; import { useEffect } from 'react'; import { useFunction } from '@utils/hooks/useFunction'; +import { useMemoizedFn } from '@utils/hooks/useMemoizedFn'; import { renderParamsSlot } from '@utils/renderParamsSlot'; import { Form as AForm, type GetProps } from 'antd'; -export const Form = sveltify< - GetProps & { - value: Record; - onValueChange: (value: Record) => void; - setSlotParams: SetSlotParams; - }, - ['requiredMark'] ->( +export interface FormProps extends GetProps { + value: Record; + onValueChange: (value: Record) => void; + setSlotParams: SetSlotParams; + formAction?: 'reset' | 'submit' | 'validate' | null; + onResetFormAction: () => void; +} + +export const Form = sveltify( ({ value, + formAction, onValueChange, requiredMark, onValuesChange, feedbackIcons, setSlotParams, slots, + onResetFormAction, ...props }) => { const [form] = AForm.useForm(); const feedbackIconsFunction = useFunction(feedbackIcons); const requiredMarkFunction = useFunction(requiredMark); + const onResetFormActionMemoized = useMemoizedFn(onResetFormAction); + + useEffect(() => { + switch (formAction) { + case 'reset': + form.resetFields(); + break; + case 'submit': + form.submit(); + break; + case 'validate': + form.validateFields(); + break; + } + onResetFormActionMemoized(); + }, [form, formAction, onResetFormActionMemoized]); + useEffect(() => { if (value) { form.setFieldsValue(value);