solara-ui 1.40.0__py2.py3-none-any.whl → 1.42.0__py2.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.
Files changed (66) hide show
  1. solara/__init__.py +1 -1
  2. solara/__main__.py +30 -11
  3. solara/_stores.py +185 -0
  4. solara/components/component_vue.py +26 -2
  5. solara/components/echarts.py +5 -2
  6. solara/components/echarts.vue +22 -5
  7. solara/components/file_drop.py +20 -0
  8. solara/components/input.py +16 -0
  9. solara/components/markdown.py +22 -13
  10. solara/components/spinner-solara.vue +2 -2
  11. solara/components/spinner.py +17 -2
  12. solara/hooks/use_reactive.py +8 -1
  13. solara/reactive.py +9 -3
  14. solara/server/assets/style.css +2 -0
  15. solara/server/kernel.py +2 -1
  16. solara/server/qt.py +113 -0
  17. solara/server/settings.py +1 -0
  18. solara/server/starlette.py +2 -2
  19. solara/server/static/main-vuetify.js +10 -0
  20. solara/server/static/solara_bootstrap.py +1 -1
  21. solara/server/templates/loader-solara.html +1 -1
  22. solara/server/templates/solara.html.j2 +6 -1
  23. solara/settings.py +14 -0
  24. solara/template/portal/pyproject.toml +1 -1
  25. solara/test/pytest_plugin.py +3 -0
  26. solara/toestand.py +139 -16
  27. solara/util.py +22 -0
  28. solara/website/components/markdown.py +45 -1
  29. solara/website/components/sidebar.py +3 -1
  30. solara/website/pages/__init__.py +13 -7
  31. solara/website/pages/changelog/changelog.md +9 -0
  32. solara/website/pages/documentation/advanced/content/20-understanding/40-routing.md +17 -1
  33. solara/website/pages/documentation/api/cross_filter/cross_filter_dataframe.py +4 -5
  34. solara/website/pages/documentation/api/cross_filter/cross_filter_report.py +3 -5
  35. solara/website/pages/documentation/api/cross_filter/cross_filter_select.py +3 -5
  36. solara/website/pages/documentation/api/cross_filter/cross_filter_slider.py +3 -5
  37. solara/website/pages/documentation/api/hooks/use_cross_filter.py +3 -5
  38. solara/website/pages/documentation/api/hooks/use_exception.py +9 -11
  39. solara/website/pages/documentation/api/hooks/use_previous.py +6 -9
  40. solara/website/pages/documentation/api/hooks/use_state_or_update.py +23 -26
  41. solara/website/pages/documentation/api/hooks/use_thread.py +11 -13
  42. solara/website/pages/documentation/api/utilities/on_kernel_start.py +17 -0
  43. solara/website/pages/documentation/components/input/input.py +22 -0
  44. solara/website/pages/documentation/components/viz/echarts.py +3 -1
  45. solara/website/pages/documentation/examples/__init__.py +13 -21
  46. solara/website/pages/documentation/examples/ai/chatbot.py +1 -1
  47. solara/website/pages/documentation/examples/general/pokemon_search.py +3 -3
  48. solara/website/pages/documentation/examples/general/vue_component.py +1 -1
  49. solara/website/pages/documentation/examples/libraries/altair.py +1 -0
  50. solara/website/pages/documentation/examples/libraries/bqplot.py +1 -1
  51. solara/website/pages/documentation/examples/libraries/ipyleaflet.py +1 -1
  52. solara/website/pages/documentation/examples/libraries/ipyleaflet_advanced.py +1 -1
  53. solara/website/pages/documentation/examples/utilities/countdown_timer.py +18 -20
  54. solara/website/pages/documentation/examples/visualization/annotator.py +1 -3
  55. solara/website/pages/documentation/examples/visualization/linked_views.py +3 -6
  56. solara/website/pages/documentation/getting_started/content/00-quickstart.md +19 -1
  57. solara/website/pages/documentation/getting_started/content/01-introduction.md +1 -1
  58. solara/website/pages/roadmap/roadmap.md +3 -0
  59. solara/widgets/vue/navigator.vue +46 -16
  60. solara/widgets/vue/vegalite.vue +18 -0
  61. {solara_ui-1.40.0.dist-info → solara_ui-1.42.0.dist-info}/METADATA +8 -5
  62. {solara_ui-1.40.0.dist-info → solara_ui-1.42.0.dist-info}/RECORD +66 -64
  63. {solara_ui-1.40.0.dist-info → solara_ui-1.42.0.dist-info}/WHEEL +1 -1
  64. {solara_ui-1.40.0.data → solara_ui-1.42.0.data}/data/etc/jupyter/jupyter_notebook_config.d/solara.json +0 -0
  65. {solara_ui-1.40.0.data → solara_ui-1.42.0.data}/data/etc/jupyter/jupyter_server_config.d/solara.json +0 -0
  66. {solara_ui-1.40.0.dist-info → solara_ui-1.42.0.dist-info}/licenses/LICENSE +0 -0
