Skip to content

Commit 558a3c9

Browse files
daimond1137nikRich-Harris
authored
fix: transform input defaults from spread (#16481)
* fix: transform input defaults from spread * chore: add changeset * fix: prevent duplicates * do not remove defaults if they are in spreads * fix spreading twice * tweak * tweak * drive-by: remove unused export * tweak * undo comment change, to minimise diff * oops * tweak --------- Co-authored-by: 7nik <[email protected]> Co-authored-by: Rich Harris <[email protected]>
1 parent 2743cd0 commit 558a3c9

File tree

10 files changed

+85
-7
lines changed

10 files changed

+85
-7
lines changed

.changeset/quiet-planes-doubt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: transform input defaults from spread

packages/svelte/src/compiler/phases/3-transform/client/visitors/RegularElement.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export function RegularElement(node, context) {
7272

7373
let has_spread = node.metadata.has_spread;
7474
let has_use = false;
75+
let should_remove_defaults = false;
7576

7677
for (const attribute of node.attributes) {
7778
switch (attribute.type) {
@@ -172,7 +173,12 @@ export function RegularElement(node, context) {
172173
bindings.has('group') ||
173174
(!bindings.has('group') && has_value_attribute))
174175
) {
175-
context.state.init.push(b.stmt(b.call('$.remove_input_defaults', context.state.node)));
176+
if (has_spread) {
177+
// remove_input_defaults will be called inside set_attributes
178+
should_remove_defaults = true;
179+
} else {
180+
context.state.init.push(b.stmt(b.call('$.remove_input_defaults', context.state.node)));
181+
}
176182
}
177183
}
178184

@@ -202,7 +208,15 @@ export function RegularElement(node, context) {
202208
bindings.has('checked');
203209

204210
if (has_spread) {
205-
build_attribute_effect(attributes, class_directives, style_directives, context, node, node_id);
211+
build_attribute_effect(
212+
attributes,
213+
class_directives,
214+
style_directives,
215+
context,
216+
node,
217+
node_id,
218+
should_remove_defaults
219+
);
206220
} else {
207221
for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) {
208222
if (is_event_attribute(attribute)) {

packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/element.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,16 @@ import { build_expression, build_template_chunk, Memoizer } from './utils.js';
1616
* @param {ComponentContext} context
1717
* @param {AST.RegularElement | AST.SvelteElement} element
1818
* @param {Identifier} element_id
19+
* @param {boolean} [should_remove_defaults]
1920
*/
2021
export function build_attribute_effect(
2122
attributes,
2223
class_directives,
2324
style_directives,
2425
context,
2526
element,
26-
element_id
27+
element_id,
28+
should_remove_defaults = false
2729
) {
2830
/** @type {ObjectExpression['properties']} */
2931
const values = [];
@@ -91,6 +93,7 @@ export function build_attribute_effect(
9193
element.metadata.scoped &&
9294
context.state.analysis.css.hash !== '' &&
9395
b.literal(context.state.analysis.css.hash),
96+
should_remove_defaults && b.true,
9497
is_ignored(element, 'hydration_attribute_changed') && b.true
9598
)
9699
)

packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import { regex_starts_with_newline } from '../../../../patterns.js';
1212
import * as b from '#compiler/builders';
1313
import {
14+
ELEMENT_IS_INPUT,
1415
ELEMENT_IS_NAMESPACED,
1516
ELEMENT_PRESERVE_ATTRIBUTE_CASE
1617
} from '../../../../../../constants.js';
@@ -401,6 +402,8 @@ function build_element_spread_attributes(
401402
flags |= ELEMENT_IS_NAMESPACED | ELEMENT_PRESERVE_ATTRIBUTE_CASE;
402403
} else if (is_custom_element_node(element)) {
403404
flags |= ELEMENT_PRESERVE_ATTRIBUTE_CASE;
405+
} else if (element.type === 'RegularElement' && element.name === 'input') {
406+
flags |= ELEMENT_IS_INPUT;
404407
}
405408

406409
const object = build_spread_object(element, attributes, context);

packages/svelte/src/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const HYDRATION_ERROR = {};
2828

2929
export const ELEMENT_IS_NAMESPACED = 1;
3030
export const ELEMENT_PRESERVE_ATTRIBUTE_CASE = 1 << 1;
31+
export const ELEMENT_IS_INPUT = 1 << 2;
3132

3233
export const UNINITIALIZED = Symbol();
3334

packages/svelte/src/internal/client/dom/elements/attributes.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,10 +268,27 @@ export function set_custom_element_data(node, prop, value) {
268268
* @param {Record<string | symbol, any> | undefined} prev
269269
* @param {Record<string | symbol, any>} next New attributes - this function mutates this object
270270
* @param {string} [css_hash]
271+
* @param {boolean} [should_remove_defaults]
271272
* @param {boolean} [skip_warning]
272273
* @returns {Record<string, any>}
273274
*/
274-
export function set_attributes(element, prev, next, css_hash, skip_warning = false) {
275+
function set_attributes(
276+
element,
277+
prev,
278+
next,
279+
css_hash,
280+
should_remove_defaults = false,
281+
skip_warning = false
282+
) {
283+
if (hydrating && should_remove_defaults && element.tagName === 'INPUT') {
284+
var input = /** @type {HTMLInputElement} */ (element);
285+
var attribute = input.type === 'checkbox' ? 'defaultChecked' : 'defaultValue';
286+
287+
if (!(attribute in next)) {
288+
remove_input_defaults(input);
289+
}
290+
}
291+
275292
var attributes = get_attributes(element);
276293

277294
var is_custom_element = attributes[IS_CUSTOM_ELEMENT];
@@ -467,6 +484,7 @@ export function set_attributes(element, prev, next, css_hash, skip_warning = fal
467484
* @param {Array<() => any>} sync
468485
* @param {Array<() => Promise<any>>} async
469486
* @param {string} [css_hash]
487+
* @param {boolean} [should_remove_defaults]
470488
* @param {boolean} [skip_warning]
471489
*/
472490
export function attribute_effect(
@@ -475,6 +493,7 @@ export function attribute_effect(
475493
sync = [],
476494
async = [],
477495
css_hash,
496+
should_remove_defaults = false,
478497
skip_warning = false
479498
) {
480499
flatten(sync, async, (values) => {
@@ -490,7 +509,14 @@ export function attribute_effect(
490509
block(() => {
491510
var next = fn(...values.map(get));
492511
/** @type {Record<string | symbol, any>} */
493-
var current = set_attributes(element, prev, next, css_hash, skip_warning);
512+
var current = set_attributes(
513+
element,
514+
prev,
515+
next,
516+
css_hash,
517+
should_remove_defaults,
518+
skip_warning
519+
);
494520

495521
if (inited && is_select && 'value' in next) {
496522
select_option(/** @type {HTMLSelectElement} */ (element), next.value);

packages/svelte/src/internal/client/index.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ export { attach } from './dom/elements/attachments.js';
2828
export {
2929
remove_input_defaults,
3030
set_attribute,
31-
set_attributes,
3231
attribute_effect,
3332
set_custom_element_data,
3433
set_xlink_attribute,

packages/svelte/src/internal/server/index.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import { subscribe_to_store } from '../../store/utils.js';
88
import {
99
UNINITIALIZED,
1010
ELEMENT_PRESERVE_ATTRIBUTE_CASE,
11-
ELEMENT_IS_NAMESPACED
11+
ELEMENT_IS_NAMESPACED,
12+
ELEMENT_IS_INPUT
1213
} from '../../constants.js';
1314
import { escape_html } from '../../escaping.js';
1415
import { DEV } from 'esm-env';
@@ -187,6 +188,7 @@ export function spread_attributes(attrs, css_hash, classes, styles, flags = 0) {
187188

188189
const is_html = (flags & ELEMENT_IS_NAMESPACED) === 0;
189190
const lowercase = (flags & ELEMENT_PRESERVE_ATTRIBUTE_CASE) === 0;
191+
const is_input = (flags & ELEMENT_IS_INPUT) !== 0;
190192

191193
for (name in attrs) {
192194
// omit functions, internal svelte properties and invalid attribute names
@@ -200,6 +202,13 @@ export function spread_attributes(attrs, css_hash, classes, styles, flags = 0) {
200202
name = name.toLowerCase();
201203
}
202204

205+
if (is_input) {
206+
if (name === 'defaultvalue' || name === 'defaultchecked') {
207+
name = name === 'defaultvalue' ? 'value' : 'checked';
208+
if (attrs[name]) continue;
209+
}
210+
}
211+
203212
attr_str += attr(name, value, is_html && is_boolean_attribute(name));
204213
}
205214

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
mode: ['server'],
5+
html: `
6+
<input value="a">
7+
<input type="checkbox" checked>
8+
<input value="b">
9+
`
10+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script>
2+
let text = { defaultValue: "a" };
3+
let checkbox = { defaultChecked: true }
4+
</script>
5+
6+
<input {...text} />
7+
<input type="checkbox" {...checkbox} />
8+
<input value="b" {...text} />

0 commit comments

Comments
 (0)