Skip to content

Commit a3b9829

Browse files
authored
fix(x-model): flush x-model.blur value before form submit handlers run (#4729)
When using `x-model.blur` on an input inside a form, submitting via Enter key does not sync the value before the submit handler executes. The browser fires events in order: keydown → submit → blur, so the blur listener never runs before the submit handler reads the value. Register pending model update callbacks on the form element and flush them before any submit handler runs via the `on()` utility.
1 parent dcc8e6b commit a3b9829

File tree

3 files changed

+43
-0
lines changed

3 files changed

+43
-0
lines changed

packages/alpinejs/src/directives/x-model.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,19 @@ directive('model', (el, { modifiers, expression }, { effect, cleanup }) => {
7777

7878
if (hasBlurModifier) {
7979
listeners.push(on(el, 'blur', modifiers, syncValue))
80+
81+
// The browser fires "submit" before "blur", so if this input
82+
// is inside a form, the model value would be stale when the
83+
// submit handler runs. Register a pending update on the form
84+
// so it can be flushed before submit handlers execute.
85+
if (el.form) {
86+
let syncCallback = () => syncValue({ target: el })
87+
88+
if (!el.form._x_pendingModelUpdates) el.form._x_pendingModelUpdates = []
89+
el.form._x_pendingModelUpdates.push(syncCallback)
90+
91+
cleanup(() => el.form._x_pendingModelUpdates.splice(el.form._x_pendingModelUpdates.indexOf(syncCallback), 1))
92+
}
8093
}
8194

8295
if (hasEnterModifier) {

packages/alpinejs/src/utils/on.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,18 @@ export default function on (el, event, modifiers, callback) {
6666

6767
if (modifiers.includes('self')) handler = wrapHandler(handler, (next, e) => { e.target === el && next(e) })
6868

69+
// Flush any pending model updates before submit handlers run
70+
// (e.g. x-model.blur inputs that haven't synced yet).
71+
if (event === 'submit') {
72+
handler = wrapHandler(handler, (next, e) => {
73+
if (e.target._x_pendingModelUpdates) {
74+
e.target._x_pendingModelUpdates.forEach(fn => fn())
75+
}
76+
77+
next(e)
78+
})
79+
}
80+
6981
// Handle :keydown and :keyup listeners.
7082
// Handle :click and :auxclick listeners.
7183
if (isKeyEvent(event) || isClickEvent(event)) {

tests/cypress/integration/directives/x-model.spec.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,3 +642,21 @@ test('x-model.enter.blur updates on enter OR blur (enter should work)',
642642
}
643643
)
644644

645+
test('x-model.blur syncs value before form submit handler runs',
646+
html`
647+
<div x-data="{ value: '', capturedValue: '' }">
648+
<form @submit.prevent="capturedValue = value">
649+
<input x-model.blur="value" type="text">
650+
</form>
651+
<span id="captured" x-text="capturedValue"></span>
652+
</div>
653+
`,
654+
({ get }) => {
655+
get('input').type('hello')
656+
get('#captured').should(haveText(''))
657+
// Submit the form without blurring the input first
658+
get('form').then(([form]) => form.requestSubmit())
659+
get('#captured').should(haveText('hello'))
660+
}
661+
)
662+

0 commit comments

Comments
 (0)