Skip to content

Commit b6c5944

Browse files
authored
Merge branch 'main' into decouple-select-selectize
2 parents ae93148 + 2740257 commit b6c5944

File tree

10 files changed

+150
-63
lines changed

10 files changed

+150
-63
lines changed

CHANGELOG.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [UNRELEASED]
99

10-
### Changes
10+
### New features
11+
12+
### Improvements
13+
14+
* `selectize`, `remove_button`, and `options` parameters of `ui.input_select()` have been deprecated; use `ui.input_selectize()` instead. (Thanks, @ErdaradunGaztea!) (#1947)
15+
16+
* Improved the styling and readability of markdown tables rendered by `ui.Chat()` and `ui.MarkdownStream()`. (#1973)
17+
18+
### Bug fixes
1119

12-
* `selectize`, `remove_button`, and `options` parameters of `ui.input_select()` have been deprecated; use `ui.input_selectize()` instead. (#1947)
1320

1421
## [1.4.0] - 2025-04-08
1522

js/markdown-stream/markdown-stream.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,28 +45,38 @@ const SVG_DOT = createSVGIcon(
4545
`<svg width="12" height="12" xmlns="http://www.w3.org/2000/svg" class="${SVG_DOT_CLASS}" style="margin-left:.25em;margin-top:-.25em"><circle cx="6" cy="6" r="6"/></svg>`
4646
);
4747

48-
// For rendering chat output, we use typical Markdown behavior of passing through raw
49-
// HTML (albeit sanitizing afterwards).
50-
//
51-
// For echoing chat input, we escape HTML. This is not for security reasons but just
52-
// because it's confusing if the user is using tag-like syntax to demarcate parts of
53-
// their prompt for other reasons (like <User>/<Assistant> for providing examples to the
54-
// chat model), and those tags simply vanish.
55-
const rendererEscapeHTML = new Renderer();
56-
rendererEscapeHTML.html = (html: string) =>
48+
// 'markdown' renderer (for assistant messages)
49+
const markdownRenderer = new Renderer();
50+
51+
// Add some basic Bootstrap styling to markdown tables
52+
markdownRenderer.table = (header: string, body: string) => {
53+
return `<table class="table table-striped table-bordered">
54+
<thead>${header}</thead>
55+
<tbody>${body}</tbody>
56+
</table>`;
57+
};
58+
59+
// 'semi-markdown' renderer (for user messages)
60+
const semiMarkdownRenderer = new Renderer();
61+
62+
// Escape HTML, not for security reasons, but just because it's confusing if the user is
63+
// using tag-like syntax to demarcate parts of their prompt for other reasons (like
64+
// <User>/<Assistant> for providing examples to the model), and those tags vanish.
65+
semiMarkdownRenderer.html = (html: string) =>
5766
html
5867
.replaceAll("&", "&amp;")
5968
.replaceAll("<", "&lt;")
6069
.replaceAll(">", "&gt;")
6170
.replaceAll('"', "&quot;")
6271
.replaceAll("'", "&#039;");
63-
const markedEscapeOpts = { renderer: rendererEscapeHTML };
6472

6573
function contentToHTML(content: string, content_type: ContentType) {
6674
if (content_type === "markdown") {
67-
return unsafeHTML(sanitizeHTML(parse(content) as string));
75+
const html = parse(content, { renderer: markdownRenderer });
76+
return unsafeHTML(sanitizeHTML(html as string));
6877
} else if (content_type === "semi-markdown") {
69-
return unsafeHTML(sanitizeHTML(parse(content, markedEscapeOpts) as string));
78+
const html = parse(content, { renderer: semiMarkdownRenderer });
79+
return unsafeHTML(sanitizeHTML(html as string));
7080
} else if (content_type === "html") {
7181
return unsafeHTML(sanitizeHTML(content));
7282
} else if (content_type === "text") {

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ dev = [
100100
"isort>=5.10.1",
101101
"libsass>=0.23.0",
102102
"brand_yml>=0.1.0",
103-
"pyright>=1.1.398",
103+
"pyright==1.1.398",
104104
"pre-commit>=2.15.0",
105105
"wheel",
106106
"matplotlib",

shiny/api-examples/Module/app-core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ def counter_ui(label: str = "Increment counter") -> ui.TagChild:
99
return ui.card(
1010
ui.h2("This is " + label),
1111
ui.input_action_button(id="button", label=label),
12-
ui.output_text_verbatim(id="out"),
12+
ui.output_text(id="out"),
1313
)
1414

1515

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from shiny import reactive
2+
from shiny.express import module, render, ui
3+
4+
5+
# ============================================================
6+
# Counter module
7+
# ============================================================
8+
@module
9+
def counter(input, output, session, label, starting_value: int = 0):
10+
count = reactive.value(starting_value)
11+
with ui.card():
12+
ui.h2(f"This is {label}")
13+
ui.input_action_button("button", f"{label}")
14+
15+
@render.text
16+
def out():
17+
return f"Click count is {count()}"
18+
19+
@reactive.effect
20+
@reactive.event(input.button)
21+
def _():
22+
count.set(count() + 1)
23+
24+
25+
# =============================================================================
26+
# App that uses module
27+
# =============================================================================
28+
counter("counter1", "Counter 1", starting_value=0)
29+
ui.hr()
30+
counter("counter2", "Counter 2", starting_value=0)

shiny/api-examples/express_module/app-express.py

Lines changed: 0 additions & 25 deletions
This file was deleted.

shiny/express/_module.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
__all__ = ("module",)
1616

1717

18-
@add_example(ex_dir="../api-examples/express_module")
18+
@add_example(ex_dir="../api-examples/Module")
1919
def module(
2020
fn: Callable[Concatenate[Inputs, Outputs, Session, P], R],
2121
) -> Callable[Concatenate[Id, P], R]:

shiny/module.py

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from typing import TYPE_CHECKING, Callable, TypeVar
66

7-
from ._docstring import no_example
7+
from ._docstring import add_example
88
from ._namespaces import (
99
Id,
1010
ResolvedId,
@@ -24,25 +24,87 @@
2424
_: Id # type: ignore
2525

2626

27-
@no_example()
27+
@add_example(ex_dir="api-examples/Module")
2828
def ui(fn: Callable[P, R]) -> Callable[Concatenate[str, P], R]:
29+
"""
30+
Decorator for defining a Shiny module UI function.
31+
32+
This decorator allows you to write the UI portion of a Shiny module.
33+
When your decorated `ui` function is called with an `id`,
34+
the UI elements defined within will automatically be namespaced using that `id`.
35+
This enables reuse of UI components and consistent input/output handling
36+
when paired with a :func:`shiny.module.server` function.
37+
38+
Parameters
39+
----------
40+
fn
41+
A function that returns a Shiny UI element or layout (e.g., a `ui.panel_*` component).
42+
This function should **not** accept an `id` parameter itself; the decorator injects it.
43+
44+
Returns
45+
-------
46+
:
47+
A function that takes a `str` `id` as its first argument, followed by any additional
48+
parameters accepted by `fn`. When called, it returns UI elements with input/output
49+
IDs automatically namespaced using the provided module `id`.
50+
51+
See Also
52+
--------
53+
* Shiny Modules documentation: <https://shiny.posit.co/py/docs/modules.html>
54+
* :func:`shiny.module.server`
55+
"""
56+
2957
def wrapper(id: Id, *args: P.args, **kwargs: P.kwargs) -> R:
3058
with namespace_context(id):
3159
return fn(*args, **kwargs)
3260

3361
return wrapper
3462

3563

36-
@no_example()
64+
@add_example(ex_dir="api-examples/Module")
3765
def server(
3866
fn: Callable[Concatenate[Inputs, Outputs, Session, P], R],
3967
) -> Callable[Concatenate[str, P], R]:
68+
"""
69+
Decorator for defining a Shiny module server function.
70+
71+
This decorator is used to encapsulate the server logic for a Shiny module.
72+
It automatically creates a namespaced child `Session` using the provided module `id`,
73+
and passes the appropriate `input`, `output`, and `session` objects to your server function.
74+
75+
This ensures that the server logic is scoped correctly for each module instance and
76+
allows for reuse of logic across multiple instances of the same module.
77+
78+
Parameters
79+
----------
80+
fn
81+
A server function that takes `input`, `output`, and `session` as its first
82+
three arguments, followed by any additional arguments defined by the user.
83+
84+
Returns
85+
-------
86+
:
87+
A function that takes a module `id` (as a string) as its first argument,
88+
followed by any arguments expected by `fn`. When called, it will register
89+
the module's server logic in a namespaced context.
90+
91+
See Also
92+
--------
93+
* Shiny Modules documentation: <https://shiny.posit.co/py/docs/modules.html>
94+
* :func:`shiny.module.ui`
95+
"""
4096
from .session import require_active_session, session_context
4197

4298
def wrapper(id: Id, *args: P.args, **kwargs: P.kwargs) -> R:
4399
sess = require_active_session(None)
44100
child_sess = sess.make_scope(id)
45101
with session_context(child_sess):
46-
return fn(child_sess.input, child_sess.output, child_sess, *args, **kwargs)
102+
return fn(
103+
child_sess.input,
104+
child_sess.output,
105+
child_sess,
106+
*args,
107+
**kwargs,
108+
)
47109

48110
return wrapper

shiny/www/py-shiny/markdown-stream/markdown-stream.js

Lines changed: 17 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

shiny/www/py-shiny/markdown-stream/markdown-stream.js.map

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)