Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix component renderer: querying web component children #1066

Merged
merged 2 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mesop/examples/web_component/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ py_library(
"//mesop/examples/web_component/complex_props",
"//mesop/examples/web_component/copy_to_clipboard",
"//mesop/examples/web_component/custom_font_csp_repro",
"//mesop/examples/web_component/dynamic_slot",
"//mesop/examples/web_component/firebase_auth",
"//mesop/examples/web_component/hotkeys",
"//mesop/examples/web_component/markedjs",
Expand Down
3 changes: 3 additions & 0 deletions mesop/examples/web_component/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
from mesop.examples.web_component.custom_font_csp_repro import (
custom_font_app as custom_font_app,
)
from mesop.examples.web_component.dynamic_slot import (
dynamic_slot_app as dynamic_slot_app,
)
from mesop.examples.web_component.firebase_auth import (
firebase_auth_app as firebase_auth_app,
)
Expand Down
14 changes: 14 additions & 0 deletions mesop/examples/web_component/dynamic_slot/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
load("//build_defs:defaults.bzl", "py_library")

package(
default_visibility = ["//build_defs:mesop_examples"],
)

py_library(
name = "dynamic_slot",
srcs = glob(["*.py"]),
data = glob(["*.js"]),
deps = [
"//mesop",
],
)
38 changes: 38 additions & 0 deletions mesop/examples/web_component/dynamic_slot/dynamic_slot_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import mesop as me
from mesop.examples.web_component.dynamic_slot.outer_component import (
outer_component,
outer_component2,
)


@me.page(
path="/web_component/dynamic_slot/dynamic_slot_app",
security_policy=me.SecurityPolicy(
allowed_script_srcs=[
"https://cdn.jsdelivr.net",
]
),
)
def page():
s = me.state(State)
me.checkbox("check", on_change=on_change)
if s.checked:
with outer_component():
me.text("abc")
me.text("2")
else:
with outer_component():
me.text("def")
me.text("1")
me.text("3")
with outer_component2():
me.text("end")


def on_change(e: me.CheckboxChangeEvent):
me.state(State).checked = not me.state(State).checked


@me.stateclass
class State:
checked: bool = False
18 changes: 18 additions & 0 deletions mesop/examples/web_component/dynamic_slot/outer_component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {
LitElement,
html,
} from 'https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js';

class OuterComponent extends LitElement {
render() {
return html`
<div class="container">
&ltouter1&gt
<slot></slot>
&lt/outer1&gt
</div>
`;
}
}

customElements.define('dynamic-slot-outer-component', OuterComponent);
23 changes: 23 additions & 0 deletions mesop/examples/web_component/dynamic_slot/outer_component.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import mesop.labs as mel


@mel.web_component(path="./outer_component.js")
def outer_component(
*,
key: str | None = None,
):
return mel.insert_web_component(
name="dynamic-slot-outer-component",
key=key,
)


@mel.web_component(path="./outer_component2.js")
def outer_component2(
*,
key: str | None = None,
):
return mel.insert_web_component(
name="dynamic-slot-outer-component2",
key=key,
)
18 changes: 18 additions & 0 deletions mesop/examples/web_component/dynamic_slot/outer_component2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {
LitElement,
html,
} from 'https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js';

class OuterComponent extends LitElement {
render() {
return html`
<div class="container">
&ltouter2&gt
<slot></slot>
&lt/outer2&gt
</div>
`;
}
}

customElements.define('dynamic-slot-outer-component2', OuterComponent);
24 changes: 24 additions & 0 deletions mesop/tests/e2e/web_components/update_children_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {test, expect} from '@playwright/test';

test('web components - update children has no error', async ({page}) => {
await page.goto('/web_component/dynamic_slot/dynamic_slot_app');

const consoleErrors: string[] = [];

// Listen for console errors
page.on('console', (msg) => {
if (msg.type() === 'error') {
consoleErrors.push(msg.text());
}
});

// Toggling the checkbox causes web component children to be updated.
await page.getByRole('checkbox').check();
await page.getByRole('checkbox').check();
await page.getByRole('checkbox').check();

// Specifically looking for errors like the following
// which are signs that we're incorrectly updating the children:
// NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
expect(consoleErrors).toHaveLength(0);
});
7 changes: 5 additions & 2 deletions mesop/web/src/component_renderer/component_renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,11 @@ export class ComponentRenderer {
}

private updateCustomElementChildren() {
const existingChildren = Array.from(
this.customElement!.querySelectorAll(COMPONENT_RENDERER_ELEMENT_NAME),
const existingChildren = Array.from(this.customElement!.children).filter(
(child) =>
// tagName is uppercased in HTML.
// See: https://developer.mozilla.org/docs/Web/API/Element/tagName
child.tagName === COMPONENT_RENDERER_ELEMENT_NAME.toUpperCase(),
wwwillchen marked this conversation as resolved.
Show resolved Hide resolved
);
const newChildren = this.component.getChildrenList();

Expand Down
Loading