fix spreading twice

pull/16481/head
7nik 1 month ago
parent 2d17f4af36
commit aeab7b7c01

@ -72,6 +72,7 @@ export function RegularElement(node, context) {
let has_spread = node.metadata.has_spread; let has_spread = node.metadata.has_spread;
let has_use = false; let has_use = false;
let should_remove_defaults = false;
for (const attribute of node.attributes) { for (const attribute of node.attributes) {
switch (attribute.type) { switch (attribute.type) {
@ -172,16 +173,12 @@ export function RegularElement(node, context) {
bindings.has('group') || bindings.has('group') ||
(!bindings.has('group') && has_value_attribute)) (!bindings.has('group') && has_value_attribute))
) { ) {
const spreads = has_spread if (has_spread) {
? b.object( // remove_input_defaults will be called inside set_attributes
attributes should_remove_defaults = true;
.filter((attr) => attr.type === 'SpreadAttribute') } else {
.map((attr) => b.spread(attr.expression)) context.state.init.push(b.stmt(b.call('$.remove_input_defaults', context.state.node)));
) }
: null;
context.state.init.push(
b.stmt(b.call('$.remove_input_defaults', context.state.node, spreads))
);
} }
} }
@ -211,7 +208,15 @@ export function RegularElement(node, context) {
bindings.has('checked'); bindings.has('checked');
if (has_spread) { 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 { } else {
for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) { for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) {
if (is_event_attribute(attribute)) { if (is_event_attribute(attribute)) {

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

@ -31,20 +31,14 @@ const IS_CUSTOM_ELEMENT = Symbol('is custom element');
const IS_HTML = Symbol('is html'); const IS_HTML = Symbol('is html');
/** /**
* The value/checked attribute in the template actually corresponds to the defaultValue property, * The value/checked attribute in the template actually corresponds to the defaultValue property, so we need to remove
* so we need to remove it upon hydration to avoid a bug when someone resets the form value, * it upon hydration to avoid a bug when someone resets the form value
* unless the property is presented in the spreaded objects and is handled by `set_attributes()`
* @param {HTMLInputElement} input * @param {HTMLInputElement} input
* @param {Record<string, any>} [spread]
* @returns {void} * @returns {void}
*/ */
export function remove_input_defaults(input, spread) { export function remove_input_defaults(input) {
if (!hydrating) return; if (!hydrating) return;
if (spread && (input.type === 'checkbox' ? 'defaultChecked' : 'defaultValue') in spread) {
return;
}
var already_removed = false; var already_removed = false;
// We try and remove the default attributes later, rather than sync during hydration. // We try and remove the default attributes later, rather than sync during hydration.
@ -274,10 +268,30 @@ export function set_custom_element_data(node, prop, value) {
* @param {Record<string | symbol, any> | undefined} prev * @param {Record<string | symbol, any> | undefined} prev
* @param {Record<string | symbol, any>} next New attributes - this function mutates this object * @param {Record<string | symbol, any>} next New attributes - this function mutates this object
* @param {string} [css_hash] * @param {string} [css_hash]
* @param {boolean} [should_remove_defaults]
* @param {boolean} [skip_warning] * @param {boolean} [skip_warning]
* @returns {Record<string, any>} * @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 attributes = get_attributes(element);
var is_custom_element = attributes[IS_CUSTOM_ELEMENT]; var is_custom_element = attributes[IS_CUSTOM_ELEMENT];
@ -471,6 +485,7 @@ export function set_attributes(element, prev, next, css_hash, skip_warning = fal
* @param {Array<() => any>} sync * @param {Array<() => any>} sync
* @param {Array<() => Promise<any>>} async * @param {Array<() => Promise<any>>} async
* @param {string} [css_hash] * @param {string} [css_hash]
* @param {boolean} [should_remove_defaults]
* @param {boolean} [skip_warning] * @param {boolean} [skip_warning]
*/ */
export function attribute_effect( export function attribute_effect(
@ -479,6 +494,7 @@ export function attribute_effect(
sync = [], sync = [],
async = [], async = [],
css_hash, css_hash,
should_remove_defaults = false,
skip_warning = false skip_warning = false
) { ) {
flatten(sync, async, (values) => { flatten(sync, async, (values) => {
@ -494,7 +510,14 @@ export function attribute_effect(
block(() => { block(() => {
var next = fn(...values.map(get)); var next = fn(...values.map(get));
/** @type {Record<string | symbol, any>} */ /** @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) { if (inited && is_select && 'value' in next) {
select_option(/** @type {HTMLSelectElement} */ (element), next.value); select_option(/** @type {HTMLSelectElement} */ (element), next.value);

Loading…
Cancel
Save