refine approach, add test

pull/14508/head
Simon Holthausen 2 years ago
parent 39d65a47fc
commit b1dbb94d30

@ -146,7 +146,7 @@ export function set_xlink_attribute(dom, attribute, value) {
}
/**
* @param {any} node
* @param {HTMLElement} node
* @param {string} prop
* @param {any} value
*/
@ -161,10 +161,21 @@ export function set_custom_element_data(node, prop, value) {
set_active_reaction(null);
set_active_effect(null);
try {
if (get_setters(node).includes(prop)) {
if (
// Don't compute setters for custom elements while they aren't registered yet,
// because during their upgrade/instantiation they might add more setters.
// Instead, fall back to a simple "an object, then set as property" heuristic.
setters_cache.has(node.nodeName) || customElements.get(node.tagName.toLowerCase())
? get_setters(node).includes(prop)
: value && typeof value === 'object'
) {
// @ts-expect-error
node[prop] = value;
} else {
set_attribute(node, prop, value);
// We did getters etc checks already, stringify before passing to set_attribute
// to ensure it doesn't invoke the same logic again, and potentially populating
// the setters cache too early.
set_attribute(node, prop, value == null ? value : String(value));
}
} finally {
set_active_reaction(previous_reaction);
@ -323,21 +334,11 @@ var setters_cache = new Map();
/** @param {Element} element */
function get_setters(element) {
var name = element.nodeName;
var setters = setters_cache.get(name);
if (setters) return setters;
setters = [];
// Don't cache the result for custom elements while they aren't connected yet,
// because during their upgrade they might add more setters
if (!name.includes('-') || element.isConnected) {
setters_cache.set(name, setters);
}
var setters = setters_cache.get(element.nodeName);
setters_cache.set(element.nodeName, (setters = []));
var descriptors;
var proto = element;
var proto = element; // In the case of custom elements there might be setters on the instance
var element_proto = Element.prototype;
// Stop at Element, from there on there's only unnecessary setters we're not interested in

@ -0,0 +1,25 @@
import { flushSync } from 'svelte';
import { test } from '../../assert';
const tick = () => Promise.resolve();
// Check that rendering a custom element and setting a property before it is registered
// does not break the "when to set this as a property" logic
export default test({
async test({ assert, target }) {
target.innerHTML = '<custom-element></custom-element>';
await tick();
await tick();
const ce_root = target.querySelector('custom-element').shadowRoot;
ce_root.querySelector('button')?.click();
flushSync();
await tick();
await tick();
const inner_ce_root = ce_root.querySelectorAll('set-property-before-mounted');
assert.htmlEqual(inner_ce_root[0].shadowRoot.innerHTML, 'object|{"foo":"bar"}');
assert.htmlEqual(inner_ce_root[1].shadowRoot.innerHTML, 'object|{"foo":"bar"}');
}
});

@ -0,0 +1,31 @@
<svelte:options customElement="custom-element" />
<script lang="ts">
import { onMount } from 'svelte';
class CustomElement extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
Object.defineProperty(this, 'property', {
set: (value) => {
this.shadowRoot.innerHTML = typeof value + '|' + JSON.stringify(value);
}
});
}
}
onMount(async () => {
customElements.define('set-property-before-mounted', CustomElement);
});
let property = $state();
</script>
<button onclick={() => (property = { foo: 'bar' })}>Update</button>
<!-- one that's there before it's registered -->
<set-property-before-mounted {property}></set-property-before-mounted>
<!-- and one that's after registration but sets property to an object right away -->
{#if property}
<set-property-before-mounted {property}></set-property-before-mounted>
{/if}
Loading…
Cancel
Save