diff --git a/CHANGELOG.md b/CHANGELOG.md index 45baf9522..bf20f203c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,24 +14,57 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added support for integration with Quarto (#746). * Added `shiny.render.renderer_components` decorator to help create new output renderers (#621). * Added `shiny.experimental.ui.popover()`, `update_popover()`, and `toggle_popover()` for easy creation (and server-side updating) of [Bootstrap popovers](https://getbootstrap.com/docs/5.2/components/popovers/). Popovers are similar to tooltips, but are more persistent, and should primarily be used with button-like UI elements (e.g. `input_action_button()` or icons) (#680). -* Added `shiny.experimental.ui.toggle_switch()` (#680). * Added CSS classes to UI input methods (#680) . * `Session` objects can now accept an asynchronous (or synchronous) function for `.on_flush(fn=)`, `.on_flushed(fn=)`, and `.on_ended(fn=)` (#686). * `App()` now allows `static_assets` to represent multiple paths. To do this, pass in a dictionary instead of a string (#763). ### API changes -* Renamed `shiny.ui.navset_pill_card` to `shiny.ui.navset_card_pill`. `shiny.ui.navset_pill_card` will throw a deprecated warning (#492). -* Renamed `shiny.ui.navset_tab_card` to `shiny.ui.navset_card_tab`. `shiny.ui.navset_tab_card` will throw a deprecated warning (#492). - -#### Experimental API changes - -* Renamed `shiny.experimental.ui.navset_pill_card` to `shiny.experimental.ui.navset_card_pill` (#492). -* Renamed `shiny.experimental.ui.navset_tab_card` to `shiny.experimental.ui.navset_card_tab` (#492). -* Renamed `shiny.experimental.ui.sidebar_toggle()` to `shiny.experimental.ui.toggle_sidebar()` (#680). -* Renamed `shiny.experimental.ui.tooltip_toggle()` to `shiny.experimental.ui.toggle_tooltip()` (#680). -* Renamed `shiny.experimental.ui.tooltip_update()` to `shiny.experimental.ui.update_tooltip()` (#680). - +* TODO-barret-API; `shiny.ui.panel_main()` and `shiny.ui.panel_sidebar()` are deprecated in favor of new API for `shiny.ui.layout_sidebar()`. Please use `shiny.ui.sidebar()` to construct a sidebar and supply it (along with the main content) to `shiny.ui.layout_sidebar(*args, **kwargs)`. (#680) + +#### API relocations + +* `shiny.ui`'s `navset_pill_card()` and `navset_tab_card()` have been renamed to `.navset_card_pill()` and `navset_tab_card()` respectively (#492). + +The following methods have been moved from `shiny.experimental.ui` and integrated into `shiny.ui` (final locations under `shiny.ui` are displayed) (#680): + +* Sidebar - Sidebar layout or manipulation + * `page_sidebar()`, `toggle_sidebar()`, `layout_sidebar()` +* Filling layout - Allow UI components to expand into the parent container and/or allow its content to expand + * `page_fillable()`, `fill.as_fillable_container()`, `fill.as_fill_item()`, `fill.is_fillable_container()`, `fill.is_fill_item()`, `fill.remove_all_fill()` + * `output_plot(fill=)`, `output_image(fill=)`, `output_ui(fill=, fillable=)` +* CSS units - CSS units and padding + * `css.as_css_unit()`, `css.as_css_padding()`, `css.as_width_unit()`, `css.CssUnit` +* Tooltip - Hover-based context UI element + * `tooltip()`, `toggle_tooltip()`, `update_tooltip()` +* Popover - Click-based context UI element + * `popover()`, `toggle_popover()`, `update_popover()` +* Accordion - Vertically collapsible UI element + * `accordion()`, `accordion_panel()`, `accordion_panel_close()`, `accordion_panel_insert()`, `accordion_panel_open()`, `accordion_panel_remove()`, `accordion_panel_set()`, `Accordion` +* Card - A general purpose container for grouping related UI elements together + * `card()`, `card_header()`, `card_footer()`, `CardItem` +* Valuebox - Opinionated container for displaying a value and title + * `valuebox()` +* Navs - Navigation within a page + * `navset_bar()`, `navset_tab_card()`, `navset_pill_card()` + * `page_navbar(sidebar=, fillable=, fillable_mobile=, gap=, padding=, inverse=True)`, `navset_card_tab(sidebar=)`, `navset_card_pill(sidebar=)`, `navset_bar(sidebar=, fillable=, gap=, padding=)` +* Inputs - UI elements for user input + * `toggle_switch()` + * `input_text_area(autoresize=)` + +If a ported method is called from `shiny.experimental.ui`, a deprecation warning will be displayed. + +Methods still under consideration in `shiny.experimental.ui`: +* `value_box(showcase=)` +* `card(wrapper=)`, `card_body()`, `card_image()`, `card_header()` + + +#### API removals + +* `shiny.experimental.ui.FillingLayout` has been removed. (#481) +* Support for `min_height=`, `max_height=`, and `gap=` in `shiny.experimental.ui.as_fillable_container()` and `as_fill_item()` has been removed. (#481) +* `shiny.experimental.ui.TagCallable` has been deprecated. Its type is equivalent to `htmltools.TagFunction`. (#680) +* `shiny.eperimental.ui.as_fill_carrier()` and `shiny.eperimental.ui.is_fill_carrier()` have been deprecated. Please use `shiny.ui.fill.as_fill_item()` and `shiny.ui.fill.as_fillable_container()` or `shiny.ui.fill.is_fill_item()` and `shiny.ui.fill.is_fillable_container()` respectively in combination to achieve similar behavior. (#680) ### Bug fixes diff --git a/docs/_quartodoc.yml b/docs/_quartodoc.yml index 465990e7c..bc33e6b30 100644 --- a/docs/_quartodoc.yml +++ b/docs/_quartodoc.yml @@ -12,16 +12,27 @@ quartodoc: - title: Page containers desc: Create a user interface page container. contents: + - ui.page_sidebar - ui.page_navbar + - ui.page_sidebar + - ui.page_fillable - ui.page_fluid - ui.page_fixed - ui.page_bootstrap - title: UI Layout desc: Control the layout of multiple UI components. contents: + - ui.sidebar - ui.layout_sidebar - - ui.panel_sidebar - - ui.panel_main + - ui.layout_column_wrap + - ui.card + - ui.card_header + - ui.card_footer + - ui.value_box + - ui.popover + - ui.tooltip + - ui.accordion + - ui.accordion_panel - ui.column - ui.row - title: UI Inputs @@ -42,33 +53,6 @@ quartodoc: - ui.input_password - ui.input_action_button - ui.input_action_link - - title: Update inputs - desc: Programmatically update input values. - contents: - - name: ui.update_select - dynamic: true - - name: ui.update_selectize - dynamic: true - - name: ui.update_slider - dynamic: true - - ui.update_date - - name: ui.update_date_range - dynamic: true - - name: ui.update_checkbox - dynamic: true - - name: ui.update_checkbox_group - dynamic: true - - name: ui.update_switch - dynamic: true - - name: ui.update_radio_buttons - dynamic: true - - name: ui.update_numeric - dynamic: true - - ui.update_text - - name: ui.update_text_area - dynamic: "shiny.ui.update_text" - - name: ui.update_navs - dynamic: true - title: Navigation (tab) panels desc: Create segments of UI content. contents: @@ -77,6 +61,7 @@ quartodoc: - ui.nav_spacer - ui.nav_menu - ui.navset_tab + - ui.navset_bar - ui.navset_card_tab - ui.navset_pill - ui.navset_card_pill @@ -107,6 +92,50 @@ quartodoc: - ui.include_js - ui.insert_ui - ui.remove_ui + - ui.fill.as_fillable_container + - ui.fill.as_fill_item + - ui.fill.remove_all_fill + - ui.fill.is_fillable_container + - ui.fill.is_fill_item + - ui.css.as_css_unit + - ui.css.as_css_padding + - title: Update inputs + desc: Programmatically update input values. + contents: + - name: ui.update_select + dynamic: true + - name: ui.update_selectize + dynamic: true + - name: ui.update_slider + dynamic: true + - ui.update_date + - name: ui.update_date_range + dynamic: true + - name: ui.update_checkbox + dynamic: true + - name: ui.update_checkbox_group + dynamic: true + - name: ui.update_switch + dynamic: true + - name: ui.update_radio_buttons + dynamic: true + - name: ui.update_numeric + dynamic: true + - ui.update_text + - name: ui.update_text_area + dynamic: "shiny.ui.update_text" + - name: ui.update_navs + dynamic: true + - title: Update UI Layouts + desc: "" + contents: + - ui.toggle_sidebar + - ui.toggle_switch + - ui.toggle_tooltip + - ui.toggle_popover + - ui.update_tooltip + - ui.update_popover + - ui.update_accordion_panel - title: Rendering outputs desc: "UI (output_*()) and server (render)ing functions for generating content server-side." contents: @@ -207,6 +236,10 @@ quartodoc: - types.FileInfo - types.ImgData - types.NavSetArg + - ui.Sidebar + - ui.CardItem + - ui.AccordionPanel + - ui.css.CssUnit - ui._input_slider.SliderValueArg - ui._input_slider.SliderStepArg - kind: page @@ -232,28 +265,14 @@ quartodoc: - types.SilentException - types.SilentCancelOutputException - types.SafeException + - title: Deprecated + desc: "" + contents: + - ui.panel_main + - ui.panel_sidebar - title: Experimental desc: "These methods are under consideration and are considered unstable. However, if there is a method you are excited about, please let us know!" contents: - - kind: page - path: ExSidebar - summary: - name: "Sidebar" - desc: "Sidebar layouts allow users to easily access filters, settings, and other inputs alongside interactive features they control." - flatten: true - contents: - - experimental.ui.page_sidebar - - experimental.ui.sidebar - - experimental.ui.layout_sidebar - - experimental.ui.page_navbar - - experimental.ui.navset_bar - - experimental.ui.navset_card_tab - - experimental.ui.navset_card_pill - - experimental.ui.toggle_sidebar - - experimental.ui.toggle_switch - - experimental.ui.panel_main - - experimental.ui.panel_sidebar - - experimental.ui.Sidebar - kind: page path: ExCard summary: @@ -261,32 +280,11 @@ quartodoc: desc: "Cards are a common organizing unit for modern user interfaces (UI). At their core, they’re just rectangular containers with borders and padding. However, when utilized properly to group related information, they help users better digest, engage, and navigate through content." flatten: true contents: - - experimental.ui.card - - experimental.ui.card_header - - experimental.ui.card_title - experimental.ui.card_body + - experimental.ui.card_title - experimental.ui.card_image - - experimental.ui.card_footer - - experimental.ui.CardItem - experimental.ui.ImgContainer - - experimental.ui.TagCallable - experimental.ui.WrapperCallable - - kind: page - path: ExAccordionPanels - summary: - name: "Accordion panels" - desc: "Methods related to creating and updating vertically collapsing accordion panels." - flatten: true - contents: - - experimental.ui.accordion - - experimental.ui.accordion_panel - - experimental.ui.accordion_panel_set - - experimental.ui.accordion_panel_open - - experimental.ui.accordion_panel_close - - experimental.ui.accordion_panel_insert - - experimental.ui.accordion_panel_remove - - experimental.ui.update_accordion_panel - - experimental.ui.AccordionPanel - kind: page path: ExValueBoxes summary: @@ -297,61 +295,3 @@ quartodoc: - experimental.ui.value_box - experimental.ui.showcase_left_center - experimental.ui.showcase_top_right - - kind: page - path: ExTooltip - summary: - name: "Tooltips" - desc: "Display additional information when focusing (or hovering over) a UI element." - flatten: true - contents: - - experimental.ui.tooltip - - experimental.ui.toggle_tooltip - - experimental.ui.update_tooltip - - kind: page - path: ExPopover - summary: - name: "Popovers" - desc: "Display additional information when clicking on a UI element (typically a button)." - flatten: true - contents: - - experimental.ui.popover - - experimental.ui.toggle_popover - - experimental.ui.update_popover - - kind: page - path: ExFillingLayout - summary: - name: "Filling layouts" - desc: "Methods to create containers that are allowed to shrink or expand available space." - flatten: true - contents: - - experimental.ui.page_fillable - - experimental.ui.layout_column_wrap - - experimental.ui.as_fill_carrier - - experimental.ui.as_fillable_container - - experimental.ui.as_fill_item - - experimental.ui.remove_all_fill - - experimental.ui.is_fill_carrier - - experimental.ui.is_fillable_container - - experimental.ui.is_fill_item - - experimental.ui.FillingLayout - - experimental.ui.output_image - - experimental.ui.output_plot - - experimental.ui.output_ui - - kind: page - path: ExUiInputs - summary: - name: "UI Inputs" - desc: "Additional or upgraded UI inputs." - flatten: true - contents: - - experimental.ui.input_text_area - - kind: page - path: ExCss - summary: - name: "Css" - desc: "Helper methods related to CSS." - flatten: true - contents: - - experimental.ui.as_css_unit - - experimental.ui.as_css_padding - - experimental.ui.CssUnit diff --git a/examples/model-score/app.py b/examples/model-score/app.py index 780353eb6..085077c17 100644 --- a/examples/model-score/app.py +++ b/examples/model-score/app.py @@ -9,7 +9,6 @@ from plotly_streaming import render_plotly_streaming from shinywidgets import output_widget -import shiny.experimental as x from shiny import App, Inputs, Outputs, Session, reactive, render, ui THRESHOLD_MID = 0.85 @@ -82,8 +81,8 @@ def app_ui(req): end_time = datetime.now(timezone.utc) start_time = end_time - timedelta(minutes=1) - return x.ui.page_sidebar( - x.ui.sidebar( + return ui.page_sidebar( + ui.sidebar( ui.input_checkbox_group( "models", "Models", model_names, selected=model_names ), @@ -124,10 +123,10 @@ def app_ui(req): ui.div( ui.h1("Model monitoring dashboard"), ui.p( - x.ui.output_ui("value_boxes"), + ui.output_ui("value_boxes"), ), - x.ui.card(output_widget("plot_timeseries")), - x.ui.card(output_widget("plot_dist")), + ui.card(output_widget("plot_timeseries")), + ui.card(output_widget("plot_dist")), style="max-width: 800px;", ), fillable=False, @@ -190,12 +189,12 @@ def value_boxes(): # Round scores to 2 decimal places scores_by_model = {x: round(y, 2) for x, y in scores_by_model.items()} - return x.ui.layout_column_wrap( + return ui.layout_column_wrap( "135px", *[ # For each model, return a value_box with the score, colored based on # how high the score is. - x.ui.value_box( + ui.value_box( model, ui.h2(score), theme_color="success" diff --git a/examples/penguins/app.py b/examples/penguins/app.py index e7bbe5919..bb4a52fe0 100644 --- a/examples/penguins/app.py +++ b/examples/penguins/app.py @@ -20,8 +20,8 @@ species: List[str] = df["Species"].unique().tolist() species.sort() -app_ui = x.ui.page_sidebar( - x.ui.sidebar( +app_ui = ui.page_sidebar( + ui.sidebar( # Artwork by @allison_horst ui.tags.img(src="palmerpenguins.png", width="80%", class_="mt-0 mb-2 mx-auto"), ui.input_selectize( @@ -44,7 +44,7 @@ ui.input_switch("show_margins", "Show marginal plots", value=True), ), ui.output_ui("value_boxes"), - x.ui.output_plot("scatter", fill=True), + ui.output_plot("scatter", fill=True), ) @@ -87,7 +87,7 @@ def penguin_value_box(title: str, count: int, bgcol: str, showcase_img: str): title, count, {"class": "pt-1 pb-0"}, - showcase=x.ui.as_fill_item( + showcase=ui.fill.as_fill_item( ui.tags.img( {"style": "object-fit:contain;"}, src=showcase_img, @@ -120,7 +120,7 @@ def penguin_value_box(title: str, count: int, bgcol: str, showcase_img: str): if name in input.species() ] - return x.ui.layout_column_wrap(1 / len(value_boxes), *value_boxes) + return ui.layout_column_wrap(1 / len(value_boxes), *value_boxes) app = App( diff --git a/js/build.ts b/js/build.ts index ababf75bd..61b35b247 100644 --- a/js/build.ts +++ b/js/build.ts @@ -1,14 +1,18 @@ import { BuildOptions, build } from "esbuild"; import { sassPlugin } from "esbuild-sass-plugin"; -import * as fs from "node:fs"; +import * as fs from "node:fs/promises"; -async function bundle() { +const outDir = "../shiny/www/shared/py-shiny"; + +// TODO-barret-future; Map over options and wait their build to finish + +async function bundle_dataframe() { try { const options: BuildOptions = { entryPoints: { dataframe: "dataframe/index.tsx" }, format: "esm", bundle: true, - outdir: "../shiny/www/shared/dataframe", + outdir: outDir + "/dataframe", minify: true, sourcemap: true, plugins: [sassPlugin({ type: "css-text", sourceMap: false })], @@ -16,12 +20,39 @@ async function bundle() { }; const result = await build(options); - console.log("Build completed successfully!"); + console.log("Building dataframe completed successfully!"); // console.log("Output:", result); - fs.writeFileSync("esbuild-metadata.json", JSON.stringify(result.metafile)); + await fs.writeFile( + "esbuild-metadata.json", + JSON.stringify(result.metafile) + ); } catch (error) { console.error("Build failed:", error); } } -bundle(); +async function bundle_textarea() { + try { + const options: BuildOptions = { + entryPoints: { + "textarea-autoresize": "text-area/textarea-autoresize.ts", + }, + format: "esm", + bundle: true, + outdir: outDir + "/text-area", + minify: false, + sourcemap: false, + metafile: false, + }; + const result = await build(options); + console.log("Building textarea-autoresize completed successfully!"); + } catch (error) { + console.error("Build failed for textarea-autoresize:", error); + } +} + +// Run function to avoid top level await +async function main(): Promise { + await Promise.all([bundle_dataframe(), bundle_textarea()]); +} +main(); diff --git a/js/package.json b/js/package.json index cc67ffe7d..cf584fe86 100644 --- a/js/package.json +++ b/js/package.json @@ -2,7 +2,6 @@ "name": "shiny-dataframe-binding", "private": true, "license": "MIT", - "main": "index.js", "scripts": { "build": "tsc -noEmit && eslint . && tsx build.ts", "watch": "npx nodemon --exec 'npm run build' --ext '*' --ignore dist/ --ignore esbuild-metadata.json", diff --git a/shiny/experimental/www/textarea-autoresize.css b/js/text-area/textarea-autoresize.css similarity index 100% rename from shiny/experimental/www/textarea-autoresize.css rename to js/text-area/textarea-autoresize.css diff --git a/js/text-area/textarea-autoresize.ts b/js/text-area/textarea-autoresize.ts new file mode 100644 index 000000000..94856ce13 --- /dev/null +++ b/js/text-area/textarea-autoresize.ts @@ -0,0 +1,48 @@ +// By importing the css file, it will be copied to the output directory. +import "./textarea-autoresize.css"; + +export interface DOMEvent extends Event { + readonly target: T; +} + +function onDelegatedEvent( + eventName: string, + selector: string, + callback: (target: EventTarget) => void +) { + document.addEventListener(eventName, (e: DOMEvent) => { + if (e.target.matches(selector)) { + callback(e.target); + } + }); +} + +function update_height(target: HTMLTextAreaElement) { + // Automatically resize the textarea to fit its content. + target.style.height = "auto"; + target.style.height = target.scrollHeight + "px"; +} + +// Update on change +onDelegatedEvent( + "input", + "textarea.textarea-autoresize", + (target: HTMLTextAreaElement) => { + update_height(target); + } +); + +// Update on load +function update_on_load() { + if (document.readyState === "loading") { + // Document still loading, wait 10ms to check again. + setTimeout(update_on_load, 10); + return; + } + + // document.readyState in ["interactive", "complete"];\ + document + .querySelectorAll("textarea.textarea-autoresize") + .forEach(update_height); +} +update_on_load(); diff --git a/scripts/htmlDependencies.R b/scripts/htmlDependencies.R index a432a8ef3..76f1cc2d6 100755 --- a/scripts/htmlDependencies.R +++ b/scripts/htmlDependencies.R @@ -11,7 +11,7 @@ versions <- list() message("Installing GitHub packages: bslib, shiny, htmltools") withr::local_temp_libpaths() ignore <- capture.output({ - pak::pkg_install(c("rstudio/bslib@main", "rstudio/shiny@main", "rstudio/htmltools@main")) + pak::pkg_install(c("rstudio/bslib@a076e72e78562d7f006889da4118cd781c66c84c", "rstudio/shiny@68546c319e465a9cb113ea4499912823e264e75f", "rstudio/htmltools@9338b7f3e2ed7b3fef8fd813904b9b05281344aa")) }) # pak::pkg_install(c("cran::bslib", "cran::shiny", "cran::htmltools")) @@ -48,10 +48,8 @@ shiny_path <- fs::path(getwd(), "shiny") www <- fs::path(shiny_path, "www") www_shared <- fs::path(www, "shared") -x_www <- fs::path(shiny_path, "experimental", "www") -x_www_bslib_components <- fs::path(x_www, "bslib", "components") -x_www_htmltools_fill <- fs::path(x_www, "htmltools", "fill") -main_x_www <- fs::path(www_shared, "_x") +www_bslib_components <- fs::path(www_shared, "bslib", "components") +www_htmltools_fill <- fs::path(www_shared, "htmltools", "fill") # Copy over shiny's www/shared directory copy_from_pkg <- function(pkg_name, pkg_dir, local_dir, version_dir = fs::path_dir(local_dir)) { @@ -85,14 +83,20 @@ copy_from_pkg <- function(pkg_name, pkg_dir, local_dir, version_dir = fs::path_d } +# ------------------------------------------------------------------------------ +# Must come first! +message("Copy shiny www/shared") +# Copy over shiny's www/shared directory +copy_from_pkg("shiny", "www/shared", www_shared, www_shared) + # ------------------------------------------------------------------------------ message("Copy bslib components") # Copy over bslib's components directory -copy_from_pkg("bslib", "components/dist", x_www_bslib_components) +copy_from_pkg("bslib", "components/dist", www_bslib_components) # Remove non-minified files fs::file_delete( fs::dir_ls( - x_www_bslib_components, + www_bslib_components, type = "file", recurse = TRUE, regexp = "\\.(min\\.|css)", @@ -100,18 +104,10 @@ fs::file_delete( ) ) - # ------------------------------------------------------------------------------ message("Copy htmltools - fill") # Copy over htmltools's fill directory -copy_from_pkg("htmltools", "fill", fs::path(x_www, "htmltools", "fill")) - - -# ------------------------------------------------------------------------------ -message("Copy shiny www/shared") -# Copy over shiny's www/shared directory -copy_from_pkg("shiny", "www/shared", www_shared, www_shared) - +copy_from_pkg("htmltools", "fill", www_htmltools_fill) # ------------------------------------------------------------------------------ message("Cleanup shiny www/shared") @@ -119,6 +115,8 @@ message("Cleanup shiny www/shared") fs::dir_delete(fs::path(www_shared, "legacy")) # Don't need dataTables (hopefully) fs::dir_delete(fs::path(www_shared, "datatables")) +# Don't need sass files (hopefully) +fs::dir_delete(fs::path(www_shared, "shiny_scss")) # jQuery will come in via bslib (below) fs::file_delete( @@ -181,7 +179,7 @@ withr::with_options( bs_ver <- names(bslib::versions())[bslib::versions() == "5"] versions["bootstrap"] <- bs_ver write_json( - "shiny/www/shared/bootstrap/_version.json", + fs::path(www_shared, "bootstrap/_version.json"), list( shiny_version = shiny_version, bslib_version = bslib_version, @@ -191,8 +189,8 @@ write_json( ) message("Reduce font files") -font_txt <- unlist(strsplit(readLines("shiny/www/shared/bootstrap/font.css"), ";")) -woff_files <- list.files("shiny/www/shared/bootstrap/fonts", pattern = "\\.woff", full.names = TRUE) +font_txt <- unlist(strsplit(readLines(fs::path(www_shared, "bootstrap/font.css")), ";")) +woff_files <- list.files(fs::path(www_shared, "bootstrap/fonts"), pattern = "\\.woff", full.names = TRUE) ignored <- lapply(woff_files, function(woff_file) { file_name <- basename(woff_file) if (!any(grepl(file_name, font_txt, fixed = TRUE))) { @@ -251,31 +249,10 @@ cat( ) -# ------------------------------------------------------------------------------ -message("Copy www/shared/_x assets") - -if (fs::dir_exists(main_x_www)) fs::dir_delete(main_x_www) - -main_x_bslib_components <- fs::path(main_x_www, "bslib") -main_x_htmltools_fill <- fs::path(main_x_www, "htmltools") -fs::dir_create(c(main_x_bslib_components, main_x_htmltools_fill)) - -# Copy bslib -fs::dir_copy(x_www_bslib_components, main_x_bslib_components) -# Copy htmltools -fs::dir_copy(x_www_htmltools_fill, main_x_htmltools_fill) -# Remove unused files -fs::file_delete( - fs::dir_ls( - fs::path(main_x_bslib_components, "components"), - regexp="(_version|sidebar|nav_spacer|bslibShiny)", - invert = TRUE - ) -) # ------------------------------------------------------------------------------ -message("Create dataframe.js via npm") +message("Copy ./js deps via npm") js_path <- fs::path_abs(fs::path(shiny_path, "..", "js")) ignore <- system( diff --git a/shiny/api-examples/accordion/app.py b/shiny/api-examples/accordion/app.py new file mode 100644 index 000000000..3ff0568ba --- /dev/null +++ b/shiny/api-examples/accordion/app.py @@ -0,0 +1,40 @@ +from shiny import App, Inputs, Outputs, Session, reactive, render, ui + +items = [ + ui.accordion_panel(f"Section {letter}", f"Some narrative for section {letter}") + for letter in "ABCDE" +] + +# # First shown by default +# ui.accordion(*items) + +# # Nothing shown by default +# ui.accordion(*items, open=False) +# # Everything shown by default +# ui.accordion(*items, open=True) + +# # Show particular sections +# ui.accordion(*items, open="Section B") +# ui.accordion(*items, open=["Section A", "Section B"]) + + +app_ui = ui.page_fluid( + # Provide an id to create a shiny input binding + ui.accordion(*items, id="acc"), + ui.h4("Accordion:"), + ui.output_text_verbatim("acc_val", placeholder=True), +) + + +def server(input: Inputs, output: Outputs, session: Session): + @reactive.Effect + def _(): + print(input.acc()) + + @output + @render.text + def acc_val(): + return input.acc() + + +app = App(app_ui, server) diff --git a/shiny/api-examples/accordion_panel/app.py b/shiny/api-examples/accordion_panel/app.py new file mode 100644 index 000000000..2bde9c9a6 --- /dev/null +++ b/shiny/api-examples/accordion_panel/app.py @@ -0,0 +1,27 @@ +from shiny import App, Inputs, Outputs, Session, reactive, render, ui + +items = [ + ui.accordion_panel(f"Section {letter}", f"Some narrative for section {letter}") + for letter in "ABCDE" +] + +app_ui = ui.page_fluid( + # Provide an id to create a shiny input binding + ui.accordion(*items, id="acc"), + ui.h4("Accordion:"), + ui.output_text_verbatim("acc_val", placeholder=True), +) + + +def server(input: Inputs, output: Outputs, session: Session): + @reactive.Effect + def _(): + print(input.acc()) + + @output + @render.text + def acc_val(): + return input.acc() + + +app = App(app_ui, server) diff --git a/shiny/experimental/api-examples/accordion_panel_close/app.py b/shiny/api-examples/accordion_panel_close/app.py similarity index 58% rename from shiny/experimental/api-examples/accordion_panel_close/app.py rename to shiny/api-examples/accordion_panel_close/app.py index b2bd8e79b..9f396c7e0 100644 --- a/shiny/experimental/api-examples/accordion_panel_close/app.py +++ b/shiny/api-examples/accordion_panel_close/app.py @@ -1,16 +1,13 @@ -from __future__ import annotations - -import shiny.experimental as x from shiny import App, Inputs, Outputs, Session, reactive, ui items = [ - x.ui.accordion_panel(f"Section {letter}", f"Some narrative for section {letter}") + ui.accordion_panel(f"Section {letter}", f"Some narrative for section {letter}") for letter in "ABCDE" ] app_ui = ui.page_fluid( ui.input_action_button("close_acc", "Close Section C", class_="mt-3 mb-3"), - x.ui.accordion(*items, id="acc", multiple=True), + ui.accordion(*items, id="acc", multiple=True), ) @@ -18,7 +15,7 @@ def server(input: Inputs, output: Outputs, session: Session): @reactive.Effect @reactive.event(input.close_acc) def _(): - x.ui.accordion_panel_close("acc", "Section C") + ui.accordion_panel_close("acc", "Section C") app = App(app_ui, server) diff --git a/shiny/experimental/api-examples/accordion_panel_insert/app.py b/shiny/api-examples/accordion_panel_insert/app.py similarity index 62% rename from shiny/experimental/api-examples/accordion_panel_insert/app.py rename to shiny/api-examples/accordion_panel_insert/app.py index e2f3d7104..e5ac5d227 100644 --- a/shiny/experimental/api-examples/accordion_panel_insert/app.py +++ b/shiny/api-examples/accordion_panel_insert/app.py @@ -1,13 +1,10 @@ -from __future__ import annotations - import random -import shiny.experimental as x from shiny import App, Inputs, Outputs, Session, reactive, ui -def make_panel(letter: str) -> x.ui.AccordionPanel: - return x.ui.accordion_panel( +def make_panel(letter: str) -> ui.AccordionPanel: + return ui.accordion_panel( f"Section {letter}", f"Some narrative for section {letter}" ) @@ -16,7 +13,7 @@ def make_panel(letter: str) -> x.ui.AccordionPanel: app_ui = ui.page_fluid( ui.input_action_button("add_panel", "Add random panel", class_="mt-3 mb-3"), - x.ui.accordion(*items, id="acc", multiple=True), + ui.accordion(*items, id="acc", multiple=True), ) @@ -24,7 +21,7 @@ def server(input: Inputs, output: Outputs, session: Session): @reactive.Effect @reactive.event(input.add_panel) def _(): - x.ui.accordion_panel_insert("acc", make_panel(str(random.randint(0, 10000)))) + ui.accordion_panel_insert("acc", make_panel(str(random.randint(0, 10000)))) app = App(app_ui, server) diff --git a/shiny/experimental/api-examples/accordion_panel_open/app.py b/shiny/api-examples/accordion_panel_open/app.py similarity index 58% rename from shiny/experimental/api-examples/accordion_panel_open/app.py rename to shiny/api-examples/accordion_panel_open/app.py index 93c77d4f5..4d92bc6c3 100644 --- a/shiny/experimental/api-examples/accordion_panel_open/app.py +++ b/shiny/api-examples/accordion_panel_open/app.py @@ -1,16 +1,13 @@ -from __future__ import annotations - -import shiny.experimental as x from shiny import App, Inputs, Outputs, Session, reactive, ui items = [ - x.ui.accordion_panel(f"Section {letter}", f"Some narrative for section {letter}") + ui.accordion_panel(f"Section {letter}", f"Some narrative for section {letter}") for letter in "ABCDE" ] app_ui = ui.page_fluid( ui.input_action_button("open_acc", "Open Section C", class_="mt-3 mb-3"), - x.ui.accordion(*items, id="acc", multiple=True), + ui.accordion(*items, id="acc", multiple=True), ) @@ -18,7 +15,7 @@ def server(input: Inputs, output: Outputs, session: Session): @reactive.Effect @reactive.event(input.open_acc) def _(): - x.ui.accordion_panel_open("acc", "Section C") + ui.accordion_panel_open("acc", "Section C") app = App(app_ui, server) diff --git a/shiny/experimental/api-examples/accordion_panel_remove/app.py b/shiny/api-examples/accordion_panel_remove/app.py similarity index 78% rename from shiny/experimental/api-examples/accordion_panel_remove/app.py rename to shiny/api-examples/accordion_panel_remove/app.py index ba379bc89..6f8454a65 100644 --- a/shiny/experimental/api-examples/accordion_panel_remove/app.py +++ b/shiny/api-examples/accordion_panel_remove/app.py @@ -1,13 +1,10 @@ -from __future__ import annotations - import random -import shiny.experimental as x from shiny import App, Inputs, Outputs, Session, reactive, ui -def make_panel(letter: str) -> x.ui.AccordionPanel: - return x.ui.accordion_panel( +def make_panel(letter: str) -> ui.AccordionPanel: + return ui.accordion_panel( f"Section {letter}", f"Some narrative for section {letter}" ) @@ -23,7 +20,7 @@ def make_panel(letter: str) -> x.ui.AccordionPanel: f"Remove Section {choices[-1]}", class_="mt-3 mb-3", ), - x.ui.accordion(*items, id="acc", multiple=True), + ui.accordion(*items, id="acc", multiple=True), ) @@ -39,7 +36,7 @@ def _(): return # Remove panel - x.ui.accordion_panel_remove("acc", f"Section { user_choices.pop() }") + ui.accordion_panel_remove("acc", f"Section { user_choices.pop() }") label = "No more panels to remove!" if len(user_choices) > 0: diff --git a/shiny/experimental/api-examples/accordion_panel_set/app.py b/shiny/api-examples/accordion_panel_set/app.py similarity index 57% rename from shiny/experimental/api-examples/accordion_panel_set/app.py rename to shiny/api-examples/accordion_panel_set/app.py index 58c847207..31ea62139 100644 --- a/shiny/experimental/api-examples/accordion_panel_set/app.py +++ b/shiny/api-examples/accordion_panel_set/app.py @@ -1,17 +1,14 @@ -from __future__ import annotations - -import shiny.experimental as x from shiny import App, Inputs, Outputs, Session, reactive, ui items = [ - x.ui.accordion_panel(f"Section {letter}", f"Some narrative for section {letter}") + ui.accordion_panel(f"Section {letter}", f"Some narrative for section {letter}") for letter in "ABCDE" ] app_ui = ui.page_fluid( ui.input_action_button("set_acc", "Only open sections A,C,E", class_="mt-3 mb-3"), # Provide an id to create a shiny input binding - x.ui.accordion(*items, id="acc", open=["Section B", "Section D"], multiple=True), + ui.accordion(*items, id="acc", open=["Section B", "Section D"], multiple=True), ) @@ -19,7 +16,7 @@ def server(input: Inputs, output: Outputs, session: Session): @reactive.Effect @reactive.event(input.set_acc) def _(): - x.ui.accordion_panel_set("acc", ["Section A", "Section C", "Section E"]) + ui.accordion_panel_set("acc", ["Section A", "Section C", "Section E"]) app = App(app_ui, server) diff --git a/shiny/experimental/api-examples/accordion_panel_update/app.py b/shiny/api-examples/accordion_panel_update/app.py similarity index 75% rename from shiny/experimental/api-examples/accordion_panel_update/app.py rename to shiny/api-examples/accordion_panel_update/app.py index 7a43186e8..f406b8890 100644 --- a/shiny/experimental/api-examples/accordion_panel_update/app.py +++ b/shiny/api-examples/accordion_panel_update/app.py @@ -1,11 +1,8 @@ -from __future__ import annotations - -import shiny.experimental as x from shiny import App, Inputs, Outputs, Session, reactive, ui -def make_panel(letter: str) -> x.ui.AccordionPanel: - return x.ui.accordion_panel( +def make_panel(letter: str) -> ui.AccordionPanel: + return ui.accordion_panel( f"Section {letter}", f"Some narrative for section {letter}", value=f"sec_{letter}", @@ -16,7 +13,7 @@ def make_panel(letter: str) -> x.ui.AccordionPanel: app_ui = ui.page_fluid( ui.input_switch("update_panel", "Update Sections"), - x.ui.accordion(*items, id="acc", multiple=True), + ui.accordion(*items, id="acc", multiple=True), ) @@ -26,7 +23,7 @@ def server(input: Inputs, output: Outputs, session: Session): def _(): txt = " (updated)" if input.update_panel() else "" for letter in "ABCDE": - x.ui.update_accordion_panel( + ui.update_accordion_panel( "acc", f"sec_{letter}", f"Some{txt} narrative for section {letter}", diff --git a/shiny/experimental/api-examples/as_fill_item/app.py b/shiny/api-examples/as_fill_item/app.py similarity index 88% rename from shiny/experimental/api-examples/as_fill_item/app.py rename to shiny/api-examples/as_fill_item/app.py index 17ea40530..70dfb98d1 100644 --- a/shiny/experimental/api-examples/as_fill_item/app.py +++ b/shiny/api-examples/as_fill_item/app.py @@ -2,8 +2,8 @@ import htmltools -import shiny.experimental as x from shiny import App, ui +from shiny.ui import fill def outer_inner() -> tuple[htmltools.Tag, htmltools.Tag]: @@ -28,11 +28,11 @@ def outer_inner() -> tuple[htmltools.Tag, htmltools.Tag]: outer0, inner0 = outer_inner() outer1, inner1 = outer_inner() -x.ui.as_fill_item(inner1) +fill.as_fill_item(inner1) outer2, inner2 = outer_inner() -x.ui.as_fillable_container(outer2) -x.ui.as_fill_item(inner2) +fill.as_fillable_container(outer2) +fill.as_fill_item(inner2) app_ui = ui.page_fluid( @@ -58,7 +58,7 @@ def outer_inner() -> tuple[htmltools.Tag, htmltools.Tag]: ui.row( ui.column(4, ui.div(outer0)), ui.column(4, ui.div(outer1)), - ui.column(4, x.ui.as_fill_carrier(ui.span(outer2))), + ui.column(4, ui.span(outer2)), ), ) diff --git a/shiny/experimental/api-examples/as_fillable_container/app.py b/shiny/api-examples/as_fillable_container/app.py similarity index 87% rename from shiny/experimental/api-examples/as_fillable_container/app.py rename to shiny/api-examples/as_fillable_container/app.py index 000bfa1fd..47fd96208 100644 --- a/shiny/experimental/api-examples/as_fillable_container/app.py +++ b/shiny/api-examples/as_fillable_container/app.py @@ -2,8 +2,8 @@ import htmltools -import shiny.experimental as x from shiny import App, ui +from shiny.ui import fill def outer_inner() -> tuple[htmltools.Tag, htmltools.Tag]: @@ -29,10 +29,10 @@ def outer_inner() -> tuple[htmltools.Tag, htmltools.Tag]: outer1, inner1 = outer_inner() outer2, inner2 = outer_inner() -x.ui.as_fillable_container(outer2) +fill.as_fillable_container(outer2) -x.ui.as_fillable_container(outer2) -x.ui.as_fill_item(inner2) +fill.as_fillable_container(outer2) +fill.as_fill_item(inner2) app_ui = ui.page_fluid( @@ -58,7 +58,7 @@ def outer_inner() -> tuple[htmltools.Tag, htmltools.Tag]: ui.row( ui.column(4, ui.div(outer0)), ui.column(4, ui.div(outer1)), - ui.column(4, x.ui.as_fill_carrier(ui.span(outer2))), + ui.column(4, ui.span(outer2)), ), ) diff --git a/shiny/api-examples/card/app.py b/shiny/api-examples/card/app.py new file mode 100644 index 000000000..930983c43 --- /dev/null +++ b/shiny/api-examples/card/app.py @@ -0,0 +1,14 @@ +from shiny import App, ui + +app_ui = ui.page_fluid( + ui.card( + ui.card_header("This is the header"), + ui.p("This is the body."), + ui.p("This is still the body."), + ui.card_footer("This is the footer"), + full_screen=True, + ), +) + + +app = App(app_ui, server=None) diff --git a/shiny/api-examples/card_footer/app.py b/shiny/api-examples/card_footer/app.py new file mode 100644 index 000000000..9aedaaf7a --- /dev/null +++ b/shiny/api-examples/card_footer/app.py @@ -0,0 +1,14 @@ +from shiny import App, ui + +app_ui = ui.page_fluid( + ui.card( + ui.card_header("This is the header"), + ui.p("This is the body."), + ui.p("This is still the body."), + ui.card_footer("This is the footer"), + full_screen=True, + ) +) + + +app = App(app_ui, server=None) diff --git a/shiny/api-examples/card_header/app.py b/shiny/api-examples/card_header/app.py new file mode 100644 index 000000000..9aedaaf7a --- /dev/null +++ b/shiny/api-examples/card_header/app.py @@ -0,0 +1,14 @@ +from shiny import App, ui + +app_ui = ui.page_fluid( + ui.card( + ui.card_header("This is the header"), + ui.p("This is the body."), + ui.p("This is still the body."), + ui.card_footer("This is the footer"), + full_screen=True, + ) +) + + +app = App(app_ui, server=None) diff --git a/shiny/api-examples/data_frame/app.py b/shiny/api-examples/data_frame/app.py index 57b0a2f1c..552e0b3cf 100644 --- a/shiny/api-examples/data_frame/app.py +++ b/shiny/api-examples/data_frame/app.py @@ -3,9 +3,7 @@ import plotly.graph_objs as go from shinywidgets import output_widget, render_widget -from shiny import App -from shiny import experimental as x -from shiny import reactive, render, req, session, ui +from shiny import App, reactive, render, req, session, ui # Load the Gapminder dataset df = px.data.gapminder() @@ -26,23 +24,23 @@ summary_df.columns = ["_".join(col).strip() for col in summary_df.columns.values] summary_df.rename(columns={"country_": "country"}, inplace=True) -app_ui = x.ui.page_fillable( +app_ui = ui.page_fillable( {"class": "p-3"}, ui.p( ui.strong("Instructions:"), " Select one or more countries in the table below to see more information.", ), - x.ui.layout_column_wrap( + ui.layout_column_wrap( 1, - x.ui.card( + ui.card( ui.output_data_frame("summary_data"), ), - x.ui.layout_column_wrap( + ui.layout_column_wrap( 1 / 2, - x.ui.card( + ui.card( output_widget("country_detail_pop", height="100%"), ), - x.ui.card( + ui.card( output_widget("country_detail_percap", height="100%"), ), ), diff --git a/shiny/api-examples/input_text_area/app.py b/shiny/api-examples/input_text_area/app.py index 8f97334e3..cd59703c1 100644 --- a/shiny/api-examples/input_text_area/app.py +++ b/shiny/api-examples/input_text_area/app.py @@ -1,16 +1,32 @@ from shiny import App, Inputs, Outputs, Session, render, ui app_ui = ui.page_fluid( - ui.input_text_area("caption", "Caption:", "Data summary"), - ui.output_text_verbatim("value"), + ui.input_text_area( + "caption_regular", + "Caption:", + "Data summary\nwith\nmultiple\nlines", + ), + ui.output_text_verbatim("value_regular", placeholder=True), + ui.input_text_area( + "caption_autoresize", + ui.markdown("Caption (w/ `autoresize=True`):"), + "Data summary\nwith\nmultiple\nlines", + autoresize=True, + ), + ui.output_text_verbatim("value_autoresize", placeholder=True), ) def server(input: Inputs, output: Outputs, session: Session): @output @render.text - def value(): - return input.caption() + def value_regular(): + return input.caption_regular() + + @output + @render.text + def value_autoresize(): + return input.caption_autoresize() app = App(app_ui, server) diff --git a/shiny/experimental/api-examples/layout_column_wrap/app.py b/shiny/api-examples/layout_column_wrap/app.py similarity index 51% rename from shiny/experimental/api-examples/layout_column_wrap/app.py rename to shiny/api-examples/layout_column_wrap/app.py index 596d65752..17f336278 100644 --- a/shiny/experimental/api-examples/layout_column_wrap/app.py +++ b/shiny/api-examples/layout_column_wrap/app.py @@ -1,16 +1,13 @@ -from __future__ import annotations - -import shiny.experimental as x from shiny import App, ui -y = x.ui.card("A simple card") +y = ui.card("A simple card") app_ui = ui.page_fluid( # Always has 2 columns (on non-mobile) - x.ui.layout_column_wrap(1 / 2, y, y, y), + ui.layout_column_wrap(1 / 2, y, y, y), ui.hr(), # Has three columns when viewport is wider than 750px - x.ui.layout_column_wrap("250px", y, y, y), + ui.layout_column_wrap("250px", y, y, y), ) diff --git a/shiny/api-examples/layout_sidebar/app.py b/shiny/api-examples/layout_sidebar/app.py index 30a724a50..081343c7c 100644 --- a/shiny/api-examples/layout_sidebar/app.py +++ b/shiny/api-examples/layout_sidebar/app.py @@ -5,12 +5,10 @@ app_ui = ui.page_fluid( ui.layout_sidebar( - ui.panel_sidebar( + ui.sidebar( ui.input_slider("n", "N", min=0, max=100, value=20), ), - ui.panel_main( - ui.output_plot("plot"), - ), + ui.output_plot("plot"), ), ) diff --git a/shiny/api-examples/page_sidebar/app.py b/shiny/api-examples/page_sidebar/app.py new file mode 100644 index 000000000..121eac970 --- /dev/null +++ b/shiny/api-examples/page_sidebar/app.py @@ -0,0 +1,8 @@ +from shiny import App, ui + +app_ui = ui.page_sidebar( + ui.sidebar("Sidebar content"), + "Main content", +) + +app = App(app_ui, server=None) diff --git a/shiny/experimental/api-examples/popover/app.py b/shiny/api-examples/popover/app.py similarity index 93% rename from shiny/experimental/api-examples/popover/app.py rename to shiny/api-examples/popover/app.py index 9d233a7f5..498e615f9 100644 --- a/shiny/experimental/api-examples/popover/app.py +++ b/shiny/api-examples/popover/app.py @@ -1,6 +1,3 @@ -from __future__ import annotations - -import shiny.experimental as x from shiny import App, Inputs, Outputs, Session, render, ui # https://icons.getbootstrap.com/icons/gear-fill/ @@ -9,17 +6,17 @@ ) app_ui = ui.page_fluid( - x.ui.popover( + ui.popover( ui.input_action_button("btn", "A button", class_="mt-3"), "A popover with more context and information than should be used in a tooltip.", "You can even have multiple DOM elements in a popover!", id="btn_popover", ), ui.hr(), - x.ui.card( - x.ui.card_header( + ui.card( + ui.card_header( "Plot title (Click the gear to change variables)", - x.ui.popover( + ui.popover( ui.span( gear_fill, style="position:absolute; top: 5px; right: 7px;", diff --git a/shiny/experimental/api-examples/sidebar/app.py b/shiny/api-examples/sidebar/app.py similarity index 61% rename from shiny/experimental/api-examples/sidebar/app.py rename to shiny/api-examples/sidebar/app.py index ec7a7e128..29d000d7b 100644 --- a/shiny/experimental/api-examples/sidebar/app.py +++ b/shiny/api-examples/sidebar/app.py @@ -1,30 +1,27 @@ -from __future__ import annotations - -import shiny.experimental as x from shiny import App, Inputs, Outputs, Session, render, ui app_ui = ui.page_fluid( - x.ui.card( - x.ui.layout_sidebar( - x.ui.sidebar("Left sidebar content", id="sidebar_left"), + ui.card( + ui.layout_sidebar( + ui.sidebar("Left sidebar content", id="sidebar_left"), ui.output_text_verbatim("state_left"), ) ), - x.ui.card( - x.ui.layout_sidebar( - x.ui.sidebar("Right sidebar content", id="sidebar_right", position="right"), + ui.card( + ui.layout_sidebar( + ui.sidebar("Right sidebar content", id="sidebar_right", position="right"), ui.output_text_verbatim("state_right"), ), ), - x.ui.card( - x.ui.layout_sidebar( - x.ui.sidebar("Closed sidebar content", id="sidebar_closed", open="closed"), + ui.card( + ui.layout_sidebar( + ui.sidebar("Closed sidebar content", id="sidebar_closed", open="closed"), ui.output_text_verbatim("state_closed"), ) ), - x.ui.card( - x.ui.layout_sidebar( - x.ui.sidebar("Always sidebar content", id="sidebar_always", open="always"), + ui.card( + ui.layout_sidebar( + ui.sidebar("Always sidebar content", id="sidebar_always", open="always"), ui.output_text_verbatim("state_always"), ) ), diff --git a/shiny/experimental/api-examples/toggle_popover/app.py b/shiny/api-examples/toggle_popover/app.py similarity index 80% rename from shiny/experimental/api-examples/toggle_popover/app.py rename to shiny/api-examples/toggle_popover/app.py index ca905450f..4c4141833 100644 --- a/shiny/experimental/api-examples/toggle_popover/app.py +++ b/shiny/api-examples/toggle_popover/app.py @@ -1,6 +1,3 @@ -from __future__ import annotations - -import shiny.experimental as x from shiny import App, Inputs, Outputs, Session, reactive, req, ui app_ui = ui.page_fluid( @@ -10,7 +7,7 @@ ui.input_action_button("btn_toggle", "Toggle popover", class_="mt-3 me-3"), ui.br(), ui.br(), - x.ui.popover( + ui.popover( ui.input_action_button("btn_w_popover", "A button w/ a popover", class_="mt-3"), "A message", id="popover_id", @@ -23,19 +20,19 @@ def server(input: Inputs, output: Outputs, session: Session): def _(): req(input.btn_show()) - x.ui.toggle_popover("popover_id", show=True) + ui.toggle_popover("popover_id", show=True) @reactive.Effect def _(): req(input.btn_close()) - x.ui.toggle_popover("popover_id", show=False) + ui.toggle_popover("popover_id", show=False) @reactive.Effect def _(): req(input.btn_toggle()) - x.ui.toggle_popover("popover_id") + ui.toggle_popover("popover_id") @reactive.Effect def _(): diff --git a/shiny/experimental/api-examples/toggle_sidebar/app.py b/shiny/api-examples/toggle_sidebar/app.py similarity index 73% rename from shiny/experimental/api-examples/toggle_sidebar/app.py rename to shiny/api-examples/toggle_sidebar/app.py index c10c67303..9df3da115 100644 --- a/shiny/experimental/api-examples/toggle_sidebar/app.py +++ b/shiny/api-examples/toggle_sidebar/app.py @@ -1,10 +1,7 @@ -from __future__ import annotations - -import shiny.experimental as x from shiny import App, Inputs, Outputs, Session, reactive, render, ui -app_ui = x.ui.page_sidebar( - x.ui.sidebar("Sidebar content", id="sidebar"), +app_ui = ui.page_sidebar( + ui.sidebar("Sidebar content", id="sidebar"), ui.input_action_button( "toggle_sidebar", label="Toggle sidebar", @@ -18,7 +15,7 @@ def server(input: Inputs, output: Outputs, session: Session): @reactive.Effect @reactive.event(input.toggle_sidebar) def _(): - x.ui.toggle_sidebar("sidebar") + ui.toggle_sidebar("sidebar") @output @render.text diff --git a/shiny/experimental/api-examples/toggle_switch/app.py b/shiny/api-examples/toggle_switch/app.py similarity index 84% rename from shiny/experimental/api-examples/toggle_switch/app.py rename to shiny/api-examples/toggle_switch/app.py index 8a6a022aa..754082133 100644 --- a/shiny/experimental/api-examples/toggle_switch/app.py +++ b/shiny/api-examples/toggle_switch/app.py @@ -1,6 +1,3 @@ -from __future__ import annotations - -import shiny.experimental as x from shiny import App, Inputs, Outputs, Session, reactive, render, ui app_ui = ui.page_fluid( @@ -18,7 +15,7 @@ def server(input: Inputs, output: Outputs, session: Session): @reactive.Effect @reactive.event(input.toggle_btn) def _(): - x.ui.toggle_switch("switch_value") + ui.toggle_switch("switch_value") @output @render.text diff --git a/shiny/experimental/api-examples/toggle_tooltip/app.py b/shiny/api-examples/toggle_tooltip/app.py similarity index 80% rename from shiny/experimental/api-examples/toggle_tooltip/app.py rename to shiny/api-examples/toggle_tooltip/app.py index 520cc00ae..ce4ad6885 100644 --- a/shiny/experimental/api-examples/toggle_tooltip/app.py +++ b/shiny/api-examples/toggle_tooltip/app.py @@ -1,6 +1,3 @@ -from __future__ import annotations - -import shiny.experimental as x from shiny import App, Inputs, Outputs, Session, reactive, req, ui app_ui = ui.page_fluid( @@ -10,7 +7,7 @@ ui.input_action_button("btn_toggle", "Toggle tooltip", class_="mt-3 me-3"), ui.br(), ui.br(), - x.ui.tooltip( + ui.tooltip( ui.input_action_button("btn_w_tooltip", "A button w/ a tooltip", class_="mt-3"), "A message", id="tooltip_id", @@ -23,19 +20,19 @@ def server(input: Inputs, output: Outputs, session: Session): def _(): req(input.btn_show()) - x.ui.toggle_tooltip("tooltip_id", show=True) + ui.toggle_tooltip("tooltip_id", show=True) @reactive.Effect def _(): req(input.btn_close()) - x.ui.toggle_tooltip("tooltip_id", show=False) + ui.toggle_tooltip("tooltip_id", show=False) @reactive.Effect def _(): req(input.btn_toggle()) - x.ui.toggle_tooltip("tooltip_id") + ui.toggle_tooltip("tooltip_id") @reactive.Effect def _(): diff --git a/shiny/experimental/api-examples/tooltip/app.py b/shiny/api-examples/tooltip/app.py similarity index 89% rename from shiny/experimental/api-examples/tooltip/app.py rename to shiny/api-examples/tooltip/app.py index 5e4d8c845..63566dd39 100644 --- a/shiny/experimental/api-examples/tooltip/app.py +++ b/shiny/api-examples/tooltip/app.py @@ -1,6 +1,3 @@ -from __future__ import annotations - -import shiny.experimental as x from shiny import App, ui # https://icons.getbootstrap.com/icons/question-circle-fill/ @@ -9,15 +6,15 @@ ) app_ui = ui.page_fluid( - x.ui.tooltip( + ui.tooltip( ui.input_action_button("btn", "A button", class_="mt-3"), "A message", id="btn_tooltip", ), ui.hr(), - x.ui.card( - x.ui.card_header( - x.ui.tooltip( + ui.card( + ui.card_header( + ui.tooltip( ui.span("Card title ", question_circle_fill), "Additional info", placement="right", diff --git a/shiny/experimental/api-examples/update_tooltip/app.py b/shiny/api-examples/update_tooltip/app.py similarity index 77% rename from shiny/experimental/api-examples/update_tooltip/app.py rename to shiny/api-examples/update_tooltip/app.py index 9d0ce7399..786c73c41 100644 --- a/shiny/experimental/api-examples/update_tooltip/app.py +++ b/shiny/api-examples/update_tooltip/app.py @@ -1,13 +1,10 @@ -from __future__ import annotations - -import shiny.experimental as x from shiny import App, Inputs, Outputs, Session, reactive, ui app_ui = ui.page_fluid( ui.input_action_button("btn_update", "Update tooltip phrase", class_="mt-3 me-3"), ui.br(), ui.br(), - x.ui.tooltip( + ui.tooltip( ui.input_action_button("btn_w_tooltip", "A button w/ a tooltip", class_="mt-3"), "A message", id="tooltip_id", @@ -19,7 +16,7 @@ def server(input: Inputs, output: Outputs, session: Session): @reactive.Effect def _(): # Immediately display tooltip - x.ui.toggle_tooltip("tooltip_id", show=True) + ui.toggle_tooltip("tooltip_id", show=True) @reactive.Effect @reactive.event(input.btn_update) @@ -28,8 +25,8 @@ def _(): "A " + " ".join(["NEW" for _ in range(input.btn_update())]) + " message" ) - x.ui.update_tooltip("tooltip_id", content) - x.ui.toggle_tooltip("tooltip_id", show=True) + ui.update_tooltip("tooltip_id", content) + ui.toggle_tooltip("tooltip_id", show=True) @reactive.Effect @reactive.event(input.btn_w_tooltip) diff --git a/shiny/api-examples/value_box/app.py b/shiny/api-examples/value_box/app.py new file mode 100644 index 000000000..77fd5a1c7 --- /dev/null +++ b/shiny/api-examples/value_box/app.py @@ -0,0 +1,22 @@ +from shiny import App, ui + +piggy_bank = ui.HTML( + '' +) +arrow_up = ui.HTML( + '' +) + +app_ui = ui.page_fluid( + ui.value_box( + "KPI Title", + ui.h1(ui.HTML("$1 Billion Dollars")), + ui.span(arrow_up, " 30% VS PREVIOUS 30 DAYS"), + showcase=piggy_bank, + class_="bg-success", + full_screen=True, + ), +) + + +app = App(app_ui, server=None) diff --git a/shiny/experimental/api-examples/accordion/app.py b/shiny/experimental/api-examples/accordion/app.py deleted file mode 100644 index 5569481c4..000000000 --- a/shiny/experimental/api-examples/accordion/app.py +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import annotations - -import shiny.experimental as x -from shiny import App, Inputs, Outputs, Session, reactive, ui - -items = [ - x.ui.accordion_panel(f"Section {letter}", f"Some narrative for section {letter}") - for letter in "ABCDE" -] - -# # First shown by default -# x.ui.accordion(*items) - -# # Nothing shown by default -# x.ui.accordion(*items, open=False) -# # Everything shown by default -# x.ui.accordion(*items, open=True) - -# # Show particular sections -# x.ui.accordion(*items, open="Section B") -# x.ui.accordion(*items, open=["Section A", "Section B"]) - -app_ui = ui.page_fluid( - # Provide an id to create a shiny input binding - x.ui.accordion(*items, id="acc"), -) - - -def server(input: Inputs, output: Outputs, session: Session): - @reactive.Effect - def _(): - print(input.acc()) - - -app = App(app_ui, server) diff --git a/shiny/experimental/api-examples/accordion_panel/app.py b/shiny/experimental/api-examples/accordion_panel/app.py deleted file mode 100644 index 66fe52942..000000000 --- a/shiny/experimental/api-examples/accordion_panel/app.py +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import annotations - -import shiny.experimental as x -from shiny import App, Inputs, Outputs, Session, reactive, ui - -items = [ - x.ui.accordion_panel(f"Section {letter}", f"Some narrative for section {letter}") - for letter in "ABCDE" -] - -app_ui = ui.page_fluid( - # Provide an id to create a shiny input binding - x.ui.accordion(*items, id="acc"), -) - - -def server(input: Inputs, output: Outputs, session: Session): - @reactive.Effect - def _(): - print(input.acc()) - - -app = App(app_ui, server) diff --git a/shiny/experimental/api-examples/as_fill_carrier/app.py b/shiny/experimental/api-examples/as_fill_carrier/app.py deleted file mode 100644 index 3f6931757..000000000 --- a/shiny/experimental/api-examples/as_fill_carrier/app.py +++ /dev/null @@ -1,86 +0,0 @@ -from __future__ import annotations - -import htmltools - -import shiny.experimental as x -from shiny import App, ui - - -def outer_inner() -> tuple[htmltools.Tag, htmltools.Tag, htmltools.Tag]: - inner = ui.div( - id="inner", - style=htmltools.css( - height="200px", - border="3px blue solid", - ), - ) - middle = ui.div( - inner, - id="middle", - style=htmltools.css( - height="300px", - border="3px green solid", - ), - ) - outer = ui.div( - middle, - id="outer", - style=htmltools.css( - height="400px", - border="3px red solid", - ), - ) - return outer, middle, inner - - -# outer0, inner0 = outer_inner() - -outer0, middle0, inner0 = outer_inner() -outer1, middle1, inner1 = outer_inner() -outer2, middle2, inner2 = outer_inner() -outer3, middle3, inner3 = outer_inner() -x.ui.as_fillable_container(outer0) -x.ui.as_fillable_container(outer1) -x.ui.as_fillable_container(outer2) -x.ui.as_fillable_container(outer3) - -x.ui.as_fill_item(inner0) -x.ui.as_fill_item(inner1) -x.ui.as_fill_item(inner2) -x.ui.as_fill_item(inner3) - -x.ui.as_fill_item(middle1) -x.ui.as_fillable_container(middle2) -x.ui.as_fill_carrier(middle3) - -app_ui = ui.page_fluid( - ui.markdown( - """\ - # `as_fill_carrier()` - - For an element to pass through the ability to fill layout, - * the element must have `as_fill_item(el)` be called to allow it to expand - * the element must have `as_fillable_container(el)` called on it to allow its contents to expand - - This can be shortened by calling `as_fill_carrier(el)`. - - For all examples below, - * `as_fillable_container(red)` has been called, allowing its contents to naturally expand. - * `as_fill_item(blue)` has been called, allowing it to expand into its parent container - """ - ), - ui.row( - ui.column(3, ui.h5("Default")), - ui.column(3, ui.h5(ui.markdown("`as_fill_item(green)`"))), - ui.column(3, ui.h5(ui.markdown("`as_fillable_container(green)`"))), - ui.column(3, ui.h5(ui.markdown("`as_fill_carrier(green)`"))), - ), - ui.row( - ui.column(3, ui.div(outer0)), - ui.column(3, ui.div(outer1)), - ui.column(3, ui.div(outer2)), - ui.column(3, ui.div(outer3)), - ), -) - -app = App(app_ui, server=None) diff --git a/shiny/experimental/api-examples/card/app.py b/shiny/experimental/api-examples/card/app.py deleted file mode 100644 index 64268743e..000000000 --- a/shiny/experimental/api-examples/card/app.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations - -import shiny.experimental as x -from shiny import App, ui - -app_ui = ui.page_fluid( - x.ui.card( - x.ui.card_header("This is the header"), - x.ui.card_title("This is the title"), - ui.p("This is the body."), - x.ui.card_image( - file=None, - src="https://posit.co/wp-content/uploads/2022/10/Posit-logo-h-full-color-RGB-TM.svg", - ), - ui.p("This is still the body."), - x.ui.card_footer("This is the footer"), - full_screen=True, - ), -) - - -app = App(app_ui, server=None) diff --git a/shiny/experimental/api-examples/card_body/app.py b/shiny/experimental/api-examples/card_body/app.py index a69ec1d4c..3d51164fe 100644 --- a/shiny/experimental/api-examples/card_body/app.py +++ b/shiny/experimental/api-examples/card_body/app.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import shiny.experimental as x from shiny import App, ui diff --git a/shiny/experimental/api-examples/card_footer/app.py b/shiny/experimental/api-examples/card_footer/app.py deleted file mode 100644 index d9ce26a21..000000000 --- a/shiny/experimental/api-examples/card_footer/app.py +++ /dev/null @@ -1,20 +0,0 @@ -from __future__ import annotations - -import shiny.experimental as x -from shiny import App, ui - -app_ui = ui.page_fluid( - x.ui.card( - x.ui.card_header("This is the header"), - x.ui.card_body( - x.ui.card_title("This is the title"), - ui.p("This is the body."), - ui.p("This is still the body."), - ), - x.ui.card_footer("This is the footer"), - full_screen=True, - ) -) - - -app = App(app_ui, server=None) diff --git a/shiny/experimental/api-examples/card_header/app.py b/shiny/experimental/api-examples/card_header/app.py deleted file mode 100644 index d9ce26a21..000000000 --- a/shiny/experimental/api-examples/card_header/app.py +++ /dev/null @@ -1,20 +0,0 @@ -from __future__ import annotations - -import shiny.experimental as x -from shiny import App, ui - -app_ui = ui.page_fluid( - x.ui.card( - x.ui.card_header("This is the header"), - x.ui.card_body( - x.ui.card_title("This is the title"), - ui.p("This is the body."), - ui.p("This is still the body."), - ), - x.ui.card_footer("This is the footer"), - full_screen=True, - ) -) - - -app = App(app_ui, server=None) diff --git a/shiny/experimental/api-examples/card_image/app.py b/shiny/experimental/api-examples/card_image/app.py index 373014ce7..2a2eb871c 100644 --- a/shiny/experimental/api-examples/card_image/app.py +++ b/shiny/experimental/api-examples/card_image/app.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import shiny.experimental as x from shiny import App, ui diff --git a/shiny/experimental/api-examples/card_title/app.py b/shiny/experimental/api-examples/card_title/app.py index d9ce26a21..dfb9830d6 100644 --- a/shiny/experimental/api-examples/card_title/app.py +++ b/shiny/experimental/api-examples/card_title/app.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import shiny.experimental as x from shiny import App, ui diff --git a/shiny/experimental/api-examples/input_text_area/app.py b/shiny/experimental/api-examples/input_text_area/app.py deleted file mode 100644 index b18010e56..000000000 --- a/shiny/experimental/api-examples/input_text_area/app.py +++ /dev/null @@ -1,17 +0,0 @@ -import shiny.experimental as x -from shiny import App, Inputs, Outputs, Session, render, ui - -app_ui = ui.page_fluid( - x.ui.input_text_area("caption", "Caption:", "Data summary", autoresize=True), - ui.output_text_verbatim("value"), -) - - -def server(input: Inputs, output: Outputs, session: Session): - @output - @render.text - def value(): - return input.caption() - - -app = App(app_ui, server) diff --git a/shiny/experimental/api-examples/layout_sidebar/app.py b/shiny/experimental/api-examples/layout_sidebar/app.py deleted file mode 100644 index 208c05407..000000000 --- a/shiny/experimental/api-examples/layout_sidebar/app.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import annotations - -import shiny.experimental as x -from shiny import App, ui - -app_ui = ui.page_fluid( - x.ui.layout_sidebar( - "Sidebar content", - "Main content", - ), -) - - -app = App(app_ui, server=None) diff --git a/shiny/experimental/api-examples/page_sidebar/app.py b/shiny/experimental/api-examples/page_sidebar/app.py deleted file mode 100644 index 7af3d8f83..000000000 --- a/shiny/experimental/api-examples/page_sidebar/app.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -import shiny.experimental as x -from shiny import App - -app_ui = x.ui.page_sidebar( - "Sidebar content", - "Main content", -) - -app = App(app_ui, server=None) diff --git a/shiny/experimental/api-examples/showcase_left_center/app.py b/shiny/experimental/api-examples/showcase_left_center/app.py index 52c18f9d3..cc51e4f16 100644 --- a/shiny/experimental/api-examples/showcase_left_center/app.py +++ b/shiny/experimental/api-examples/showcase_left_center/app.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import shiny.experimental as x from shiny import App, ui diff --git a/shiny/experimental/api-examples/showcase_top_right/app.py b/shiny/experimental/api-examples/showcase_top_right/app.py index 52c18f9d3..cc51e4f16 100644 --- a/shiny/experimental/api-examples/showcase_top_right/app.py +++ b/shiny/experimental/api-examples/showcase_top_right/app.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import shiny.experimental as x from shiny import App, ui diff --git a/shiny/experimental/api-examples/value_box/app.py b/shiny/experimental/api-examples/value_box/app.py index 1f845f70e..a89436f23 100644 --- a/shiny/experimental/api-examples/value_box/app.py +++ b/shiny/experimental/api-examples/value_box/app.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import shiny.experimental as x from shiny import App, ui diff --git a/shiny/experimental/ui/__init__.py b/shiny/experimental/ui/__init__.py index 8515d05e0..9b39affae 100644 --- a/shiny/experimental/ui/__init__.py +++ b/shiny/experimental/ui/__init__.py @@ -1,4 +1,49 @@ -from ._accordion import ( +# Experimental + +from ._valuebox import showcase_left_center, showcase_top_right, value_box +from ._card import ( + WrapperCallable, + ImgContainer, + card, + card_body, + card_image, + card_title, +) + +from ._deprecated import ( + # Input Switch + toggle_switch, + # Input Text Area + input_text_area, + # Navs + navset_pill_card, + navset_tab_card, + # Sidebar + Sidebar, + sidebar, + layout_sidebar, + toggle_sidebar, + sidebar_toggle, + panel_sidebar, + panel_main, + DeprecatedPanelSidebar, + DeprecatedPanelMain, + # Tooltip + tooltip_update, + tooltip_toggle, + tooltip, + toggle_tooltip, + update_tooltip, + # Css + CssUnit, + as_css_padding, + as_css_unit, + as_width_unit, + # Popover + popover, + toggle_popover, + update_popover, + # Accordion AccordionPanel, accordion, accordion_panel, @@ -8,22 +53,7 @@ accordion_panel_remove, accordion_panel_set, update_accordion_panel, -) -from ._card import ( - CardItem, - ImgContainer, - TagCallable, - WrapperCallable, - card, - card_body, - card_footer, - card_header, - card_image, - card_title, -) -from ._css_unit import CssUnit, as_css_padding, as_css_unit -from ._fill import ( # bind_fill_role, - FillingLayout, + # Fill as_fill_carrier, as_fill_item, as_fillable_container, @@ -31,92 +61,80 @@ is_fill_item, is_fillable_container, remove_all_fill, + # Card + TagCallable, + CardItem, + card_footer, + card_header, + # Layout + layout_column_wrap, + # Navs + navset_bar, + navset_card_pill, + navset_card_tab, + # Outputs + output_image, + output_plot, + output_ui, + # Page + page_fillable, + page_navbar, + page_sidebar, ) -from ._input_switch import toggle_switch -from ._input_text import input_text_area -from ._layout import layout_column_wrap -from ._navs import navset_bar, navset_card_pill, navset_card_tab -from ._output import output_image, output_plot, output_ui -from ._page import page_fillable, page_navbar, page_sidebar -from ._sidebar import ( - DeprecatedPanelMain, - DeprecatedPanelSidebar, - Sidebar, - layout_sidebar, - panel_main, - panel_sidebar, - sidebar, - toggle_sidebar, -) -from ._tooltip import tooltip, toggle_tooltip, update_tooltip -from ._popover import popover, toggle_popover, update_popover -from ._valuebox import showcase_left_center, showcase_top_right, value_box + __all__ = ( - # Css - "CssUnit", - "as_css_unit", - "as_css_padding", - # Sidebar - "DeprecatedPanelMain", - "DeprecatedPanelSidebar", - "Sidebar", - "layout_sidebar", - "panel_main", - "panel_sidebar", - "sidebar", - "toggle_sidebar", - # Page - "page_sidebar", - "page_fillable", - "page_navbar", - # Navs - "navset_bar", - "navset_card_tab", - "navset_card_pill", # Card - "CardItem", + "WrapperCallable", "ImgContainer", "TagCallable", - "WrapperCallable", "card", - "card_header", "card_title", "card_body", "card_image", - "card_footer", - # Layout - "layout_column_wrap", - # Popover - "popover", - "toggle_popover", - "update_popover", - # Tooltip - "tooltip", - "toggle_tooltip", - "update_tooltip", # ValueBox "value_box", "showcase_left_center", "showcase_top_right", - # Fill - "FillingLayout", - # "bind_fill_role", - "as_fill_carrier", - "as_fillable_container", - "as_fill_item", - "remove_all_fill", - "is_fill_carrier", - "is_fillable_container", - "is_fill_item", - # Output - "output_image", - "output_plot", - "output_ui", - # input_switch + # Deprecated + # # Sidebar + "Sidebar", + "sidebar", + "layout_sidebar", + "toggle_sidebar", + "sidebar_toggle", + "panel_sidebar", + "panel_main", + "DeprecatedPanelSidebar", + "DeprecatedPanelMain", + # # Input Switch "toggle_switch", - # input_text_area + # # Input Text Area "input_text_area", + # # Navs + "navset_pill_card", + "navset_tab_card", + # # Tooltip + "tooltip", + "toggle_tooltip", + "update_tooltip", + "tooltip_update", + "tooltip_toggle", + # # Sidebar + "sidebar_toggle", + "panel_sidebar", + "panel_main", + "DeprecatedPanelSidebar", + "DeprecatedPanelMain", + # # Css + "CssUnit", + "as_css_unit", + "as_css_padding", + "as_width_unit", + # # Popover + "popover", + "toggle_popover", + "update_popover", # Accordion "AccordionPanel", "accordion", @@ -127,4 +145,31 @@ "accordion_panel_insert", "accordion_panel_remove", "update_accordion_panel", + # # Fill + "as_fill_carrier", + "as_fillable_container", + "as_fill_item", + "remove_all_fill", + "is_fill_carrier", + "is_fillable_container", + "is_fill_item", + # # Card + "TagCallable", + "CardItem", + "card_header", + "card_footer", + # # Layout + "layout_column_wrap", + # # Navs + "navset_bar", + "navset_card_tab", + "navset_card_pill", + # # Output + "output_image", + "output_plot", + "output_ui", + # # Page + "page_sidebar", + "page_fillable", + "page_navbar", ) diff --git a/shiny/experimental/ui/_card.py b/shiny/experimental/ui/_card.py index f25a3f8d6..6acd6b641 100644 --- a/shiny/experimental/ui/_card.py +++ b/shiny/experimental/ui/_card.py @@ -7,32 +7,28 @@ from typing import Literal, Optional, Protocol from htmltools import ( - HTML, Tag, TagAttrs, TagAttrValue, TagChild, + TagFunction, Tagifiable, - TagList, css, - div, tags, ) from ...types import MISSING, MISSING_TYPE -from ._css_unit import CssUnit, as_css_padding, as_css_unit -from ._fill import as_fill_carrier, as_fill_item, as_fillable_container -from ._htmldeps import card_dependency -from ._tooltip import tooltip -from ._utils import consolidate_attrs +from ...ui._card import CardItem, WrapperCallable, _card_impl, card_body +from ...ui.css import CssUnit, as_css_unit +from ...ui.fill import as_fill_item, as_fillable_container __all__ = ( - "CardItem", + # Worried about `wrapper` "card", + # Do not want to expose card_body yet "card_body", + # Questioning: "card_title", - "card_header", - "card_footer", "card_image", ) @@ -60,8 +56,7 @@ def card( ---------- *args Unnamed arguments can be any valid child of an :class:`~htmltools.Tag` (which - includes card items such as :func:`~shiny.experimental.ui.card_body`. Named - arguments become HTML attributes on the returned Tag. + includes card items such as :func:`~shiny.experimental.ui.card_body`. full_screen If `True`, an icon will appear when hovering over the card body. Clicking the icon expands the card to fit viewport size. @@ -71,17 +66,19 @@ def card( :func:`~shiny.experimental.ui.card_body`). fill Whether or not to allow the card to grow/shrink to fit a fillable container with - an opinionated height (e.g., :func:`~shiny.experimental.ui.page_fillable`). + an opinionated height (e.g., :func:`~shiny.ui.page_fillable`). class_ Additional CSS classes for the returned Tag. wrapper A function (which returns a UI element) to call on unnamed arguments in `*args` which are not already card item(s) (like - :func:`~shiny.experimental.ui.card_header`, + :func:`~shiny.ui.card_header`, :func:`~shiny.experimental.ui.card_body`, etc.). Note that non-card items are grouped together into one `wrapper` call (e.g. given `card("a", "b", card_body("c"), "d")`, `wrapper` would be called twice, once with `"a"` and `"b"` and once with `"d"`). + **kwargs + HTML attributes on the returned Tag. Returns ------- @@ -90,360 +87,41 @@ def card( See Also -------- - * :func:`~shiny.experimental.ui.layout_column_wrap` for laying out multiple cards + * :func:`~shiny.ui.layout_column_wrap` for laying out multiple cards (or multiple columns inside a card). - * :func:`~shiny.experimental.ui.card_header` for creating a header within the card. + * :func:`~shiny.ui.card_header` for creating a header within the card. * :func:`~shiny.experimental.ui.card_title` for creating a title within the card body. * :func:`~shiny.experimental.ui.card_body` for putting content inside the card. - * :func:`~shiny.experimental.ui.card_footer` for creating a footer within the card. + * :func:`~shiny.ui.card_footer` for creating a footer within the card. * :func:`~shiny.experimental.ui.card_image` for adding an image to the card. """ - if isinstance(wrapper, MISSING_TYPE): - wrapper = card_body - - attrs, children = consolidate_attrs(*args, class_=class_, **kwargs) - children = _wrap_children_in_card(*children, wrapper=wrapper) - - tag = div( - { - "class": "card bslib-card bslib-mb-spacing", - "style": css( - height=as_css_unit(height), - max_height=as_css_unit(max_height), - min_height=as_css_unit(min_height), - ), - "data-bslib-card-init": True, - "data-full-screen": "false" if full_screen else None, - }, - *children, - attrs, - _full_screen_toggle() if full_screen else None, - card_dependency(), - _card_js_init(), - ) - tag = as_fillable_container(tag) - if fill: - tag = as_fill_item(tag) - - return tag - - -def _card_js_init() -> Tag: - return tags.script( - {"data-bslib-card-init": True}, - "window.bslib.Card.initializeAllCards();", - ) - - -def _full_screen_toggle() -> Tag: - return tooltip( - tags.span( - {"class": "bslib-full-screen-enter badge rounded-pill bg-dark"}, - _full_screen_toggle_icon(), - ), - "Expand", - ) - - -# via bsicons::bs_icon("arrows-fullscreen") -def _full_screen_toggle_icon() -> HTML: - return HTML( - '' - ) - - -############################################################################ - - -class CardItem: - """ - A wrapper around a :class:`~htmltools.Tag` object that represents a card item (e.g., - :func:`~shiny.experimental.ui.card_body`, - :func:`~shiny.experimental.ui.card_header`, etc.). - - This class is used to allow for consecutive non-card items to be bundled into a - single `card_body` within :func:`~shiny.experimental.ui.card`. - - Parameters - ---------- - item - A :class:`~htmltools.Tag` object that represents a card item (e.g., - :func:`~shiny.experimental.ui.card_body`, :func:`~shiny.experimental.ui.card_header`, etc.). - - See Also - -------- - * :func:`~shiny.experimental.ui.card` for creating a card component. - * :func:`~shiny.experimental.ui.card_header` for creating a header within the card. - * :func:`~shiny.experimental.ui.card_title` for creating a title within the card body. - * :func:`~shiny.experimental.ui.card_body` for putting content inside the card. - * :func:`~shiny.experimental.ui.card_image` for adding an image to the card. - * :func:`~shiny.experimental.ui.card_footer` for creating a footer within the card. - """ - - def __init__( - self, - item: TagChild, - ): - self._item = item - - def resolve(self) -> TagChild: - """ - Resolves the `CardItem` class by returning the `item` provided at initialization. - - Returns - ------- - : - The `item` provided at initialization. - """ - return self._item - - def tagify(self) -> TagList: - """ - Tagify the `item` - - Returns - ------- - : - A tagified :class:`~htmltools.TagList` object. - """ - return TagList(self.resolve()).tagify() - - -# TODO-maindocs; @add_example() -def card_body( - *args: TagChild | TagAttrs, - fillable: bool = True, - min_height: Optional[CssUnit] = None, - max_height: Optional[CssUnit] = None, - max_height_full_screen: Optional[CssUnit] | MISSING_TYPE = MISSING, - height: Optional[CssUnit] = None, - padding: Optional[CssUnit | list[CssUnit]] = None, - gap: Optional[CssUnit] = None, - fill: bool = True, - class_: Optional[str] = None, - **kwargs: TagAttrValue, -) -> CardItem: - # For a general overview of the :func:`~shiny.experimental.ui.card` API, see [this article](https://rstudio.github.io/bslib/articles/cards.html). - """ - Card body container - - A general container for the "main content" of a :func:`~shiny.experimental.ui.card`. This component is designed - to be provided as direct children to :func:`~shiny.experimental.ui.card`. - - Parameters - ---------- - *args - Contents to the card's body. Or tag attributes that are supplied to the - resolved :class:`~htmltools.Tag` object. - fillable - Whether or not the card item should be a fillable (i.e. flexbox) container. - min_height,max_height,max_height_full_screen - Any valid CSS length unit. If `max_height_full_screen` is missing, it is set to - `max_height`. - height - Any valid CSS unit (e.g., `height="200px"`). Doesn't apply when a card is made - `full_screen` (in this case, consider setting a `height` in - :func:`~shiny.experimental.ui.card_body`). - padding - Padding to use for the body. This can be a numeric vector - (which will be interpreted as pixels) or a character vector with valid CSS - lengths. The length can be between one and four. If one, then that value - will be used for all four sides. If two, then the first value will be used - for the top and bottom, while the second value will be used for left and - right. If three, then the first will be used for top, the second will be - left and right, and the third will be bottom. If four, then the values will - be interpreted as top, right, bottom, and left respectively. - gap - A CSS length unit defining the `gap` (i.e., spacing) between elements provided - to `*args`. This argument is only applicable when `fillable = TRUE`. - fill - Whether to allow this element to grow/shrink to fit its `card` container. - class_ - Additional CSS classes for the returned Tag. - **kwargs - Additional HTML attributes for the returned Tag. - - Returns - ------- - : - A :class:`~shiny.experimental.ui.CardItem` object. - - See Also - -------- - * :func:`~shiny.experimental.ui.layout_column_wrap` for laying out multiple cards - (or multiple columns inside a card). - * :func:`~shiny.experimental.ui.card` for creating a card component. - * :func:`~shiny.experimental.ui.card_header` for creating a header within the card. - * :func:`~shiny.experimental.ui.card_title` for creating a title within the card body. - * :func:`~shiny.experimental.ui.card_footer` for creating a footer within the card. - * :func:`~shiny.experimental.ui.card_image` for adding an image to the card. - """ - if isinstance(max_height_full_screen, MISSING_TYPE): - max_height_full_screen = max_height - - div_style_args = { - "min-height": as_css_unit(min_height), - "--bslib-card-body-max-height": as_css_unit(max_height), - "--bslib-card-body-max-height-full-screen": as_css_unit(max_height_full_screen), - "margin-top": "auto", - "margin-bottom": "auto", - # .card-body already adds `flex: 1 1 auto` so make sure to override it - "flex": "1 1 auto" if fill else "0 0 auto", - "padding": as_css_padding(padding), - "gap": as_css_unit(gap), - "height": as_css_unit(height), - } - tag = tags.div( - { - "class": "card-body bslib-gap-spacing", - "style": css(**div_style_args), - }, + return _card_impl( *args, + full_screen=full_screen, + height=height, + max_height=max_height, + min_height=min_height, + fill=fill, class_=class_, + wrapper=wrapper, **kwargs, ) - if fillable: - tag = as_fillable_container(tag) - if fill: - tag = as_fill_item(tag) - - return CardItem(tag) - - -# https://mypy.readthedocs.io/en/stable/protocols.html#callback-protocols -class WrapperCallable(Protocol): - """ - A callable that wraps children into a :class:`~shiny.experimental.ui.CardItem`. - """ - - def __call__(self, *args: TagChild) -> CardItem: - """ - Wraps children into a :class:`~shiny.experimental.ui.CardItem`. - - Parameters - ---------- - *args - `TagChild` children to wrap. - - Returns - ------- - : - A :class:`~shiny.experimental.ui.CardItem` object. - """ - ... - -def _as_card_items( - *children: TagChild | CardItem | None, # `TagAttrs` are not allowed here - wrapper: WrapperCallable | None, -) -> list[CardItem]: - # We don't want `None`s creating empty card bodies - children_vals = [child for child in children if child is not None] - - attrs, children_vals = consolidate_attrs(*children_vals) - if len(attrs) > 0: - raise ValueError("`TagAttrs` are not allowed in `_as_card_items(*children=)`.") - - if not callable(wrapper): - ret: list[CardItem] = [] - for child in children_vals: - if isinstance(child, CardItem): - ret.append(child) - else: - ret.append(CardItem(child)) - return ret - - # Any children that are `is.card_item` should be included verbatim. Any - # children that are not, should be wrapped in card_body(). Consecutive children - # that are not card_item, should be wrapped in a single card_body(). - state = "asis" # "wrap" | "asis" - new_children: list[CardItem] = [] - children_to_wrap: list[TagChild] = [] - - def wrap_children(): - nonlocal children_to_wrap - wrapped_children = wrapper(*children_to_wrap) - new_children.append(wrapped_children) - children_to_wrap = [] - - for child in children_vals: - if isinstance(child, CardItem): - if state == "wrap": - wrap_children() - state = "asis" - new_children.append(child) - else: - # Not a card, collect it for wrapping - state = "wrap" - children_to_wrap.append(child) - if state == "wrap": - wrap_children() - - return new_children - - -def _wrap_children_in_card( - *children: TagChild | CardItem | None, # `TagAttrs` are not allowed here - wrapper: WrapperCallable | None, -) -> list[TagChild]: - card_items = _as_card_items(*children, wrapper=wrapper) - tag_children = [card_item.resolve() for card_item in card_items] - return tag_children - - -# https://mypy.readthedocs.io/en/stable/protocols.html#callback-protocols -class TagCallable(Protocol): # Should this be exported from htmltools? - """ - Callable definition for a defined :class:`~htmltools.Tag` method. - - This is used to define the `container` argument in :func:`~shiny.experimental.ui.card_title`, - :func:`~shiny.experimental.ui.card_header`, and :func:`~shiny.experimental.ui.card_footer`. - - See Also - -------- - * :class:`~htmltools.Tag` - """ - - def __call__( - self, - *args: TagChild | TagAttrs, - _add_ws: bool = True, - **kwargs: TagAttrValue, - ) -> Tagifiable: - """ - A tag method. - - Parameters - ---------- - *args - Contents to the tag method. Or tag attributes that are supplied to the - resolved :class:`~htmltools.Tag` object. - _add_ws - Whether or not to add whitespace to the returned :class:`~htmltools.Tag` - object. - **kwargs - Additional HTML attributes for the returned Tag. - - Returns - ------- - : - A :class:`~htmltools.Tag` object. - """ - ... +############################################################################ # TODO-maindocs; @add_example() def card_title( *args: TagChild | TagAttrs, - container: TagCallable = tags.h5, + container: TagFunction = tags.h5, **kwargs: TagAttrValue, ) -> Tagifiable: """ Card title container - A general container for the "title" of a :func:`~shiny.experimental.ui.card`. This component is designed - to be provided as a direct child to :func:`~shiny.experimental.ui.card`. + A general container for the "title" of a :func:`~shiny.ui.card`. This component is designed + to be provided as a direct child to :func:`~shiny.ui.card`. Parameters ---------- @@ -462,99 +140,18 @@ def card_title( See Also -------- - * :func:`~shiny.experimental.ui.card` for creating a card component. - * :func:`~shiny.experimental.ui.card_header` for creating a header within the card. + * :func:`~shiny.ui.card` for creating a card component. + * :func:`~shiny.ui.card_header` for creating a header within the card. * :func:`~shiny.experimental.ui.card_body` for putting content inside the card. - * :func:`~shiny.experimental.ui.card_footer` for creating a footer within the card. + * :func:`~shiny.ui.card_footer` for creating a footer within the card. * :func:`~shiny.experimental.ui.card_image` for adding an image to the card. """ return container(*args, **kwargs) -# TODO-maindocs; @add_example() -def card_header( - *args: TagChild | TagAttrs, - container: TagCallable = tags.div, - **kwargs: TagAttrValue, -) -> CardItem: - """ - Card header container - - A general container for the "header" of a :func:`~shiny.experimental.ui.card`. This component is designed - to be provided as a direct child to :func:`~shiny.experimental.ui.card`. - - The header has a different background color and border than the rest of the card. - - Parameters - ---------- - *args - Contents to the header container. Or tag attributes that are supplied to the - resolved :class:`~htmltools.Tag` object. - container - Method for the returned Tag object. Defaults to :func:`~shiny.ui.tags.div`. - **kwargs - Additional HTML attributes for the returned Tag. - - Returns - ------- - : - A :class:`~shiny.experimental.ui.CardItem` object. - - See Also - -------- - * :func:`~shiny.experimental.ui.card` for creating a card component. - * :func:`~shiny.experimental.ui.card_title` for creating a title within the card body. - * :func:`~shiny.experimental.ui.card_body` for putting content inside the card. - * :func:`~shiny.experimental.ui.card_footer` for creating a footer within the card. - * :func:`~shiny.experimental.ui.card_image` for adding an image to the card. - """ - return CardItem( - container({"class": "card-header"}, *args, **kwargs), - ) - - -# TODO-maindocs; @add_example() -def card_footer( - *args: TagChild | TagAttrs, - **kwargs: TagAttrValue, -) -> CardItem: - """ - Card footer container - - A general container for the "footer" of a :func:`~shiny.experimental.ui.card`. This component is designed - to be provided as a direct child to :func:`~shiny.experimental.ui.card`. - - The footer has a different background color and border than the rest of the card. - - Parameters - ---------- - *args - Contents to the footer container. Or tag attributes that are supplied to the - resolved :class:`~htmltools.Tag` object. - **kwargs - Additional HTML attributes for the returned Tag. - - Returns - ------- - : - A :class:`~shiny.experimental.ui.CardItem` object. - - See Also - -------- - * :func:`~shiny.experimental.ui.card` for creating a card component. - * :func:`~shiny.experimental.ui.card_title` for creating a title within the card body. - * :func:`~shiny.experimental.ui.card_body` for putting content inside the card. - * :func:`~shiny.experimental.ui.card_footer` for creating a footer within the card. - * :func:`~shiny.experimental.ui.card_image` for adding an image to the card. - """ - return CardItem( - tags.div({"class": "card-footer"}, *args, **kwargs), - ) - - class ImgContainer(Protocol): """ - A callable that wraps the return value of `card_image()`. To isolate your object in a card, return a :class:`~shiny.experimental.ui.CardItem`. + A callable that wraps the return value of `card_image()`. To isolate your object in a card, return a :class:`~shiny.ui.CardItem`. """ def __call__(self, *args: Tag) -> Tagifiable: @@ -570,7 +167,7 @@ def __call__(self, *args: Tag) -> Tagifiable: ------- : A tagifiable object, such as a :class:`~htmltools.Tag` or - :class:`~shiny.experimental.ui.CardItem` object. + :class:`~shiny.ui.CardItem` object. """ ... @@ -593,8 +190,8 @@ def card_image( """ Card image container - A general container for an image within a :func:`~shiny.experimental.ui.card`. This component is designed to be - provided as a direct child to :func:`~shiny.experimental.ui.card`. + A general container for an image within a :func:`~shiny.ui.card`. This component is designed to be + provided as a direct child to :func:`~shiny.ui.card`. Parameters ---------- @@ -626,9 +223,9 @@ def card_image( container Method to wrap the returned Tag object. Defaults to :func:`~shiny.experimental.ui.card_body`. If :func:`~shiny.experimental.ui.card_body` is used, each image will be in separate cards. If - the `container` method does not return a :class:`~shiny.experimental.ui.CardItem`, it + the `container` method does not return a :class:`~shiny.ui.CardItem`, it allows for consecutive non-`CardItem` objects to be bundled into a single - :func:`~shiny.experimental.ui.card_body` within :func:`~shiny.experimental.ui.card`. + :func:`~shiny.experimental.ui.card_body` within :func:`~shiny.ui.card`. **kwargs Additional HTML attributes for the resolved Tag. """ @@ -674,10 +271,12 @@ def card_image( image = as_fill_item(image) if href is not None: - image = as_fill_carrier( - tags.a( - image, - href=href, + image = as_fillable_container( + as_fill_item( + tags.a( + image, + href=href, + ) ) ) diff --git a/shiny/experimental/ui/_color.py b/shiny/experimental/ui/_color.py deleted file mode 100644 index d7c75c8da..000000000 --- a/shiny/experimental/ui/_color.py +++ /dev/null @@ -1,3 +0,0 @@ -def get_color_contrast(color: str) -> str: - # TODO-future: Implement - return color diff --git a/shiny/experimental/ui/_deprecated.py b/shiny/experimental/ui/_deprecated.py new file mode 100644 index 000000000..78143adae --- /dev/null +++ b/shiny/experimental/ui/_deprecated.py @@ -0,0 +1,1371 @@ +from __future__ import annotations + +from typing import Any, Literal, Optional, Sequence, TypeVar, overload + +from htmltools import ( + MetadataNode, + Tag, + TagAttrs, + TagAttrValue, + TagChild, + TagFunction, + TagList, +) + +from ..._deprecated import warn_deprecated +from ...session import Session +from ...types import MISSING, MISSING_TYPE +from ...ui import AccordionPanel as MainAccordionPanel +from ...ui import accordion as main_accordion +from ...ui import accordion_panel as main_accordion_panel +from ...ui import accordion_panel_close as main_accordion_panel_close +from ...ui import accordion_panel_insert as main_accordion_panel_insert +from ...ui import accordion_panel_open as main_accordion_panel_open +from ...ui import accordion_panel_remove as main_accordion_panel_remove +from ...ui import accordion_panel_set as main_accordion_panel_set +from ...ui import input_text_area as main_input_text_area +from ...ui import popover as main_popover +from ...ui import tags +from ...ui import toggle_popover as main_toggle_popover +from ...ui import toggle_switch as main_toggle_switch +from ...ui import toggle_tooltip as main_toggle_tooltip +from ...ui import tooltip as main_tooltip +from ...ui import update_accordion_panel as main_update_accordion_panel +from ...ui import update_popover as main_update_popover +from ...ui import update_tooltip as main_update_tooltip +from ...ui._card import CardItem as MainCardItem +from ...ui._card import card_footer as main_card_footer +from ...ui._card import card_header as main_card_header +from ...ui._layout import layout_column_wrap as main_layout_column_wrap +from ...ui._navs import NavSetArg +from ...ui._navs import NavSetBar as MainNavSetBar +from ...ui._navs import NavSetCard as MainNavSetCard +from ...ui._navs import navset_bar as main_navset_bar +from ...ui._navs import navset_card_pill as main_navset_card_pill +from ...ui._navs import navset_card_tab as main_navset_card_tab +from ...ui._output import output_image as main_output_image +from ...ui._output import output_plot as main_output_plot +from ...ui._output import output_ui as main_output_ui +from ...ui._page import page_fillable as main_page_fillable +from ...ui._page import page_navbar as main_page_navbar +from ...ui._page import page_sidebar as main_page_sidebar +from ...ui._plot_output_opts import BrushOpts as MainBrushOpts +from ...ui._plot_output_opts import ClickOpts as MainClickOpts +from ...ui._plot_output_opts import DblClickOpts as MainDblClickOpts +from ...ui._plot_output_opts import HoverOpts as MainHoverOpts +from ...ui._sidebar import DeprecatedPanelMain, DeprecatedPanelSidebar +from ...ui._sidebar import Sidebar as MainSidebar +from ...ui._sidebar import layout_sidebar as main_layout_sidebar +from ...ui._sidebar import panel_main as main_panel_main +from ...ui._sidebar import panel_sidebar as main_panel_sidebar +from ...ui._sidebar import sidebar as main_sidebar +from ...ui._sidebar import toggle_sidebar as main_toggle_sidebar +from ...ui.css._css_unit import CssUnit as MainCssUnit +from ...ui.css._css_unit import as_css_padding as main_as_css_padding +from ...ui.css._css_unit import as_css_unit as main_as_css_unit +from ...ui.css._css_unit import as_width_unit as main_as_width_unit +from ...ui.fill import as_fill_item as main_as_fill_item +from ...ui.fill import as_fillable_container as main_as_fillable_container +from ...ui.fill import is_fill_item as main_is_fill_item +from ...ui.fill import is_fillable_container as main_is_fillable_container +from ...ui.fill import remove_all_fill as main_remove_all_fill + +__all__ = ( + # Input Switch + "toggle_switch", + # Input Text Area + "input_text_area", + # Navs + "navset_pill_card", + "navset_tab_card", + # Tooltip + "tooltip_update", + "tooltip_toggle", + "tooltip", + "toggle_tooltip", + "update_tooltip", + # Sidebar + "Sidebar", + "sidebar", + "layout_sidebar", + "toggle_sidebar", + "sidebar_toggle", + "panel_sidebar", + "panel_main", + "DeprecatedPanelSidebar", + "DeprecatedPanelMain", + # Css Unit + "CssUnit", + "as_css_unit", + "as_css_padding", + "as_width_unit", + # Popover + "popover", + "toggle_popover", + "update_popover", + # # Accordion + "AccordionPanel", + "accordion", + "accordion_panel", + "accordion_panel_set", + "accordion_panel_open", + "accordion_panel_close", + "accordion_panel_insert", + "accordion_panel_remove", + "update_accordion_panel", + # Fill + "as_fill_carrier", + "as_fillable_container", + "as_fill_item", + "remove_all_fill", + "is_fill_carrier", + "is_fillable_container", + "is_fill_item", + # Card + "TagCallable", + "CardItem", + "card_header", + "card_footer", + # Navs + "navset_bar", + "navset_card_tab", + "navset_card_pill", +) + +###################### +# Input Switch +###################### + + +# Deprecated 2023-09-12 +def toggle_switch( + id: str, + value: Optional[bool] = None, + session: Optional[Session] = None, +) -> None: + """Deprecated. Please use `shiny.ui.toggle_switch()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.toggle_switch()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.toggle_switch()` instead." + ) + return main_toggle_switch(id, value, session=session) + + +###################### +# Input Text Area +###################### + + +# Deprecated 2023-09-12 +def input_text_area( + id: str, + label: TagChild, + value: str = "", + *, + width: Optional[str] = None, + height: Optional[str] = None, + cols: Optional[int] = None, + rows: Optional[int] = None, + placeholder: Optional[str] = None, + resize: Optional[Literal["none", "both", "horizontal", "vertical"]] = None, + autoresize: bool = False, + autocomplete: Optional[str] = None, + spellcheck: Optional[Literal["true", "false"]] = None, +) -> Tag: + """Deprecated. Please use `shiny.ui.input_text_area()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.input_text_area()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.input_text_area()` instead." + ) + return main_input_text_area( + id, + label, + value=value, + width=width, + height=height, + cols=cols, + rows=rows, + placeholder=placeholder, + resize=resize, + autoresize=autoresize, + autocomplete=autocomplete, + spellcheck=spellcheck, + ) + + +###################### +# Navs +###################### + + +# Deprecated 2023-08-15 +def navset_pill_card( + *args: NavSetArg, + id: Optional[str] = None, + selected: Optional[str] = None, + sidebar: Optional[MainSidebar] = None, + header: TagChild = None, + footer: TagChild = None, + placement: Literal["above", "below"] = "above", +) -> MainNavSetCard: + """Deprecated. Please use `navset_card_pill()` instead of `navset_pill_card()`.""" + warn_deprecated( + "`shiny.experimental.ui.navset_pill_card()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.experimental.ui.navset_card_pill()` instead." + ) + return main_navset_card_pill( + *args, + id=id, + selected=selected, + sidebar=sidebar, + header=header, + footer=footer, + placement=placement, + ) + + +# Deprecated 2023-08-15 +def navset_tab_card( + *args: NavSetArg, + id: Optional[str] = None, + selected: Optional[str] = None, + sidebar: Optional[MainSidebar] = None, + header: TagChild = None, + footer: TagChild = None, +) -> MainNavSetCard: + """Deprecated. Please use `navset_card_tab()` instead of `navset_tab_card()`.""" + warn_deprecated( + "`shiny.experimental.ui.navset_tab_card()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.experimental.ui.navset_card_tab()` instead." + ) + return main_navset_card_tab( + *args, + id=id, + selected=selected, + header=header, + footer=footer, + ) + + +###################### +# Tooltip +###################### + + +# Deprecated 2023-09-12 +def tooltip( + trigger: TagChild, + *args: TagChild | TagAttrs, + id: Optional[str] = None, + placement: Literal["auto", "top", "right", "bottom", "left"] = "auto", + options: Optional[dict[str, object]] = None, + **kwargs: TagAttrValue, +) -> Tag: + """Deprecated. Please use `shiny.ui.tooltip()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.tooltip()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.tooltip()` instead." + ) + return main_tooltip( + trigger, + *args, + id=id, + placement=placement, + options=options, + **kwargs, + ) + + +# Deprecated 2023-08-23 +def tooltip_update(id: str, *args: TagChild, session: Optional[Session] = None) -> None: + """Deprecated. Please use `shiny.ui.update_tooltip()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.tooltip_update()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.update_tooltip()` instead." + ) + main_update_tooltip( + id, + *args, + session=session, + ) + + +# Deprecated 2023-09-12 +def update_tooltip(id: str, *args: TagChild, session: Optional[Session] = None) -> None: + """Deprecated. Please use `shiny.ui.update_tooltip()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.update_tooltip()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.update_tooltip()` instead." + ) + main_update_tooltip( + id, + *args, + session=session, + ) + + +# Deprecated 2023-08-23 +def tooltip_toggle( + id: str, + show: Optional[bool] = None, + session: Optional[Session] = None, +) -> None: + """Deprecated. Please use `shiny.ui.toggle_tooltip()`.""" + warn_deprecated( + "`shiny.experimental.ui.tooltip_toggle()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.toggle_tooltip()` instead." + ) + main_toggle_tooltip( + id=id, + show=show, + session=session, + ) + + +# Deprecated 2023-09-12 +def toggle_tooltip( + id: str, + show: Optional[bool] = None, + session: Optional[Session] = None, +) -> None: + """Deprecated. Please use `shiny.ui.toggle_tooltip()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.tooltip_toggle()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.toggle_tooltip()` instead." + ) + main_toggle_tooltip( + id=id, + show=show, + session=session, + ) + + +###################### +# Sidebar +###################### + + +class Sidebar(MainSidebar): + """Deprecated. Please use `shiny.ui.Sidebar` instead.""" + + def __init__( + self, + tag: Tag, + collapse_tag: Optional[Tag], + position: Literal["left", "right"], + open: Literal["desktop", "open", "closed", "always"], + width: CssUnit, + max_height_mobile: Optional[str | float], + color_fg: Optional[str], + color_bg: Optional[str], + ): + warn_deprecated( + "`shiny.experimental.ui.Sidebar` is deprecated. " + "This class will be removed in a future version, " + "please use :class:`shiny.ui.Sidebar` instead." + ) + super().__init__( + tag, + collapse_tag, + position, + open, + width, + max_height_mobile, + color_fg, + color_bg, + ) + + +def sidebar( + *args: TagChild | TagAttrs, + width: CssUnit = 250, + position: Literal["left", "right"] = "left", + open: Literal["desktop", "open", "closed", "always"] = "desktop", + id: Optional[str] = None, + title: TagChild | str = None, + bg: Optional[str] = None, + fg: Optional[str] = None, + class_: Optional[str] = None, # TODO-future; Consider using `**kwargs` instead + max_height_mobile: Optional[str | float] = None, + gap: Optional[CssUnit] = None, + padding: Optional[CssUnit | list[CssUnit]] = None, +) -> MainSidebar: + """Deprecated. Please use `shiny.ui.sidebar()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.sidebar()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.sidebar()` instead." + ) + return main_sidebar( + *args, + width=width, + position=position, + open=open, + id=id, + title=title, + bg=bg, + fg=fg, + class_=class_, + max_height_mobile=max_height_mobile, + gap=gap, + padding=padding, + ) + + +def layout_sidebar( + sidebar: Sidebar | TagChild | TagAttrs, + *args: TagChild | TagAttrs, + fillable: bool = True, + fill: bool = True, + bg: Optional[str] = None, + fg: Optional[str] = None, + border: Optional[bool] = None, + border_radius: Optional[bool] = None, + border_color: Optional[str] = None, + gap: Optional[CssUnit] = None, + padding: Optional[CssUnit | list[CssUnit]] = None, + height: Optional[CssUnit] = None, + **kwargs: TagAttrValue, +) -> MainCardItem: + """Deprecated. Please use `shiny.ui.layout_sidebar()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.layout_sidebar()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.layout_sidebar()` instead." + ) + return main_layout_sidebar( + sidebar, + *args, + fillable=fillable, + fill=fill, + bg=bg, + fg=fg, + border=border, + border_radius=border_radius, + border_color=border_color, + gap=gap, + padding=padding, + height=height, + **kwargs, + ) + + +def toggle_sidebar( + id: str, + open: Literal["toggle", "open", "closed", "always"] | bool | None = None, + session: Session | None = None, +) -> None: + """Deprecated. Please use `shiny.ui.toggle_sidebar()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.toggle_sidebar()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.toggle_sidebar()` instead." + ) + return main_toggle_sidebar( + id, + open=open, + session=session, + ) + + +# ---------------------------- + + +# ---------------------------- + + +# Deprecated 2023-08-23 +def sidebar_toggle( + id: str, + open: Literal["toggle", "open", "closed", "always"] | bool | None = None, + session: Session | None = None, +) -> None: + """Deprecated. Please use `shiny.ui.toggle_sidebar()` instead of `sidebar_toggle()`.""" + warn_deprecated( + "`shiny.experimental.ui.sidebar_toggle()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.toggle_sidebar()` instead." + ) + main_toggle_sidebar( + id=id, + open=open, + session=session, + ) + + +# Deprecated 2023-06-13 +# Includes: DeprecatedPanelSidebar +def panel_sidebar( + *args: TagChild | TagAttrs, + width: int = 4, + **kwargs: TagAttrValue, +) -> DeprecatedPanelSidebar: + """Deprecated. Please use `shiny.ui.sidebar()` instead of + `shiny.experimental.ui.panel_sidebar()`.""" + warn_deprecated( + "`shiny.experimental.ui.panel_sidebar()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.sidebar()` with `shiny.ui.layout_sidebar()` instead." + ) + return main_panel_sidebar( + *args, + width=width, + **kwargs, + ) + + +# Deprecated 2023-06-13 +# Includes: DeprecatedPanelMain +def panel_main( + *args: TagChild | TagAttrs, + width: int = 8, + **kwargs: TagAttrValue, +) -> DeprecatedPanelMain: + """Deprecated. Please use `shiny.ui.layout_sidebar()` instead of + `shiny.experimental.ui.panel_main()`.""" + warn_deprecated( + "`shiny.experimental.ui.panel_main()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.sidebar()` with `shiny.ui.layout_sidebar()` instead." + ) + return main_panel_main( + *args, + width=width, + **kwargs, + ) + + +###################### +# Css Unit +###################### + +# Deprecated 2023-09-12 +CssUnit = MainCssUnit +""" +Deprecated. Please use `shiny.ui.css_unit.CssUnit` instead. +""" + + +@overload +def as_css_unit(value: None) -> None: + ... + + +@overload +def as_css_unit(value: CssUnit) -> str: + ... + + +# Deprecated 2023-09-12 +def as_css_unit(value: None | CssUnit) -> None | str: + """ + Deprecated. Please use `shiny.ui.css_unit.as_css_unit()` instead. + """ + warn_deprecated( + "`shiny.experimental.ui.as_css_unit()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.css_unit.as_css_unit()` instead." + ) + return main_as_css_unit(value) + + +@overload +def as_css_padding(padding: CssUnit | list[CssUnit]) -> str: + ... + + +@overload +def as_css_padding(padding: None) -> None: + ... + + +# Deprecated 2023-09-12 +def as_css_padding(padding: CssUnit | list[CssUnit] | None) -> str | None: + """ + Deprecated. Please use `shiny.ui.css_unit.as_css_padding()` instead. + """ + warn_deprecated( + "`shiny.experimental.ui.as_css_padding()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.css_unit.as_css_padding()` instead." + ) + return main_as_css_padding(padding) + + +# Deprecated 2023-09-12 +def as_width_unit(x: str | float | int) -> str: + """Deprecated. Please use `shiny.ui.css_unit.as_width_unit()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.as_width_unit()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.css_unit.as_width_unit()` instead." + ) + return main_as_width_unit(x) + + +###################### +# Popover +###################### + + +# Deprecated 2023-09-12 +def popover( + trigger: TagChild, + *args: TagChild | TagAttrs, + title: Optional[TagChild] = None, + id: Optional[str] = None, + placement: Literal["auto", "top", "right", "bottom", "left"] = "auto", + options: Optional[dict[str, Any]] = None, + **kwargs: TagAttrValue, +) -> Tag: + """Deprecated. Please use `shiny.ui.popover()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.popover()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.popover()` instead." + ) + return main_popover( + trigger, + *args, + title=title, + id=id, + placement=placement, + options=options, + **kwargs, + ) + + +# Deprecated 2023-09-12 +def toggle_popover( + id: str, + show: Optional[bool] = None, + session: Optional[Session] = None, +) -> None: + """Deprecated. Please use `shiny.ui.toggle_popover()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.toggle_popover()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.toggle_popover()` instead." + ) + return main_toggle_popover(id, show, session=session) + + +# Deprecated 2023-09-12 +def update_popover( + id: str, + *args: TagChild, + title: Optional[TagChild] = None, + session: Optional[Session] = None, +) -> None: + """Deprecated. Please use `shiny.ui.update_popover()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.update_popover()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.update_popover()` instead." + ) + return main_update_popover(id, *args, title=title, session=session) + + +# ###################### +# # Accordion +# ###################### + + +# Deprecated 2023-09-12 +class AccordionPanel(MainAccordionPanel): + """ + Deprecated. Please use `shiny.ui.AccordionPanel` instead. + """ + + ... + + +# Deprecated 2023-09-12 +def accordion( + *args: AccordionPanel | TagAttrs, + id: Optional[str] = None, + open: Optional[bool | str | list[str]] = None, + multiple: bool = True, + class_: Optional[str] = None, + width: Optional[CssUnit] = None, + height: Optional[CssUnit] = None, + **kwargs: TagAttrValue, +) -> Tag: + """Deprecated. Please use `shiny.ui.accordion()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.accordion()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.accordion()` instead." + ) + return main_accordion( + *args, + id=id, + open=open, + multiple=multiple, + class_=class_, + width=width, + height=height, + **kwargs, + ) + + +# Deprecated 2023-09-12 +def accordion_panel( + title: TagChild, + *args: TagChild | TagAttrs, + value: Optional[str] | MISSING_TYPE = MISSING, + icon: Optional[TagChild] = None, + **kwargs: TagAttrValue, +) -> MainAccordionPanel: + """Deprecated. Please use `shiny.ui.accordion_panel()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.accordion_panel()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.accordion_panel()` instead." + ) + return main_accordion_panel( + title, + *args, + value=value, + icon=icon, + **kwargs, + ) + + +# # Deprecated 2023-09-12 +def accordion_panel_set( + id: str, + values: bool | str | list[str], + session: Optional[Session] = None, +) -> None: + """Deprecated. Please use `shiny.ui.accordion_panel_set()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.accordion_panel_set()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.accordion_panel_set()` instead." + ) + return main_accordion_panel_set(id, values, session=session) + + +# # Deprecated 2023-09-12 +def accordion_panel_open( + id: str, + values: bool | str | list[str], + session: Optional[Session] = None, +) -> None: + """Deprecated. Please use `shiny.ui.accordion_panel_open()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.accordion_panel_open()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.accordion_panel_open()` instead." + ) + return main_accordion_panel_open(id, values, session=session) + + +# # Deprecated 2023-09-12 +def accordion_panel_close( + id: str, + values: bool | str | list[str], + session: Optional[Session] = None, +) -> None: + """Deprecated. Please use `shiny.ui.accordion_panel_close()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.accordion_panel_close()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.accordion_panel_close()` instead." + ) + return main_accordion_panel_close(id, values, session=session) + + +# # Deprecated 2023-09-12 +def accordion_panel_insert( + id: str, + panel: AccordionPanel, + target: Optional[str] = None, + position: Literal["after", "before"] = "after", + session: Optional[Session] = None, +) -> None: + """Deprecated. Please use `shiny.ui.accordion_panel_insert()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.accordion_panel_insert()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.accordion_panel_insert()` instead." + ) + return main_accordion_panel_insert( + id, + panel, + target=target, + position=position, + session=session, + ) + + +# # Deprecated 2023-09-12 +def accordion_panel_remove( + id: str, + target: str | list[str], + session: Optional[Session] = None, +) -> None: + """Deprecated. Please use `shiny.ui.accordion_panel_remove()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.accordion_panel_remove()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.accordion_panel_remove()` instead." + ) + return main_accordion_panel_remove( + id, + target=target, + session=session, + ) + + +# Deprecated 2023-09-12 +def update_accordion_panel( + id: str, + target: str, + *body: TagChild, + title: TagChild | None | MISSING_TYPE = MISSING, + value: str | None | MISSING_TYPE = MISSING, + icon: TagChild | None | MISSING_TYPE = MISSING, + session: Optional[Session] = None, +) -> None: + """Deprecated. Please use `shiny.ui.update_accordion_panel()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.update_accordion_panel()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.update_accordion_panel()` instead." + ) + return main_update_accordion_panel( + id, + target, + *body, + title=title, + value=value, + icon=icon, + session=session, + ) + + +# ###################### +# # Fill +# ###################### +TagT = TypeVar("TagT", bound="Tag") + + +def as_fill_carrier( + tag: TagT, + *, + min_height: None = None, + max_height: None = None, + gap: None = None, +) -> TagT: + """Deprecated. Please use a combination of `shiny.ui.fill.as_fillable_container()` and `shiny.ui.fill.as_fill_item()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.as_fill_carrier()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.fill.as_fill_container()` and `shiny.ui.fill.as_fillable_item()` instead." + ) + + if min_height is not None: + raise RuntimeError( + "`min_height` is no longer supported. Please add the attribute directly to the Tag's style." + ) + if max_height is not None: + raise RuntimeError( + "`max_height` is no longer supported. Please add the attribute directly to the Tag's style." + ) + if gap is not None: + raise RuntimeError( + "`gap` is no longer supported. Please add the attribute directly to the Tag's style." + ) + return main_as_fillable_container(main_as_fill_item(tag)) + + +def as_fillable_container( + tag: TagT, + *, + min_height: None = None, + max_height: None = None, + gap: None = None, +) -> TagT: + """Deprecated. Please use `shiny.ui.fill.as_fillable_container()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.as_fillable_container()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.fill.as_fillable_container()` instead." + ) + if min_height is not None: + raise RuntimeError( + "`min_height` is no longer supported. Please add the attribute directly to the Tag's style." + ) + if max_height is not None: + raise RuntimeError( + "`max_height` is no longer supported. Please add the attribute directly to the Tag's style." + ) + if gap is not None: + raise RuntimeError( + "`gap` is no longer supported. Please add the attribute directly to the Tag's style." + ) + + return main_as_fillable_container(tag) + + +def as_fill_item( + tag: TagT, + *, + min_height: None = None, + max_height: None = None, +) -> TagT: + """Deprecated. Please use `shiny.ui.fill.as_fill_item()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.as_fill_item()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.fill.as_fill_item()` instead." + ) + if min_height is not None: + raise RuntimeError( + "`min_height` is no longer supported. Please add the attribute directly to the Tag's style." + ) + if max_height is not None: + raise RuntimeError( + "`max_height` is no longer supported. Please add the attribute directly to the Tag's style." + ) + + return main_as_fill_item(tag) + + +def remove_all_fill(tag: TagT) -> TagT: + """Deprecated. Please use `shiny.ui.fill.remove_all_fill()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.remove_all_fill()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.fill.remove_all_fill()` instead." + ) + return main_remove_all_fill(tag) + + +def is_fill_carrier(tag: Tag) -> bool: + """Deprecated. Please use a combination of `shiny.ui.fill.is_fillable_container()` and `shiny.ui.fill.is_fill_item()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.is_fill_carrier()` is deprecated. " + "This method will be removed in a future version, " + "please use a combination of `shiny.ui.fill.is_fillable_container()` and `shiny.ui.fill.is_fill_item()` instead." + ) + return main_is_fill_item(main_is_fillable_container(tag)) + + +def is_fillable_container(tag: TagChild) -> bool: + """Deprecated. Please use `shiny.ui.fill.is_fillable_container()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.is_fillable_container()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.fill.is_fillable_container()` instead." + ) + return main_is_fillable_container(tag) + + +def is_fill_item(tag: TagChild) -> bool: + """Deprecated. Please use `shiny.ui.fill.is_fill_item()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.is_fill_item()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.fill.is_fill_item()` instead." + ) + return main_is_fill_item(tag) + + +# ###################### +# # Card +# ###################### + +TagCallable = TagFunction +"""Deprecated. Please use `htmltools.TagFunction""" + + +class CardItem(MainCardItem): + """Deprecated. Please use `shiny.ui.CardItem` instead.""" + + def __init__( + self, + item: TagChild, + ): + warn_deprecated( + "`shiny.experimental.ui.CardItem()` is deprecated. " + "This class will be removed in a future version, " + "please use :class:`shiny.ui.CardItem` instead." + ) + super().__init__(item) + + +def card_header( + *args: TagChild | TagAttrs, + container: TagFunction = tags.div, + **kwargs: TagAttrValue, +) -> MainCardItem: + """Deprecated. Please use `shiny.ui.card_header()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.card_header()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.card_header()` instead." + ) + return main_card_header(*args, container=container, **kwargs) + + +def card_footer( + *args: TagChild | TagAttrs, + **kwargs: TagAttrValue, +) -> MainCardItem: + """Deprecated. Please use `shiny.ui.card_footer()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.card_footer()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.card_footer()` instead." + ) + + return main_card_footer(*args, **kwargs) + + +# ###################### +# # Layout +# ###################### +def layout_column_wrap( + width: Optional[CssUnit], + *args: TagChild | TagAttrs, + fixed_width: bool = False, + heights_equal: Literal["all", "row"] = "all", + fill: bool = True, + fillable: bool = True, + height: Optional[CssUnit] = None, + height_mobile: Optional[CssUnit] = None, + gap: Optional[CssUnit] = None, + class_: Optional[str] = None, + **kwargs: TagAttrValue, +) -> Tag: + """Deprecated. Please use `shiny.ui.layout_column_wrap()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.layout_column_wrap()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.layout_column_wrap()` instead." + ) + return main_layout_column_wrap( + width, + *args, + fixed_width=fixed_width, + heights_equal=heights_equal, + fill=fill, + fillable=fillable, + height=height, + height_mobile=height_mobile, + gap=gap, + class_=class_, + **kwargs, + ) + + +# ###################### +# # Navs +# ###################### + + +def navset_bar( + *args: NavSetArg | MetadataNode | Sequence[MetadataNode], + title: TagChild, + id: Optional[str] = None, + selected: Optional[str] = None, + sidebar: Optional[MainSidebar] = None, + fillable: bool | list[str] = True, + gap: Optional[CssUnit] = None, + padding: Optional[CssUnit | list[CssUnit]] = None, + position: Literal[ + "static-top", "fixed-top", "fixed-bottom", "sticky-top" + ] = "static-top", + header: TagChild = None, + footer: TagChild = None, + bg: Optional[str] = None, + # TODO-bslib: default to 'auto', like we have in R (parse color via webcolors?) + inverse: bool = False, + collapsible: bool = True, + fluid: bool = True, +) -> MainNavSetBar: + """Deprecated. Please use `shiny.ui.navset_bar()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.navset_bar()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.navset_bar()` instead." + ) + return main_navset_bar( + *args, + title=title, + id=id, + selected=selected, + sidebar=sidebar, + fillable=fillable, + gap=gap, + padding=padding, + position=position, + header=header, + footer=footer, + bg=bg, + inverse=inverse, + collapsible=collapsible, + fluid=fluid, + ) + + +def navset_card_tab( + *args: NavSetArg, + id: Optional[str] = None, + selected: Optional[str] = None, + sidebar: Optional[MainSidebar] = None, + header: TagChild = None, + footer: TagChild = None, +) -> MainNavSetCard: + """Deprecated. Please use `shiny.ui.navset_card_tab()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.navset_card_tab()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.navset_card_tab()` instead." + ) + return main_navset_card_tab( + *args, + id=id, + selected=selected, + sidebar=sidebar, + header=header, + footer=footer, + ) + + +def navset_card_pill( + *args: NavSetArg, + id: Optional[str] = None, + selected: Optional[str] = None, + sidebar: Optional[MainSidebar] = None, + header: TagChild = None, + footer: TagChild = None, + placement: Literal["above", "below"] = "above", +) -> MainNavSetCard: + """Deprecated. Please use `shiny.ui.navset_card_pill()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.navset_card_pill()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.navset_card_pill()` instead." + ) + return main_navset_card_pill( + *args, + id=id, + selected=selected, + sidebar=sidebar, + header=header, + footer=footer, + placement=placement, + ) + + +# ###################### +# # Outputs +# ###################### +def output_plot( + id: str, + width: str = "100%", + height: str = "400px", + *, + inline: bool = False, + click: bool | MainClickOpts = False, + dblclick: bool | MainDblClickOpts = False, + hover: bool | MainHoverOpts = False, + brush: bool | MainBrushOpts = False, + fill: bool | MISSING_TYPE = MISSING, +) -> Tag: + """Deprecated. Please use `shiny.ui.output_plot()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.output_plot()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.output_plot()` instead." + ) + return main_output_plot( + id=id, + width=width, + height=height, + inline=inline, + click=click, + dblclick=dblclick, + hover=hover, + brush=brush, + fill=fill, + ) + + +# @add_example() +def output_image( + id: str, + width: str = "100%", + height: str = "400px", + *, + inline: bool = False, + click: bool | MainClickOpts = False, + dblclick: bool | MainDblClickOpts = False, + hover: bool | MainHoverOpts = False, + brush: bool | MainBrushOpts = False, + # NEW + fill: bool = False, + # /NEW +) -> Tag: + """Deprecated. Please use `shiny.ui.output_image()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.output_image()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.output_image()` instead." + ) + return main_output_image( + id=id, + width=width, + height=height, + inline=inline, + click=click, + dblclick=dblclick, + hover=hover, + brush=brush, + fill=fill, + ) + + +# @add_example() +def output_ui( + id: str, + inline: bool = False, + container: Optional[TagFunction] = None, + fill: bool = False, + fillable: bool = False, + **kwargs: TagAttrValue, +) -> Tag: + """Deprecated. Please use `shiny.ui.output_ui()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.output_ui()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.output_ui()` instead." + ) + return main_output_ui( + id=id, + inline=inline, + container=container, + fill=fill, + fillable=fillable, + **kwargs, + ) + + +# ###################### +# # Page +# ###################### +def page_sidebar( + sidebar: MainSidebar | TagChild | TagAttrs, + *args: TagChild | TagAttrs, + title: Optional[str | Tag | TagList] = None, + fillable: bool = True, + fillable_mobile: bool = False, + window_title: str | MISSING_TYPE = MISSING, + lang: Optional[str] = None, + **kwargs: TagAttrValue, +) -> Tag: + """Deprecated. Please use `shiny.ui.page_sidebar()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.page_sidebar()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.page_sidebar()` instead." + ) + return main_page_sidebar( + sidebar, + *args, + title=title, + fillable=fillable, + fillable_mobile=fillable_mobile, + window_title=window_title, + lang=lang, + **kwargs, + ) + + +def page_navbar( + *args: NavSetArg | MetadataNode | Sequence[MetadataNode], + title: Optional[str | Tag | TagList] = None, + id: Optional[str] = None, + selected: Optional[str] = None, + sidebar: Optional[Sidebar] = None, + # Only page_navbar gets enhancedtreatement for `fillable` + # If an `*args`'s `data-value` attr string is in `fillable`, then the component is fillable + fillable: bool | list[str] = True, + fillable_mobile: bool = False, + gap: Optional[CssUnit] = None, + padding: Optional[CssUnit | list[CssUnit]] = None, + position: Literal["static-top", "fixed-top", "fixed-bottom"] = "static-top", + header: Optional[TagChild] = None, + footer: Optional[TagChild] = None, + bg: Optional[str] = None, + inverse: bool = True, + collapsible: bool = True, + fluid: bool = True, + window_title: str | MISSING_TYPE = MISSING, + lang: Optional[str] = None, +) -> Tag: + """Deprecated. Please use `shiny.ui.page_navbar()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.page_navbar()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.page_navbar()` instead." + ) + return main_page_navbar( + *args, + title=title, + id=id, + selected=selected, + sidebar=sidebar, + fillable=fillable, + fillable_mobile=fillable_mobile, + gap=gap, + padding=padding, + position=position, + header=header, + footer=footer, + bg=bg, + inverse=inverse, + collapsible=collapsible, + fluid=fluid, + window_title=window_title, + lang=lang, + ) + + +def page_fillable( + *args: TagChild | TagAttrs, + padding: Optional[CssUnit | list[CssUnit]] = None, + gap: Optional[CssUnit] = None, + fillable_mobile: bool = False, + title: Optional[str] = None, + lang: Optional[str] = None, + **kwargs: TagAttrValue, +) -> Tag: + """Deprecated. Please use `shiny.ui.page_fillable()` instead.""" + warn_deprecated( + "`shiny.experimental.ui.page_fillable()` is deprecated. " + "This method will be removed in a future version, " + "please use `shiny.ui.page_fillable()` instead." + ) + return main_page_fillable( + *args, + padding=padding, + gap=gap, + fillable_mobile=fillable_mobile, + title=title, + lang=lang, + **kwargs, + ) diff --git a/shiny/experimental/ui/_fill.py b/shiny/experimental/ui/_fill.py deleted file mode 100644 index cf8e90f05..000000000 --- a/shiny/experimental/ui/_fill.py +++ /dev/null @@ -1,639 +0,0 @@ -from __future__ import annotations - -from typing import Literal, Optional, Protocol, TypeVar, runtime_checkable - -from htmltools import Tag, TagChild, Tagifiable, css - -from ._css_unit import CssUnit, as_css_unit -from ._htmldeps import fill_dependency -from ._tag import tag_add_style, tag_prepend_class, tag_remove_class - -""" -examples: - - [ ] remove_all_fill - - [ ] is_fill_carrier - - [ ] is_fillable_container - - [ ] is_fill_item - - [ ] FillingLayout -""" - - -__all__ = ( - "bind_fill_role", - "as_fill_carrier", - "as_fillable_container", - "as_fill_item", - "remove_all_fill", - "is_fill_carrier", - "is_fillable_container", - "is_fill_item", - "FillingLayout", -) - -TagFillingLayoutT = TypeVar("TagFillingLayoutT", bound="Tag | FillingLayout") -""" -A :class:`~htmltools.Tag` object or an object that implements the -:class:`~shiny.experimental.ui.FillingLayout` protocol. -""" - -TagT = TypeVar("TagT", bound="Tag") - - -FILL_ITEM_CLASS = "html-fill-item" -FILL_CONTAINER_CLASS = "html-fill-container" - -# We're currently not supporting the div(as_fillable_container()) API, in order to keep -# the function easier to understand. If we do implement something like that, we should -# use a different name, because the function is conceptually different, and it also is -# behaviorally different (it doesn't include the HTML dependency.) - -# TODO-future-approach: bind_fill_role() should return None? -# From @wch: -# > For functions like this, which modify the original object, I think the Pythonic way -# > of doing things is to return None, to make it clearer that the object is modified in -# > place. -# From @schloerke: -# > It makes for a very clunky interface. Keeping as is for now. -# > Should we copy the tag before modifying it? (If we are not doing that elsewhere, then I am hesitant to start here.) -# > If it is not utilizing `nonlocal foo`, then it should be returned. Even if it is altered in-place - - -def _add_role( - tag: TagT, *, condition: bool | None, class_: str, overwrite: bool = False -) -> TagT: - if condition is None: - return tag - - # Remove the class if it already exists and we're going to add it, - # or if we're requiring it to be removed - if (condition and tag.has_class(class_)) or overwrite: - tag = tag_remove_class(tag, class_) - - if condition: - tag = tag_prepend_class(tag, class_) - tag.append(fill_dependency()) - return tag - - -def bind_fill_role( - tag: TagT, - *, - item: Optional[bool] = None, - container: Optional[bool] = None, - overwrite: bool = False, -) -> TagT: - """ - Allow tags to intelligently fill their container - - Create fill containers and items. If a fill item is a direct child of a fill - container, and that container has an opinionated height, then the item is allowed to - grow and shrink to its container's size. - - Parameters - ---------- - tag - a T object. - item - whether or not to treat `tag` as a fill item. - container - whether or not to treat `x` as a fill container. Note, this will set the CSS - `display` property on the tag to `flex` which can change how its direct children - are rendered. Thus, one should be careful not to mark a tag as a fill container - when it needs to rely on other `display` behavior. - overwrite - whether or not to override previous filling layout calls (e.g., to remove the - item/container role from a tag). - - Returns - ------- - : - The original :class:`~htmltools.Tag` object (`tag`) with additional attributes - (and an :class:`~htmltools.HTMLDependency`). - """ - tag = _add_role( - tag, - condition=item, - class_=FILL_ITEM_CLASS, - overwrite=overwrite, - ) - tag = _add_role( - tag, - condition=container, - class_=FILL_CONTAINER_CLASS, - overwrite=overwrite, - ) - return tag - - -########################################### - - -# Test and/or coerce fill behavior - -# TODO-future; When `css_selector` can be implemented, the three parameters below should be added where appropriate -# @param class A character vector of class names to add to the tag. -# @param style A character vector of CSS properties to add to the tag. -# @param css_selector A character string containing a CSS selector for -# targeting particular (inner) tag(s) of interest. For more details on what -# selector(s) are supported, see [tagAppendAttributes()]. - - -def as_fill_carrier( - tag: TagFillingLayoutT, - *, - min_height: Optional[CssUnit] = None, - max_height: Optional[CssUnit] = None, - gap: Optional[CssUnit] = None, - # class_: Optional[str] = None, - # style: Optional[str] = None, - # css_selector: Optional[str], -) -> TagFillingLayoutT: - """ - Make a tag a fill carrier - - Filling layouts are built on the foundation of _fillable containers_ and _fill - items_ (_fill carriers_ are both _fillable containers_ and _fill items_). This is - why most UI components (e.g., :func:`~shiny.experimental.ui.card`, - :func:`~shiny.experimental.ui.card_body`, - :func:`~shiny.experimental.ui.layout_sidebar`) possess both `fillable` and `fill` - arguments (to control their fill behavior). However, sometimes it's useful to add, - remove, and/or test fillable/fill properties on arbitrary :class:`~htmltools.Tag`, - which these functions are designed to do. - - Parameters - ---------- - tag - a Tag object. - min_height,max_height,gap - Any valid CSS unit (e.g., `150`) to be applied to `tag`. - - Returns - ------- - : - The original :class:`~htmltools.Tag` object (`tag`) with additional attributes - (and an :class:`~htmltools.HTMLDependency`). - - See Also - -------- - * :func:`~shiny.experimental.ui.as_fill_item` - * :func:`~shiny.experimental.ui.as_fillable_container` - * :func:`~shiny.experimental.ui.remove_all_fill` - * :func:`~shiny.experimental.ui.is_fill_carrier` - * :func:`~shiny.experimental.ui.is_fill_item` - * :func:`~shiny.experimental.ui.is_fillable_container` - """ - return _add_filling_attrs( - tag, - item=True, - container=True, - min_height=min_height, - max_height=max_height, - gap=gap, - ) - - -def as_fillable_container( - tag: TagFillingLayoutT, - *, - min_height: Optional[CssUnit] = None, - max_height: Optional[CssUnit] = None, - gap: Optional[CssUnit] = None, - # class_: Optional[str] = None, - # style: Optional[str] = None, - # css_selector: Optional[str] = None, -) -> TagFillingLayoutT: - """ - Coerce a tag to be a fillable container - - Filling layouts are built on the foundation of _fillable containers_ and _fill - items_ (_fill carriers_ are both _fillable containers_ and _fill items_). This is - why most UI components (e.g., :func:`~shiny.experimental.ui.card`, - :func:`~shiny.experimental.ui.card_body`, - :func:`~shiny.experimental.ui.layout_sidebar`) possess both `fillable` and `fill` - arguments (to control their fill behavior). However, sometimes it's useful to add, - remove, and/or test fillable/fill properties on arbitrary :class:`~htmltools.Tag`, - which these functions are designed to do. - - Parameters - ---------- - tag - a Tag object. - min_height,max_height,gap - Any valid CSS unit (e.g., `150`) to be applied to `tag`. - - Returns - ------- - : - The original :class:`~htmltools.Tag` object (`tag`) with additional attributes - (and an :class:`~htmltools.HTMLDependency`). - - See Also - -------- - * :func:`~shiny.experimental.ui.as_fill_carrier` - * :func:`~shiny.experimental.ui.as_fill_item` - * :func:`~shiny.experimental.ui.as_fillable_container` - * :func:`~shiny.experimental.ui.remove_all_fill` - * :func:`~shiny.experimental.ui.is_fill_carrier` - * :func:`~shiny.experimental.ui.is_fill_item` - * :func:`~shiny.experimental.ui.is_fillable_container` - """ - return _add_filling_attrs( - tag, - container=True, - min_height=min_height, - max_height=max_height, - gap=gap, - ) - - -def as_fill_item( - tag: TagFillingLayoutT, - *, - min_height: Optional[CssUnit] = None, - max_height: Optional[CssUnit] = None, - # class_: Optional[str] = None, - # style: Optional[str] = None, - # css_selector: Optional[str] = None, -) -> TagFillingLayoutT: - """ - Coerce a tag to a fill item - - Filling layouts are built on the foundation of _fillable containers_ and _fill - items_ (_fill carriers_ are both _fillable containers_ and _fill items_). This is - why most UI components (e.g., :func:`~shiny.experimental.ui.card`, - :func:`~shiny.experimental.ui.card_body`, - :func:`~shiny.experimental.ui.layout_sidebar`) possess both `fillable` and `fill` - arguments (to control their fill behavior). However, sometimes it's useful to add, - remove, and/or test fillable/fill properties on arbitrary :class:`~htmltools.Tag`, - which these functions are designed to do. - - Parameters - ---------- - tag - a Tag object. - min_height,max_height - Any valid CSS unit (e.g., `150`) to be applied to `tag`. - - Returns - ------- - : - The original :class:`~htmltools.Tag` object (`tag`) with additional attributes - (and an :class:`~htmltools.HTMLDependency`). - - See Also - -------- - * :func:`~shiny.experimental.ui.as_fill_carrier` - * :func:`~shiny.experimental.ui.as_fillable_container` - * :func:`~shiny.experimental.ui.remove_all_fill` - * :func:`~shiny.experimental.ui.is_fill_carrier` - * :func:`~shiny.experimental.ui.is_fill_item` - * :func:`~shiny.experimental.ui.is_fillable_container` - """ - return _add_filling_attrs( - tag, - item=True, - min_height=min_height, - max_height=max_height, - ) - - -def remove_all_fill(tag: TagFillingLayoutT) -> TagFillingLayoutT: - """ - Remove any filling layouts from a tag - - Filling layouts are built on the foundation of _fillable containers_ and _fill - items_ (_fill carriers_ are both _fillable containers_ and _fill items_). This is - why most UI components (e.g., :func:`~shiny.experimental.ui.card`, - :func:`~shiny.experimental.ui.card_body`, - :func:`~shiny.experimental.ui.layout_sidebar`) possess both `fillable` and `fill` - arguments (to control their fill behavior). However, sometimes it's useful to add, - remove, and/or test fillable/fill properties on arbitrary :class:`~htmltools.Tag`, - which these functions are designed to do. - - Parameters - ---------- - tag - a Tag object. - - Returns - ------- - : - The original :class:`~htmltools.Tag` object with filling layout attributes - removed. - - - See Also - -------- - * :func:`~shiny.experimental.ui.as_fill_carrier` - * :func:`~shiny.experimental.ui.as_fill_item` - * :func:`~shiny.experimental.ui.as_fillable_container` - * :func:`~shiny.experimental.ui.is_fill_carrier` - * :func:`~shiny.experimental.ui.is_fill_item` - * :func:`~shiny.experimental.ui.is_fillable_container` - """ - - if isinstance(tag, FillingLayout): - return tag.remove_all_fill() - - return bind_fill_role( - tag, - item=False, - container=False, - overwrite=True, - ) - - -def is_fill_carrier(tag: Tag | FillingLayout) -> bool: - """ - Test a tag for being a fill carrier - - Filling layouts are built on the foundation of _fillable containers_ and _fill - items_ (_fill carriers_ are both _fillable containers_ and _fill items_). This is - why most UI components (e.g., :func:`~shiny.experimental.ui.card`, - :func:`~shiny.experimental.ui.card_body`, - :func:`~shiny.experimental.ui.layout_sidebar`) possess both `fillable` and `fill` - arguments (to control their fill behavior). However, sometimes it's useful to add, - remove, and/or test fillable/fill properties on arbitrary :class:`~htmltools.Tag`, - which these functions are designed to do. - - Parameters - ---------- - tag - a Tag object. - - Returns - ------- - : - Whether or not `tag` is a fill carrier. - - See Also - -------- - * :func:`~shiny.experimental.ui.as_fill_carrier` - * :func:`~shiny.experimental.ui.as_fill_item` - * :func:`~shiny.experimental.ui.as_fillable_container` - * :func:`~shiny.experimental.ui.remove_all_fill` - * :func:`~shiny.experimental.ui.is_fill_item` - * :func:`~shiny.experimental.ui.is_fillable_container` - """ - return is_fillable_container(tag) and is_fill_item(tag) - - -def is_fillable_container(tag: TagChild | FillingLayout) -> bool: - """ - Test a tag for being a fillable container - - Filling layouts are built on the foundation of _fillable containers_ and _fill - items_ (_fill carriers_ are both _fillable containers_ and _fill items_). This is - why most UI components (e.g., :func:`~shiny.experimental.ui.card`, - :func:`~shiny.experimental.ui.card_body`, - :func:`~shiny.experimental.ui.layout_sidebar`) possess both `fillable` and `fill` - arguments (to control their fill behavior). However, sometimes it's useful to add, - remove, and/or test fillable/fill properties on arbitrary :class:`~htmltools.Tag`, - which these functions are designed to do. - - Parameters - ---------- - tag - a Tag object. - - Returns - ------- - : - Whether or not `tag` is a fillable container. - - - See Also - -------- - * :func:`~shiny.experimental.ui.as_fill_carrier` - * :func:`~shiny.experimental.ui.as_fill_item` - * :func:`~shiny.experimental.ui.as_fillable_container` - * :func:`~shiny.experimental.ui.remove_all_fill` - * :func:`~shiny.experimental.ui.is_fill_item` - * :func:`~shiny.experimental.ui.is_fillable_container` - """ - # TODO-future; Handle widgets - # # won't actually work until (htmltools#334) gets fixed - # renders_to_tag_class(x, FILL_CONTAINER_CLASS, ".html-widget") - - return _is_fill_layout(tag, layout="fillable") - - -def is_fill_item(tag: TagChild | FillingLayout) -> bool: - """ - Test a tag for being a fill item - - Filling layouts are built on the foundation of _fillable containers_ and _fill - items_ (_fill carriers_ are both _fillable containers_ and _fill items_). This is - why most UI components (e.g., :func:`~shiny.experimental.ui.card`, - :func:`~shiny.experimental.ui.card_body`, - :func:`~shiny.experimental.ui.layout_sidebar`) possess both `fillable` and `fill` - arguments (to control their fill behavior). However, sometimes it's useful to add, - remove, and/or test fillable/fill properties on arbitrary :class:`~htmltools.Tag`, - which these functions are designed to do. - - Parameters - ---------- - tag - a Tag object. - - Returns - ------- - : - Whether or not `tag` is a fill item. - - See Also - -------- - * :func:`~shiny.experimental.ui.as_fill_carrier` - * :func:`~shiny.experimental.ui.as_fill_item` - * :func:`~shiny.experimental.ui.as_fillable_container` - * :func:`~shiny.experimental.ui.remove_all_fill` - * :func:`~shiny.experimental.ui.is_fill_carrier` - * :func:`~shiny.experimental.ui.is_fillable_container` - """ - # TODO-future; Handle widgets - # # won't actually work until (htmltools#334) gets fixed - # renders_to_tag_class(x, FILL_ITEM_CLASS, ".html-widget") - - return _is_fill_layout(tag, layout="fill") - - -def _is_fill_layout( - tag: TagChild | FillingLayout, - layout: Literal["fill", "fillable"], - # recurse: bool = True, -) -> bool: - if not isinstance(tag, (Tag, Tagifiable, FillingLayout)): - return False - - # tag: Tag | FillingLayout | Tagifiable - - if layout == "fill": - if isinstance(tag, Tag): - return tag.has_class(FILL_ITEM_CLASS) - if isinstance(tag, FillingLayout): - return tag.is_fill_item() - - elif layout == "fillable": - if isinstance(tag, Tag): - return tag.has_class(FILL_CONTAINER_CLASS) - if isinstance(tag, FillingLayout): - return tag.is_fillable_container() - - # tag: Tagifiable and not (Tag or FillingLayout) - raise TypeError( - f"`_is_fill_layout(tag=)` must be a `Tag` or implement the `FillingLayout` protocol methods. Received object of type: `{type(tag).__name__}`" - ) - - -T = TypeVar("T") - - -@runtime_checkable -class FillingLayout(Protocol): - """ - Generic protocol for filling layouts objects - """ - - def add_class( - self: T, - class_: str, - # *, - # # Currently Unused - # css_selector: Optional[str] = None, - # Currently Unused - **kwargs: object, - ) -> T: - """ - Generic method to handle adding a CSS `class` to an object - - Parameters - ---------- - class_ - A character vector of class names to add to the tag. - **kwargs - Possible future arguments - - Returns - ------- - : - The updated object. - """ - ... - - def add_style( - self: T, - style: str, - # *, - # # Currently Unused - # css_selector: Optional[str] = None, - # Currently Unused - **kwargs: object, - ) -> T: - """ - Generic method to handle adding a CSS `style` to an object - - Parameters - ---------- - style - A character vector of CSS properties to add to the tag. - **kwargs - Possible future arguments - - Returns - ------- - : - The updated object. - """ - ... - - def is_fill_item(self) -> bool: - """ - Generic method to handle testing if an object is a fill item - - Returns - ------- - : - Whether or not the object is a fill item - """ - ... - - def is_fillable_container(self) -> bool: - """ - Generic method to handle testing if an object is a fillable container - - Returns - ------- - : - Whether or not the object is a fillable container - """ - ... - - def as_fill_item( - self: T, - ) -> T: - """ - Generic method to handle coercing an object to a fill item - - Returns - ------- - : - The updated object. - """ - ... - - def as_fillable_container( - self: T, - ) -> T: - """ - Generic method to handle coercing an object to a fillable container - - Returns - ------- - : - The updated object. - """ - ... - - def remove_all_fill( - self: T, - ) -> T: - """ - Generic method to handle removing all fill properties from an object - - Returns - ------- - : - The updated object. - """ - ... - - -def _style_units_to_str(**kwargs: CssUnit | None) -> str | None: - style_items: dict[str, CssUnit] = {} - for k, v in kwargs.items(): - if v is not None: - style_items[k] = as_css_unit(v) - - return css(**style_items) - - -def _add_filling_attrs( - tag: TagFillingLayoutT, - item: Optional[bool] = None, - container: Optional[bool] = None, - **kwargs: CssUnit | None, -) -> TagFillingLayoutT: - new_style = _style_units_to_str(**kwargs) - - if isinstance(tag, FillingLayout): - if new_style: - tag.add_style(new_style) - if item: - tag.as_fill_item() - if container: - tag.as_fillable_container() - return tag - - # Tag - tag = tag_add_style(tag, new_style) - return bind_fill_role(tag, item=item, container=container) diff --git a/shiny/experimental/ui/_input_switch.py b/shiny/experimental/ui/_input_switch.py deleted file mode 100644 index be94b0d77..000000000 --- a/shiny/experimental/ui/_input_switch.py +++ /dev/null @@ -1,35 +0,0 @@ -from __future__ import annotations - -from typing import Optional - -from ..._utils import drop_none -from ...module import resolve_id -from ...session import Session, require_active_session - - -def toggle_switch( - id: str, value: Optional[bool] = None, session: Optional[Session] = None -): - """ - Toggle a switch input. - - Parameters - ---------- - id - The id of the switch input. - value - The new value of the switch input. If `NULL`, the value will be toggled. - session - The session object passed to `server()`. - """ - - if value is not None and not isinstance(value, bool): - raise TypeError("`value` must be `None` or a single boolean value.") - - msg = drop_none({"id": resolve_id(id), "value": value}) - session = require_active_session(session) - - async def callback(): - await session.send_custom_message("bslib.toggle-input-binary", msg) - - session.on_flush(callback, once=True) diff --git a/shiny/experimental/ui/_input_text.py b/shiny/experimental/ui/_input_text.py deleted file mode 100644 index 5a504c1f8..000000000 --- a/shiny/experimental/ui/_input_text.py +++ /dev/null @@ -1,120 +0,0 @@ -__all__ = ("input_text_area",) - -from typing import Literal, Optional - -from htmltools import Tag, TagChild, css, div, tags - -# from ..._docstring import add_example -from ..._namespaces import resolve_id -from ...ui._utils import shiny_input_label -from ._htmldeps import autoresize_dependency - - -# TODO-maindocs; -# @add_example() -def input_text_area( - id: str, - label: TagChild, - value: str = "", - *, - width: Optional[str] = None, - height: Optional[str] = None, - cols: Optional[int] = None, - rows: Optional[int] = None, - placeholder: Optional[str] = None, - resize: Optional[Literal["none", "both", "horizontal", "vertical"]] = None, - autoresize: bool = False, - autocomplete: Optional[str] = None, - spellcheck: Optional[Literal["true", "false"]] = None, -) -> Tag: - """ - Create a textarea input control for entry of unstructured text values. This is an - experimental version of :func:`~shiny.ui.input_text_area` that can automatically - resize to fit the input text. - - Parameters - ---------- - id - An input id. - label - An input label. - value - Initial value. - width - The CSS width, e.g. '400px', or '100%' - height - The CSS height, e.g. '400px', or '100%' - cols - Value of the visible character columns of the input, e.g. 80. This argument will - only take effect if there is not a CSS width rule defined for this element; such - a rule could come from the width argument of this function or from a containing - page layout such as :func:`~shiny.ui.page_fluid`. - rows - The value of the visible character rows of the input, e.g. 6. If the height - argument is specified, height will take precedence in the browser's rendering. - placeholder - A hint as to what can be entered into the control. - resize - Which directions the textarea box can be resized. Can be one of "both", "none", - "vertical", and "horizontal". The default, ``None``, will use the client - browser's default setting for resizing textareas. - autoresize - If True, then the textarea will automatically resize the height to fit the input - text. - autocomplete - Whether to enable browser autocompletion of the text input (default is "off"). - If `None`, then it will use the browser's default behavior. Other possible - values include "on", "name", "username", and "email". See [Mozila's autocomplete - documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) - for more. - spellcheck - Whether to enable browser spell checking of the text input (default is None). If - None, then it will use the browser's default behavior. - - Returns - ------- - : - A UI element - - Notes - ------ - - ::: {.callout-note title="Server value"} - A string containing the current text input. The default value is ``""`` unless - ``value`` is provided. - ::: - - See Also - ------- - :func:`~shiny.ui.input_text` - """ - - if resize and resize not in ["none", "both", "horizontal", "vertical"]: - raise ValueError("Invalid resize value: " + str(resize)) - - classes = ["form-control"] - if autoresize: - classes.append("textarea-autoresize") - if rows is None: - rows = 1 - - resolved_id = resolve_id(id) - area = tags.textarea( - value, - id=resolved_id, - class_=" ".join(classes), - style=css(width=None if width else "100%", height=height, resize=resize), - placeholder=placeholder, - rows=rows, - cols=cols, - autocomplete=autocomplete, - spellcheck=spellcheck, - ) - - return div( - shiny_input_label(resolved_id, label), - area, - autoresize_dependency() if autoresize else None, - class_="shiny-input-textarea form-group shiny-input-container", - style=css(width=width), - ) diff --git a/shiny/experimental/ui/_navs.py b/shiny/experimental/ui/_navs.py deleted file mode 100644 index 3ccf5d731..000000000 --- a/shiny/experimental/ui/_navs.py +++ /dev/null @@ -1,629 +0,0 @@ -from __future__ import annotations - -__all__ = ( - "navset_bar", - "navset_card_tab", - "navset_card_pill", -) - -import copy -from typing import Any, Literal, Optional, Sequence, cast - -from htmltools import MetadataNode, Tag, TagChild, TagList, css, div, tags - -from ..._namespaces import resolve_id_or_none -from ..._utils import private_random_int -from ...types import NavSetArg -from ...ui._html_dependencies import bootstrap_deps -from ._card import CardItem, card, card_body, card_footer, card_header -from ._css_unit import CssUnit, as_css_padding, as_css_unit -from ._fill import as_fill_carrier -from ._sidebar import Sidebar, layout_sidebar -from ._tag import tag_add_style - - -# ----------------------------------------------------------------------------- -# Navigation items -# ----------------------------------------------------------------------------- -class Nav: - nav: Tag - content: Optional[Tag] - - def __init__(self, nav: Tag, content: Optional[Tag] = None) -> None: - self.nav = nav - # nav_control()/nav_spacer() have None as their content - self.content = content - - def resolve( - self, selected: Optional[str], context: dict[str, Any] - ) -> tuple[TagChild, TagChild]: - # Nothing to do for nav_control()/nav_spacer() - if self.content is None: - return self.nav, None - - # At least currently, in the case where both nav and content are tags - # (i.e., nav()), the nav always has a child tag...I'm not sure if - # there's a way to statically type this - nav = copy.deepcopy(self.nav) - a_tag = cast(Tag, nav.children[0]) - if context.get("is_menu", False): - a_tag.add_class("dropdown-item") - else: - a_tag.add_class("nav-link") - nav.add_class("nav-item") - - # Hyperlink the nav to the content - content = copy.copy(self.content) - if "tabsetid" in context and "index" in context: - id = f"tab-{context['tabsetid']}-{context['index']}" - content.attrs["id"] = id - a_tag.attrs["href"] = f"#{id}" - - # Mark the nav/content as active if it should be - if isinstance(selected, str) and selected == self.get_value(): - content.add_class("active") - a_tag.add_class("active") - - nav.children[0] = a_tag - - return nav, content - - def get_value(self) -> Optional[str]: - if self.content is None: - return None - a_tag = cast(Tag, self.nav.children[0]) - return a_tag.attrs.get("data-value", None) - - def tagify(self) -> None: - raise NotImplementedError( - "nav() items must appear within navset_*() container." - ) - - -class NavSet: - args: tuple[NavSetArg | MetadataNode] - ul_class: str - id: Optional[str] - selected: Optional[str] - header: TagChild - footer: TagChild - - def __init__( - self, - *args: NavSetArg | MetadataNode, - ul_class: str, - id: Optional[str], - selected: Optional[str], - header: TagChild = None, - footer: TagChild = None, - ) -> None: - self.args = args - self.ul_class = ul_class - self.id = id - self.selected = selected - self.header = header - self.footer = footer - - def tagify(self) -> TagList | Tag: - id = self.id - ul_class = self.ul_class - if id is not None: - ul_class += " shiny-tab-input" - - nav, content = _render_navset( - *self.args, ul_class=ul_class, id=id, selected=self.selected, context={} - ) - return self.layout(nav, content).tagify() - - # Types must match output of `_render_navset() -> Tuple[Tag, Tag]` - def layout(self, nav: Tag, content: Tag) -> TagList | Tag: - return TagList(nav, self.header, content, self.footer) - - -# ----------------------------------------------------------------------------- -# Navigation containers -# ----------------------------------------------------------------------------- - - -class NavSetCard(NavSet): - placement: Literal["above", "below"] - sidebar: Optional[Sidebar] - - def __init__( - self, - *args: NavSetArg, - ul_class: str, - id: Optional[str], - selected: Optional[str], - sidebar: Optional[Sidebar] = None, - header: TagChild = None, - footer: TagChild = None, - placement: Literal["above", "below"] = "above", - ) -> None: - super().__init__( - *args, - ul_class=ul_class, - id=id, - selected=selected, - header=header, - footer=footer, - ) - self.sidebar = sidebar - self.placement = placement - - def layout(self, nav: Tag, content: Tag) -> Tag: - # navs = [child for child in content.children if isinstance(child, Nav)] - # not_navs = [child for child in content.children if child not in navs] - content_val: Tag | CardItem = content - - if self.sidebar: - content_val = navset_card_body(content, sidebar=self.sidebar) - - if self.placement == "below": - # TODO-carson; have carson double check this change - return card( - card_header(self.header) if self.header else None, - content_val, - card_body(self.footer, fillable=False, fill=False) - if self.footer - else None, - card_footer(nav), - ) - else: - # TODO-carson; have carson double check this change - return card( - card_header(nav), - card_body(self.header, fill=False, fillable=False) - if self.header - else None, - content_val, - card_footer(self.footer) if self.footer else None, - ) - - -def navset_card_body(content: Tag, sidebar: Optional[Sidebar] = None) -> CardItem: - content = _make_tabs_fillable(content, fillable=True, gap=0, padding=0) - if sidebar: - return layout_sidebar( - sidebar, - content, - fillable=True, - border=False, - ) - else: - return CardItem(content) - - -def navset_card_tab( - *args: NavSetArg, - id: Optional[str] = None, - selected: Optional[str] = None, - sidebar: Optional[Sidebar] = None, - header: TagChild = None, - footer: TagChild = None, -) -> NavSetCard: - """ - Render nav items as a tabset inside a card container. - - Parameters - ---------- - *args - A collection of nav items (e.g., :func:`shiny.ui.nav`). - id - If provided, will create an input value that holds the currently selected nav - item. - selected - Choose a particular nav item to select by default value (should match it's - ``value``). - header - UI to display above the selected content. - footer - UI to display below the selected content. - - See Also - ------- - * ~shiny.ui.nav - * ~shiny.ui.nav_menu - * ~shiny.ui.nav_control - * ~shiny.ui.nav_spacer - * ~shiny.experimental.ui.navset_bar - * ~shiny.ui.navset_tab - * ~shiny.ui.navset_pill - * ~shiny.experimental.ui.navset_card_pill - * ~shiny.ui.navset_hidden - - Example - ------- - See :func:`~shiny.ui.nav` - """ - - return NavSetCard( - *args, - ul_class="nav nav-tabs card-header-tabs", - id=resolve_id_or_none(id), - selected=selected, - sidebar=sidebar, - header=header, - footer=footer, - placement="above", - ) - - -def navset_card_pill( - *args: NavSetArg, - id: Optional[str] = None, - selected: Optional[str] = None, - sidebar: Optional[Sidebar] = None, - header: TagChild = None, - footer: TagChild = None, - placement: Literal["above", "below"] = "above", -) -> NavSetCard: - """ - Render nav items as a pillset inside a card container. - - Parameters - ---------- - *args - A collection of nav items (e.g., :func:`shiny.ui.nav`). - id - If provided, will create an input value that holds the currently selected nav - item. - selected - Choose a particular nav item to select by default value (should match it's - ``value``). - header - UI to display above the selected content. - footer - UI to display below the selected content. - placement - Placement of the nav items relative to the content. - - See Also - ------- - * ~shiny.ui.nav - * ~shiny.ui.nav_menu - * ~shiny.ui.nav_control - * ~shiny.ui.nav_spacer - * ~shiny.experimental.ui.navset_bar - * ~shiny.ui.navset_tab - * ~shiny.ui.navset_pill - * ~shiny.experimental.ui.navset_card_tab - * ~shiny.ui.navset_hidden - - Example - ------- - See :func:`~shiny.ui.nav` - """ - - return NavSetCard( - *args, - ul_class="nav nav-pills card-header-pills", - id=resolve_id_or_none(id), - selected=selected, - sidebar=sidebar, - header=header, - footer=footer, - placement=placement, - ) - - -class NavSetBar(NavSet): - title: TagChild - sidebar: Optional[Sidebar] - fillable: bool | list[str] - gap: Optional[CssUnit] - padding: Optional[CssUnit | list[CssUnit]] - position: Literal["static-top", "fixed-top", "fixed-bottom", "sticky-top"] - bg: Optional[str] - inverse: bool - collapsible: bool - fluid: bool - - def __init__( - self, - *args: NavSetArg | MetadataNode, - ul_class: str, - title: TagChild, - id: Optional[str], - selected: Optional[str], - sidebar: Optional[Sidebar] = None, - fillable: bool | list[str] = False, - gap: Optional[CssUnit], - padding: Optional[CssUnit | list[CssUnit]], - position: Literal[ - "static-top", "fixed-top", "fixed-bottom", "sticky-top" - ] = "static-top", - header: TagChild = None, - footer: TagChild = None, - bg: Optional[str] = None, - # TODO-bslib: default to 'auto', like we have in R (parse color via webcolors?) - inverse: bool = False, - collapsible: bool = True, - fluid: bool = True, - ) -> None: - super().__init__( - *args, - ul_class=ul_class, - id=id, - selected=selected, - header=header, - footer=footer, - ) - self.title = title - self.sidebar = sidebar - self.fillable = fillable - self.gap = gap - self.padding = padding - - self.position = position - self.bg = bg - self.inverse = inverse - self.collapsible = collapsible - self.fluid = fluid - - def layout(self, nav: Tag, content: Tag) -> TagList: - nav_container = div( - {"class": "container-fluid" if self.fluid else "container"}, - tags.a({"class": "navbar-brand", "href": "#"}, self.title), - ) - if self.collapsible: - collapse_id = "navbar-collapse-" + private_random_int(1000, 10000) - nav_container.append( - tags.button( - tags.span(class_="navbar-toggler-icon"), - class_="navbar-toggler", - type="button", - data_bs_toggle="collapse", - data_bs_target="#" + collapse_id, - aria_controls=collapse_id, - aria_expanded="false", - aria_label="Toggle navigation", - ) - ) - nav = div(nav, id=collapse_id, class_="collapse navbar-collapse") - - nav_container.append(nav) - nav_final = tags.nav({"class": "navbar navbar-expand-md"}, nav_container) - - if self.position != "static-top": - nav_final.add_class(self.position) - - nav_final.add_class(f"navbar-{'dark' if self.inverse else 'light'}") - - if self.bg: - nav_final.attrs["style"] = "background-color: " + self.bg - else: - nav_final.add_class(f"bg-{'dark' if self.inverse else 'light'}") - - content = _make_tabs_fillable( - content, self.fillable, gap=self.gap, padding=self.padding, navbar=True - ) - - # 2023-05-11; Do not wrap `row()` around `self.header` and `self.footer` - contents: list[TagChild] = [ - child for child in [self.header, content, self.footer] if child is not None - ] - - if self.sidebar is None: - content_div = div( - *contents, - class_="container-fluid" if self.fluid else "container", - ) - # If fillable is truthy, the .container also needs to be fillable - if self.fillable: - content_div = as_fill_carrier(content_div) - else: - content_div = div( - # In the fluid case, the sidebar layout should be flush (i.e., - # the .container-fluid class adds padding that we don't want) - {"class": "container"} if not self.fluid else None, - layout_sidebar( - self.sidebar, - contents, - fillable=self.fillable is not False, - border_radius=False, - border=not self.fluid, - ), - ) - # Always have the sidebar layout fill its parent (in this case - # fillable controls whether the _main_ content portion is fillable) - content_div = as_fill_carrier(content_div) - - return TagList(nav_final, content_div) - - -# Given a .tab-content container, mark each relevant .tab-pane as a fill container/item. -def _make_tabs_fillable( - content: Tag, - fillable: bool | list[str] = True, - gap: Optional[CssUnit] = None, - padding: Optional[CssUnit | list[CssUnit]] = None, - navbar: bool = False, -) -> Tag: - if not fillable: - return content - - # Even if only one .tab-pane wants fillable behavior, the .tab-content - # must to be a fillable container. - content = as_fill_carrier(content) - - for child in content.children: - # Only work on Tags - if not isinstance(child, Tag): - continue - # Only work on .tab-pane children - if not child.has_class("tab-pane"): - continue - # If `fillable` is a list, only fill the .tab-pane if its data-value is contained in `fillable` - if isinstance(fillable, list): - child_attr = child.attrs.get("data-value") - if child_attr is None or child_attr not in fillable: - continue - styles = css( - gap=as_css_unit(gap), - padding=as_css_padding(padding), - __bslib_navbar_margin="0;" if navbar else None, - ) - child = tag_add_style(child, styles) - child = as_fill_carrier(child) - - return content - - -def navset_bar( - *args: NavSetArg | MetadataNode | Sequence[MetadataNode], - title: TagChild, - id: Optional[str] = None, - selected: Optional[str] = None, - sidebar: Optional[Sidebar] = None, - fillable: bool | list[str] = True, - gap: Optional[CssUnit] = None, - padding: Optional[CssUnit | list[CssUnit]] = None, - position: Literal[ - "static-top", "fixed-top", "fixed-bottom", "sticky-top" - ] = "static-top", - header: TagChild = None, - footer: TagChild = None, - bg: Optional[str] = None, - # TODO-bslib: default to 'auto', like we have in R (parse color via webcolors?) - inverse: bool = False, - collapsible: bool = True, - fluid: bool = True, -) -> NavSetBar: - """ - Render nav items as a navbar. - - Parameters - ---------- - *args - A collection of nav items (e.g., :func:`shiny.ui.nav`). - title - Title to display in the navbar. - id - If provided, will create an input value that holds the currently selected nav - item. - selected - Choose a particular nav item to select by default value (should match it's - ``value``). - position - Determines whether the navbar should be displayed at the top of the page with - normal scrolling behavior ("static-top"), pinned at the top ("fixed-top"), or - pinned at the bottom ("fixed-bottom"). Note that using "fixed-top" or - "fixed-bottom" will cause the navbar to overlay your body content, unless you - add padding (e.g., ``tags.style("body {padding-top: 70px;}")``). - header - UI to display above the selected content. - footer - UI to display below the selected content. - bg - Background color of the navbar (a CSS color). - inverse - Either ``True`` for a light text color or ``False`` for a dark text color. - collapsible - ``True`` to automatically collapse the navigation elements into an expandable menu on mobile devices or narrow window widths. - fluid - ``True`` to use fluid layout; ``False`` to use fixed layout. - - See Also - ------- - * ~shiny.ui.page_navbar - * ~shiny.ui.nav - * ~shiny.ui.nav_menu - * ~shiny.ui.nav_control - * ~shiny.ui.nav_spacer - * ~shiny.ui.navset_tab - * ~shiny.ui.navset_pill - * ~shiny.experimental.ui.navset_card_tab - * ~shiny.experimental.ui.navset_card_pill - * ~shiny.ui.navset_hidden - - Example - ------- - See :func:`~shiny.ui.nav`. - """ - - # If args contains any lists, flatten them into args. - new_args: Sequence[NavSetArg | MetadataNode] = [] - for arg in args: - if isinstance(arg, (list, tuple)): - new_args.extend(arg) - else: - new_args.append(cast(NavSetArg, arg)) - - return NavSetBar( - *new_args, - ul_class="nav navbar-nav", - id=resolve_id_or_none(id), - selected=selected, - sidebar=sidebar, - fillable=fillable, - gap=gap, - padding=padding, - title=title, - position=position, - header=header, - footer=footer, - bg=bg, - inverse=inverse, - collapsible=collapsible, - fluid=fluid, - ) - - -# ----------------------------------------------------------------------------- -# Utilities for rendering navs -# -----------------------------------------------------------------------------\ -def _render_navset( - *items: NavSetArg | MetadataNode, - ul_class: str, - id: Optional[str], - selected: Optional[str], - context: dict[str, Any], -) -> tuple[Tag, Tag]: - tabsetid = private_random_int(1000, 10000) - - # Separate MetadataNodes from NavSetArgs. - metadata_args = [x for x in items if isinstance(x, MetadataNode)] - navset_args = [x for x in items if not isinstance(x, MetadataNode)] - - # If the user hasn't provided a selected value, use the first one - if selected is None: - for x in navset_args: - selected = x.get_value() - if selected is not None: - break - - ul_tag = tags.ul( - bootstrap_deps(), - metadata_args, - class_=ul_class, - id=id, - data_tabsetid=tabsetid, - ) - div_tag = div(class_="tab-content", data_tabsetid=tabsetid) - for i, x in enumerate(navset_args): - nav, contents = x.resolve( - selected, {**context, "tabsetid": tabsetid, "index": i} - ) - ul_tag.append(nav) - div_tag.append(contents) - - return ul_tag, div_tag - - -# # Card definition was gutted for bslib version. -# # * Bootstrap deps are not added - -# def card(*args: TagChild, header: TagChild = None, footer: TagChild = None) -> Tag: -# if header: -# header = div(header, class_="card-header") -# if footer: -# footer = div(footer, class_="card-footer") - -# return div( -# header, -# div(*args, class_="card-body"), -# footer, -# bootstrap_deps(), -# class_="card", -# ) diff --git a/shiny/experimental/ui/_output.py b/shiny/experimental/ui/_output.py deleted file mode 100644 index a83f94b3e..000000000 --- a/shiny/experimental/ui/_output.py +++ /dev/null @@ -1,280 +0,0 @@ -# !!`shiny.experimental`!! This file is a direct copy to `shiny/_output.py`. Functions that were not altered were removed. Inserted code as been marked with `NEW`&`/NEW` -# Related: https://github.com/rstudio/shiny/pull/3715 - -from __future__ import annotations - -from typing import Optional - -from htmltools import Tag, TagAttrValue, TagFunction, css, div, tags - -from ..._namespaces import resolve_id -from ...types import MISSING, MISSING_TYPE -from ...ui._plot_output_opts import ( - BrushOpts, - ClickOpts, - DblClickOpts, - HoverOpts, - brush_opts, - click_opts, - dblclick_opts, - format_opt_names, - hover_opts, -) -from ._fill import as_fill_item, as_fillable_container - - -# @add_example() -def output_plot( - id: str, - width: str = "100%", - height: str = "400px", - *, - inline: bool = False, - click: bool | ClickOpts = False, - dblclick: bool | DblClickOpts = False, - hover: bool | HoverOpts = False, - brush: bool | BrushOpts = False, - # NEW - fill: bool | MISSING_TYPE = MISSING, - # /NEW -) -> Tag: - """ - Create a output container for a static plot. - - Place a :func:`~shiny.render.plot` result in the user interface. See - :func:`~shiny.render.plot` for more details on what types of plots are supported. - - Parameters - ---------- - id - An output id. - width - The CSS width, e.g. '400px', or '100%'. - height - The CSS height, e.g. '100%' or '600px'. - inline - If ``True``, the result is displayed inline. - click - This can be a boolean or an object created by :func:`~shiny.ui.click_opts`. The - default is `False`, but if you use `True` (or equivalently, `click_opts()`), the - plot will send coordinates to the server whenever it is clicked, and the value - will be accessible via `input.xx_click()`, where `xx` is replaced with the ID of - this plot. The input value will be a dictionary with `x` and `y` elements - indicating the mouse position. - dblclick - This is just like the `click` parameter, but for double-click events. - hover - Similar to the `click` argument, this can be a boolean or an object created by - :func:`~shiny.ui.hover_opts`. The default is `False`, but if you use `True` (or - equivalently, `hover_opts()`), the plot will send coordinates to the server - whenever it is clicked, and the value will be accessible via `input.xx_hover()`, - where `xx` is replaced with the ID of this plot. The input value will be a - dictionary with `x` and `y` elements indicating the mouse position. To control - the hover time or hover delay type, use :func:`~shiny.ui.hover_opts`. - brush - Similar to the `click` argument, this can be a boolean or an object created by - :func:`~shiny.ui.brush_opts`. The default is `False`, but if you use `True` (or - equivalently, `brush_opts()`), the plot will allow the user to "brush" in the - plotting area, and will send information about the brushed area to the server, - and the value will be accessible via `input.plot_brush()`. Brushing means that - the user will be able to draw a rectangle in the plotting area and drag it - around. The value will be a named list with `xmin`, `xmax`, `ymin`, and `ymax` - elements indicating the brush area. To control the brush behavior, use - :func:`~shiny.ui.brush_opts`. Multiple `output_image`/`output_plot` calls may - share the same `id` value; brushing one image or plot will cause any other - brushes with the same `id` to disappear. - - Returns - ------- - : - A UI element - - See Also - ------- - * :func:`~shiny.render.plot` - * :func:`~shiny.ui.output_image` - """ - - # NEW - if isinstance(fill, MISSING_TYPE): - fill = not inline - # /NEW - - res = output_image( - id=id, - width=width, - height=height, - inline=inline, - click=click, - dblclick=dblclick, - hover=hover, - brush=brush, - # NEW - fill=fill, - # /NEW - ) - res.add_class("shiny-plot-output") - return res - - -# @add_example() -def output_image( - id: str, - width: str = "100%", - height: str = "400px", - *, - inline: bool = False, - click: bool | ClickOpts = False, - dblclick: bool | DblClickOpts = False, - hover: bool | HoverOpts = False, - brush: bool | BrushOpts = False, - # NEW - fill: bool = False, - # /NEW -) -> Tag: - """ - Create a output container for a static image. - - Parameters - ---------- - id - An output id. - width - The CSS width, e.g. '400px', or '100%'. - height - The CSS height, e.g. '100%' or '600px'. - inline - If ``True``, the result is displayed inline. - click - This can be a boolean or an object created by :func:`~shiny.ui.click_opts`. The - default is `False`, but if you use `True` (or equivalently, `click_opts()`), the - plot will send coordinates to the server whenever it is clicked, and the value - will be accessible via `input.xx_click()`, where `xx` is replaced with the ID of - this plot. The input value will be a dictionary with `x` and `y` elements - indicating the mouse position. - dblclick - This is just like the `click` parameter, but for double-click events. - hover - Similar to the `click` argument, this can be a boolean or an object created by - :func:`~shiny.ui.hover_opts`. The default is `False`, but if you use `True` (or - equivalently, `hover_opts()`), the plot will send coordinates to the server - whenever it is clicked, and the value will be accessible via `input.xx_hover()`, - where `xx` is replaced with the ID of this plot. The input value will be a - dictionary with `x` and `y` elements indicating the mouse position. To control - the hover time or hover delay type, use :func:`~shiny.ui.hover_opts`. - brush - Similar to the `click` argument, this can be a boolean or an object created by - :func:`~shiny.ui.brush_opts`. The default is `False`, but if you use `True` (or - equivalently, `brush_opts()`), the plot will allow the user to "brush" in the - plotting area, and will send information about the brushed area to the server, - and the value will be accessible via `input.plot_brush()`. Brushing means that - the user will be able to draw a rectangle in the plotting area and drag it - around. The value will be a named list with `xmin`, `xmax`, `ymin`, and `ymax` - elements indicating the brush area. To control the brush behavior, use - :func:`~shiny.ui.brush_opts`. Multiple `output_image`/`output_plot` calls may - share the same `id` value; brushing one image or plot will cause any other - brushes with the same `id` to disappear. - - Returns - ------- - : - A UI element - - See Also - ------- - ~shiny.render.image - ~shiny.ui.output_plot - """ - func = tags.span if inline else div - style = None if inline else css(width=width, height=height) - - args: dict[str, str] = dict() - - id_resolved = resolve_id(id) - - if click is not False: - if click is True: - click = click_opts() - click["id"] = id_resolved + "_click" - args.update(**format_opt_names(click, "click")) - - if dblclick is not False: - if dblclick is True: - dblclick = dblclick_opts() - dblclick["id"] = id_resolved + "_dblclick" - args.update(**format_opt_names(dblclick, "dblclick")) - - if hover is not False: - if hover is True: - hover = hover_opts() - hover["id"] = id_resolved + "_hover" - args.update(**format_opt_names(hover, "hover")) - - if brush is not False: - if brush is True: - brush = brush_opts() - brush["id"] = id_resolved + "_brush" - args.update(**format_opt_names(brush, "brush")) - - container = func( - id=id_resolved, - class_="shiny-image-output", - style=style, - **args, - ) - if fill: - container = as_fill_item(container) - - return container - - -# @add_example() -def output_ui( - id: str, - inline: bool = False, - container: Optional[TagFunction] = None, - # NEW - fill: bool = False, - fillable: bool = False, - # /NEW - **kwargs: TagAttrValue, -) -> Tag: - """ - Create a output container for a UI (i.e., HTML) element. - - Parameters - ---------- - id - An output id. - inline - If ``True``, the result is displayed inline - container - A Callable that returns the output container. - kwargs - Attributes to be applied to the output container. - - Returns - ------- - : - A UI element - - See Also - ------- - ~shiny.render.ui - ~shiny.ui.output_text - """ - - if not container: - container = tags.span if inline else tags.div - res = container( - {"class": "shiny-html-output"}, - id=resolve_id(id), - **kwargs, - ) - - if fillable: - res = as_fillable_container(res) - if fill: - res = as_fill_item(res) - - return res diff --git a/shiny/experimental/ui/_page.py b/shiny/experimental/ui/_page.py deleted file mode 100644 index 304850b4a..000000000 --- a/shiny/experimental/ui/_page.py +++ /dev/null @@ -1,292 +0,0 @@ -from __future__ import annotations - -from typing import Literal, Optional, Sequence - -from htmltools import ( - MetadataNode, - Tag, - TagAttrs, - TagAttrValue, - TagChild, - TagList, - css, - head_content, - tags, -) - -from ...types import MISSING, MISSING_TYPE, NavSetArg -from ...ui._page import page_bootstrap -from ...ui._utils import get_window_title -from ._css_unit import CssUnit, as_css_padding, as_css_unit -from ._fill import as_fillable_container -from ._htmldeps import page_fillable_dependency, page_sidebar_dependency -from ._navs import navset_bar -from ._sidebar import Sidebar, layout_sidebar -from ._utils import consolidate_attrs - - -def page_sidebar( - sidebar: Sidebar | TagChild | TagAttrs, - *args: TagChild | TagAttrs, - title: Optional[str | Tag | TagList] = None, - fillable: bool = True, - fillable_mobile: bool = False, - window_title: str | MISSING_TYPE = MISSING, - lang: Optional[str] = None, - **kwargs: TagAttrValue, -) -> Tag: - """ - Create a page with a sidebar and a title. - - Parameters - ---------- - sidebar - Content to display in the sidebar. - args - UI elements. - title - A title to display at the top of the page. - fillable - Whether or not the main content area should be considered a fillable - (i.e., flexbox) container. - fillable_mobile - Whether or not ``fillable`` should apply on mobile devices. - window_title - The browser's window title (defaults to the host URL of the page). Can also be - set as a side effect via :func:`~shiny.ui.panel_title`. - lang - ISO 639-1 language code for the HTML page, such as ``"en"`` or ``"ko"``. This - will be used as the lang in the ```` tag, as in ````. The - default, `None`, results in an empty string. - kwargs - Additional attributes passed to :func:`~shiny.ui.layout_sidebar`. - - Returns - ------- - : - A UI element. - """ - - if isinstance(title, str): - title = tags.h1(title, class_="bslib-page-title") - - attrs, children = consolidate_attrs(*args, **kwargs) - - return page_fillable( - title, - layout_sidebar( - sidebar, - *children, - attrs, - fillable=fillable, - border=False, - border_radius=False, - ), - get_window_title(title, window_title=window_title), - page_sidebar_dependency(), - padding=0, - gap=0, - lang=lang, - fillable_mobile=fillable_mobile, - ) - - -def page_navbar( - *args: NavSetArg | MetadataNode | Sequence[MetadataNode], - title: Optional[str | Tag | TagList] = None, - id: Optional[str] = None, - selected: Optional[str] = None, - sidebar: Optional[Sidebar] = None, - # Only page_navbar gets enhancedtreatement for `fillable` - # If an `*args`'s `data-value` attr string is in `fillable`, then the component is fillable - fillable: bool | list[str] = True, - fillable_mobile: bool = False, - gap: Optional[CssUnit] = None, - padding: Optional[CssUnit | list[CssUnit]] = None, - position: Literal["static-top", "fixed-top", "fixed-bottom"] = "static-top", - header: Optional[TagChild] = None, - footer: Optional[TagChild] = None, - bg: Optional[str] = None, - inverse: bool = True, - collapsible: bool = True, - fluid: bool = True, - window_title: str | MISSING_TYPE = MISSING, - lang: Optional[str] = None, -) -> Tag: - """ - Create a page with a navbar and a title. - - Parameters - ---------- - args - UI elements. - title - The browser window title (defaults to the host URL of the page). Can also be set - as a side effect via :func:`~shiny.ui.panel_title`. - id - If provided, will create an input value that holds the currently selected nav - item. - selected - Choose a particular nav item to select by default value (should match it's - ``value``). - sidebar - A :func:`~shiny.experimental.ui.sidebar` component to display on every page. - fillable - Whether or not the main content area should be considered a fillable - (i.e., flexbox) container. - fillable_mobile - Whether or not ``fillable`` should apply on mobile devices. - position - Determines whether the navbar should be displayed at the top of the page with - normal scrolling behavior ("static-top"), pinned at the top ("fixed-top"), or - pinned at the bottom ("fixed-bottom"). Note that using "fixed-top" or - "fixed-bottom" will cause the navbar to overlay your body content, unless you - add padding (e.g., ``tags.style("body {padding-top: 70px;}")``). - header - UI to display above the selected content. - footer - UI to display below the selected content. - bg - Background color of the navbar (a CSS color). - inverse - Either ``True`` for a light text color or ``False`` for a dark text color. - collapsible - ``True`` to automatically collapse the elements into an expandable menu on mobile devices or narrow window widths. - fluid - ``True`` to use fluid layout; ``False`` to use fixed layout. - window_title - The browser's window title (defaults to the host URL of the page). Can also be - set as a side effect via :func:`~shiny.ui.panel_title`. - lang - ISO 639-1 language code for the HTML page, such as ``"en"`` or ``"ko"``. This - will be used as the lang in the ```` tag, as in ````. The - default, `None`, results in an empty string. - - Returns - ------- - : - A UI element. - - See Also - ------- - * :func:`~shiny.ui.nav` - * :func:`~shiny.ui.nav_menu` - * :func:`~shiny.experimental.ui.navset_bar` - * :func:`~shiny.ui.page_fluid` - - Example - ------- - See :func:`~shiny.ui.nav`. - """ - if sidebar is not None and not isinstance(sidebar, Sidebar): - raise TypeError( - "`sidebar=` is not a `Sidebar` instance. Use `ui.sidebar(...)` to create one." - ) - - # If a sidebar is provided, we want the layout_sidebar(fill = TRUE) component - # (which is a sibling of the