@@ -9,7 +9,6 @@ The UI code demonstrates a lot of conditional rendering.
9
9
  import time
10
10
 
11
11
  import solara
12
- from solara.alias import rv
13
12
 
14
13
 
15
14
  @solara.component
@@ -41,24 +40,23 @@ def Page():
41
40
 
42
41
  solara.use_thread(run_timer, dependencies=[duration, running])
43
42
 
44
- with solara.VBox() as main:
45
- if not running:
46
- if duration < 1:
47
- solara.Error("Duration must be at least 1 second")
48
- else:
49
- solara.Markdown(f"# Timer set to {seconds} seconds")
43
+ if not running:
44
+ if duration < 1:
45
+ solara.Error("Duration must be at least 1 second")
50
46
  else:
51
- if seconds:
52
- solara.Markdown(f"# {seconds} seconds left")
53
- else:
54
- solara.solara.Markdown("# Time's up!")
55
- rv.TextField(type="number", v_model=duration, on_v_model=on_duration, disabled=running)
56
- with solara.HBox():
57
- if running:
58
- solara.Button("Stop", on_click=lambda: set_running(False), icon_name="mdi-stop")
47
+ solara.Markdown(f"# Timer set to {seconds} seconds")
48
+ else:
49
+ if seconds:
50
+ solara.Markdown(f"# {seconds} seconds left")
51
+ else:
52
+ solara.solara.Markdown("# Time's up!")
53
+
54
+ solara.v.TextField(type="number", v_model=duration, on_v_model=on_duration, disabled=running)
55
+ with solara.Row():
56
+ if running:
57
+ solara.Button("Stop", on_click=lambda: set_running(False), icon_name="mdi-stop")
58
+ else:
59
+ if duration != seconds:
60
+ solara.Button("Reset", on_click=lambda: set_seconds(duration), icon_name="mdi-restart")
59
61
  else:
