bslibreact 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bslibreact/__init__.py +19 -0
- bslibreact/_catalog.py +493 -0
- bslibreact/_dep.py +73 -0
- bslibreact/_deps.py +103 -0
- bslibreact/_host.py +116 -0
- bslibreact/_version.py +16 -0
- bslibreact/www/bslibreact.js +9 -0
- bslibreact-0.1.0.dist-info/METADATA +77 -0
- bslibreact-0.1.0.dist-info/RECORD +11 -0
- bslibreact-0.1.0.dist-info/WHEEL +4 -0
- bslibreact-0.1.0.dist-info/licenses/LICENSE +21 -0
bslibreact/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Server helpers for bslibreact: React components reproducing bslib and Shiny markup.
|
|
2
|
+
|
|
3
|
+
Provides page dependency discovery so a client bundle can request the same
|
|
4
|
+
CSS and JavaScript a bslib page would attach on its own, and a hosted-input
|
|
5
|
+
helper for widgets rendered server-side and displayed by the client. The
|
|
6
|
+
matching React components live in the ``bslibreact`` npm package.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from ._dep import bslibreact_dep
|
|
10
|
+
from ._deps import page_deps
|
|
11
|
+
from ._host import host_input
|
|
12
|
+
from ._version import __version__
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"__version__",
|
|
16
|
+
"bslibreact_dep",
|
|
17
|
+
"host_input",
|
|
18
|
+
"page_deps",
|
|
19
|
+
]
|
bslibreact/_catalog.py
ADDED
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
"""The markup catalog: sample UI calls and the live shiny markup they produce.
|
|
2
|
+
|
|
3
|
+
This is the Python half of the spec in ``docs/markup-catalog.md``. bslib
|
|
4
|
+
(and py-shiny) are the ground truth for what bslibreact's React components
|
|
5
|
+
should render: instead of committing HTML fixtures that go stale the moment
|
|
6
|
+
shiny's markup changes, this module holds a catalog of *calls* -- Python
|
|
7
|
+
source text -- that get evaluated against whatever version of ``shiny`` is
|
|
8
|
+
actually installed, by :func:`catalog_eval`. ``tools/markup/render_py.py``
|
|
9
|
+
turns the result into the JSON file the JS test suite reads.
|
|
10
|
+
|
|
11
|
+
Sample names are frozen: they must match the R catalog's names wherever
|
|
12
|
+
py-shiny has the component (see ``docs/markup-catalog.md``, "Sample names").
|
|
13
|
+
Renaming or removing one is a deliberate, visible edit -- ``test_catalog.py``
|
|
14
|
+
pins the whole name set as a literal.
|
|
15
|
+
|
|
16
|
+
Six of the R catalog's names have no Python entry, because ``shiny`` alone
|
|
17
|
+
cannot produce them (there is no "extra" third-party kind here the way R's
|
|
18
|
+
DT/reactable/htmlwidgets samples exist):
|
|
19
|
+
|
|
20
|
+
- ``plotOutput_with_spinner`` -- wraps a plot in ``shinycssloaders``.
|
|
21
|
+
- ``DTOutput``, ``DTOutput_no_fill`` -- the ``DT`` package.
|
|
22
|
+
- ``htmlwidget_output`` -- the generic ``htmlwidgets`` output shape.
|
|
23
|
+
Python's analogue lives in the separate ``shinywidgets`` package.
|
|
24
|
+
- ``reactableOutput`` -- the ``reactable`` package.
|
|
25
|
+
- ``icon`` -- R's ``shiny::icon()`` renders Font Awesome markup that ships
|
|
26
|
+
with base R shiny. py-shiny has no built-in icon helper at all (an icon is
|
|
27
|
+
just whatever ``TagChild`` you pass), so there is no ``shiny``-only call
|
|
28
|
+
that stands in for it.
|
|
29
|
+
|
|
30
|
+
``call`` strings are Python source, evaluated by :func:`catalog_eval` in a
|
|
31
|
+
small fixed namespace exposing only ``ui`` (``shiny.ui``). They are fixed
|
|
32
|
+
strings written by this module, never user input, so a plain ``eval()`` is
|
|
33
|
+
fine here -- the empty ``__builtins__`` just keeps the namespace from growing
|
|
34
|
+
by accident as this file evolves.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import hashlib
|
|
40
|
+
from dataclasses import dataclass
|
|
41
|
+
from typing import TYPE_CHECKING, Literal, Sequence
|
|
42
|
+
|
|
43
|
+
from shiny import ui
|
|
44
|
+
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
from htmltools import TagChild
|
|
47
|
+
|
|
48
|
+
SampleKind = Literal["tag", "page"]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class Sample:
|
|
53
|
+
"""One catalog entry: a name, and the call that produces its markup."""
|
|
54
|
+
|
|
55
|
+
name: str
|
|
56
|
+
kind: SampleKind
|
|
57
|
+
group: str
|
|
58
|
+
call: str
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _s(name: str, kind: SampleKind, group: str, call: str) -> Sample:
|
|
62
|
+
return Sample(name=name, kind=kind, group=group, call=call)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# Kept from the original R kit (`tools/kit/dump-markup.R`), same sample
|
|
66
|
+
# names, translated to the equivalent py-shiny call.
|
|
67
|
+
SAMPLES: tuple[Sample, ...] = (
|
|
68
|
+
_s(
|
|
69
|
+
"selectInput",
|
|
70
|
+
"tag",
|
|
71
|
+
"input",
|
|
72
|
+
'ui.input_select("x", "Quick pick", choices=["10", "20", "30"])',
|
|
73
|
+
),
|
|
74
|
+
_s(
|
|
75
|
+
"selectizeInput",
|
|
76
|
+
"tag",
|
|
77
|
+
"input",
|
|
78
|
+
'ui.input_selectize("x", "Genes", choices=["A", "B", "C"], selected="A", '
|
|
79
|
+
"multiple=True)",
|
|
80
|
+
),
|
|
81
|
+
_s(
|
|
82
|
+
"radioButtons",
|
|
83
|
+
"tag",
|
|
84
|
+
"input",
|
|
85
|
+
'ui.input_radio_buttons("x", "Values", choices={"row": "Row z-score", '
|
|
86
|
+
'"none": "Expression"})',
|
|
87
|
+
),
|
|
88
|
+
_s(
|
|
89
|
+
"checkboxInput",
|
|
90
|
+
"tag",
|
|
91
|
+
"input",
|
|
92
|
+
'ui.input_checkbox("x", "Cluster genes", value=True)',
|
|
93
|
+
),
|
|
94
|
+
_s(
|
|
95
|
+
"sliderInput",
|
|
96
|
+
"tag",
|
|
97
|
+
"input",
|
|
98
|
+
'ui.input_slider("x", "Bins", min=1, max=50, value=30)',
|
|
99
|
+
),
|
|
100
|
+
_s("actionButton", "tag", "input", 'ui.input_action_button("x", "Go")'),
|
|
101
|
+
_s("plotOutput", "tag", "output", 'ui.output_plot("x")'),
|
|
102
|
+
_s(
|
|
103
|
+
"card_full_screen",
|
|
104
|
+
"tag",
|
|
105
|
+
"card",
|
|
106
|
+
'ui.card(ui.card_header("Title"), ui.card_body("Body"), full_screen=True)',
|
|
107
|
+
),
|
|
108
|
+
_s(
|
|
109
|
+
"sidebar_layout",
|
|
110
|
+
"page",
|
|
111
|
+
"page",
|
|
112
|
+
'ui.page_sidebar(ui.sidebar(ui.input_select("x", "Pick", choices=["A"]), '
|
|
113
|
+
"width=290), "
|
|
114
|
+
'ui.card(ui.card_header("Main"), ui.card_body("Content")), title="Demo")',
|
|
115
|
+
),
|
|
116
|
+
_s(
|
|
117
|
+
"selectizeInput_server_placeholder",
|
|
118
|
+
"tag",
|
|
119
|
+
"input",
|
|
120
|
+
'ui.input_selectize("x", "Pick genes", choices=[], multiple=True, '
|
|
121
|
+
'options={"placeholder": "type to search..."})',
|
|
122
|
+
),
|
|
123
|
+
_s(
|
|
124
|
+
"textAreaInput",
|
|
125
|
+
"tag",
|
|
126
|
+
"input",
|
|
127
|
+
'ui.input_text_area("x", None, rows=3, placeholder="GENE1, GENE2, GENE3...")',
|
|
128
|
+
),
|
|
129
|
+
_s("textOutput", "tag", "output", 'ui.output_text("x")'),
|
|
130
|
+
_s(
|
|
131
|
+
"verbatimTextOutput",
|
|
132
|
+
"tag",
|
|
133
|
+
"output",
|
|
134
|
+
'ui.output_code("x", placeholder=False)',
|
|
135
|
+
),
|
|
136
|
+
_s(
|
|
137
|
+
"details_summary",
|
|
138
|
+
"tag",
|
|
139
|
+
"misc",
|
|
140
|
+
'ui.tags.details(ui.tags.summary("Paste a list"), '
|
|
141
|
+
'ui.input_text_area("x", None, rows=3))',
|
|
142
|
+
),
|
|
143
|
+
_s(
|
|
144
|
+
"card_bare_children",
|
|
145
|
+
"tag",
|
|
146
|
+
"card",
|
|
147
|
+
'ui.card(ui.card_header("Current selection"), ui.output_text("count"), '
|
|
148
|
+
'ui.output_code("selected", placeholder=False))',
|
|
149
|
+
),
|
|
150
|
+
_s(
|
|
151
|
+
"actionButton_class_override",
|
|
152
|
+
"tag",
|
|
153
|
+
"input",
|
|
154
|
+
'ui.input_action_button("x", "Apply", class_="btn-sm")',
|
|
155
|
+
),
|
|
156
|
+
_s(
|
|
157
|
+
"fileInput",
|
|
158
|
+
"tag",
|
|
159
|
+
"input",
|
|
160
|
+
'ui.input_file("x", "de_results.csv", accept=".csv")',
|
|
161
|
+
),
|
|
162
|
+
_s(
|
|
163
|
+
"helpText_hr",
|
|
164
|
+
"tag",
|
|
165
|
+
"misc",
|
|
166
|
+
'ui.TagList(ui.hr(), ui.help_text("Some help text here."))',
|
|
167
|
+
),
|
|
168
|
+
_s(
|
|
169
|
+
"layout_columns_value_box",
|
|
170
|
+
"tag",
|
|
171
|
+
"layout",
|
|
172
|
+
'ui.layout_columns(ui.value_box("PC1 variance", ui.output_text("pc1"), '
|
|
173
|
+
'theme="primary"), '
|
|
174
|
+
'ui.value_box("PC2 variance", ui.output_text("pc2"), theme="warning"), '
|
|
175
|
+
'ui.value_box("Genes used", ui.output_text("ngenes"), theme="secondary"), '
|
|
176
|
+
"fill=False, col_widths=[4, 4, 4])",
|
|
177
|
+
),
|
|
178
|
+
_s(
|
|
179
|
+
"plotOutput_click",
|
|
180
|
+
"tag",
|
|
181
|
+
"output",
|
|
182
|
+
'ui.output_plot("x", height="560px", click=True)',
|
|
183
|
+
),
|
|
184
|
+
_s(
|
|
185
|
+
"card_footer",
|
|
186
|
+
"tag",
|
|
187
|
+
"card",
|
|
188
|
+
'ui.card(ui.card_header("Title"), ui.card_body("Body"), '
|
|
189
|
+
'ui.card_footer("Footer text"))',
|
|
190
|
+
),
|
|
191
|
+
_s(
|
|
192
|
+
"sidebar_layout_titled",
|
|
193
|
+
"tag",
|
|
194
|
+
"layout",
|
|
195
|
+
'ui.layout_sidebar(ui.sidebar("filters", title="Filters", width=260), '
|
|
196
|
+
'ui.div("main"))',
|
|
197
|
+
),
|
|
198
|
+
_s(
|
|
199
|
+
"sidebar_layout_right_closed",
|
|
200
|
+
"tag",
|
|
201
|
+
"layout",
|
|
202
|
+
'ui.layout_sidebar(ui.sidebar("controls", title="Selected study", '
|
|
203
|
+
'position="right", '
|
|
204
|
+
'width=360, open="closed", id="details_pane"), ui.div("main"))',
|
|
205
|
+
),
|
|
206
|
+
_s(
|
|
207
|
+
"accordion_checkbox_groups",
|
|
208
|
+
"tag",
|
|
209
|
+
"layout",
|
|
210
|
+
'ui.accordion(ui.accordion_panel("Organism", '
|
|
211
|
+
'ui.input_checkbox_group("organisms", None, '
|
|
212
|
+
'choices={}), value="organism"), ui.accordion_panel("Data source", '
|
|
213
|
+
'ui.input_checkbox_group("sources", None, choices={}), value="source"), '
|
|
214
|
+
'id="filters", multiple=True, open=["organism", "source"])',
|
|
215
|
+
),
|
|
216
|
+
_s(
|
|
217
|
+
"textInput",
|
|
218
|
+
"tag",
|
|
219
|
+
"input",
|
|
220
|
+
'ui.input_text("q", None, placeholder="Search accession, title or abstract", '
|
|
221
|
+
'width="100%")',
|
|
222
|
+
),
|
|
223
|
+
_s(
|
|
224
|
+
"actionLink",
|
|
225
|
+
"tag",
|
|
226
|
+
"input",
|
|
227
|
+
'ui.input_action_link("clear", "Clear all filters", class_="small")',
|
|
228
|
+
),
|
|
229
|
+
_s(
|
|
230
|
+
"downloadButton",
|
|
231
|
+
"tag",
|
|
232
|
+
"input",
|
|
233
|
+
'ui.download_button("qc_pdf", "PDF", class_="btn-sm ms-auto")',
|
|
234
|
+
),
|
|
235
|
+
_s(
|
|
236
|
+
"sliderInput_no_ticks",
|
|
237
|
+
"tag",
|
|
238
|
+
"input",
|
|
239
|
+
'ui.input_slider("font_size", "Font size", min=10, max=26, value=21, step=1, '
|
|
240
|
+
"ticks=False)",
|
|
241
|
+
),
|
|
242
|
+
_s(
|
|
243
|
+
"selectizeInput_remove_button",
|
|
244
|
+
"tag",
|
|
245
|
+
"input",
|
|
246
|
+
'ui.input_selectize("columns", "Columns", choices=[], multiple=True, '
|
|
247
|
+
'width="100%", '
|
|
248
|
+
'options={"plugins": ["remove_button"]})',
|
|
249
|
+
),
|
|
250
|
+
_s(
|
|
251
|
+
"selectizeInput_max_options",
|
|
252
|
+
"tag",
|
|
253
|
+
"input",
|
|
254
|
+
'ui.input_selectize("gene", "Search for a gene", choices=[], '
|
|
255
|
+
'options={"placeholder": "Symbol or Ensembl id", "maxOptions": 50})',
|
|
256
|
+
),
|
|
257
|
+
_s(
|
|
258
|
+
"radioButtons_inline",
|
|
259
|
+
"tag",
|
|
260
|
+
"input",
|
|
261
|
+
'ui.input_radio_buttons("geom", "Plot", choices={"box": "Boxplot", '
|
|
262
|
+
'"violin": "Violin"}, inline=True)',
|
|
263
|
+
),
|
|
264
|
+
_s(
|
|
265
|
+
"numericInput",
|
|
266
|
+
"tag",
|
|
267
|
+
"input",
|
|
268
|
+
'ui.input_numeric("loading_pc", "Loadings for PC", value=1, min=1, max=10, '
|
|
269
|
+
"step=1)",
|
|
270
|
+
),
|
|
271
|
+
_s(
|
|
272
|
+
"passwordInput",
|
|
273
|
+
"tag",
|
|
274
|
+
"input",
|
|
275
|
+
'ui.input_password("api_key", "API key", placeholder="Paste your key")',
|
|
276
|
+
),
|
|
277
|
+
_s("uiOutput", "tag", "output", 'ui.output_ui("x")'),
|
|
278
|
+
_s("uiOutput_fill", "tag", "output", 'ui.output_ui("x", fill=True)'),
|
|
279
|
+
_s(
|
|
280
|
+
"page_fillable",
|
|
281
|
+
"page",
|
|
282
|
+
"page",
|
|
283
|
+
'ui.page_fillable(ui.tags.h2("Title"), padding=8, gap=4)',
|
|
284
|
+
),
|
|
285
|
+
_s(
|
|
286
|
+
"layout_columns_fill",
|
|
287
|
+
"tag",
|
|
288
|
+
"layout",
|
|
289
|
+
'ui.layout_columns(ui.card(ui.card_header("Left"), ui.card_body("Left body")), '
|
|
290
|
+
'ui.card(ui.card_header("Right"), ui.card_body("Right body")), col_widths=[7, '
|
|
291
|
+
"5])",
|
|
292
|
+
),
|
|
293
|
+
_s(
|
|
294
|
+
"page_navbar",
|
|
295
|
+
"page",
|
|
296
|
+
"page",
|
|
297
|
+
'ui.page_navbar(ui.nav_panel("Browse", "x"), ui.nav_panel("Overview", "y"), '
|
|
298
|
+
"ui.nav_spacer(), "
|
|
299
|
+
'ui.nav_control(ui.output_ui("chip", inline=True)), '
|
|
300
|
+
'ui.nav_control(ui.input_dark_mode(id="dark_mode")), title="Demo", id="nav", '
|
|
301
|
+
'theme=ui.Theme("shiny"), '
|
|
302
|
+
'navbar_options=ui.navbar_options(bg="var(--brand-roast)", theme="dark"), '
|
|
303
|
+
'fillable=["Browse"], footer=ui.div("foot"))',
|
|
304
|
+
),
|
|
305
|
+
_s(
|
|
306
|
+
"page_navbar_icon",
|
|
307
|
+
"page",
|
|
308
|
+
"page",
|
|
309
|
+
'ui.page_navbar(ui.nav_panel("Explorer", "x", '
|
|
310
|
+
'icon=ui.tags.i(class_="bi bi-bar-chart-line")), '
|
|
311
|
+
'ui.nav_panel("About", "y", icon=ui.tags.i(class_="bi bi-info-circle")), '
|
|
312
|
+
'title="Demo", id="nav", theme=ui.Theme("shiny"))',
|
|
313
|
+
),
|
|
314
|
+
_s(
|
|
315
|
+
"page_navbar_sidebar",
|
|
316
|
+
"page",
|
|
317
|
+
"page",
|
|
318
|
+
'ui.page_navbar(ui.nav_panel("Build", ui.div("build content")), '
|
|
319
|
+
'ui.nav_panel("About", ui.div("about content")), title="Demo", id="nav", '
|
|
320
|
+
'sidebar=ui.sidebar(ui.div("chat"), id="dock", position="left", open="closed", '
|
|
321
|
+
"width=600), "
|
|
322
|
+
'footer=ui.tags.footer("foot"))',
|
|
323
|
+
),
|
|
324
|
+
_s(
|
|
325
|
+
"sidebar_custom_class",
|
|
326
|
+
"tag",
|
|
327
|
+
"layout",
|
|
328
|
+
'ui.layout_sidebar(ui.sidebar(ui.div("a"), width=420, '
|
|
329
|
+
'class_="my-sidebar"), ui.div("main"))',
|
|
330
|
+
),
|
|
331
|
+
# `page_navbar_dropdown` replaces the old hand-corrected fixture: a
|
|
332
|
+
# live render needs no correction for a shape `as.character()` could not
|
|
333
|
+
# represent faithfully.
|
|
334
|
+
_s(
|
|
335
|
+
"page_navbar_dropdown",
|
|
336
|
+
"page",
|
|
337
|
+
"page",
|
|
338
|
+
'ui.page_navbar(ui.nav_panel("Review", "x"), ui.nav_menu("Docs", '
|
|
339
|
+
'ui.nav_panel("Guide", "g"), '
|
|
340
|
+
'ui.nav_panel("FAQ", "f")), id="nav", title="Demo")',
|
|
341
|
+
),
|
|
342
|
+
# Added for coverage the original kit did not have (see docs/markup-catalog.md).
|
|
343
|
+
_s(
|
|
344
|
+
"page_navbar_not_fillable",
|
|
345
|
+
"page",
|
|
346
|
+
"page",
|
|
347
|
+
'ui.page_navbar(ui.nav_panel("One", "a"), ui.nav_panel("Two", "b"), '
|
|
348
|
+
'title="Demo", id="nav", '
|
|
349
|
+
"fillable=False)",
|
|
350
|
+
),
|
|
351
|
+
_s(
|
|
352
|
+
"sidebar_layout_gap_padding",
|
|
353
|
+
"tag",
|
|
354
|
+
"layout",
|
|
355
|
+
'ui.layout_sidebar(ui.sidebar("filters", width=260), ui.div("main"), '
|
|
356
|
+
'gap="1rem", padding="1rem")',
|
|
357
|
+
),
|
|
358
|
+
_s(
|
|
359
|
+
"layout_columns_single_child",
|
|
360
|
+
"tag",
|
|
361
|
+
"layout",
|
|
362
|
+
'ui.layout_columns(ui.card(ui.card_header("Solo"), '
|
|
363
|
+
'ui.card_body("Only one column")))',
|
|
364
|
+
),
|
|
365
|
+
_s(
|
|
366
|
+
"card_id",
|
|
367
|
+
"tag",
|
|
368
|
+
"card",
|
|
369
|
+
'ui.card(ui.card_header("Title"), ui.card_body("Body"), id="my_card")',
|
|
370
|
+
),
|
|
371
|
+
_s(
|
|
372
|
+
"card_height",
|
|
373
|
+
"tag",
|
|
374
|
+
"card",
|
|
375
|
+
'ui.card(ui.card_header("Title"), ui.card_body("Body"), height="300px")',
|
|
376
|
+
),
|
|
377
|
+
_s(
|
|
378
|
+
"card_no_fill",
|
|
379
|
+
"tag",
|
|
380
|
+
"card",
|
|
381
|
+
'ui.card(ui.card_header("Title"), ui.card_body("Body"), fill=False)',
|
|
382
|
+
),
|
|
383
|
+
_s(
|
|
384
|
+
"tooltip",
|
|
385
|
+
"tag",
|
|
386
|
+
"tooltip",
|
|
387
|
+
'ui.tooltip(ui.input_action_button("btn", "Hover me"), "Extra information", '
|
|
388
|
+
'id="tt")',
|
|
389
|
+
),
|
|
390
|
+
_s(
|
|
391
|
+
"popover",
|
|
392
|
+
"tag",
|
|
393
|
+
"tooltip",
|
|
394
|
+
'ui.popover(ui.input_action_button("btn2", "Click me"), "Popover body", '
|
|
395
|
+
'title="More info", id="pop")',
|
|
396
|
+
),
|
|
397
|
+
_s(
|
|
398
|
+
"value_box",
|
|
399
|
+
"tag",
|
|
400
|
+
"card",
|
|
401
|
+
'ui.value_box("KPI", "42", "up 3% this week", theme="primary")',
|
|
402
|
+
),
|
|
403
|
+
_s("uiOutput_inline", "tag", "output", 'ui.output_ui("x", inline=True)'),
|
|
404
|
+
_s("input_dark_mode", "tag", "input", 'ui.input_dark_mode(id="dark_mode")'),
|
|
405
|
+
_s(
|
|
406
|
+
"input_switch",
|
|
407
|
+
"tag",
|
|
408
|
+
"input",
|
|
409
|
+
'ui.input_switch("x", "Enable feature", value=True)',
|
|
410
|
+
),
|
|
411
|
+
_s(
|
|
412
|
+
"input_task_button",
|
|
413
|
+
"tag",
|
|
414
|
+
"input",
|
|
415
|
+
'ui.input_task_button("go", "Run", label_busy="Running...")',
|
|
416
|
+
),
|
|
417
|
+
_s(
|
|
418
|
+
"accordion",
|
|
419
|
+
"tag",
|
|
420
|
+
"layout",
|
|
421
|
+
'ui.accordion(ui.accordion_panel("Section A", "Body A", value="a"), '
|
|
422
|
+
'ui.accordion_panel("Section B", "Body B", value="b"), id="acc")',
|
|
423
|
+
),
|
|
424
|
+
_s(
|
|
425
|
+
"navset_card_tab",
|
|
426
|
+
"tag",
|
|
427
|
+
"nav",
|
|
428
|
+
'ui.navset_card_tab(ui.nav_panel("Tab 1", "One"), '
|
|
429
|
+
'ui.nav_panel("Tab 2", "Two"))',
|
|
430
|
+
),
|
|
431
|
+
_s(
|
|
432
|
+
"navset_pill",
|
|
433
|
+
"tag",
|
|
434
|
+
"nav",
|
|
435
|
+
'ui.navset_pill(ui.nav_panel("Tab 1", "One"), ui.nav_panel("Tab 2", "Two"))',
|
|
436
|
+
),
|
|
437
|
+
_s(
|
|
438
|
+
"checkboxGroupInput",
|
|
439
|
+
"tag",
|
|
440
|
+
"input",
|
|
441
|
+
'ui.input_checkbox_group("opts", "Options", choices={"a": "Alpha", '
|
|
442
|
+
'"b": "Beta"}, selected=["a"])',
|
|
443
|
+
),
|
|
444
|
+
_s("dateInput", "tag", "input", 'ui.input_date("d", "Date")'),
|
|
445
|
+
_s("dateRangeInput", "tag", "input", 'ui.input_date_range("dr", "Date range")'),
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def markup_catalog(kind: Sequence[str] | None = None) -> tuple[Sample, ...]:
|
|
450
|
+
"""Return catalog samples, optionally filtered to the given ``kind`` values."""
|
|
451
|
+
if kind is None:
|
|
452
|
+
return SAMPLES
|
|
453
|
+
kinds = set(kind)
|
|
454
|
+
return tuple(sample for sample in SAMPLES if sample.kind in kinds)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def catalog_names(kind: Sequence[str] | None = None) -> list[str]:
|
|
458
|
+
"""Names of the catalog samples matching ``kind`` (all, if omitted)."""
|
|
459
|
+
return [sample.name for sample in markup_catalog(kind)]
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def catalog_eval(names: Sequence[str]) -> list["TagChild"]:
|
|
463
|
+
"""Evaluate each named sample's ``call`` and return the resulting tags.
|
|
464
|
+
|
|
465
|
+
Uses a fixed namespace exposing only ``ui`` (``shiny.ui``) plus an empty
|
|
466
|
+
``__builtins__``. Every ``call`` string is one of this module's own
|
|
467
|
+
literals (see :data:`SAMPLES`), not user input, so a plain ``eval()`` is
|
|
468
|
+
appropriate here.
|
|
469
|
+
"""
|
|
470
|
+
by_name = {sample.name: sample for sample in SAMPLES}
|
|
471
|
+
namespace = {"ui": ui, "__builtins__": {}}
|
|
472
|
+
results: list["TagChild"] = []
|
|
473
|
+
for name in names:
|
|
474
|
+
sample = by_name.get(name)
|
|
475
|
+
if sample is None:
|
|
476
|
+
raise ValueError(f"unknown catalog sample: {name!r}")
|
|
477
|
+
results.append(eval(sample.call, namespace)) # noqa: S307
|
|
478
|
+
return results
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def catalog_hash(samples: Sequence[Sample] | None = None) -> str:
|
|
482
|
+
"""A stable hash of the catalog's names, kinds, groups and call text.
|
|
483
|
+
|
|
484
|
+
Part of the catalog render key (see ``tools/markup/render_py.py``): it changes
|
|
485
|
+
whenever a sample is added, removed, or its call text edited, so a stale
|
|
486
|
+
cache gets re-rendered.
|
|
487
|
+
"""
|
|
488
|
+
selected = samples if samples is not None else SAMPLES
|
|
489
|
+
lines = "\n".join(
|
|
490
|
+
f"{sample.name}|{sample.kind}|{sample.group}|{sample.call}"
|
|
491
|
+
for sample in selected
|
|
492
|
+
)
|
|
493
|
+
return hashlib.sha256(lines.encode("utf-8")).hexdigest()
|
bslibreact/_dep.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""``bslibreact_dep()``: the vendored JS/CSS bundle as an HTMLDependency.
|
|
2
|
+
|
|
3
|
+
Mirrors the same pattern ``posit-dev/shinyreact``'s own Python package uses
|
|
4
|
+
for its vendored bundle (``pkg-py/src/shinyreact/_dep.py`` in that repo):
|
|
5
|
+
the bundle in ``www/`` is vendored in by ``make update-dist`` from
|
|
6
|
+
``pkg-js/dist/bslibreact.js``, and the version string gets an mtime suffix
|
|
7
|
+
in a dev checkout so a rebuild still busts the browser cache even though the
|
|
8
|
+
release version hasn't changed.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from htmltools import HTMLDependency
|
|
16
|
+
|
|
17
|
+
from ._version import __version__
|
|
18
|
+
|
|
19
|
+
_WWW_DIR = Path(__file__).parent / "www"
|
|
20
|
+
|
|
21
|
+
# Only reachable from a repo checkout (this package installed editable from
|
|
22
|
+
# source), never from an installed wheel -- a wheel has no `pkg-js/` sibling
|
|
23
|
+
# at all. Its presence is the "dev checkout" signal `_bundle_version()` uses.
|
|
24
|
+
_JS_PACKAGE_JSON = Path(__file__).parents[3] / "pkg-js" / "package.json"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _file_mtime_int(path: Path) -> int | None:
|
|
28
|
+
"""Return ``path``'s mtime in whole seconds, or ``None`` if it doesn't exist."""
|
|
29
|
+
try:
|
|
30
|
+
return int(path.stat().st_mtime)
|
|
31
|
+
except FileNotFoundError:
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _bundle_version() -> str:
|
|
36
|
+
"""The package version, suffixed with the bundle's mtime in a dev checkout.
|
|
37
|
+
|
|
38
|
+
An installed package serves ``/lib/bslibreact-<version>/``. In a repo
|
|
39
|
+
checkout it is ``/lib/bslibreact-<version>+<mtime>/`` instead, so
|
|
40
|
+
``make update-dist`` still cache-busts the browser. The mtime goes in a
|
|
41
|
+
PEP 440 local segment (``+``), which stays valid after any version,
|
|
42
|
+
pre-releases included.
|
|
43
|
+
"""
|
|
44
|
+
if _JS_PACKAGE_JSON.is_file():
|
|
45
|
+
mtime = _file_mtime_int(_WWW_DIR / "bslibreact.js")
|
|
46
|
+
if mtime is not None:
|
|
47
|
+
return f"{__version__}+{mtime}"
|
|
48
|
+
return __version__
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def bslibreact_dep() -> HTMLDependency:
|
|
52
|
+
"""The bslibreact JS (and CSS, if built) bundle as an HTMLDependency.
|
|
53
|
+
|
|
54
|
+
The bundle sets ``window.bslibreact``. Attach it only for a client with no
|
|
55
|
+
build step that reads the kit from that global; a client built with Vite
|
|
56
|
+
bundles the kit itself.
|
|
57
|
+
|
|
58
|
+
The stylesheet is attached only when ``www/bslibreact.css`` actually
|
|
59
|
+
exists -- the bundle may ship JS only -- so this never points a
|
|
60
|
+
``<link>`` at a file that isn't there. Likewise the JS file itself may be
|
|
61
|
+
absent before the first ``make update-dist``; :func:`_bundle_version`
|
|
62
|
+
handles that (falls back to the bare release string) instead of
|
|
63
|
+
erroring, since importing ``bslibreact`` must work before the bundle
|
|
64
|
+
has ever been built.
|
|
65
|
+
"""
|
|
66
|
+
css = _WWW_DIR / "bslibreact.css"
|
|
67
|
+
return HTMLDependency(
|
|
68
|
+
name="bslibreact",
|
|
69
|
+
version=_bundle_version(),
|
|
70
|
+
source={"subdir": str(_WWW_DIR)},
|
|
71
|
+
script={"src": "bslibreact.js", "defer": ""},
|
|
72
|
+
stylesheet={"href": "bslibreact.css"} if css.exists() else None,
|
|
73
|
+
)
|
bslibreact/_deps.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""``page_deps()``: the CSS/JS a bslibreact page needs, without a real page.
|
|
2
|
+
|
|
3
|
+
bslibreact's React components reproduce bslib markup, but some of that
|
|
4
|
+
markup depends on component CSS and JS that a normal bslib/shiny app only
|
|
5
|
+
picks up because the *components themselves* are somewhere on the page (the
|
|
6
|
+
ion.rangeSlider assets a slider needs, selectize's assets, bslib's own
|
|
7
|
+
component JS). A React page that never calls ``ui.input_slider()`` directly
|
|
8
|
+
still needs those files if it renders a `<SliderInput>`. ``page_deps()``
|
|
9
|
+
closes that gap: it renders a representative sample of the catalog (or a
|
|
10
|
+
caller-chosen subset) purely to ask htmltools what dependencies that markup
|
|
11
|
+
would pull in, then hands back just the dependency tags.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import TYPE_CHECKING, Sequence
|
|
18
|
+
|
|
19
|
+
from htmltools import TagList
|
|
20
|
+
from shiny.ui import Theme
|
|
21
|
+
|
|
22
|
+
from ._catalog import catalog_eval, catalog_names
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from htmltools import TagChild
|
|
26
|
+
|
|
27
|
+
# Every page function shiny ships (`page_bootstrap`, `page_navbar`, ...)
|
|
28
|
+
# attaches these itself. Including them here too would be harmless (htmltools
|
|
29
|
+
# de-duplicates dependencies by name when hoisting to <head>) but misleading:
|
|
30
|
+
# a caller reading page_deps()'s output should see only the extra things
|
|
31
|
+
# bslibreact needs, not the baseline every shiny page already has.
|
|
32
|
+
_ALWAYS_EXCLUDED = frozenset({"bootstrap", "jquery"})
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _validate_theme(theme: object) -> None:
|
|
36
|
+
"""Raise ``TypeError`` unless ``theme`` is one of the accepted shapes, so a
|
|
37
|
+
wrong ``theme=`` fails loudly even though the value is not used yet."""
|
|
38
|
+
if theme is None:
|
|
39
|
+
return
|
|
40
|
+
if isinstance(theme, (str, Path)):
|
|
41
|
+
return
|
|
42
|
+
if isinstance(theme, Theme):
|
|
43
|
+
return
|
|
44
|
+
raise TypeError(
|
|
45
|
+
"page_deps(theme=...) must be None, a str or Path to a _brand.yml, "
|
|
46
|
+
f"or a shiny.ui.Theme instance; got {type(theme).__name__}. "
|
|
47
|
+
"py-shiny resolves component CSS against whatever theme the page "
|
|
48
|
+
"itself declares, so this value is validated but otherwise unused."
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def page_deps(
|
|
53
|
+
theme: str | Path | Theme | None = None,
|
|
54
|
+
*extra: "TagChild",
|
|
55
|
+
components: Sequence[str] | None = None,
|
|
56
|
+
) -> TagList:
|
|
57
|
+
"""The HTML dependencies bslibreact's components need at runtime.
|
|
58
|
+
|
|
59
|
+
Attach the result to a shinyreact page, for example
|
|
60
|
+
``page_react(page_deps(app_theme), theme=app_theme)``. A client with no
|
|
61
|
+
build step also attaches :func:`bslibreact_dep`.
|
|
62
|
+
It works by rendering a representative sample of the markup catalog --
|
|
63
|
+
the same sample calls the catalog renderer uses for ``bslibreact
|
|
64
|
+
markup`` -- and reading back whichever :class:`~htmltools.HTMLDependency`
|
|
65
|
+
objects that markup pulled in, minus the ones every shiny page already
|
|
66
|
+
attaches on its own (``bootstrap``, ``jquery``).
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
theme: The page's theme: ``None``, a ``str``/``Path`` to a
|
|
70
|
+
``_brand.yml``, or a :class:`shiny.ui.Theme`. Validated but not
|
|
71
|
+
used yet (py-shiny themes component CSS from the page's own
|
|
72
|
+
theme); it keeps the call the same as R's ``page_deps()`` and
|
|
73
|
+
leaves room to use it later.
|
|
74
|
+
*extra: Additional tags to render alongside the catalog sample --
|
|
75
|
+
typically the actual inputs and outputs your page uses, so their
|
|
76
|
+
dependencies are included too even if the catalog doesn't happen
|
|
77
|
+
to cover them.
|
|
78
|
+
components: Catalog sample names to render, instead of every
|
|
79
|
+
``"tag"``/``"page"`` sample. Pass ``[]`` to render none of the
|
|
80
|
+
catalog (only ``*extra``). Unknown names raise ``ValueError``.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
A :class:`~htmltools.TagList` of :class:`~htmltools.HTMLDependency`
|
|
84
|
+
objects, ready to splat into a page.
|
|
85
|
+
"""
|
|
86
|
+
_validate_theme(theme)
|
|
87
|
+
|
|
88
|
+
if components is None:
|
|
89
|
+
names: Sequence[str] = catalog_names(("tag", "page"))
|
|
90
|
+
else:
|
|
91
|
+
names = list(components)
|
|
92
|
+
known = set(catalog_names())
|
|
93
|
+
for name in names:
|
|
94
|
+
if name not in known:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
"page_deps(components=...) named an unknown catalog "
|
|
97
|
+
f"sample: {name!r}"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
samples = catalog_eval(names)
|
|
101
|
+
rendered = TagList(*samples, *extra).tagify()
|
|
102
|
+
deps = rendered.get_dependencies()
|
|
103
|
+
return TagList(*(dep for dep in deps if dep.name not in _ALWAYS_EXCLUDED))
|
bslibreact/_host.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""``host_input()``: mount one server-rendered Shiny widget inside a React page.
|
|
2
|
+
|
|
3
|
+
The ui.tsx pattern puts the client in charge of markup, but a handful of
|
|
4
|
+
widgets (a color picker, a fancy date range control, anything a team already
|
|
5
|
+
has a battle-tested ``renderUI`` for) do not need a React rebuild right
|
|
6
|
+
away. ``host_input()`` is the escape hatch: it registers an ordinary
|
|
7
|
+
``@render.ui`` output on the server, and hands back the output id your React
|
|
8
|
+
component should mount at (conventionally via a `<SliderInput id="bins_ui"
|
|
9
|
+
/>`-style hosted element).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import TYPE_CHECKING, Any, Callable, Protocol
|
|
15
|
+
|
|
16
|
+
from htmltools import Tag, TagList
|
|
17
|
+
from shiny import render
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from htmltools import TagChild
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class _OutputsLike(Protocol):
|
|
24
|
+
"""The one sliver of ``shiny.session.Outputs`` that ``host_input()`` needs.
|
|
25
|
+
|
|
26
|
+
A structural type, not an import of ``Outputs`` itself: the real
|
|
27
|
+
class and a test double both satisfy this without either one needing to
|
|
28
|
+
inherit from the other, and it documents exactly what ``host_input()``
|
|
29
|
+
depends on.
|
|
30
|
+
|
|
31
|
+
The real ``Outputs.__call__`` is overloaded: called with a bare renderer
|
|
32
|
+
it registers immediately, called with only keyword arguments (as
|
|
33
|
+
``host_input()`` always does) it returns a decorator instead. Modelling
|
|
34
|
+
only that second shape here, instead of merging both into one signature
|
|
35
|
+
with an optional positional ``renderer``, is what lets a real
|
|
36
|
+
``Outputs`` instance satisfy this Protocol structurally -- a flattened
|
|
37
|
+
signature does not match an overloaded one under strict type checking.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __call__(
|
|
41
|
+
self,
|
|
42
|
+
*,
|
|
43
|
+
id: str | None = None,
|
|
44
|
+
suspend_when_hidden: bool = True,
|
|
45
|
+
priority: int = 0,
|
|
46
|
+
) -> Callable[[Any], Any]: ...
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _check_no_id_collision(tag: "TagChild", name: str) -> None:
|
|
50
|
+
"""Raise ``ValueError`` if some element inside ``tag`` already has id ``name``.
|
|
51
|
+
|
|
52
|
+
Walks ``Tag.children`` (a :class:`~htmltools.TagList`, which flattens
|
|
53
|
+
nested lists as it is iterated) looking for an ``id`` attribute equal to
|
|
54
|
+
``name``. A collision means the widget's own markup would fight with the
|
|
55
|
+
output wrapper Shiny binds to for the same DOM id, so it is caught here
|
|
56
|
+
instead of being left to produce a confusing duplicate-id bug in the browser.
|
|
57
|
+
"""
|
|
58
|
+
if isinstance(tag, Tag):
|
|
59
|
+
if tag.attrs.get("id") == name:
|
|
60
|
+
raise ValueError(
|
|
61
|
+
f"host_input(): the widget passed for id={name!r} contains its own "
|
|
62
|
+
f"element with that same id. Give the widget a different id, or pass "
|
|
63
|
+
"a different suffix= to host_input() so the output id it registers "
|
|
64
|
+
"doesn't collide with markup the widget itself produces."
|
|
65
|
+
)
|
|
66
|
+
for child in tag.children:
|
|
67
|
+
_check_no_id_collision(child, name)
|
|
68
|
+
elif isinstance(tag, TagList):
|
|
69
|
+
for child in tag:
|
|
70
|
+
_check_no_id_collision(child, name)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def host_input(
|
|
74
|
+
output: "_OutputsLike",
|
|
75
|
+
id: str,
|
|
76
|
+
widget: Callable[[], "TagChild"],
|
|
77
|
+
suffix: str = "_ui",
|
|
78
|
+
) -> str:
|
|
79
|
+
"""Register ``widget`` as a hosted output and return its output id.
|
|
80
|
+
|
|
81
|
+
Registers ``output[f"{id}{suffix}"]`` as an ``@render.ui`` output that
|
|
82
|
+
calls ``widget()`` lazily -- once per render, the same as any other
|
|
83
|
+
Shiny output -- and checks the result for an id collision (see
|
|
84
|
+
:func:`_check_no_id_collision`) before returning it. The registration
|
|
85
|
+
uses ``suspend_when_hidden=False``: Shiny normally skips rendering an
|
|
86
|
+
output it believes is hidden, based on the *server-rendered* DOM
|
|
87
|
+
visibility of its container, but a hosted output's container is a React
|
|
88
|
+
element the client mounts on its own terms, which Shiny's own visibility
|
|
89
|
+
tracking cannot see. Without this, a hosted widget can simply never
|
|
90
|
+
render on some page shapes.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
output: The ``output`` object from your server function.
|
|
94
|
+
id: The base id. Your React element reads this output at
|
|
95
|
+
``f"{id}{suffix}"`` (the default suffix makes that ``"bins_ui"``
|
|
96
|
+
for ``id="bins"``).
|
|
97
|
+
widget: A zero-argument callable returning the widget's markup, e.g.
|
|
98
|
+
``lambda: ui.input_slider("bins", "Bins", 1, 50, 30)``. Called
|
|
99
|
+
fresh on every render, exactly like the body of any
|
|
100
|
+
``@render.ui`` function.
|
|
101
|
+
suffix: Appended to ``id`` to form the registered output name.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
The output name (``f"{id}{suffix}"``) the widget was registered
|
|
105
|
+
under -- pass this straight to your React element's ``id`` prop.
|
|
106
|
+
"""
|
|
107
|
+
name = f"{id}{suffix}"
|
|
108
|
+
|
|
109
|
+
@output(id=name, suspend_when_hidden=False)
|
|
110
|
+
@render.ui
|
|
111
|
+
def _() -> "TagChild":
|
|
112
|
+
tag = widget()
|
|
113
|
+
_check_no_id_collision(tag, name)
|
|
114
|
+
return tag
|
|
115
|
+
|
|
116
|
+
return name
|
bslibreact/_version.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""The installed package version.
|
|
2
|
+
|
|
3
|
+
Read from installed package metadata, so it always matches what ``pip show``
|
|
4
|
+
reports. The fallback only applies to a source tree imported without being
|
|
5
|
+
installed at all; ``uv sync`` installs the project in editable mode, which
|
|
6
|
+
writes the metadata.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
__version__ = version("bslibreact")
|
|
13
|
+
except PackageNotFoundError:
|
|
14
|
+
__version__ = "0.0.0"
|
|
15
|
+
|
|
16
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
(function(c){"use strict";let R;function U(e){R=e}function v(){const e=R??window.shinyreact;if(!e)throw new Error("window.shinyreact is not available, and no module was passed to provideShinyReact(). Is the shinyreact page dependency loaded, or did an npm-tier app forget to call provideShinyReact() at startup?");return e}function Y(e,t,n){return v().useShinyInput(e,t,n)}function G(e,t,n){return v().useSetShinyInput(e,t,n)}function J(e){return v().useShinyInputValue(e)}function Z(e,t){return v().useShinyOutputValue(e,t)}function X(e){return v().useShinyOutputStatus(e)}function Q(e){return v().useShinyOutputError(e)}function P(){return v().useShinyInitialized()}function K(){return v().useShinyBusy()}function ee(e,t){return v().useShinyMessageHandler(e,t)}const g=e=>v().ShinyOutput(e),te=e=>v().ImageOutput(e),$=e=>v().ShinyModuleProvider(e);function o(...e){return e.filter(Boolean).join(" ")}var _={exports:{}},w={};/**
|
|
2
|
+
* @license React
|
|
3
|
+
* react-jsx-runtime.production.js
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
6
|
+
*
|
|
7
|
+
* This source code is licensed under the MIT license found in the
|
|
8
|
+
* LICENSE file in the root directory of this source tree.
|
|
9
|
+
*/var ae=Symbol.for("react.transitional.element"),ne=Symbol.for("react.fragment");function A(e,t,n){var l=null;if(n!==void 0&&(l=""+n),t.key!==void 0&&(l=""+t.key),"key"in t){n={};for(var i in t)i!=="key"&&(n[i]=t[i])}else n=t;return t=n.ref,{$$typeof:ae,type:e,key:l,ref:t!==void 0?t:null,props:n}}w.Fragment=ne,w.jsx=A,w.jsxs=A,_.exports=w;var a=_.exports;const B=c.createContext(void 0);function le({namespace:e,children:t}){return a.jsx(B.Provider,{value:e,children:a.jsx($,{namespace:e,children:t})})}function f(e){const t=c.useContext(B);return t?`${t}-${e}`:e}function m({id:e}){return a.jsx(g,{id:f(e),namespace:null,className:"shiny-html-output"})}const ie=m,se=m,re=m,oe=m,ue=m,ce=m,de=m,be=m,pe=m,me=m,fe=m,he=m,ve=m,xe=m;function k(e){var l,i;if(typeof window>"u")return;const t=(i=(l=window.Shiny)==null?void 0:l.shinyapp)==null?void 0:i.$inputValues;if(!t)return;if(Object.prototype.hasOwnProperty.call(t,e))return t[e];const n=Object.keys(t).find(s=>s.startsWith(`${e}:`));return n===void 0?void 0:t[n]}function ge(e){const t=f(e),[n,l]=c.useState(()=>({id:t,value:k(t)}));let i=n;return n.id!==t&&(i={id:t,value:k(t)},l(i)),c.useEffect(()=>{const s=window.$;if(!s)return;const u=r=>{const b=r;(b==null?void 0:b.name)===t&&l({id:t,value:b.value})};return s(document).on("shiny:inputchanged",u),()=>{s(document).off("shiny:inputchanged",u)}},[t]),i.value}function ye({children:e}){return a.jsx("span",{className:"help-block",children:e})}function je({id:e,width:t="100%",height:n="400px",className:l,click:i}){const s=f(i??"");return a.jsx(g,{id:f(e),namespace:null,className:o("shiny-plot-output html-fill-item",l),style:{width:t,height:n},"data-click-id":i?s:void 0,"data-click-clip":i?"TRUE":void 0})}function Ne({id:e,className:t}){return a.jsx(g,{id:f(e),namespace:null,className:o("shiny-text-output",t)})}function we({id:e,className:t}){return a.jsx(g,{id:f(e),namespace:null,tagName:"pre",className:o("shiny-text-output noplaceholder",t)})}function Se({id:e,className:t,fill:n=!0}){return a.jsx(g,{id:f(e),namespace:null,className:o("datatables html-widget html-widget-output shiny-report-size",n&&"html-fill-item",t),style:{width:"100%",height:"auto"}})}function Ie({id:e,className:t,height:n="auto",width:l="auto"}){const i=f(e);return a.jsx(g,{id:i,namespace:null,className:o("reactable html-widget html-widget-output shiny-report-size html-fill-item",t),style:{width:l,height:n},"data-reactable-output":i})}function Ce({id:e,widgetClass:t,height:n="400px",width:l="100%",className:i}){return a.jsx(g,{id:f(e),namespace:null,className:o(`${t} html-widget html-widget-output shiny-report-size html-fill-item`,i),style:{width:l,height:n}})}function Ee({id:e,className:t,fill:n=!1,inline:l=!1}){return a.jsx(g,{id:f(e),namespace:null,tagName:l?"span":"div",className:o("shiny-html-output",n&&"html-fill-item html-fill-container",t)})}function M({text:e,children:t}){return a.jsxs("bslib-tooltip",{placement:"auto",bsOptions:"[]","data-require-bs-version":"5","data-require-bs-caller":"tooltip()",children:[a.jsx("template",{dangerouslySetInnerHTML:{__html:e}}),t]})}const Te="M20 5C20 4.4 19.6 4 19 4H13C12.4 4 12 3.6 12 3C12 2.4 12.4 2 13 2H21C21.6 2 22 2.4 22 3V11C22 11.6 21.6 12 21 12C20.4 12 20 11.6 20 11V5ZM4 19C4 19.6 4.4 20 5 20H11C11.6 20 12 20.4 12 21C12 21.6 11.6 22 11 22H3C2.4 22 2 21.6 2 21V13C2 12.4 2.4 12 3 12C3.6 12 4 12.4 4 13V19Z";function Oe({fullScreen:e=!1,fill:t=!0,height:n,className:l,id:i,children:s}){const u=c.useId(),r=f(i??"");c.useLayoutEffect(()=>{var p,h;(h=(p=window.bslib)==null?void 0:p.Card)==null||h.initializeAllCards()},[]);const b=n?{height:n}:void 0;return a.jsxs("div",{id:i?r:e?`bslib-card-${u}`:void 0,className:o("card bslib-card bslib-mb-spacing html-fill-container",i&&"bslib-card-input",t&&"html-fill-item",l),"data-bslib-card-init":"","data-full-screen":e?"false":void 0,"data-require-bs-caller":"card()","data-require-bs-version":"5",style:b,children:[s,e&&a.jsx(M,{text:"Expand",children:a.jsx("button",{"aria-expanded":"false","aria-label":"Expand card",className:"bslib-full-screen-enter badge rounded-pill",children:a.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",style:{height:"1em",width:"1em",fill:"currentColor"},"aria-hidden":"true",role:"img",children:a.jsx("path",{d:Te})})})})]})}function Re({className:e,id:t,children:n}){return a.jsx("div",{className:o("card-header bslib-gap-spacing",e),id:t,children:n})}function Pe({fill:e=!0,className:t,style:n,html:l,children:i}){return a.jsx("div",{className:o("card-body bslib-gap-spacing html-fill-item",e&&"html-fill-container",t),style:{marginTop:"auto",marginBottom:"auto",flex:"1 1 auto",...n},...l!==void 0?{dangerouslySetInnerHTML:{__html:l}}:{},children:l===void 0?i:void 0})}function $e({className:e,children:t}){return a.jsx("div",{className:o("card-footer",e),children:t})}function _e({title:e,theme:t="primary",children:n}){return c.useLayoutEffect(()=>{var l,i;(i=(l=window.bslib)==null?void 0:l.Card)==null||i.initializeAllCards()},[]),a.jsx("div",{className:o("card bslib-card bslib-mb-spacing html-fill-item html-fill-container bslib-value-box",`bg-${t}`),"data-bslib-card-init":"","data-require-bs-caller":"card() value_box()","data-require-bs-version":"5 5",children:a.jsx("div",{className:"card-body bslib-gap-spacing html-fill-item html-fill-container",style:{marginTop:"auto",marginBottom:"auto",flex:"1 1 auto"},children:a.jsxs("div",{className:"value-box-area html-fill-item html-fill-container",children:[a.jsx("p",{className:"value-box-title",children:e}),n]})})})}function Ae({colWidths:e,fill:t=!0,className:n,id:l,children:i}){return a.jsx("bslib-layout-columns",{id:l,class:o("bslib-grid grid bslib-mb-spacing",t&&"html-fill-item",n),"col-widths-sm":e.join(","),"data-require-bs-version":"5","data-require-bs-caller":"layout_columns()",children:c.Children.toArray(i).map((s,u)=>a.jsx("div",{className:"bslib-grid-item bslib-gap-spacing html-fill-container",children:s},u))})}function L(e,t,n=!0){const l=P(),[i,s]=c.useState(!1),u=typeof window<"u"&&!!window.Shiny,r=n&&u,[b,p]=c.useState(r);return r!==b&&(p(r),r||s(!1)),c.useEffect(()=>{if(!r||!l)return;const h=e.current,d=window.Shiny;if(!h||!d)return;let x=!1;const j=t(h)??h,y=()=>{x||s(!0)},C=N=>{y(),console.error("bslibreact: binding to Shiny failed",N)};try{d.initializeInputs(j),Promise.resolve(d.bindAll(j)).then(y,C)}catch(N){C(N)}return()=>{x=!0,d.unbindAll(h,!0)}},[r,l]),r?i:!0}function E({sidebar:e,sidebarWidth:t=250,title:n,position:l="left",open:i="open",id:s,collapsibleMobile:u=!0,openMobile:r="closed",border:b,borderRadius:p,sidebarClassName:h,gap:d,padding:x,children:j}){const y=c.useId(),C=f(s??""),N=s?C:`bslib-sidebar-${y}`,V=c.useRef(null),q=c.useRef(null);c.useLayoutEffect(()=>{var D,W;(W=(D=window.bslib)==null?void 0:D.Sidebar)==null||W.initCollapsibleAll()},[]);const F=L(V,()=>q.current,!!s);return a.jsxs("div",{ref:q,className:o("bslib-sidebar-layout bslib-mb-spacing html-fill-item",l==="right"&&"sidebar-right",i==="closed"&&"sidebar-collapsed"),"data-bslib-sidebar-border":b===void 0?void 0:b?"true":"false","data-bslib-sidebar-border-radius":p===void 0?void 0:p?"true":"false","data-bslib-sidebar-init":"TRUE","data-collapsible-desktop":"true","data-collapsible-mobile":u?"true":"false","data-open-desktop":i,"data-open-mobile":r,"data-require-bs-caller":"layout_sidebar()","data-require-bs-version":"5",style:{"--_sidebar-width":`${t}px`},children:[a.jsx("div",{className:"main bslib-gap-spacing html-fill-container",children:a.jsx("main",{className:"bslib-page-main bslib-gap-spacing html-fill-item html-fill-container",children:F&&j})}),a.jsx("aside",{ref:V,id:N,className:o("sidebar",s&&"bslib-sidebar-input",h),hidden:!0,"data-resizable":"",children:a.jsxs("div",{className:"sidebar-content bslib-gap-spacing",style:d!==void 0||x!==void 0?{gap:d,padding:x}:void 0,children:[n!==void 0&&a.jsx("header",{className:"sidebar-title",children:n}),F&&e]})}),a.jsx("button",{className:"collapse-toggle",type:"button",title:"Toggle sidebar","aria-expanded":i==="open"?"true":"false","aria-controls":N,children:a.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 16 16",className:"bi bi-chevron-left collapse-icon",style:{fill:"currentColor",verticalAlign:"-0.125em"},"aria-hidden":"true",role:"img",children:a.jsx("path",{fillRule:"evenodd",d:"M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z"})})})]})}const H="bslib-html-height";function S(e,t={},n=!0){c.useLayoutEffect(()=>{let l=document.getElementById(H);const i=n&&!l;i&&(l=document.createElement("style"),l.id=H,l.textContent="html { height: 100%; }",document.head.appendChild(l));const s=document.body,u=s.className,r=s.getAttribute("style");return s.className=e,Object.assign(s.style,t),()=>{s.className=u,r===null?s.removeAttribute("style"):s.setAttribute("style",r),i&&(l==null||l.remove())}},[e,JSON.stringify(t),n])}function Be({title:e,sidebar:t,sidebarWidth:n=250,children:l}){return S("bslib-page-fill bslib-gap-spacing bslib-flow-mobile html-fill-container bslib-page-sidebar",{padding:"0px",gap:"0px"}),a.jsxs(a.Fragment,{children:[a.jsx("div",{className:"navbar navbar-static-top",children:a.jsx("div",{className:"container-fluid",children:a.jsx("h1",{className:"bslib-page-title navbar-brand",children:e})})}),a.jsx(E,{sidebar:t,sidebarWidth:n,border:!1,borderRadius:!1,collapsibleMobile:!1,openMobile:"always",children:l})]})}function ke({padding:e=12,gap:t=12,children:n}){return S("bslib-page-fill bslib-gap-spacing bslib-flow-mobile html-fill-container",{padding:`${e}px`,gap:`${t}px`}),a.jsx(a.Fragment,{children:n})}const z=c.createContext(!1),T="bslib-navbar-collapse";function Me({title:e,bg:t,navbarTheme:n,tabStrip:l,footer:i,sidebar:s,sidebarId:u,sidebarWidth:r=250,sidebarOpen:b="open",fillable:p=!0,children:h}){const d=p||!!s;S(d?o("bslib-page-fill bslib-gap-spacing bslib-flow-mobile html-fill-container bslib-page-navbar",!!s&&"has-page-sidebar"):"bslib-page-navbar",d?{padding:"0px",gap:"0px"}:{},d);const x=c.useRef(null);c.useLayoutEffect(()=>{const y=x.current;if(!(!y||!t))return y.style.setProperty("background-color",t,"important"),()=>{y.style.removeProperty("background-color")}},[t]);const j=a.jsx("div",{className:o("tab-content",p&&"html-fill-item html-fill-container"),children:a.jsx(z.Provider,{value:p,children:h})});return a.jsxs(a.Fragment,{children:[a.jsx("nav",{ref:x,className:o("navbar navbar-default navbar-static-top",n&&"navbar-inverse"),role:"navigation","data-bs-theme":n??"auto",children:a.jsxs("div",{className:"container-fluid",children:[a.jsxs("div",{className:"navbar-header",children:[a.jsxs("button",{type:"button",className:"navbar-toggle collapsed","data-toggle":"collapse","data-target":`#${T}`,"data-bs-toggle":"collapse","data-bs-target":`#${T}`,children:[a.jsx("span",{className:"sr-only visually-hidden",children:"Toggle navigation"}),a.jsx("span",{className:"icon-bar"}),a.jsx("span",{className:"icon-bar"}),a.jsx("span",{className:"icon-bar"})]}),a.jsx("span",{className:"navbar-brand",children:e})]}),a.jsx("div",{className:"navbar-collapse collapse",id:T,children:l})]})}),s?a.jsx("div",{className:o(p&&"html-fill-item html-fill-container"),children:a.jsxs(E,{sidebar:s,sidebarWidth:r,id:u,open:b,openMobile:b,collapsibleMobile:!0,border:!1,borderRadius:!1,children:[j,i]})}):a.jsxs("div",{className:o("container-fluid",p&&"html-fill-item html-fill-container"),children:[j,i]})]})}function Le(e){return"dropdown"in e}function He(e){return e.replace(/\s+/g,"-")}function I(e,t){return`tab-${e}-${He(t)}`}function ze({id:e,panels:t,children:n}){const l=f(e),i=c.useRef(null),s=L(i,r=>r.parentElement);let u=0;return a.jsxs("ul",{ref:i,id:l,className:"nav navbar-nav nav-underline shiny-tab-input",children:[t.map(r=>{if(Le(r)){let p=!1;const h=r.dropdown.map(d=>{const x=u===0;return u++,x&&(p=!0),a.jsx("li",{children:a.jsxs("a",{href:`#${I(e,d.value)}`,"data-toggle":"tab","data-bs-toggle":"tab","data-value":d.value,className:o("dropdown-item",x&&"active"),children:[a.jsx(O,{icon:d.icon}),d.label]})},d.value)});return a.jsxs("li",{className:"dropdown nav-item",children:[a.jsxs("a",{href:"#",className:o("dropdown-toggle nav-link",p&&"active"),"data-toggle":"dropdown","data-bs-toggle":"dropdown","data-value":r.label,children:[a.jsx(O,{icon:r.icon}),r.label,a.jsx("b",{className:"caret"})]}),a.jsx("ul",{className:"dropdown-menu",children:h})]},r.label)}const b=u===0;return u++,a.jsx("li",{className:"nav-item",children:a.jsxs("a",{href:`#${I(e,r.value)}`,"data-toggle":"tab","data-bs-toggle":"tab","data-value":r.value,className:o("nav-link",b&&"active"),children:[a.jsx(O,{icon:r.icon}),r.label]})},r.value)}),s&&n]})}function O({icon:e}){if(!e)return null;const t=/^fa[a-z]? /.test(e)?e:`fas ${e}`,n=t.split(" ").find(l=>l.startsWith("fa-"))??e;return a.jsx("i",{className:`${t} fa-fw`,role:"presentation","aria-label":`${n.replace(/^fa-/,"")} icon`})}function Ve(){return a.jsx("div",{className:"bslib-nav-spacer"})}function qe({children:e}){return a.jsx("li",{className:"bslib-nav-item nav-item form-inline",children:e})}const Fe={"--bslib-navbar-margin":"0"};function De({navId:e,value:t,active:n=!1,fillable:l,children:i}){const s=c.useContext(z),u=l??s,r=o("tab-pane",u&&"html-fill-item html-fill-container bslib-gap-spacing",n&&"active");return a.jsx("div",{className:r,"data-value":t,id:I(e,t),style:u?Fe:void 0,children:i})}const We=Object.freeze(Object.defineProperty({__proto__:null,ActionButton:ie,ActionLink:se,Card:Oe,CardBody:Pe,CardFooter:$e,CardHeader:Re,CheckboxInput:re,DTOutput:Se,DarkModeInput:oe,DownloadButton:ue,FileInput:ce,HelpText:ye,HostedInput:m,ImageOutput:te,LayoutColumns:Ae,LayoutSidebar:E,Module:le,NavItem:qe,NavSpacer:Ve,NavbarTabStrip:ze,NumericInput:de,PageFillable:ke,PageNavbar:Me,PageSidebar:Be,PasswordInput:be,PlotOutput:je,RadioButtons:pe,ReactableOutput:Ie,SelectInput:me,SelectizeInput:fe,ShinyModuleProvider:$,ShinyOutput:g,SliderInput:he,TabPane:De,TextAreaInput:ve,TextInput:xe,TextOutput:Ne,Tooltip:M,UiOutput:Ee,ValueBox:_e,VerbatimTextOutput:we,WidgetOutput:Ce,cx:o,provideShinyReact:U,tabPaneId:I,useHostedInputValue:ge,useNs:f,usePageBody:S,useSetShinyInput:G,useShinyBusy:K,useShinyInitialized:P,useShinyInput:Y,useShinyInputValue:J,useShinyMessageHandler:ee,useShinyOutputError:Q,useShinyOutputStatus:X,useShinyOutputValue:Z},Symbol.toStringTag,{value:"Module"}));window.bslibreact=We})(window.shinyreact.React);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: bslibreact
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Server helpers for bslibreact, React components that reproduce bslib and Shiny markup
|
|
5
|
+
Project-URL: Homepage, https://github.com/samuelbharti/bslibreact
|
|
6
|
+
Project-URL: Repository, https://github.com/samuelbharti/bslibreact
|
|
7
|
+
Project-URL: Changelog, https://github.com/samuelbharti/bslibreact/blob/main/pkg-py/CHANGELOG.md
|
|
8
|
+
Project-URL: Issues, https://github.com/samuelbharti/bslibreact/issues
|
|
9
|
+
Author-email: Samuel Bharti <samuelbharti.io@gmail.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: bslib,react,shiny,shinyreact
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: htmltools>=0.7.0
|
|
24
|
+
Requires-Dist: shiny>=1.8.0
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# bslibreact (Python)
|
|
28
|
+
|
|
29
|
+
[](https://lifecycle.r-lib.org/articles/stages.html#experimental)
|
|
30
|
+
|
|
31
|
+
Server helpers for [bslibreact](https://github.com/samuelbharti/bslibreact):
|
|
32
|
+
a React component kit that reproduces the exact markup `bslib` and Shiny
|
|
33
|
+
render, for apps built with [shinyreact](https://github.com/posit-dev/shinyreact)'s
|
|
34
|
+
`ui.tsx` pattern. This package is the Python half. It ships no UI of its own;
|
|
35
|
+
the components live in the npm package `bslibreact`.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install bslibreact shinyreact
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Use
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from pathlib import Path
|
|
47
|
+
from shiny import App, render, ui
|
|
48
|
+
from shinyreact import page_react
|
|
49
|
+
from bslibreact import page_deps, host_input
|
|
50
|
+
|
|
51
|
+
app_theme = (
|
|
52
|
+
ui.Theme.from_brand(Path(__file__).parent)
|
|
53
|
+
if (Path(__file__).parent / "_brand.yml").exists()
|
|
54
|
+
else ui.Theme()
|
|
55
|
+
)
|
|
56
|
+
app_ui = page_react(page_deps(app_theme), title="Demo", theme=app_theme)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def server(input, output, session):
|
|
60
|
+
host_input(output, "bins", lambda: ui.input_slider("bins", "Bins", 1, 50, 30))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
app = App(app_ui, server)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`page_deps()` attaches the CSS and JS that bslibreact's React components
|
|
67
|
+
need at runtime (ion.rangeSlider, selectize, and friends), the same way a
|
|
68
|
+
plain bslib page would pick them up automatically. A client with no build
|
|
69
|
+
step also attaches `bslibreact_dep()`, the prebuilt component bundle it
|
|
70
|
+
reads as `window.bslibreact`. `host_input()` mounts one server-rendered
|
|
71
|
+
Shiny widget (here, a slider) inside a React page and hands back the output
|
|
72
|
+
id your `<SliderInput id="bins_ui" />` element should read.
|
|
73
|
+
|
|
74
|
+
See the [root README](https://github.com/samuelbharti/bslibreact#readme) for
|
|
75
|
+
the full picture across R, Python and JS, and `docs/` in the repository for
|
|
76
|
+
the porting guide and the markup catalog spec this package's catalog
|
|
77
|
+
renderer implements.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
bslibreact/__init__.py,sha256=JgmdxsGIrsm1JWq7VcRyyyKhKLgWM3bLDaAnaTb0GC0,600
|
|
2
|
+
bslibreact/_catalog.py,sha256=eJRcEMNr2LAbkXKYAA-LWIMyz6F0zCqWM1GwSJ8xV9U,15925
|
|
3
|
+
bslibreact/_dep.py,sha256=ZbI-9E84MnhsT-ut4KsSI2Bj0gE0ODcyJYW5AMCvruQ,2899
|
|
4
|
+
bslibreact/_deps.py,sha256=cwklhhPJqDVM72UuRVlPNfS62aviNSqiJmVmt0AVm7E,4423
|
|
5
|
+
bslibreact/_host.py,sha256=iSSuRaHuXPA9WoX27h1l-HzqiBhvQr9QqkwznQbK-4M,4851
|
|
6
|
+
bslibreact/_version.py,sha256=GiUc8okB8Dx7_XWfs1lw7ZuslpgQ5GMwxMlaaDmhI2M,477
|
|
7
|
+
bslibreact/www/bslibreact.js,sha256=ugB3coc6_N0Pl81-MI4ShbttWlMs3vLhi0wFkFbuJXw,15622
|
|
8
|
+
bslibreact-0.1.0.dist-info/METADATA,sha256=DDVszUjUhWsz93_kHnf6X9gCTGxAWxJlPdjCNMzgd_M,3041
|
|
9
|
+
bslibreact-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
10
|
+
bslibreact-0.1.0.dist-info/licenses/LICENSE,sha256=u-yPv1bpA5DwVMUswncGzTfXyQQv_RvF5fqRoaKL32w,1070
|
|
11
|
+
bslibreact-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Samuel Bharti
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|