diff --git a/src/npm-fastui/src/components/FormField.tsx b/src/npm-fastui/src/components/FormField.tsx index 23614570..3aeec849 100644 --- a/src/npm-fastui/src/components/FormField.tsx +++ b/src/npm-fastui/src/components/FormField.tsx @@ -24,7 +24,7 @@ interface FormFieldInputProps extends FormFieldInput { } export const FormFieldInputComp: FC = (props) => { - const { name, placeholder, required, htmlType, locked, autocomplete, onChange } = props + const { name, placeholder, required, htmlType, locked, autocomplete, onChange, step } = props return (
@@ -39,6 +39,7 @@ export const FormFieldInputComp: FC = (props) => { disabled={locked} placeholder={placeholder} autoComplete={autocomplete} + step={step} aria-describedby={descId(props)} onChange={onChange} /> diff --git a/src/npm-fastui/src/models.d.ts b/src/npm-fastui/src/models.d.ts index 258254de..100af916 100644 --- a/src/npm-fastui/src/models.d.ts +++ b/src/npm-fastui/src/models.d.ts @@ -433,6 +433,7 @@ export interface FormFieldInput { initial?: string | number placeholder?: string autocomplete?: string + step?: number | 'any' type: 'FormFieldInput' } /** diff --git a/src/python-fastui/fastui/components/forms.py b/src/python-fastui/fastui/components/forms.py index b4f2f2d9..92d8f386 100644 --- a/src/python-fastui/fastui/components/forms.py +++ b/src/python-fastui/fastui/components/forms.py @@ -58,6 +58,9 @@ class FormFieldInput(BaseFormField): autocomplete: _t.Union[str, None] = None """Autocomplete value for the field.""" + step: _t.Union[float, _t.Literal['any'], None] = None + """Step value for the field.""" + type: _t.Literal['FormFieldInput'] = 'FormFieldInput' """The type of the component. Always 'FormFieldInput'.""" diff --git a/src/python-fastui/fastui/json_schema.py b/src/python-fastui/fastui/json_schema.py index c822f180..562793c7 100644 --- a/src/python-fastui/fastui/json_schema.py +++ b/src/python-fastui/fastui/json_schema.py @@ -198,6 +198,7 @@ def json_schema_field_to_field( autocomplete=schema.get('autocomplete'), description=schema.get('description'), placeholder=schema.get('placeholder'), + step=schema.get('step', _get_default_step(schema)), ) @@ -373,6 +374,14 @@ def input_html_type(schema: JsonSchemaField) -> InputHtmlType: raise ValueError(f'Unknown schema: {schema}') from e +def _get_default_step(schema: JsonSchemaField) -> _t.Union[_t.Literal['any'], None]: + key = schema['type'] + if key == 'integer': + return None + if key == 'number': + return 'any' + + def schema_is_field(schema: JsonSchemaConcrete) -> _ta.TypeGuard[JsonSchemaField]: """ Determine if a schema is a field `JsonSchemaField` diff --git a/src/python-fastui/tests/test_forms.py b/src/python-fastui/tests/test_forms.py index ceaa7a5d..2b45c26c 100644 --- a/src/python-fastui/tests/test_forms.py +++ b/src/python-fastui/tests/test_forms.py @@ -547,3 +547,47 @@ def test_form_fields(): 'submitUrl': '/foobar/', 'type': 'ModelForm', } + + +class FormNumbersDefaultStep(BaseModel): + size: int + cost: float + fees: float = Field(json_schema_extra={'step': '0.01'}) + + +def test_form_numbers_default_step(): + m = components.ModelForm(model=FormNumbersDefaultStep, submit_url='/foobar') + + assert m.model_dump(by_alias=True, exclude_none=True) == { + 'submitUrl': '/foobar', + 'method': 'POST', + 'type': 'ModelForm', + 'formFields': [ + { + 'name': 'size', + 'title': ['Size'], + 'required': True, + 'locked': False, + 'htmlType': 'number', + 'type': 'FormFieldInput', + }, + { + 'name': 'cost', + 'title': ['Cost'], + 'required': True, + 'locked': False, + 'htmlType': 'number', + 'step': 'any', + 'type': 'FormFieldInput', + }, + { + 'name': 'fees', + 'title': ['Fees'], + 'required': True, + 'locked': False, + 'htmlType': 'number', + 'step': 0.01, + 'type': 'FormFieldInput', + }, + ], + }