Skip to content

Commit b7a0a82

Browse files
committed
Merge branch 'main' into update_row_selection
* main: Update `@render.data_frame` renderer to work properly with fill/fillable (#1126) Don't error on malformed date input (#1139) fix(nav_panel): Add `.bslib-gap-spacing` if nav panel is fillable (#1154) feat: Add `ui.input_dark_mode()` (#1149) chore(make): Add `make docs` and `make docs-preview` (#1150) Sync with latest bslib sidebar and card changes (#1129) docs(input_task_button): Add to reference index, also update methods for action button/link (#1151) chore(make): Update check and check-fix to replicate CI (#1148)
2 parents 8d3e73a + 2c1c32c commit b7a0a82

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1185
-6609
lines changed

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
## [UNRELEASED] - YYYY-MM-DD
1010

11+
### Breaking Changes
12+
13+
* Page-level sidebars used in `ui.page_sidebar()` and `ui.page_navbar()` will now default to being initially open but collapsible on desktop devices and always open on mobile devices. You can adjust this default choice by setting `ui.sidebar(open=)`. (#1129)
14+
15+
* `ui.sidebar()` is now a thin wrapper for the internal `ui.Sidebar` class. The `ui.Sidebar` class has been updated to store the sidebar's contents and settings and to delay rendering until the sidebar HTML is actually used. Because most users call `ui.sidebar()` instead of using the class directly, this change is not expected to affect many apps. (#1129)
16+
17+
### New features
18+
19+
* Added `ui.input_dark_mode()`, a toggle switch that allows users to switch between light and dark mode. By default, when `ui.input_dark_mode()` is added to an app, the app's color mode follows the users's system preferences, unless the app author sets the `mode` argument. When `ui.input_dark_mode(id=)` is set, the color mode is reported to the server, and server-side color mode updating is possible using `ui.update_dark_mode()`. (#1149)
20+
21+
* `ui.sidebar(open=)` now accepts a dictionary with keys `desktop` and `mobile`, allowing you to independently control the initial state of the sidebar at desktop and mobile screen sizes. (#1129)
22+
23+
### Other changes
24+
25+
* `@render.data_frame` now properly fills its container by default. (#1126)
26+
27+
* We improved the accessibility of the full screen toggle button in cards created with `ui.card(full_screen=True)`. Full-screen cards are now also supported on mobile devices. (#1129)
28+
29+
* When entering and exiting full-screen card mode, Shiny now emits a client-side custom `bslib.card` event that JavaScript-oriented users can use to react to the full screen state change. (#1129)
30+
31+
* The sidebar's collapse toggle now has a high `z-index` value to ensure it always appears above elements in the main content area of `ui.layout_sidebar()`. The sidebar overlay also now receives the same high `z-index` on mobile layouts. (#1129)
32+
1133
### Bug fixes
1234

1335
* Fixed `input_task_button` not working in a Shiny module. (#1108)
1436
* Fixed several issues with `page_navbar()` styling. (#1124)
1537
* Fixed `Renderer.output_id` to not contain the module namespace prefix, only the output id. (#1130)
38+
* Fixed gap-driven spacing between children in fillable `nav_panel()` containers. (#1152)
39+
* Fixed #1138: An empty value in a date or date range input would cause an error; now it is treated as `None`. (#1139)
1640

1741
### Other changes
1842

Makefile

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: help clean clean-test clean-pyc clean-build help lint test playwright-shiny playwright-examples playwright-deploys install-trcli install-playwright
1+
.PHONY: help clean% check% format% docs% lint test pyright playwright% install% testrail% coverage release
22
.DEFAULT_GOAL := help
33

44
define BROWSER_PYSCRIPT
@@ -70,28 +70,48 @@ typings/matplotlib/__init__.pyi:
7070

7171
pyright-typings: typings/appdirs typings/folium typings/uvicorn typings/seaborn typings/matplotlib/__init__.pyi
7272

73-
pyright: pyright-typings ## type check with pyright
74-
pyright
73+
check: check-format check-lint check-types check-tests ## check code, style, types, and test (basic CI)
74+
check-fix: format check-lint check-types check-tests ## check and format code, style, types, and test
75+
check-format: check-black check-isort
76+
check-types: check-pyright
77+
check-tests: check-pytest
7578

76-
lint: ## check style with flake8
77-
echo "Checking style with flake8"
79+
check-lint:
80+
@echo "-------- Checking style with flake8 ---------"
7881
flake8 --show-source .
82+
check-black:
83+
@echo "-------- Checking code with black -----------"
84+
black --check .
85+
check-isort:
86+
@echo "-------- Sorting imports with isort ---------"
87+
isort --check-only --diff .
88+
check-pyright: typings/uvicorn typings/matplotlib/__init__.pyi typings/seaborn
89+
@echo "-------- Checking types with pyright --------"
90+
pyright
91+
check-pytest:
92+
@echo "-------- Running tests with pytest ----------"
93+
python3 tests/pytest/asyncio_prevent.py
94+
pytest
7995

80-
format: ## format code with black and isort
81-
echo "Formatting code with black"
96+
pyright: check-types ## check types with pyright
97+
lint: check-lint ## check style with flake8
98+
test: check-tests ## check tests quickly with the default Python
99+
100+
format: format-black format-isort ## format code with black and isort
101+
format-black:
102+
@echo "-------- Formatting code with black --------"
82103
black .
83-
echo "Sorting imports with isort"
104+
format-isort:
105+
@echo "-------- Sorting imports with isort --------"
84106
isort .
85107

86-
check: ## check code quality with black and isort
87-
echo "Checking code with black"
88-
black --check .
89-
echo "Sorting imports with isort"
90-
isort --check-only --diff .
108+
docs: ## docs: build docs with quartodoc
109+
@echo "-------- Building docs with quartodoc --------"
110+
@cd docs && make quartodoc
91111

92-
test: ## run tests quickly with the default Python
93-
python3 tests/pytest/asyncio_prevent.py
94-
pytest
112+
docs-preview: ## docs: preview docs in browser
113+
@echo "-------- Previewing docs in browser --------"
114+
@cd docs && make serve
95115

96116
# Default `SUB_FILE` to empty
97117
SUB_FILE:=

docs/_quartodoc-core.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ quartodoc:
4444
- ui.input_select
4545
- ui.input_selectize
4646
- ui.input_slider
47+
- ui.input_dark_mode
4748
- ui.input_date
4849
- ui.input_date_range
4950
- ui.input_checkbox
@@ -56,6 +57,7 @@ quartodoc:
5657
- ui.input_password
5758
- ui.input_action_button
5859
- ui.input_action_link
60+
- ui.input_task_button
5961
- title: Value boxes
6062
desc: Prominently display a value and label in a box that can be expanded to show more information.
6163
contents:
@@ -122,6 +124,7 @@ quartodoc:
122124
dynamic: true
123125
- name: ui.update_slider
124126
dynamic: true
127+
- ui.update_dark_mode
125128
- ui.update_date
126129
- name: ui.update_date_range
127130
dynamic: true
@@ -140,6 +143,9 @@ quartodoc:
140143
dynamic: "shiny.ui.update_text"
141144
- name: ui.update_navs
142145
dynamic: true
146+
- ui.update_action_button
147+
- ui.update_action_link
148+
- ui.update_task_button
143149
- title: Update UI Layouts
144150
desc: ""
145151
contents:

docs/_quartodoc-express.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ quartodoc:
1616
- express.ui.input_select
1717
- express.ui.input_selectize
1818
- express.ui.input_slider
19+
- express.ui.input_dark_mode
1920
- express.ui.input_date
2021
- express.ui.input_date_range
2122
- express.ui.input_checkbox
@@ -28,6 +29,7 @@ quartodoc:
2829
- express.ui.input_password
2930
- express.ui.input_action_button
3031
- express.ui.input_action_link
32+
- express.ui.input_task_button
3133
- title: Output components
3234
desc: Reactively render output.
3335
contents:
@@ -103,6 +105,7 @@ quartodoc:
103105
dynamic: true
104106
- name: express.ui.update_slider
105107
dynamic: true
108+
- express.ui.update_dark_mode
106109
- ui.update_date
107110
- name: express.ui.update_date_range
108111
dynamic: true
@@ -121,6 +124,9 @@ quartodoc:
121124
dynamic: "shiny.ui.update_text"
122125
- name: express.ui.update_navs
123126
dynamic: true
127+
- express.ui.update_action_button
128+
- express.ui.update_action_link
129+
- express.ui.update_task_button
124130
- title: Update UI Layouts
125131
desc: ""
126132
contents:

js/dataframe/index.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ interface ShinyDataGridProps<TIndex> {
118118
const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = (props) => {
119119
const { id, data, bgcolor } = props;
120120
const { columns, type_hints, data: rowData } = data;
121-
const { width, height, filters: withFilters } = data.options;
121+
const { width, height, fill, filters: withFilters } = data.options;
122122

123123
const containerRef = useRef<HTMLDivElement>(null);
124124
const theadRef = useRef<HTMLTableSectionElement>(null);
@@ -655,10 +655,13 @@ const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = (props) => {
655655

656656
const headerRowCount = table.getHeaderGroups().length;
657657

658-
const scrollingClass =
659-
containerRef.current?.scrollHeight > containerRef.current?.clientHeight
660-
? "scrolling"
661-
: "";
658+
// Assume we're scrolling until proven otherwise
659+
let scrollingClass = "scrolling";
660+
const scrollHeight = containerRef.current?.scrollHeight;
661+
const clientHeight = containerRef.current?.clientHeight;
662+
if (scrollHeight && clientHeight && scrollHeight <= clientHeight) {
663+
scrollingClass = "";
664+
}
662665

663666
const makeHeaderKeyDown =
664667
(column: Column<unknown[], unknown>) => (event: React.KeyboardEvent) => {
@@ -669,12 +672,17 @@ const ShinyDataGrid: FC<ShinyDataGridProps<unknown>> = (props) => {
669672

670673
const measureEl = useVirtualizerMeasureWorkaround(rowVirtualizer);
671674

675+
let className = `shiny-data-grid ${containerClass} ${scrollingClass}`;
676+
if (fill) {
677+
className += " html-fill-item";
678+
}
679+
672680
return (
673681
<>
674682
<div
675-
className={`shiny-data-grid ${containerClass} ${scrollingClass}`}
683+
className={className}
676684
ref={containerRef}
677-
style={{ width, maxHeight: height, overflow: "auto" }}
685+
style={{ width, height, overflow: "auto" }}
678686
>
679687
<table
680688
className={tableClass + (withFilters ? " filtering" : "")}

js/dataframe/styles.scss

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@
3838

3939
.shiny-data-grid {
4040
max-width: 100%;
41+
height: 500px;
42+
&:not(.scrolling) {
43+
height: auto;
44+
}
4145

4246
> table {
4347
border-collapse: separate;
@@ -198,3 +202,20 @@
198202
}
199203
}
200204
}
205+
206+
/* Center the table when inside of a card */
207+
.card-body .shiny-data-grid {
208+
margin-left: auto;
209+
margin-right: auto;
210+
}
211+
212+
213+
/* When .shiny-data-grid is not scrolling, the containers shouldn't flex */
214+
shiny-data-frame {
215+
&:has(> div > .shiny-data-grid:not(.scrolling)) {
216+
flex: 0 0 auto;
217+
}
218+
> div:has(> .shiny-data-grid:not(.scrolling)) {
219+
flex: 0 0 auto;
220+
}
221+
}

js/dataframe/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface DataGridOptions {
1717
filters?: boolean;
1818
width?: string;
1919
height?: string;
20+
fill?: boolean;
2021
}
2122

2223
export interface PandasData<TIndex> {

scripts/htmlDependencies.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ message("Installing GitHub packages: bslib, shiny, htmltools")
1212
withr::local_temp_libpaths()
1313
ignore <- capture.output({
1414
pak::pkg_install(c(
15-
"rstudio/bslib@py-shiny-v0.7.0",
15+
"rstudio/bslib@main",
1616
"rstudio/shiny@main",
1717
"cran::htmltools"
1818
))

shiny/_namespaces.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ def resolve_id_or_none(id: Id | None) -> ResolvedId | None:
8181

8282

8383
def validate_id(id: str) -> None:
84+
if not isinstance(id, str):
85+
raise ValueError("`id` must be a single string")
86+
if id == "":
87+
raise ValueError("`id` must be a non-empty string")
8488
if not re_valid_id.match(id):
8589
raise ValueError(
8690
f"The string '{id}' is not a valid id; only letters, numbers, and "

shiny/_utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,14 @@ def private_random_int(min: int, max: int) -> str:
199199
return str(random.randint(min, max))
200200

201201

202+
def private_random_id(prefix: str = "", bytes: int = 3) -> str:
203+
if prefix != "" and not prefix.endswith("_"):
204+
prefix += "_"
205+
206+
with private_seed():
207+
return prefix + rand_hex(bytes)
208+
209+
202210
@contextlib.contextmanager
203211
def private_seed() -> Generator[None, None, None]:
204212
state = random.getstate()

0 commit comments

Comments
 (0)