diff --git a/packages/alpinejs/src/directives/x-model.js b/packages/alpinejs/src/directives/x-model.js index eab094d85..1b17ba797 100644 --- a/packages/alpinejs/src/directives/x-model.js +++ b/packages/alpinejs/src/directives/x-model.js @@ -71,7 +71,8 @@ directive('model', (el, { modifiers, expression }, { effect, cleanup }) => { if (modifiers.includes('fill')) if ([undefined, null, ''].includes(getValue()) - || (el.type === 'checkbox' && Array.isArray(getValue()))) { + || (el.type === 'checkbox' && Array.isArray(getValue())) + || (el.tagName.toLowerCase() === 'select' && el.multiple)) { setValue( getInputValue(el, modifiers, { target: el }, getValue()) ); @@ -91,7 +92,7 @@ directive('model', (el, { modifiers, expression }, { effect, cleanup }) => { // on nextTick so the page doesn't end up out of sync if (el.form) { let removeResetListener = on(el.form, 'reset', [], (e) => { - nextTick(() => el._x_model && el._x_model.set(el.value)) + nextTick(() => el._x_model && el._x_model.set(getInputValue(el, modifiers, { target: el }, getValue()))) }) cleanup(() => removeResetListener()) } @@ -149,7 +150,7 @@ function getInputValue(el, modifiers, event, currentValue) { newValue = event.target.value } - return event.target.checked ? currentValue.concat([newValue]) : currentValue.filter(el => ! checkedAttrLooseCompare(el, newValue)) + return event.target.checked ? (currentValue.includes(newValue) ? currentValue : currentValue.concat([newValue])) : currentValue.filter(el => !checkedAttrLooseCompare(el, newValue)); } else { return event.target.checked } diff --git a/tests/cypress/integration/directives/x-model.spec.js b/tests/cypress/integration/directives/x-model.spec.js index ca01dc83f..7bd6e9f57 100644 --- a/tests/cypress/integration/directives/x-model.spec.js +++ b/tests/cypress/integration/directives/x-model.spec.js @@ -210,6 +210,169 @@ test('x-model updates value when the form is reset', } ) +test( + "x-model radio updates value when the form is reset", + html` +
+
+ + + + +
+ +
+ `, + ({ get }) => { + get("span").should(haveText("radio2")); + get("input[value='radio1']").click(); + get("span").should(haveText("radio1")); + get("button").click(); + get("span").should(haveText("radio2")); + } +); + +test( + "x-model.number radio updates value when the form is reset", + html` +
+
+ + + + +
+
+ `, + ({ get }) => { + get("[x-data]").should(haveData("foo", 2)); + get("input[value='1']").click(); + get("[x-data]").should(haveData("foo", 1)); + get("button").click(); + get("[x-data]").should(haveData("foo", 2)); + } +); + +test( + "x-model.boolean radio updates value when the form is reset", + html` +
+
+ + + +
+
+ `, + ({ get }) => { + get("[x-data]").should(haveData("foo", true)); + get("input[value='false']").click(); + get("[x-data]").should(haveData("foo", false)); + get("button").click(); + get("[x-data]").should(haveData("foo", true)); + } +); + +test( + "x-model checkbox array updates value when the form is reset", + html` +
+
+ + + + + +
+ +
+ `, + ({ get }) => { + get("span").should(haveText("checkbox2,checkbox3")); + get("input[value='checkbox1']").click(); + get("span").should(haveText("checkbox2,checkbox3,checkbox1")); + get("input[value='checkbox3']").click(); + get("span").should(haveText("checkbox2,checkbox1")); + get("button").click(); + get("span").should(haveText("checkbox2,checkbox3")); + } +); + +test( + "x-model.number checkbox array updates value when the form is reset", + html` +
+
+ + + + + +
+
+ `, + ({ get }) => { + get("[x-data]").should(haveData("foo", [2, 3])); + get("input[value='1']").click(); + get("[x-data]").should(haveData("foo", [2, 3, 1])); + get("input[value='3']").click(); + get("[x-data]").should(haveData("foo", [2, 1])); + get("button").click(); + get("[x-data]").should(haveData("foo", [2, 3])); + } +); + +test( + "x-model select updates value when the form is reset", + html` +
+
+ + + + + +
+
+ `, + ({ get }) => { + get("[x-data]").should(haveData("a", "456")); + get("[x-data]").should(haveData("b", ["123", "789"])); + get("[x-data]").should(haveData("c", 456)); + get("[x-data]").should(haveData("d", [123, 789])); + get("select#a").select("789"); + get("select#b").select("456"); + get("select#c").select("789"); + get("select#d").select("456"); + get("[x-data]").should(haveData("a", "789")); + get("[x-data]").should(haveData("b", ["456"])); + get("[x-data]").should(haveData("c", 789)); + get("[x-data]").should(haveData("d", [456])); + get("button").click(); + get("[x-data]").should(haveData("a", "456")); + get("[x-data]").should(haveData("b", ["123", "789"])); + get("[x-data]").should(haveData("c", 456)); + get("[x-data]").should(haveData("d", [123, 789])); + } +); + + test('x-model with fill modifier takes input value on null, empty string or undefined', html`
@@ -236,7 +399,7 @@ test('x-model with fill modifier takes input value on null, empty string or unde test('x-model with fill modifier works with select elements', html` -
+
+ + + +
`, ({ get }) => { get('[x-data]').should(haveData('a', '456')); get('[x-data]').should(haveData('b', ['123', '456'])); + get('[x-data]').should(haveData('c', 456)); + get('[x-data]').should(haveData('d', [123, 456])); + get('[x-data]').should(haveData('e', true)); + get('[x-data]').should(haveData('f', [true, false])); } );