Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/shiny-cats-crash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@modelscope-studio/antd': minor
'modelscope_studio': minor
---

feat: add the `form_action` property to manually trigger form actions
2 changes: 2 additions & 0 deletions backend/modelscope_studio/components/antd/form/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion docs/components/antd/form/README-zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@ High-performance form component with data domain management. Includes data entry
## Examples

<demo name="basic"></demo>
<demo name="form_rules" title="Form Rules"></demo>
<demo name="form_rules" title="表单规则"></demo>
<demo name="dynamic_form" title="动态表单"></demo>

通过修改`form_action`可以手动触发表单动作,每当`form_action`变化并触发对应表单动作时,都会自动重置`form_action`的值,以便后续多次调用。

<demo name="form_action" title="表单动作"></demo>
5 changes: 5 additions & 0 deletions docs/components/antd/form/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ High-performance form component with data domain management. Includes data entry

<demo name="basic"></demo>
<demo name="form_rules" title="Form Rules"></demo>
<demo name="dynamic_form" title="Dynamic Form"></demo>

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.

<demo name="form_action" title="Form Action"></demo>
37 changes: 37 additions & 0 deletions docs/components/antd/form/demos/dynamic_form.py
Original file line number Diff line number Diff line change
@@ -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()
80 changes: 80 additions & 0 deletions docs/components/antd/form/demos/form_action.py
Original file line number Diff line number Diff line change
@@ -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()
}"""
Comment on lines +62 to +67
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The custom validator logic is incorrect. The callback cb is called unconditionally at the end, which will likely cause the validation to always pass, regardless of the input value. The cb() call should only be made once per validation path. Using return ensures the function exits after calling the callback, which is a robust pattern for this kind of validator.

Suggested change
"""(rule, value, cb) => {
if (value !== '123') {
cb('Password must be "123"')
}
cb()
}"""
"""(rule, value, cb) => {
if (value !== '123') {
return cb('Password must be "123"');
}
return 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()
9 changes: 9 additions & 0 deletions frontend/antd/form/Index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, any>;
export let form_action: FormProps['formAction'] | null = null;
export let props: Record<string, any> = {};
const updatedProps = writable(props);
$: updatedProps.update((prev) => ({ ...prev, ...props }));
Expand All @@ -43,6 +46,7 @@
elem_style,
as_item,
value,
form_action,
restProps: $$restProps,
},
{
Expand All @@ -61,6 +65,7 @@
elem_style,
as_item,
value,
form_action,
restProps: $$restProps,
});
</script>
Expand All @@ -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}
>
<slot />
Expand Down
37 changes: 29 additions & 8 deletions frontend/antd/form/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof AForm> & {
value: Record<string, any>;
onValueChange: (value: Record<string, any>) => void;
setSlotParams: SetSlotParams;
},
['requiredMark']
>(
export interface FormProps extends GetProps<typeof AForm> {
value: Record<string, any>;
onValueChange: (value: Record<string, any>) => void;
setSlotParams: SetSlotParams;
formAction?: 'reset' | 'submit' | 'validate' | null;
onResetFormAction: () => void;
}

export const Form = sveltify<FormProps, ['requiredMark']>(
({
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]);
Comment on lines +35 to +48
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For clarity and to prevent unintended side effects, it's better to add a guard clause to ensure that onResetFormActionMemoized is only called when an action is actually performed. This makes the code's intent clearer and more robust.

    useEffect(() => {
      if (!formAction) {
        return;
      }
      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);
Expand Down