60
- if duration != seconds:
61
- solara.Button("Reset", on_click=lambda: set_seconds(duration), icon_name="mdi-restart")
62
- else:
63
- solara.Button("Start", on_click=lambda: set_running(True), icon_name="mdi-play", disabled=seconds < 1)
64
- return main
62
+ solara.Button("Start", on_click=lambda: set_running(True), icon_name="mdi-play", disabled=seconds < 1)
@@ -45,9 +45,7 @@ def Page():
45
45
  fig = go.FigureWidget(
46
46
  layout=go.Layout(
47
47
  showlegend=False,
48
- autosize=False,
49
- width=600,
50
- height=600,
48
+ autosize=True,
51
49
  dragmode="drawrect",
52
50
  modebar={
53
51
  "add": [
@@ -49,8 +49,8 @@ def ClickScatter(df, x, y, color, click_row, on_click: Callable[[ClickPoint], No
49
49
  click_y = df[y].values[click_row]
50
50
  fig.add_trace(px.scatter(x=[click_x], y=[click_y], text=["⭐️"]).data[0])
51
51
  # make the figure a bit smaller
52
- fig.update_layout(width=400)
53
- with solara.VBox() as main:
52
+ fig.update_layout(width=340)
53
+ with solara.Column(style={"width": "340px"}) as main:
54
54
  solara.FigurePlotly(fig, on_click=on_click_trace)
55
55
  solara.Select(label="X-axis", value=x, values=columns, on_value=set_x)
56
56
  solara.Select(label="Y-axis", value=y, values=columns, on_value=set_y)
@@ -65,8 +65,7 @@ def Page():
65
65
  else:
66
66
  clicked_row = None
67
67
 
68
- with solara.VBox() as main:
69
- with solara.HBox():
68
+ with solara.Row(justify="center", style={"flex-wrap": "wrap"}):
70
69
  ClickScatter(df, "sepal_length", "sepal_width", "species", clicked_row, on_click=set_click_point)
71
70
  ClickScatter(df, "petal_length", "petal_width", "species", clicked_row, on_click=set_click_point)
72
71
  if click_point is not None:
@@ -80,5 +79,3 @@ def Page():
80
79
  )
81
80
  else:
82
81
  solara.Info("Click to select a point")
83
-
84
- return main
@@ -24,7 +24,7 @@ Run `pip install solara`, or follow the [Installation instructions](/documentati
24
24
 
25
25
  Put the following Python snippet in a file (we suggest `sol.py`), or put it in a Jupyter notebook cell:
26
26
 
27
- ```solara
27
+ ```solara {pycafe-link}
28
28
  import solara
29
29
 
30
30
  # Declare reactive variables at the top level. Components using these variables
@@ -87,3 +87,21 @@ In case you forgot how to start a notebook server:
87
87
  Or the more modern Jupyter lab:
88
88
 
89
89
  $ jupyter lab
90
+
91
+
92
+ ## Run as app (experimental)
93
+
94
+ You can also run the script as a standalone app. This requires the extra packages `qtpy` and `PySide6` (or `PyQt6`) to be installed.
95
+
96
+ ```bash
97
+ $ pip install pip install qtpy PySide6
98
+ ```
99
+
100
+ Run from the command line in the same directory where you put your file (`sol.py`):
101
+
102
+ ```bash
103
+ $ solara run sol.py --qt
104
+ ```
105
+
106
+
107
+ <img src="https://dxhl76zpt6fap.cloudfront.net/public/solara-quickstart-app.webp" alt="Markdown Monster icon"/>
@@ -48,7 +48,7 @@ Follow the [installation instructions](/documentation/getting_started/installing
48
48
 
49
49
  Create a file `myapp.py`, or put the following code in the Jupyter notebook:
50
50
 
51
- ```solara
51
+ ```solara {pycafe-link}
52
52
  import solara
53
53
 
54
54
  clicks = solara.reactive(0)
@@ -11,6 +11,9 @@ Exciting news! We aim to release Solara 2.0 by the end of the year. For the 2.0
11
11
 
12
12
  - Elimination of common mistakes, such as detecting state mutations and avoiding misuse of hooks (e.g., using hooks in loops).
13
13
 
14
+ State mutation detection will be the default for Solara 2.0, but can be enabled in Solara > 1.41.0 by setting the environment variable `SOLARA_STORAGE_MUTATION_DETECTION=1`.
15
+
16
+
14
17
  - [See more details in the 2.0 milestone on GitHub.](https://github.com/widgetti/solara/milestone/1)
15
18
 
16
19
 
@@ -14,43 +14,37 @@ modules.export = {
14
14
  }
15
15
  window.solara.router.push = (href) => {
16
16
  console.log("external router push", href);
17
- // take of the anchor
18
- if (href.indexOf("#") !== -1) {
19
- href = href.slice(0, href.indexOf("#"));
20
- }
21
- this.location = href;
17
+ const url = new URL(href, window.location.origin + solara.rootPath);
18
+ this.location = url.pathname + url.search;
19
+ this.hash = url.hash;
22
20
  };
23
21
  let location = window.location.pathname.slice(solara.rootPath.length);
24
- // take of the anchor
25
- if (location.indexOf("#") !== -1) {
26
- location = location.slice(0, location.indexOf("#"));
27
- }
28
22
  this.location = location + window.location.search;
23
+ this.hash = window.location.hash;
29
24
  window.addEventListener("popstate", this.onPopState);
30
25
  window.addEventListener("scroll", this.onScroll);
26
+ window.addEventListener("hashchange", this.onHashChange);
27
+ window.addEventListener("solara.pageReady", this.onPageLoad);
31
28
  },
32
29
  destroyed() {
33
30
  window.removeEventListener("popstate", this.onPopState);
34
31
  window.removeEventListener("scroll", this.onScroll);
32
+ window.removeEventListener("hashchange", this.onHashChange);
33
+ window.removeEventListener("solara.pageReady", this.onPageLoad);
35
34
  },
36
35
  methods: {
37
36
  onScroll() {
38
37
  window.history.replaceState(
39
38
  { top: document.documentElement.scrollTop },
40
39
  null,
41
- solara.rootPath + this.location
40
+ window.location.href
42
41
  );
43
42
  },
44
43
  onPopState(event) {
45
- console.log("pop state!", event.state, window.location.href);
46
44
  if (!window.location.pathname.startsWith(solara.rootPath)) {
47
45
  throw `window.location.pathname = ${window.location.pathname}, but it should start with the solara.rootPath = ${solara.rootPath}`;
48
46
  }
49
47
  let newLocation = window.location.pathname.slice(solara.rootPath.length);
50
- // the router/server shouldn't care about the hash, that's for the frontend
51
- if (newLocation.indexOf("#") !== -1) {
52
- newLocation = newLocation.slice(0, newLocation.indexOf("#"));
53
- }
54
48
  this.location = newLocation + window.location.search;
55
49
  if (event.state) {
56
50
  const top = event.state.top;
@@ -63,6 +57,32 @@ modules.export = {
63
57
  */
64
58
  }
65
59
  },
60
+ onHashChange(event) {
61
+ if (!window.location.pathname.startsWith(solara.rootPath)) {
62
+ throw `window.location.pathname = ${window.location.pathname}, but it should start with the solara.rootPath = ${solara.rootPath}`;
63
+ }
64
+ this.hash = window.location.hash;
65
+ },
66
+ onPageLoad(event) {
67
+ if (!window.location.pathname.startsWith(solara.rootPath)) {
68
+ throw `window.location.pathname = ${window.location.pathname}, but it should start with the solara.rootPath = ${solara.rootPath}`;
69
+ }
70
+ // If we've navigated to a hash with the same name on a different page the watch on hash won't trigger
71
+ if (this.hash && this.hash === window.location.hash) {
72
+ this.navigateToHash(this.hash);
73
+ }
74
+ this.hash = window.location.hash;
75
+ },
76
+ makeFullRelativeUrl() {
77
+ const url = new URL(this.location, window.location.origin + solara.rootPath);
78
+ return url.pathname + this.hash + url.search;
79
+ },
80
+ navigateToHash(hash) {
81
+ const targetEl = document.getElementById(hash.slice(1));
82
+ if (targetEl) {
83
+ targetEl.scrollIntoView();
84
+ }
85
+ },
66
86
  },
67
87
  watch: {
68
88
  location(value) {
@@ -89,7 +109,7 @@ modules.export = {
89
109
  document.documentElement.scrollTop
90
110
  );
91
111
  if (oldLocation != this.location) {
92
- window.history.pushState({ top: 0 }, null, solara.rootPath + this.location);
112
+ window.history.pushState({ top: 0 }, null, this.makeFullRelativeUrl());
93
113
  if (pathnameNew != pathnameOld) {
94
114
  // we scroll to the top only when we change page, not when we change
95
115
  // the search string
@@ -99,6 +119,16 @@ modules.export = {
99
119
  window.dispatchEvent(event);
100
120
  }
101
121
  },
122
+ hash(value) {
123
+ if (value) {
124
+ this.navigateToHash(value);
125
+ }
126
+ },
127
+ },
128
+ data() {
129
+ return {
130
+ hash: "",
131
+ };
102
132
  },
103
133
  };
104
134
  </script>
@@ -15,6 +15,11 @@ module.exports = {
15
15
  mounted() {
16
16
  this.do_plot_debounced();
17
17
  },
18
+ destroyed() {
19
+ if (this.observer) {
20
+ this.observer.disconnect();
21
+ }
22
+ },
18
23
  watch: {
19
24
  spec() {
20
25
  this.do_plot_debounced();
@@ -29,7 +34,15 @@ module.exports = {
29
34
  (async () => {
30
35
  const spec = {
31
36
  ...this.spec,
37
+ "renderer": "svg",
32
38
  };
39
+ if (spec.width === "container") {
40
+ this.$refs.plotElement.classList.add("width-container")
41
+ this.observer = new ResizeObserver(() => {
42
+ view.resize();
43
+ });
44
+ this.observer.observe(this.$refs.plotElement);
45
+ }
33
46
  const { view } = await vegaEmbed(this.$refs.plotElement, spec);
34
47
  // events https://github.com/vega/vega-view#event-handling
35
48
  if (this.listen_to_click) {
@@ -110,3 +123,8 @@ module.exports = {
110
123
  },
111
124
  }
112
125
  </script>
126
+ <style id="vega-embed-container-width">
127
+ .width-container.vega-embed {
128
+ width: 100%;
129
+ }
130
+ </style>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: solara-ui
3
- Version: 1.40.0
3
+ Version: 1.42.0
4
4
  Dynamic: Summary
5
5
  Project-URL: Home, https://www.github.com/widgetti/solara
6
6
  Project-URL: Documentation, https://solara.dev
@@ -26,7 +26,6 @@ License: The MIT License (MIT)
26
26
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
27
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28
28
  THE SOFTWARE.
29
- License-File: LICENSE
30
29
  Classifier: License :: OSI Approved :: MIT License
31
30
  Requires-Dist: humanize
32
31
  Requires-Dist: ipyvue>=1.9.0
@@ -35,9 +34,13 @@ Requires-Dist: ipywidgets>=7.7
35
34
  Requires-Dist: reacton>=1.7.1
36
35
  Requires-Dist: requests
37
36
  Provides-Extra: all
38
- Requires-Dist: solara-ui[cache]; extra == 'all'
39
- Requires-Dist: solara-ui[extra]; extra == 'all'
40
- Requires-Dist: solara-ui[markdown]; extra == 'all'
37
+ Requires-Dist: cachetools; extra == 'all'
38
+ Requires-Dist: markdown; extra == 'all'
39
+ Requires-Dist: numpy; extra == 'all'
40
+ Requires-Dist: pillow; extra == 'all'
41
+ Requires-Dist: pygments; extra == 'all'
42
+ Requires-Dist: pygments==2.10; (python_version < '3.7') and extra == 'all'
43
+ Requires-Dist: pymdown-extensions; extra == 'all'
41
44
  Provides-Extra: cache
42
45
  Requires-Dist: cachetools; extra == 'cache'
43
46
  Provides-Extra: extra