From 8e121ce9a2b1f12560c982c860756552ed2b4ee5 Mon Sep 17 00:00:00 2001
From: Col0ring <1561999073@qq.com>
Date: Wed, 10 Sep 2025 18:35:52 +0800
Subject: [PATCH] feat: add the `form_action` property to manually trigger form
actions
---
.changeset/shiny-cats-crash.md | 6 ++
.../components/antd/form/__init__.py | 2 +
docs/components/antd/form/README-zh_CN.md | 7 +-
docs/components/antd/form/README.md | 5 ++
.../antd/form/demos/dynamic_form.py | 37 +++++++++
.../components/antd/form/demos/form_action.py | 80 +++++++++++++++++++
frontend/antd/form/Index.svelte | 9 +++
frontend/antd/form/form.tsx | 37 +++++++--
8 files changed, 174 insertions(+), 9 deletions(-)
create mode 100644 .changeset/shiny-cats-crash.md
create mode 100644 docs/components/antd/form/demos/dynamic_form.py
create mode 100644 docs/components/antd/form/demos/form_action.py
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);