Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: handle static form values in combination with default values #14555

Merged
merged 1 commit into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading