widgetti / ipyvuetify

Jupyter widgets based on vuetify UI components
MIT License
344 stars 59 forks source link

Adding ipywidgets without an extra div #291

Open egormkn opened 9 months ago

egormkn commented 9 months ago

Hi! When I use ipywidgets inside of ipyvuetify widget, they are wrapped in a <div stye="height: 100%;"></div>. This doesn't allow customizing layout with flexbox, because I can't change the class or style of this wrapper. Is it possible to display ipywidgets without an extra div? It seems that when I put ipywidgets widget inside another ipywidgets widget (VBox), there is no extra div.

Example:

image

import ipywidgets as w
import ipyvuetify as v

button = w.Button(description="ipywidgets button")

label = v.Html(tag="span", children=["Example label"])

v_button = v.Btn(children=["Vue Button"])

input = w.BoundedFloatText(
    value=10,
    layout=w.Layout(width="auto", min_width="var(--jp-widgets-inline-width)", flex="1 0 100%"),
)

example = v.Html(
    tag="div",
    style_="display: flex; flex-flow: row nowrap; align-items: center;",
    children=[label, v_button, input],
)

# solara run example.py:example
egormkn commented 9 months ago

I found the code that creates that div in ipyvue repository. Maybe this issue should be moved there.

https://github.com/widgetti/ipyvue/blob/d307dbdceaa05f01fe22be57d157d02c9ee8fbdf/js/src/VueRenderer.js#L48-L50

Can we mount a JupyterPhosphorWidget to this.$el.parentNode and either remove this.$el from DOM or hide it with dispay: none?

Also, for Vue v3 it should be possible to create a placeholder DOM node (or a comment node) instead of div, as it's done for components with multiple root nodes. However, I hope this issue can be resolved quickly without upgrading to Vue v3.

egormkn commented 9 months ago

I tried to create a comment node in render function, mount a JupyterPhosphorWidget as a sibling to this node and then remove the comment node: https://github.com/widgetti/ipyvue/compare/master...egormkn:ipyvue:placeholder-dom-node It now renders correctly, however I'm not sure if such kind of manual DOM manipulation is safe in terms of Vue reactivity.

Also, in my tests in Jupyterlab I noticed a bug that is caused by that height: 100%: the wrapper div takes 100% of the output height. image

Code from the screenshot ```python import reacton import reacton.ipyvuetify as rv import reacton.ipywidgets as rw @reacton.component def ButtonClick(clicks=0, set_clicks=lambda _: None): def my_click_handler(*ignore_args): # trigger a new render with a new value for clicks set_clicks(clicks+1) button = rv.Btn(children=[f"Clicked {clicks} times"]) rv.use_event(button, 'click', my_click_handler) return button @reacton.component def Example(): message, set_message = reacton.use_state("") clicks, set_clicks = reacton.use_state(0) with rv.Html(tag="div") as wrapper: # with rv.Html(tag="div", style_="display: flex; flex-direction: column;"): with rv.Html(tag="div"): for i in range(-clicks, 0): rw.Button(description=f"Click {i}", on_click=lambda *args: set_message(f"Clicked on {i}"), button_style="info") ButtonClick(clicks, set_clicks) for i in range(1, clicks + 1): rw.Button(description=f"Click {i}", on_click=lambda *args: set_message(f"Clicked on {i}"), button_style="info") if message: rv.Alert(children=[message], type="info") return wrapper Example() ```
mariobuikhuizen commented 9 months ago

yeah, the extra div is indeed a pain point, but we have not found a way around it yet.

I've tried your branch and it works for getting an embedded widget in. After I removed refNode.remove(); I could also replace an embedded widget. But changing the order of widgets of the same parent doesn't work.

Notebook example:

import ipywidgets
import ipyvue as vue

children = [ipywidgets.Button(description="hi1"), ipywidgets.Button(description="hi2")]

main = vue.Html(tag="div", children=children)
main

Then change the order:

main.children = children[::-1]

Notice the order of the children doesn't change in the DOM.

A workaround for the extra div is to have a div with the right properties in ipyvue and add the embeded widget to that.