pull/16481/merge
dai 3 days ago committed by GitHub
commit 424be530dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: transform input defaults from spread

@ -72,6 +72,7 @@ export function RegularElement(node, context) {
let has_spread = node.metadata.has_spread;
let has_use = false;
let should_remove_defaults = false;
for (const attribute of node.attributes) {
switch (attribute.type) {
@ -172,7 +173,12 @@ export function RegularElement(node, context) {
bindings.has('group') ||
(!bindings.has('group') && has_value_attribute))
) {
context.state.init.push(b.stmt(b.call('$.remove_input_defaults', context.state.node)));
if (has_spread) {
// remove_input_defaults will be called inside set_attributes
should_remove_defaults = true;
} else {
context.state.init.push(b.stmt(b.call('$.remove_input_defaults', context.state.node)));
}
}
}
@ -202,7 +208,15 @@ export function RegularElement(node, context) {
bindings.has('checked');
if (has_spread) {
build_attribute_effect(attributes, class_directives, style_directives, context, node, node_id);
build_attribute_effect(
attributes,
class_directives,
style_directives,
context,
node,
node_id,
should_remove_defaults
);
} else {
for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) {
if (is_event_attribute(attribute)) {

@ -16,6 +16,7 @@ import { build_expression, build_template_chunk, Memoizer } from './utils.js';
* @param {ComponentContext} context
* @param {AST.RegularElement | AST.SvelteElement} element
* @param {Identifier} element_id
* @param {boolean} [should_remove_defaults]
*/
export function build_attribute_effect(
attributes,
@ -23,7 +24,8 @@ export function build_attribute_effect(
style_directives,
context,
element,
element_id
element_id,
should_remove_defaults = false
) {
/** @type {ObjectExpression['properties']} */
const values = [];
@ -91,6 +93,7 @@ export function build_attribute_effect(
element.metadata.scoped &&
context.state.analysis.css.hash !== '' &&
b.literal(context.state.analysis.css.hash),
should_remove_defaults && b.true,
is_ignored(element, 'hydration_attribute_changed') && b.true
)
)

@ -11,6 +11,7 @@ import {
import { regex_starts_with_newline } from '../../../../patterns.js';
import * as b from '#compiler/builders';
import {
ELEMENT_IS_INPUT,
ELEMENT_IS_NAMESPACED,
ELEMENT_PRESERVE_ATTRIBUTE_CASE
} from '../../../../../../constants.js';
@ -401,6 +402,8 @@ function build_element_spread_attributes(
flags |= ELEMENT_IS_NAMESPACED | ELEMENT_PRESERVE_ATTRIBUTE_CASE;
} else if (is_custom_element_node(element)) {
flags |= ELEMENT_PRESERVE_ATTRIBUTE_CASE;
} else if (element.type === 'RegularElement' && element.name === 'input') {
flags |= ELEMENT_IS_INPUT;
}
const object = build_spread_object(element, attributes, context);

@ -28,6 +28,7 @@ export const HYDRATION_ERROR = {};
export const ELEMENT_IS_NAMESPACED = 1;
export const ELEMENT_PRESERVE_ATTRIBUTE_CASE = 1 << 1;
export const ELEMENT_IS_INPUT = 1 << 2;
export const UNINITIALIZED = Symbol();

@ -31,8 +31,8 @@ const IS_CUSTOM_ELEMENT = Symbol('is custom element');
const IS_HTML = Symbol('is html');
/**
* The value/checked attribute in the template actually corresponds to the defaultValue property, so we need
* to remove it upon hydration to avoid a bug when someone resets the form value.
* The value/checked attribute in the template actually corresponds to the defaultValue property, so we need to remove
* it upon hydration to avoid a bug when someone resets the form value
* @param {HTMLInputElement} input
* @returns {void}
*/
@ -268,10 +268,30 @@ export function set_custom_element_data(node, prop, value) {
* @param {Record<string | symbol, any> | undefined} prev
* @param {Record<string | symbol, any>} next New attributes - this function mutates this object
* @param {string} [css_hash]
* @param {boolean} [should_remove_defaults]
* @param {boolean} [skip_warning]
* @returns {Record<string, any>}
*/
export function set_attributes(element, prev, next, css_hash, skip_warning = false) {
export function set_attributes(
element,
prev,
next,
css_hash,
should_remove_defaults = false,
skip_warning = false
) {
// prettier-ignore
if (
hydrating &&
should_remove_defaults &&
element.tagName === 'INPUT' &&
(/** @type {HTMLInputElement} */ (element).type === 'checkbox'
? !('defaultChecked' in next)
: !('defaultValue' in next))
) {
remove_input_defaults(/** @type {HTMLInputElement} */ (element));
}
var attributes = get_attributes(element);
var is_custom_element = attributes[IS_CUSTOM_ELEMENT];
@ -467,6 +487,7 @@ export function set_attributes(element, prev, next, css_hash, skip_warning = fal
* @param {Array<() => any>} sync
* @param {Array<() => Promise<any>>} async
* @param {string} [css_hash]
* @param {boolean} [should_remove_defaults]
* @param {boolean} [skip_warning]
*/
export function attribute_effect(
@ -475,6 +496,7 @@ export function attribute_effect(
sync = [],
async = [],
css_hash,
should_remove_defaults = false,
skip_warning = false
) {
flatten(sync, async, (values) => {
@ -490,7 +512,14 @@ export function attribute_effect(
block(() => {
var next = fn(...values.map(get));
/** @type {Record<string | symbol, any>} */
var current = set_attributes(element, prev, next, css_hash, skip_warning);
var current = set_attributes(
element,
prev,
next,
css_hash,
should_remove_defaults,
skip_warning
);
if (inited && is_select && 'value' in next) {
select_option(/** @type {HTMLSelectElement} */ (element), next.value);

@ -8,7 +8,8 @@ import { subscribe_to_store } from '../../store/utils.js';
import {
UNINITIALIZED,
ELEMENT_PRESERVE_ATTRIBUTE_CASE,
ELEMENT_IS_NAMESPACED
ELEMENT_IS_NAMESPACED,
ELEMENT_IS_INPUT
} from '../../constants.js';
import { escape_html } from '../../escaping.js';
import { DEV } from 'esm-env';
@ -187,6 +188,7 @@ export function spread_attributes(attrs, css_hash, classes, styles, flags = 0) {
const is_html = (flags & ELEMENT_IS_NAMESPACED) === 0;
const lowercase = (flags & ELEMENT_PRESERVE_ATTRIBUTE_CASE) === 0;
const ignore_defaults = (flags & ELEMENT_IS_INPUT) === 0;
for (name in attrs) {
// omit functions, internal svelte properties and invalid attribute names
@ -200,6 +202,16 @@ export function spread_attributes(attrs, css_hash, classes, styles, flags = 0) {
name = name.toLowerCase();
}
if (!ignore_defaults) {
if (name === 'defaultvalue') {
name = 'value';
if (attrs[name]) continue;
} else if (name === 'defaultchecked') {
name = 'checked';
if (attrs[name]) continue;
}
}
attr_str += attr(name, value, is_html && is_boolean_attribute(name));
}

@ -0,0 +1,10 @@
import { test } from '../../test';
export default test({
mode: ['server'],
html: `
<input value="a">
<input type="checkbox" checked>
<input value="b">
`
});

@ -0,0 +1,8 @@
<script>
let text = { defaultValue: "a" };
let checkbox = { defaultChecked: true }
</script>
<input {...text} />
<input type="checkbox" {...checkbox} />
<input value="b" {...text} />
Loading…
Cancel
Save