pull/15844/head
Rich Harris 3 months ago
commit 0c4af381b4

@ -22,13 +22,7 @@ import {
build_set_style build_set_style
} from './shared/element.js'; } from './shared/element.js';
import { process_children } from './shared/fragment.js'; import { process_children } from './shared/fragment.js';
import { import { build_render_statement, build_template_chunk, get_expression_id } from './shared/utils.js';
build_render_statement,
build_template_chunk,
build_update_assignment,
get_expression_id,
memoize_expression
} from './shared/utils.js';
import { visit_event_attribute } from './shared/events.js'; import { visit_event_attribute } from './shared/events.js';
/** /**
@ -200,16 +194,16 @@ export function RegularElement(node, context) {
const node_id = context.state.node; const node_id = context.state.node;
/** If true, needs `__value` for inputs */
const needs_special_value_handling =
node.name === 'option' ||
node.name === 'select' ||
bindings.has('group') ||
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);
} else { } else {
/** If true, needs `__value` for inputs */
const needs_special_value_handling =
node.name === 'option' ||
node.name === 'select' ||
bindings.has('group') ||
bindings.has('checked');
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)) {
visit_event_attribute(attribute, context); visit_event_attribute(attribute, context);
@ -217,7 +211,6 @@ export function RegularElement(node, context) {
} }
if (needs_special_value_handling && attribute.name === 'value') { if (needs_special_value_handling && attribute.name === 'value') {
build_element_special_value_attribute(node.name, node_id, attribute, context);
continue; continue;
} }
@ -401,6 +394,15 @@ export function RegularElement(node, context) {
context.state.update.push(b.stmt(b.assignment('=', dir, dir))); context.state.update.push(b.stmt(b.assignment('=', dir, dir)));
} }
if (!has_spread && needs_special_value_handling) {
for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) {
if (attribute.name === 'value') {
build_element_special_value_attribute(node.name, node_id, attribute, context);
break;
}
}
}
context.state.template.pop_element(); context.state.template.pop_element();
} }
@ -650,10 +652,7 @@ function build_element_special_value_attribute(element, node_id, attribute, cont
const { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) => const { value, has_state } = build_attribute_value(attribute.value, context, (value, metadata) =>
metadata.has_call || metadata.has_await metadata.has_call || metadata.has_await
? // if is a select with value we will also invoke `init_select` which need a reference before the template effect so we memoize separately ? get_expression_id(metadata.has_await ? state.async_expressions : state.expressions, value)
is_select_with_value
? memoize_expression(context.state, value)
: get_expression_id(metadata.has_await ? state.async_expressions : state.expressions, value)
: value : value
); );
@ -679,23 +678,21 @@ function build_element_special_value_attribute(element, node_id, attribute, cont
: inner_assignment : inner_assignment
); );
if (is_select_with_value) {
state.init.push(b.stmt(b.call('$.init_select', node_id, b.thunk(value))));
}
if (has_state) { if (has_state) {
const id = state.scope.generate(`${node_id.name}_value`); const id = b.id(state.scope.generate(`${node_id.name}_value`));
build_update_assignment(
state, // `<option>` is a special case: The value property reflects to the DOM. If the value is set to undefined,
id, // that means the value should be set to the empty string. To be able to do that when the value is
// `<option>` is a special case: The value property reflects to the DOM. If the value is set to undefined, // initially undefined, we need to set a value that is guaranteed to be different.
// that means the value should be set to the empty string. To be able to do that when the value is const init = element === 'option' ? b.object([]) : undefined;
// initially undefined, we need to set a value that is guaranteed to be different.
element === 'option' ? b.object([]) : undefined, state.init.push(b.var(id, init));
value, state.update.push(b.if(b.binary('!==', id, b.assignment('=', id, value)), b.block([update])));
update
);
} else { } else {
state.init.push(update); state.init.push(update);
} }
if (is_select_with_value) {
state.init.push(b.stmt(b.call('$.init_select', node_id)));
}
} }

