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

Not working with ShadowRoot #1003

Open
MarcSkovMadsen opened this issue Nov 25, 2023 · 2 comments
Open

Not working with ShadowRoot #1003

MarcSkovMadsen opened this issue Nov 25, 2023 · 2 comments

Comments

@MarcSkovMadsen
Copy link

MarcSkovMadsen commented Nov 25, 2023

I'm trying to upgrade the panel-chemistry Python package to work with Panel 1.x/ Bokeh 3.x. This is done in panel-chemistry #41.

Right now I'm working on the NGLViewer component and I cannot get the mouse working. When I drag or zoom it does not work.

The big change from Bokeh 2 to 3 is that all Bokeh components are now rendered inside shadow root. My hypothesis is that it is the cause.

I have a minimum, reproducible example below

<html>
<head>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ngl.js"></script>  
</head>
<body>
    <div id="host" style="width:100%; height:100%;"></div>
    <div id="viewport" style="width:100%; height:100%;"></div>
</body>
<script type="text/javascript">
var stage = new NGL.Stage( "viewport" );
function finish(o){
    o.addRepresentation("cartoon");
    o.autoView();
}
stage.loadFile("rcsb://1CRN").then(finish)
window.addEventListener( "resize", function( event ){
    stage.handleResize();
}, false );

// I need to host ngl inside shadowroot
const host = document.querySelector("#host");
const viewport = document.querySelector("#viewport");
const shadow = host.attachShadow({ mode: "open" });
shadow.appendChild(viewport)
</script>
</html>

It renders nicely. But when I try to drag or zoom nothing happens

image

I am a data scientist primarely fluent in Python. Javascript is not my strength.

I don't know if I can change the solution above to support mouse events. Or if something needs to change in NGL for this to ever work.

@ppillot
Copy link
Collaborator

ppillot commented Nov 27, 2023

The way the mouse events work in NGL is that they are attached on the document (not the canvas directly). I think this allows, for example, to detect when the user releases the mouse by dragging the pointer outside of the viewport.

When the user clicks, the mouse down event is captured by the document. The target value of the event is compared with the canvas element which is where the scene is painted.
Normally, target and canvas are the same when the use clicks on the 3D viewer, but in the test case you've written, because of the shadow root, the target element is #host. This is due to the shadow DOM which hides the internals of what's inside the shadow root.
It seems that a way to break the encapsulation from the perspective of the event listener, would be to use the event.composedPath() method and check that the first value in the array is indeed the canvas element we are checking against.
This would require changing all the event handlers to add a test for this case.

My understanding is that the shadow root is useful for encapsulating the stylings. In the case of NGL, as there are no CSS styles applied, it seems there is no benefit.
The shadow DOM offers slots which are placeholders for content to be displayed inside the shadow root. What's inside a slot is visible from the rest of the document.
I've played with your test case to try to see wether this could work. See the toy example here: https://codepen.io/ppillot/pen/vYbaExY

The general idea is to create a <SLOT/> element, append it to the shadow root. When the viewport is added to the host element, it will be appended to the slot and stay visible from the rest of the page.

@MarcSkovMadsen
Copy link
Author

Thanks so much. Really appreciated.

I've tried exploring this. My understanding is that this will not work for my situation because

  1. My viewport element will not be in the dom. It will be nested deeply inside multiple shadow roots because that is how Bokeh works.
  2. I'm not in control of creating the shadow root. Bokeh does that.
  3. When I try to assign as in the code below, I don't get the viewport connected to the slot inside the shadow root.

image

Example

import panel as pn 

pn.extension("ngl_viewer", sizing_mode="stretch_width")

import param
from panel.reactive import ReactiveHTML

class CustomComponent(ReactiveHTML):
    index = param.Integer(default=0)

    _template = """
<slot id="slot" name="slot" style="width:400px; height:400px;"></slot>
<div id="viewport" style="width:400px; height:400px;"></div>
"""
    __javascript__=["https://unpkg.com/[email protected]/dist/ngl.js"]
    _scripts = {
        "render": """
document.body.append(viewport)
var stage = new NGL.Stage(viewport.id);
function finish(o){
    o.addRepresentation("cartoon");
    o.autoView();
}
stage.loadFile("rcsb://1CRN").then(finish)

host = slot.getRootNode().host
console.log(host)
host.append(viewport)

console.log(slot)
slot.assign(viewport)
"""
    }

pn.Column(
    "# NGL Viewer",
    CustomComponent(width=400, height=400, styles={"border": "1px solid black"})
).servable()
pip install panel
panel serve script.py --autoreload --show

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants