Skip to content

Commit

Permalink
fix: handle static form values in combination with default values (#1…
Browse files Browse the repository at this point in the history
…4555)

When the `value` or `checked` attribute of an input or the contents of a textarea were static, setting the `defaulValue/defaultChecked` property caused the latter to take precedence over the former. This is due to how we transform the code: If the value is static, we put it onto
  • Loading branch information
dummdidumm authored Dec 4, 2024
1 parent c0746e0 commit a151ae1
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/blue-fans-greet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: handle static form values in combination with default values
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ export function RegularElement(node, context) {

const is = is_custom_element
? build_custom_element_attribute_update_assignment(node_id, attribute, context)
: build_element_attribute_update_assignment(node, node_id, attribute, context);
: build_element_attribute_update_assignment(node, node_id, attribute, attributes, context);
if (is) is_attributes_reactive = true;
}
}
Expand Down Expand Up @@ -519,10 +519,17 @@ function setup_select_synchronization(value_binding, context) {
* @param {AST.RegularElement} element
* @param {Identifier} node_id
* @param {AST.Attribute} attribute
* @param {Array<AST.Attribute | AST.SpreadAttribute>} attributes
* @param {ComponentContext} context
* @returns {boolean}
*/
function build_element_attribute_update_assignment(element, node_id, attribute, context) {
function build_element_attribute_update_assignment(
element,
node_id,
attribute,
attributes,
context
) {
const state = context.state;
const name = get_attribute_name(element, attribute);
const is_svg = context.state.metadata.namespace === 'svg' || element.name === 'svg';
Expand Down Expand Up @@ -565,6 +572,26 @@ function build_element_attribute_update_assignment(element, node_id, attribute,
update = b.stmt(b.call('$.set_checked', node_id, value));
} else if (name === 'selected') {
update = b.stmt(b.call('$.set_selected', node_id, value));
} else if (
// If we would just set the defaultValue property, it would override the value property,
// because it is set in the template which implicitly means it's also setting the default value,
// and if one updates the default value while the input is pristine it will also update the
// current value, which is not what we want, which is why we need to do some extra work.
name === 'defaultValue' &&
(attributes.some(
(attr) => attr.type === 'Attribute' && attr.name === 'value' && is_text_attribute(attr)
) ||
(element.name === 'textarea' && element.fragment.nodes.length > 0))
) {
update = b.stmt(b.call('$.set_default_value', node_id, value));
} else if (
// See defaultValue comment
name === 'defaultChecked' &&
attributes.some(
(attr) => attr.type === 'Attribute' && attr.name === 'checked' && attr.value === true
)
) {
update = b.stmt(b.call('$.set_default_checked', node_id, value));
} else if (is_dom_property(name)) {
update = b.stmt(b.assignment('=', b.member(node_id, name), value));
} else {
Expand Down
22 changes: 22 additions & 0 deletions packages/svelte/src/internal/client/dom/elements/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,28 @@ export function set_selected(element, selected) {
}
}

/**
* Applies the default checked property without influencing the current checked property.
* @param {HTMLInputElement} element
* @param {boolean} checked
*/
export function set_default_checked(element, checked) {
const existing_value = element.checked;
element.defaultChecked = checked;
element.checked = existing_value;
}

/**
* Applies the default value property without influencing the current value property.
* @param {HTMLInputElement | HTMLTextAreaElement} element
* @param {string} value
*/
export function set_default_value(element, value) {
const existing_value = element.value;
element.defaultValue = value;
element.value = existing_value;
}

/**
* @param {Element} element
* @param {string} attribute
Expand Down
4 changes: 3 additions & 1 deletion packages/svelte/src/internal/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ export {
handle_lazy_img,
set_value,
set_checked,
set_selected
set_selected,
set_default_checked,
set_default_value
} from './dom/elements/attributes.js';
export { set_class, set_svg_class, set_mathml_class, toggle_class } from './dom/elements/class.js';
export { apply, event, delegate, replay_events } from './dom/elements/events.js';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default test({
const after_reset = [];

const reset = /** @type {HTMLInputElement} */ (target.querySelector('input[type=reset]'));
const [test1, test2, test3, test4, test5] = target.querySelectorAll('div');
const [test1, test2, test3, test4, test5, test12] = target.querySelectorAll('div');
const [test6, test7, test8, test9] = target.querySelectorAll('select');
const [
test1_span,
Expand Down Expand Up @@ -201,6 +201,20 @@ export default test({
});
}

{
/** @type {NodeListOf<HTMLInputElement | HTMLTextAreaElement>} */
const inputs = test12.querySelectorAll('input, textarea');
assert.equal(inputs[0].value, 'x');
assert.equal(/** @type {HTMLInputElement} */ (inputs[1]).checked, true);
assert.equal(inputs[2].value, 'x');

after_reset.push(() => {
assert.equal(inputs[0].value, 'y');
assert.equal(/** @type {HTMLInputElement} */ (inputs[1]).checked, false);
assert.equal(inputs[2].value, 'y');
});
}

reset.click();
await Promise.resolve();
flushSync();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@
<option value="c">C</option>
</select>

<p>Static values</p>
<div class="test-12">
<input value="x" defaultValue="y" />
<input type="checkbox" checked defaultChecked={false} />
<textarea defaultValue="y">x</textarea>
</div>

<input type="reset" value="Reset" />
</form>

Expand Down

0 comments on commit a151ae1

Please sign in to comment.