@ -181,20 +181,6 @@ export function parse_directive_name(name) {
return expression; return expression;
} }
/**
* @param {ComponentClientTransformState} state
* @param {string} id
* @param {Expression | undefined} init
* @param {Expression} value
* @param {ExpressionStatement} update
*/
export function build_update_assignment(state, id, init, value, update) {
state.init.push(b.var(id, init));
state.update.push(
b.if(b.binary('!==', b.id(id), b.assignment('=', b.id(id), value)), b.block([update]))
);
}
/** /**
* Serializes `bind:this` for components and elements. * Serializes `bind:this` for components and elements.
* @param {Identifier | MemberExpression | SequenceExpression} expression * @param {Identifier | MemberExpression | SequenceExpression} expression

@ -20,7 +20,7 @@ import { clsx } from '../../../shared/attributes.js';
import { set_class } from './class.js'; import { set_class } from './class.js';
import { set_style } from './style.js'; import { set_style } from './style.js';
import { ATTACHMENT_KEY, NAMESPACE_HTML } from '../../../../constants.js'; import { ATTACHMENT_KEY, NAMESPACE_HTML } from '../../../../constants.js';
import { block, branch, destroy_effect } from '../../reactivity/effects.js'; import { block, branch, destroy_effect, effect } from '../../reactivity/effects.js';
import { derived } from '../../reactivity/deriveds.js'; import { derived } from '../../reactivity/deriveds.js';
import { init_select, select_option } from './bindings/select.js'; import { init_select, select_option } from './bindings/select.js';
import { flatten } from '../../reactivity/async.js'; import { flatten } from '../../reactivity/async.js';
@ -514,10 +514,12 @@ export function attribute_effect(
}); });
if (is_select) { if (is_select) {
init_select( var select = /** @type {HTMLSelectElement} */ (element);
/** @type {HTMLSelectElement} */ (element),
() => /** @type {Record<string | symbol, any>} */ (prev).value effect(() => {
); select_option(select, /** @type {Record<string | symbol, any>} */ (prev).value);
init_select(select);
});
} }
inited = true; inited = true;

@ -1,6 +1,5 @@
import { effect } from '../../../reactivity/effects.js'; import { effect, teardown } from '../../../reactivity/effects.js';
import { listen_to_event_and_reset_event } from './shared.js'; import { listen_to_event_and_reset_event } from './shared.js';
import { untrack } from '../../../runtime.js';
import { is } from '../../../proxy.js'; import { is } from '../../../proxy.js';
import { is_array } from '../../../../shared/utils.js'; import { is_array } from '../../../../shared/utils.js';
import * as w from '../../../warnings.js'; import * as w from '../../../warnings.js';
@ -51,40 +50,29 @@ export function select_option(select, value, mounting) {
* current selection to the dom when it changes. Such * current selection to the dom when it changes. Such
* changes could for example occur when options are * changes could for example occur when options are
* inside an `#each` block. * inside an `#each` block.
* @template V
* @param {HTMLSelectElement} select * @param {HTMLSelectElement} select
* @param {() => V} [get_value]
*/ */
export function init_select(select, get_value) { export function init_select(select) {
let mounting = true; var observer = new MutationObserver(() => {
effect(() => { // @ts-ignore
if (get_value) { select_option(select, select.__value);
select_option(select, untrack(get_value), mounting); // Deliberately don't update the potential binding value,
} // the model should be preserved unless explicitly changed
mounting = false; });
observer.observe(select, {
// Listen to option element changes
childList: true,
subtree: true, // because of <optgroup>
// Listen to option element value attribute changes
// (doesn't get notified of select value changes,
// because that property is not reflected as an attribute)
attributes: true,
attributeFilter: ['value']
});
var observer = new MutationObserver(() => { teardown(() => {
// @ts-ignore observer.disconnect();
var value = select.__value;
select_option(select, value);
// Deliberately don't update the potential binding value,
// the model should be preserved unless explicitly changed
});
observer.observe(select, {
// Listen to option element changes
childList: true,
subtree: true, // because of <optgroup>
// Listen to option element value attribute changes
// (doesn't get notified of select value changes,
// because that property is not reflected as an attribute)
attributes: true,
attributeFilter: ['value']
});
return () => {
observer.disconnect();
};
}); });
} }
@ -136,7 +124,6 @@ export function bind_select_value(select, get, set = get) {
mounting = false; mounting = false;
}); });
// don't pass get_value, we already initialize it in the effect above
init_select(select); init_select(select);
} }

Loading…
Cancel
Save