From f4eb21900724191750d583aed69b48e47b7c2e44 Mon Sep 17 00:00:00 2001 From: Mario Buikhuizen Date: Tue, 2 Jun 2026 15:24:05 +0200 Subject: [PATCH 01/27] feat: add support for ipyvuetify 3 --- packages/solara-assets/hatch_build.py | 6 +- .../solara_enterprise/auth/components.py | 2 +- solara/components/alert.py | 28 ++-- solara/components/applayout.py | 14 +- solara/components/button.py | 17 +-- solara/components/cross_filter.py | 8 +- solara/components/dataframe.py | 16 +- solara/components/datatable.py | 2 +- solara/components/datatable.vue | 111 ++++++++------ solara/components/details.py | 6 +- solara/components/echarts.vue | 2 +- solara/components/head_tag.vue | 2 +- solara/components/input.py | 4 +- solara/components/input_text_area.py | 5 +- solara/components/misc.py | 25 +--- solara/components/pivot_table.py | 4 +- solara/components/progress.py | 2 +- solara/components/select.py | 4 +- solara/components/select.vue | 16 +- solara/components/slider.py | 26 ++-- solara/components/sql_code.vue | 2 +- solara/components/style.py | 12 +- solara/components/tab_navigation.py | 7 +- solara/components/title.vue | 2 +- solara/components/togglebuttons.py | 20 ++- solara/components/tooltip.py | 5 +- solara/lab/components/chat.py | 2 +- solara/lab/components/input_date.py | 27 ++-- solara/lab/components/input_time.py | 26 ++-- solara/lab/components/menu.vue | 25 +++- solara/lab/components/tabs.py | 18 +-- solara/layout.py | 6 +- solara/server/static/main-vuetify.js | 2 +- .../portal/solara_portal/pages/__init__.py | 29 ++-- solara/website/components/algolia_api.vue | 54 ++++--- solara/website/components/contact.py | 4 +- solara/website/components/header.py | 4 +- solara/website/components/markdown_nav.vue | 10 +- solara/website/components/sidebar.py | 138 +++++++----------- solara/website/pages/__init__.py | 32 ++-- solara/website/pages/doc_use_download.py | 2 +- .../website/pages/documentation/__init__.py | 2 +- .../documentation/examples/ai/tokenizer.py | 4 +- .../examples/general/login_oauth.py | 6 +- .../documentation/examples/general/mycard.vue | 8 +- .../examples/general/pokemon_search.py | 2 +- solara/website/pages/home.vue | 2 +- solara/website/pages/showcase/__init__.py | 12 +- solara/widgets/vue/gridlayout.vue | 2 +- solara/widgets/vue/navigator.vue | 2 +- solara/widgets/vue/vegalite.vue | 10 +- tests/integration/api_test.py | 80 +++++----- tests/integration/input_date_test.py | 55 ++++--- tests/unit/cross_filter_component_test.py | 3 +- tests/unit/input_date_test.py | 31 ++++ tests/unit/slider_test.py | 10 +- tests/unit/tabs_test.py | 6 +- 57 files changed, 513 insertions(+), 449 deletions(-) diff --git a/packages/solara-assets/hatch_build.py b/packages/solara-assets/hatch_build.py index 5c9bc9def..8a7c7b758 100644 --- a/packages/solara-assets/hatch_build.py +++ b/packages/solara-assets/hatch_build.py @@ -11,9 +11,9 @@ ["requirejs", "2.3.6"], ["mermaid", "10.8.0"], ["codemirror", "5.65.3"], - ["vega", "5.21.0"], - ["vega-lite", "5.2.0"], - ["vega-embed", "6.20.2"], + ["vega", "6.2.0"], + ["vega-lite", "6.4.3"], + ["vega-embed", "7.1.0"], ["@widgetti/vue-grid-layout", "2.3.13-alpha.2"], ["@widgetti/solara-milkdown", "6.3.0"], ["echarts", "5.4.0"], diff --git a/packages/solara-enterprise/solara_enterprise/auth/components.py b/packages/solara-enterprise/solara_enterprise/auth/components.py index acc7589a5..dbcc1a104 100644 --- a/packages/solara-enterprise/solara_enterprise/auth/components.py +++ b/packages/solara-enterprise/solara_enterprise/auth/components.py @@ -65,7 +65,7 @@ def Page(): if not children: children = [solara.Button("logout", icon_name="mdi-logout", href=auth.get_logout_url(), text=True)] - with v.Menu(v_slots=[{"name": "activator", "children": activator, "variable": "x"}], offset_y=True) as menu: + with v.Menu(v_slots=[{"name": "activator", "children": activator, "variable": "x"}], location="bottom", offset="8") as menu: with v.List(): for child in children: with v.ListItem(children=[child]): diff --git a/solara/components/alert.py b/solara/components/alert.py index 014122268..c4bbc00d5 100644 --- a/solara/components/alert.py +++ b/solara/components/alert.py @@ -6,6 +6,14 @@ from solara.util import _combine_classes +def _alert_variant(outlined: bool, text: bool) -> str: + if outlined: + return "outlined" + if text: + return "tonal" + return "elevated" + + @solara.component def Success( label: Optional[str] = None, @@ -34,9 +42,8 @@ def Success( icon = None return v.Alert( type="success", - text=text, - outlined=outlined, - dense=dense, + variant=_alert_variant(outlined, text), + density="compact" if dense else None, icon=icon, children=[*([label] if label is not None else []), *children], class_=_combine_classes(classes), @@ -71,9 +78,8 @@ def Info( icon = None return v.Alert( type="info", - text=text, - outlined=outlined, - dense=dense, + variant=_alert_variant(outlined, text), + density="compact" if dense else None, icon=icon, children=[*([label] if label is not None else []), *children], class_=_combine_classes(classes), @@ -108,9 +114,8 @@ def Warning( icon = None return v.Alert( type="warning", - text=text, - outlined=outlined, - dense=dense, + variant=_alert_variant(outlined, text), + density="compact" if dense else None, icon=icon, children=[*([label] if label is not None else []), *children], class_=_combine_classes(classes), @@ -145,9 +150,8 @@ def Error( icon = None return v.Alert( type="error", - text=text, - outlined=outlined, - dense=dense, + variant=_alert_variant(outlined, text), + density="compact" if dense else None, icon=icon, children=[*([label] if label is not None else []), *children], class_=_combine_classes(classes), diff --git a/solara/components/applayout.py b/solara/components/applayout.py index d1118f862..77d3c76ff 100644 --- a/solara/components/applayout.py +++ b/solara/components/applayout.py @@ -295,13 +295,12 @@ def set_path(index): # also ideal in jupyter notebooks with v.Html(tag="div") as main: if show_app_bar or use_drawer: - with v.AppBar(color=color, dark=toolbar_dark, v_slots=v_slots): + with v.AppBar(color=color, theme="dark" if toolbar_dark else None, v_slots=v_slots): if use_drawer: icon = AppIcon(sidebar_open, on_click=lambda: set_sidebar_open(not sidebar_open), v_on="x.on") with v.Menu( - offset_y=True, - nudge_left="50px", - left=True, + location="bottom end", + offset="8", v_slots=[{"name": "activator", "variable": "x", "children": [icon]}], close_on_content_click=False, ): @@ -333,11 +332,8 @@ def set_path(index): v_model=sidebar_open, on_v_model=set_sidebar_open, style_="min-width: 400px; max-width: 600px", - clipped=True, - app=True, # disable_resize_watcher=True, disable_route_watcher=True, - mobile_break_point="960", class_="solara-content-main", ): if not show_app_bar: @@ -346,7 +342,7 @@ def set_path(index): if show_app_bar: # if hide_on_scroll is True, and we have a little bit of scrolling, vuetify seems to act strangely # when scrolling (on @mariobuikhuizen/vuetify v2.2.26-rc.0 - with v.AppBar(color=color, dark=toolbar_dark, app=True, clipped_left=True, hide_on_scroll=False, v_slots=v_slots).key("app-layout-appbar"): + with v.AppBar(color=color, theme="dark" if toolbar_dark else None, scroll_behavior=None, v_slots=v_slots).key("app-layout-appbar"): if use_drawer: AppIcon(sidebar_open, on_click=lambda: set_sidebar_open(not sidebar_open)) if title or children_appbartitle: @@ -370,7 +366,7 @@ def set_path(index): # use a margin: -12px which will make a horizontal scrollbar appear solara.Div(style=style, classes=classes, children=children_content) if fullscreen: - with v.Dialog(v_model=True, children=[], fullscreen=True, hide_overlay=True, persistent=True, no_click_animation=True) as dialog: + with v.Dialog(v_model=True, children=[], fullscreen=True, scrim=False, persistent=True, no_click_animation=True) as dialog: v.Sheet(class_="overflow-y-auto overflow-x-auto", children=[main]) pass return dialog diff --git a/solara/components/button.py b/solara/components/button.py index d2225760c..662d316f5 100644 --- a/solara/components/button.py +++ b/solara/components/button.py @@ -62,7 +62,7 @@ def Page(): if label: children = [label] + children if icon_name: - children = [v.Icon(left=bool(label), children=[icon_name])] + children + children = [v.Icon(start=bool(label), children=[icon_name])] + children if "class_" in kwargs: kwargs = kwargs.copy() class_ = solara.util._combine_classes([*classes, kwargs.pop("class_")]) @@ -72,14 +72,11 @@ def Page(): style_flat = solara.util._flatten_style(style) if "style_" in kwargs: style_flat += kwargs.pop("style_") - if solara.util.ipyvuetify_major_version == 3: - variant = "elevated" - if text: - variant = "text" - elif outlined: - variant = "outlined" - btn = solara.v.Btn(children=children, **kwargs, disabled=disabled, class_=class_, style_=style_flat, color=color, variant=variant) - else: - btn = solara.v.Btn(children=children, **kwargs, disabled=disabled, text=text, class_=class_, style_=style_flat, outlined=outlined, color=color) + variant = "elevated" + if text: + variant = "text" + elif outlined: + variant = "outlined" + btn = solara.v.Btn(children=children, **kwargs, disabled=disabled, class_=class_, style_=style_flat, color=color, variant=variant, value=value) ipyvue.use_event(btn, click_event, lambda *_ignore: on_click and on_click()) return btn diff --git a/solara/components/cross_filter.py b/solara/components/cross_filter.py index f4c2775f0..c25581dab 100644 --- a/solara/components/cross_filter.py +++ b/solara/components/cross_filter.py @@ -153,7 +153,7 @@ def update_filter(): if configurable: with v.Menu(v_slots=[{"name": "activator", "variable": "x", "children": btn}], close_on_content_click=False): with v.Sheet(): - with v.Container(py_0=True, px_3=True, ma_0=True): + with v.Container(class_="py-0 px-3 ma-0"): with v.Row(): with v.Col(): v.Select(v_model=column, items=columns, on_v_model=set_column, label="Choose column") @@ -195,9 +195,9 @@ def CrossFilterReport(df, classes: List[str] = []): v.Html(tag="h3", children=[summary], style_="display: inline") # always add a progress bar to make sure the layout is the same if filtered: - v.ProgressLinear(value=progress).key("visible") + v.ProgressLinear(model_value=progress).key("visible") else: - v.ProgressLinear(value=0, style_="visibility: hidden").key("hidden") + v.ProgressLinear(model_value=0, style_="visibility: hidden").key("hidden") return main @@ -295,7 +295,7 @@ def update_filter(): if configurable: with v.Menu(v_slots=[{"name": "activator", "variable": "x", "children": btn}], close_on_content_click=False): with v.Sheet(): - with v.Container(py_0=True, px_3=True, ma_0=True): + with v.Container(class_="py-0 px-3 ma-0"): with v.Row(): with v.Col(): columns_numeric = [c for c in columns if py_types[c] in [int, float]] diff --git a/solara/components/dataframe.py b/solara/components/dataframe.py index 491a55791..c9bc630a0 100644 --- a/solara/components/dataframe.py +++ b/solara/components/dataframe.py @@ -47,7 +47,7 @@ def on_value_local(value): placeholder=placeholder, prepend_icon="mdi-filter", error_messages=error, - success_messages="Looking good" if value is not None else None, + messages="Looking good" if value is not None else None, ) @@ -97,7 +97,7 @@ def TableCard(df): with v.Card(elevation=2, height=cardheight) as main: with v.CardTitle(children=[title]): if filtered: - v.ProgressLinear(value=progress) + v.ProgressLinear(model_value=progress) with v.CardText(): Table(dff) return main @@ -114,7 +114,7 @@ def HistogramCard(df, column=None, max_unique=100): with v.CardTitle(children=["Histogram"]): pass with v.CardText(): - with v.Btn(v_on="x.on", icon=True, absolute=True, style_="right: 10px; top: 10px") as btn: + with v.Btn(v_on="x.on", icon=True, position="absolute", style_="right: 10px; top: 10px") as btn: v.Icon(children=["mdi-settings"]) with v.Dialog(v_slots=[{"name": "activator", "variable": "x", "children": btn}], width="500"): with v.Sheet(): @@ -213,7 +213,7 @@ def ScatterCard(df, x=None, y=None, color=None): with v.CardTitle(children=["Scatter"]): pass with v.CardText(): - with v.Btn(v_on="x.on", icon=True, absolute=True, style_="right: 10px; top: 10px") as btn: + with v.Btn(v_on="x.on", icon=True, position="absolute", style_="right: 10px; top: 10px") as btn: v.Icon(children=["mdi-settings"]) with v.Dialog(v_slots=[{"name": "activator", "variable": "x", "children": btn}], width="500"): with v.Sheet(): @@ -360,7 +360,7 @@ def HeatmapCard(df, x=None, y=None, debounce=True): with v.CardTitle(children=["Heatmap"]): pass with v.CardText(): - with v.Btn(v_on="x.on", icon=True, absolute=True, style_="right: 10px; top: 10px") as btn: + with v.Btn(v_on="x.on", icon=True, position="absolute", style_="right: 10px; top: 10px") as btn: v.Icon(children=["mdi-settings"]) with v.Dialog(v_slots=[{"name": "activator", "variable": "x", "children": btn}], width="700"): with v.Sheet(): @@ -488,7 +488,7 @@ def SummaryCard(df): with v.Card(elevation=2, height=cardheight) as main: with v.CardTitle(children=[title]): if filtered: - v.ProgressLinear(value=progress) + v.ProgressLinear(model_value=progress) with v.CardText(): icon = "mdi-filter" v.Icon(children=[icon], style_="opacity: 0.1" if not filtered else "") @@ -529,11 +529,11 @@ def set_value_and_filter(value): with v.CardTitle(children=["Filter out single value"]): pass with v.CardText(): - with v.Btn(v_on="x.on", icon=True, absolute=True, style_="right: 10px; top: 10px") as btn: + with v.Btn(v_on="x.on", icon=True, position="absolute", style_="right: 10px; top: 10px") as btn: v.Icon(children=["mdi-settings"]) with v.Dialog(v_slots=[{"name": "activator", "variable": "x", "children": btn}], width="500"): with v.Sheet(): - with v.Container(pa_4=True, ma_0=True): + with v.Container(class_="pa-4 ma-0"): with v.Row(): with v.Col(): v.Select(v_model=column, items=columns, on_v_model=set_column, label="Choose column") diff --git a/solara/components/datatable.py b/solara/components/datatable.py index e413a8d87..4fbd2f7e4 100644 --- a/solara/components/datatable.py +++ b/solara/components/datatable.py @@ -110,7 +110,7 @@ def DataTable( item[column] = format(dfs, column, i + i1, records[i][column]) items.append(item) - headers = [{"text": name, "value": name, "sortable": False} for name in columns] + headers = [{"title": name, "key": name, "sortable": False} for name in columns] column_actions_callbacks = [k.on_click for k in column_actions] cell_actions_callbacks = [k.on_click for k in cell_actions] column_actions = [replace(k, on_click=None) for k in column_actions] diff --git a/solara/components/datatable.vue b/solara/components/datatable.vue index 3001d9fa2..83929fbf8 100644 --- a/solara/components/datatable.vue +++ b/solara/components/datatable.vue @@ -1,80 +1,78 @@