chore: update upstream fixes to be custom renderer aware

svelte-custom-renderer
paoloricciuti 2 weeks ago
parent af6419bd15
commit d2ec9dbebf

@ -234,7 +234,11 @@ export function RegularElement(node, context) {
// `<select defaultValue>` needs the options to exist before it can mark one // `<select defaultValue>` needs the options to exist before it can mark one
// as selected, so it is handled after the children, alongside `value` // as selected, so it is handled after the children, alongside `value`
if (node.name === 'select' && get_attribute_name(node, attribute) === 'defaultValue') { if (
!custom_renderer &&
node.name === 'select' &&
get_attribute_name(node, attribute) === 'defaultValue'
) {
continue; continue;
} }
@ -538,7 +542,7 @@ export function RegularElement(node, context) {
// deferred from the attribute loop above, so that the options it selects from // deferred from the attribute loop above, so that the options it selects from
// have been created and had their values assigned // have been created and had their values assigned
if (!has_spread && name === 'select') { if (!custom_renderer && !has_spread && name === 'select') {
const default_value = /** @type {AST.Attribute[]} */ (attributes).find( const default_value = /** @type {AST.Attribute[]} */ (attributes).find(
(attribute) => get_attribute_name(node, attribute) === 'defaultValue' (attribute) => get_attribute_name(node, attribute) === 'defaultValue'
); );

@ -30,7 +30,8 @@ export function build_attribute_effect(
) { ) {
/** @type {ObjectExpression['properties']} */ /** @type {ObjectExpression['properties']} */
const values = []; const values = [];
const is_select = element.type === 'RegularElement' && element.name === 'select'; const is_select =
!custom_renderer && element.type === 'RegularElement' && element.name === 'select';
const memoizer = new Memoizer(); const memoizer = new Memoizer();

@ -1,5 +1,5 @@
import { hydrate_next, hydrating } from '../hydration.js'; import { hydrate_next, hydrating } from '../hydration.js';
import { create_element, create_text } from '../operations.js'; import { append_child, create_element, create_text, set_attribute } from '../operations.js';
import { append } from '../template.js'; import { append } from '../template.js';
/** /**
@ -18,13 +18,13 @@ export function slot(anchor, $$props, name, slot_props, fallback_fn) {
// Use the stored reference because the shadow root may be closed. // Use the stored reference because the shadow root may be closed.
if ($$props.$$host?.$$shadowRoot) { if ($$props.$$host?.$$shadowRoot) {
const element = create_element('slot'); const element = create_element('slot');
if (name !== 'default') element.name = name; if (name !== 'default') set_attribute(element, 'name', name);
append(anchor, element); append(anchor, element);
if (fallback_fn !== null) { if (fallback_fn !== null) {
const fallback_anchor = create_text(); const fallback_anchor = create_text();
element.append(fallback_anchor); append_child(element, fallback_anchor);
fallback_fn(fallback_anchor); fallback_fn(fallback_anchor);
} }

@ -111,9 +111,10 @@ export function set_value(element, value) {
(attributes.value = (attributes.value =
// treat null and undefined the same for the initial value // treat null and undefined the same for the initial value
value ?? undefined) || value ?? undefined) ||
// @ts-expect-error
// `progress` elements always need their value set when it's `0` // `progress` elements always need their value set when it's `0`
(element.value === value && (value !== 0 || node_name(element) !== PROGRESS_TAG)) (current_renderer == null &&
/** @type {any} */ (element).value === value &&
(value !== 0 || node_name(element) !== PROGRESS_TAG))
) { ) {
return; return;
} }
@ -368,8 +369,7 @@ function set_attributes(
} }
if (key === 'class') { if (key === 'class') {
var is_html = var is_html = current_renderer == null && element.namespaceURI === NAMESPACE_HTML;
element.namespaceURI === 'http://www.w3.org/1999/xhtml' && current_renderer != null;
set_class(element, is_html, value, css_hash, prev?.[CLASS], next[CLASS]); set_class(element, is_html, value, css_hash, prev?.[CLASS], next[CLASS]);
current[key] = value; current[key] = value;
current[CLASS] = next[CLASS]; current[CLASS] = next[CLASS];
@ -619,18 +619,19 @@ function get_attributes(element) {
return /** @type {Record<string | symbol, unknown>} **/ ( return /** @type {Record<string | symbol, unknown>} **/ (
/** @type {any} */ (element)[ATTRIBUTES_CACHE] ??= { /** @type {any} */ (element)[ATTRIBUTES_CACHE] ??= {
[IS_CUSTOM_ELEMENT]: (node_name(element) ?? '').includes('-'), [IS_CUSTOM_ELEMENT]: (node_name(element) ?? '').includes('-'),
[IS_HTML]: element.namespaceURI === NAMESPACE_HTML [IS_HTML]: current_renderer == null && element.namespaceURI === NAMESPACE_HTML
} }
); );
} }
/** @type {Map<string, Set<string>>} */ /** @type {Map<string, Set<string>>} */
var setters_cache = new Map(); var setters_cache = new Map();
var empty_setters = new Set();
/** @param {Element} element */ /** @param {Element} element */
function get_setters(element) { function get_setters(element) {
// if we have a custom renderer we just skip the check all together // if we have a custom renderer we just skip the check all together
if (current_renderer) return []; if (current_renderer) return empty_setters;
var cache_key = get_attribute(element, 'is') || (node_name(element) ?? ''); var cache_key = get_attribute(element, 'is') || (node_name(element) ?? '');
var setters = setters_cache.get(cache_key); var setters = setters_cache.get(cache_key);
if (setters) return setters; if (setters) return setters;

@ -5,6 +5,11 @@ import { is_array } from '../../../../shared/utils.js';
import * as w from '../../../warnings.js'; import * as w from '../../../warnings.js';
import { Batch, current_batch, previous_batch } from '../../../reactivity/batch.js'; import { Batch, current_batch, previous_batch } from '../../../reactivity/batch.js';
import { async_mode_flag } from '../../../../flags/index.js'; import { async_mode_flag } from '../../../../flags/index.js';
import {
has_attribute,
remove_attribute,
set_attribute as set_attribute_op
} from '../../operations.js';
/** /**
* Sets the `selected` attribute on an option so form reset can restore it. * Sets the `selected` attribute on an option so form reset can restore it.
@ -13,9 +18,13 @@ import { async_mode_flag } from '../../../../flags/index.js';
*/ */
export function set_selected(option, selected) { export function set_selected(option, selected) {
if (selected) { if (selected) {
if (!option.hasAttribute('selected')) option.setAttribute('selected', ''); // The selected option could've changed via user selection, and
// setting the value without this check would set it back.
if (!has_attribute(option, 'selected')) {
set_attribute_op(option, 'selected', '');
}
} else { } else {
option.removeAttribute('selected'); remove_attribute(option, 'selected');
} }
} }

@ -9,9 +9,18 @@ export default test({
const button = target.children.find( const button = target.children.find(
(/** @type {any} */ n) => n.type === 'element' && n.name === 'button' (/** @type {any} */ n) => n.type === 'element' && n.name === 'button'
); );
const select = target.children.find(
(/** @type {any} */ n) => n.type === 'element' && n.name === 'select'
);
const selected_option = select.children.find(
(/** @type {any} */ n) =>
n.type === 'element' && n.name === 'option' && n.attributes['value'] === 'other'
);
assert.equal(inputs.length, 4); assert.equal(inputs.length, 4);
assert.ok(button); assert.ok(button);
assert.ok(select);
assert.ok(selected_option);
// Input 1: direct defaultValue attribute // Input 1: direct defaultValue attribute
const input_direct_val = inputs[0]; const input_direct_val = inputs[0];
@ -52,6 +61,9 @@ export default test({
'defaultChecked via spread should go through renderer.setAttribute, not element.defaultChecked' 'defaultChecked via spread should go through renderer.setAttribute, not element.defaultChecked'
); );
assert.equal(select.attributes['defaultValue'], 'default_val');
assert.equal(selected_option.attributes['selected'], '');
// --- After update --- // --- After update ---
dispatch_event(button, 'click'); dispatch_event(button, 'click');
flushSync(); flushSync();
@ -75,5 +87,8 @@ export default test({
undefined, undefined,
'updated defaultChecked=false via spread should go through renderer.removeAttribute' 'updated defaultChecked=false via spread should go through renderer.removeAttribute'
); );
assert.equal(select.attributes['defaultValue'], 'new_default');
assert.equal(selected_option.attributes['selected'], undefined);
} }
}); });

@ -10,6 +10,11 @@
<input type="checkbox" checked defaultChecked={default_chk} /> <input type="checkbox" checked defaultChecked={default_chk} />
<input type="checkbox" checked {...spread_chk} /> <input type="checkbox" checked {...spread_chk} />
<select defaultValue={default_val}>
<option value="default_val">Default</option>
<option value="other" selected={default_chk}>Other</option>
</select>
<button onclick={() => { <button onclick={() => {
default_val = "new_default"; default_val = "new_default";
default_chk = false; default_chk = false;

Loading…
Cancel
Save