Skip to content

fix(form): unify Ctrl+Enter, Shift+Enter and Submit button behavior (#8324)#9836

Open
AnshManwani wants to merge 3 commits into
marimo-team:mainfrom
AnshManwani:fix/form-submit-8324
Open

fix(form): unify Ctrl+Enter, Shift+Enter and Submit button behavior (#8324)#9836
AnshManwani wants to merge 3 commits into
marimo-team:mainfrom
AnshManwani:fix/form-submit-8324

Conversation

@AnshManwani

Copy link
Copy Markdown

Closes #8324

Summary

Fixes three inconsistent behaviors in mo.ui.form when using clear_on_submit=True:

Bug 1 — Submit button fired on_change twice**
form._convert_value() called element._update(value) which always triggers _on_change. Fixed by adding _update_value() method that updates value silently without firing the callback.

Bug 2 — Ctrl+Enter bypassed onSubmit**
FormPlugin.tsx called setValue() directly in onKeyDown, skipping validation and clearOnSubmit. Fixed by replacing with formDiv.current?.requestSubmit().

Bug 3 — Shift+Enter bypassed the form wrapper**
OnBlurredInput's onKeyDown fired setValue on any Enter with no modifier check. Fixed by adding if (event.shiftKey) return guard.

Files Changed

  • frontend/src/plugins/impl/FormPlugin.tsx — requestSubmit() instead of direct setValue()
  • frontend/src/components/ui/input.tsx — shiftKey guard in OnBlurredInput
  • marimo/_plugins/ui/_core/ui_element.py — new _update_value() method
  • marimo/_plugins/ui/_impl/input.py — form._convert_value uses _update_value
  • marimo/_plugins/ui/_impl/array.py — propagate silent update to children
  • marimo/_plugins/ui/_impl/batch.py — propagate silent update to children

Test Plan

Frontend (Vitest):

  • ✅ Ctrl+Enter triggers validation
  • ✅ Ctrl+Enter triggers clearOnSubmit
  • ✅ Shift+Enter does NOT submit

Backend (pytest):

  • ✅ Wrapped element's on_change fires exactly once on form submit
  • ✅ _update_value() does not trigger on_change
  • ✅ 40 passed, 7 skipped in test_input.py

@vercel

vercel Bot commented Jun 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Jun 13, 2026 8:15am

Request Review

@kirangadhave

Copy link
Copy Markdown
Member

@cubic-dev-ai

@cubic-dev-ai

cubic-dev-ai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai

@kirangadhave I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 8 files

Architecture diagram
sequenceDiagram
    participant User as User
    participant UI as Browser UI
    participant Form as FormWrapper Component
    participant Input as OnBlurredInput
    participant WS as WebSocket
    participant PyForm as Python Form UIElement
    participant PyElement as Wrapped Element
    participant OnChange as on_change Callback

    Note over User,OnChange: Happy Path - Ctrl+Enter Submission with clear_on_submit=True

    User->>UI: Press Ctrl+Enter in form field
    UI->>Input: onKeyDown event (key="Enter", ctrlKey=true)
    Input->>Input: Check modifier keys
    Note over Input: Shift+Enter is now blocked (returns early)
    Input->>Form: requestSubmit()
    Form->>Form: Validate and clearOnSubmit logic
    Form->>WS: Send value to backend
    WS->>PyForm: Receive submission value
    PyForm->>PyForm: _update() called
    PyForm->>PyForm: _convert_value() runs
    alt _updating_value flag set
        PyForm->>PyElement: _update_value() (silent update)
        Note over PyElement: NEW: _update_value() sets value without firing callback
    else normal update
        PyForm->>PyElement: _update() (triggers callback)
    end
    alt Element is array
        PyElement->>PyElement: Propagate _update_value() to child elements
    else Element is batch
        PyElement->>PyElement: Propagate _update_value() to child elements
    else Element is masked text
        PyElement->>PyElement: _update_value() handles mask state
    end
    PyElement-->>PyForm: Return new value
    PyForm->>OnChange: Fire form's on_change callback
    OnChange-->>PyForm: Callback executed
    PyForm->>WS: Confirm submission
    WS->>Form: Clear form if clear_on_submit=True
    Form->>UI: Render updated form state
    UI->>User: Display submitted result

    Note over User,OnChange: Error Path - Validation Failure on Ctrl+Enter

    User->>UI: Press Ctrl+Enter in form field
    UI->>Input: onKeyDown event
    Input->>Form: requestSubmit()
    Form->>Form: Validate submission
    alt Validation fails
        Form->>Form: Set error message state
        Form->>UI: Display validation error
        UI->>User: Show validation error message
        Note over Form: setValue NOT called - submission blocked
    end

    Note over User,OnChange: Shift+Enter Behavior (No Submission)

    User->>UI: Press Shift+Enter in form field
    UI->>Input: onKeyDown event (key="Enter", shiftKey=true)
    Input->>Input: CHANGED: Return early - shiftKey guard
    Note over Input: No submission or value change occurs
    Input-->>UI: No action taken
    UI->>User: Character input only, no form action
Loading

Re-trigger cubic

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes inconsistent mo.ui.form(..., clear_on_submit=True) behavior by ensuring keyboard-based submissions and the submit button all go through the same submit pathway, and by preventing wrapped elements’ on_change handlers from firing during form submission.

Changes:

  • Frontend: Ctrl/Meta+Enter now triggers a real form submit via requestSubmit() (so validation + clearOnSubmit run consistently), and Shift+Enter is prevented from committing values in OnBlurredInput.
  • Backend: adds _update_value() to update element values without invoking on_change, and uses it during form value conversion; array/batch propagate these silent updates to children.
  • Tests: adds Vitest coverage for form keyboard submit/validation/clear behavior and pytest coverage ensuring wrapped on_change is not invoked by form submission.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
frontend/src/plugins/impl/FormPlugin.tsx Uses requestSubmit() for Ctrl/Meta+Enter to unify submission behavior with the submit button.
frontend/src/components/ui/input.tsx Prevents Shift+Enter from committing a value in OnBlurredInput to avoid bypassing form logic.
frontend/src/plugins/impl/__tests__/FormPlugin.test.tsx Adds frontend tests for Ctrl+Enter submit/validation/clear and Shift+Enter non-submit behavior.
marimo/_plugins/ui/_core/ui_element.py Introduces _update_value() for silent value updates (no on_change).
marimo/_plugins/ui/_impl/input.py Uses _update_value() during form conversion; adds masked-input override for silent updates.
marimo/_plugins/ui/_impl/array.py Propagates silent update mode to child elements when updating from a parent silent update.
marimo/_plugins/ui/_impl/batch.py Propagates silent update mode to child elements when updating from a parent silent update.
tests/_plugins/ui/_impl/test_input.py Adds backend tests ensuring silent updates don’t fire on_change, and forms don’t trigger wrapped element on_change.

Comment on lines +159 to +161
// Wait a bit to ensure it wasn't triggered
await new Promise((resolve) => setTimeout(resolve, 100));

Comment on lines +472 to +478
try:
self._value = self._convert_value(value)
except MarimoConvertValueException:
raise
finally:
if hasattr(self, "_updating_value"):
del self._updating_value
@AnshManwani

Copy link
Copy Markdown
Author

Hi @kirangadhave, just checking in — let me know if you need any changes or have feedback. Happy to iterate!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Form Submit button, Ctrl+Enter, and Shift+Enter all have different behavior

3 participants