ex4nicegui 0.8.10__py3-none-any.whl → 0.9.1__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 (62) hide show
  1. ex4nicegui/__init__.py +3 -2
  2. ex4nicegui/bi/dataSource.py +1 -2
  3. ex4nicegui/bi/elements/ui_aggrid.py +1 -2
  4. ex4nicegui/bi/protocols.py +10 -20
  5. ex4nicegui/layout/rxFlex/index.py +0 -1
  6. ex4nicegui/reactive/EChartsComponent/ECharts.js +1 -1
  7. ex4nicegui/reactive/EChartsComponent/ECharts.py +1 -5
  8. ex4nicegui/reactive/UseDraggable/UseDraggable.py +3 -3
  9. ex4nicegui/reactive/__init__.py +2 -3
  10. ex4nicegui/reactive/_vmodel.py +150 -0
  11. ex4nicegui/reactive/base.py +2 -3
  12. ex4nicegui/reactive/deferredTask.py +2 -5
  13. ex4nicegui/reactive/mermaid/{mermaid.js → ex4ng_mermaid.js} +1 -1
  14. ex4nicegui/reactive/mermaid/mermaid.py +1 -6
  15. ex4nicegui/reactive/mixins/flexLayout.py +4 -8
  16. ex4nicegui/reactive/mixins/value_element.py +1 -2
  17. ex4nicegui/reactive/officials/echarts.py +1 -1
  18. ex4nicegui/reactive/systems/object_system.py +2 -4
  19. ex4nicegui/reactive/usePagination.py +2 -4
  20. ex4nicegui/reactive/vfor.py +1 -0
  21. ex4nicegui/toolbox/core/vue_use.py +1 -1
  22. ex4nicegui/utils/apiEffect.py +1 -2
  23. ex4nicegui/utils/clientScope.py +5 -8
  24. ex4nicegui/utils/effect.py +1 -2
  25. ex4nicegui/utils/page_state.py +70 -0
  26. ex4nicegui/utils/proxy/descriptor.py +0 -1
  27. ex4nicegui/utils/refComputed.py +1 -2
  28. ex4nicegui/utils/refreshable.py +122 -0
  29. ex4nicegui/utils/signals.py +5 -88
  30. {ex4nicegui-0.8.10.dist-info → ex4nicegui-0.9.1.dist-info}/METADATA +62 -21
  31. {ex4nicegui-0.8.10.dist-info → ex4nicegui-0.9.1.dist-info}/RECORD +54 -80
  32. {ex4nicegui-0.8.10.dist-info → ex4nicegui-0.9.1.dist-info}/WHEEL +1 -1
  33. ex4nicegui/gsap/__init__.py +0 -23
  34. ex4nicegui/gsap/gsap.py +0 -145
  35. ex4nicegui/gsap/timeline.js +0 -56
  36. ex4nicegui/gsap/timeline.py +0 -78
  37. ex4nicegui/gsap/wrapGsap.js +0 -48
  38. ex4nicegui/libs/gsap/.DS_Store +0 -0
  39. ex4nicegui/libs/gsap/CSSPlugin.js +0 -1577
  40. ex4nicegui/libs/gsap/CSSRulePlugin.js +0 -134
  41. ex4nicegui/libs/gsap/CustomEase.js +0 -371
  42. ex4nicegui/libs/gsap/Draggable.js +0 -2699
  43. ex4nicegui/libs/gsap/EasePack.js +0 -212
  44. ex4nicegui/libs/gsap/EaselPlugin.js +0 -341
  45. ex4nicegui/libs/gsap/Flip.js +0 -1518
  46. ex4nicegui/libs/gsap/MotionPathPlugin.js +0 -368
  47. ex4nicegui/libs/gsap/Observer.js +0 -686
  48. ex4nicegui/libs/gsap/PixiPlugin.js +0 -461
  49. ex4nicegui/libs/gsap/ScrollToPlugin.js +0 -273
  50. ex4nicegui/libs/gsap/ScrollTrigger.js +0 -2658
  51. ex4nicegui/libs/gsap/TextPlugin.js +0 -166
  52. ex4nicegui/libs/gsap/__init__.py +0 -0
  53. ex4nicegui/libs/gsap/all.js +0 -31
  54. ex4nicegui/libs/gsap/gsap-core.js +0 -4487
  55. ex4nicegui/libs/gsap/gsap.mjs +0 -6
  56. ex4nicegui/libs/gsap/utils/__init__.py +0 -0
  57. ex4nicegui/libs/gsap/utils/matrix.js +0 -420
  58. ex4nicegui/libs/gsap/utils/paths.js +0 -1487
  59. ex4nicegui/libs/gsap/utils/strings.js +0 -107
  60. ex4nicegui/reactive/local_file_picker.py +0 -208
  61. ex4nicegui/reactive/vmodel.py +0 -237
  62. {ex4nicegui-0.8.10.dist-info → ex4nicegui-0.9.1.dist-info/licenses}/LICENSE +0 -0
ex4nicegui/__init__.py CHANGED
@@ -8,8 +8,6 @@ from ex4nicegui.utils.types import (
8
8
  )
9
9
  from ex4nicegui.utils.scheduler import next_tick
10
10
  from ex4nicegui.utils.signals import (
11
- effect,
12
- effect_refreshable,
13
11
  to_raw,
14
12
  is_ref,
15
13
  to_ref,
@@ -23,9 +21,11 @@ from ex4nicegui.utils.signals import (
23
21
  batch,
24
22
  is_reactive,
25
23
  )
24
+ from ex4nicegui.utils.refreshable import effect, effect_refreshable
26
25
  from ex4nicegui.utils.asyncComputed import async_computed
27
26
  from ex4nicegui.utils.clientScope import new_scope
28
27
  from ex4nicegui.reactive.EChartsComponent.ECharts import reset_echarts_dependencies
28
+ from ex4nicegui.utils.page_state import PageState
29
29
  from .version import __version__
30
30
 
31
31
  __all__ = [
@@ -53,5 +53,6 @@ __all__ = [
53
53
  "new_scope",
54
54
  "next_tick",
55
55
  "reset_echarts_dependencies",
56
+ "PageState",
56
57
  "__version__",
57
58
  ]
@@ -182,8 +182,7 @@ class DataSource:
182
182
 
183
183
  @ng_client.on_disconnect
184
184
  def _(e: Client):
185
- if not e.shared:
186
- self._component_map.remove_client(e.id)
185
+ self._component_map.remove_client(e.id)
187
186
 
188
187
  info = ComponentInfo(
189
188
  ComponentInfoKey(client_id, element_id), update_callback, uiResult=ui_result
@@ -57,8 +57,7 @@ def ui_aggrid(
57
57
  data = self._dataSource.get_filtered_data(cp)
58
58
  new_opts = self._dataSource._idataSource.get_aggrid_options(data)
59
59
  options = _merge_options(fixed_options, new_opts)
60
- cp._props["options"] = options
61
- cp.update()
60
+ cp.options = {**options, "theme": kwargs.get("theme", "quartz")}
62
61
 
63
62
  self._dataSource._register_component(cp.id, on_source_update)
64
63
 
@@ -9,14 +9,11 @@ from ex4nicegui.utils import common as utils_common
9
9
 
10
10
 
11
11
  class IDataSourceAble(Protocol):
12
- def get_data(self) -> Any:
13
- ...
12
+ def get_data(self) -> Any: ...
14
13
 
15
- def reload(self, data) -> None:
16
- ...
14
+ def reload(self, data) -> None: ...
17
15
 
18
- def apply_filters(self, data, filters: List[_TFilterCallback]) -> Any:
19
- ...
16
+ def apply_filters(self, data, filters: List[_TFilterCallback]) -> Any: ...
20
17
 
21
18
  def duplicates_column_values(
22
19
  self,
@@ -25,30 +22,23 @@ class IDataSourceAble(Protocol):
25
22
  *,
26
23
  exclude_null_value=True,
27
24
  sort_options: Optional[_TDuplicates_column_values_sort_options] = None,
28
- ) -> List:
29
- ...
25
+ ) -> List: ...
30
26
 
31
- def get_aggrid_options(self, data) -> Dict:
32
- ...
27
+ def get_aggrid_options(self, data) -> Dict: ...
33
28
 
34
- def get_table_options(self, data) -> Dict:
35
- ...
29
+ def get_table_options(self, data) -> Dict: ...
36
30
 
37
- def slider_check(self, data, column_name: str) -> None:
38
- ...
31
+ def slider_check(self, data, column_name: str) -> None: ...
39
32
 
40
33
  def slider_min_max(
41
34
  self, data, column_name: str
42
- ) -> Tuple[Optional[float], Optional[float]]:
43
- ...
35
+ ) -> Tuple[Optional[float], Optional[float]]: ...
44
36
 
45
- def range_check(self, data, column_name: str) -> None:
46
- ...
37
+ def range_check(self, data, column_name: str) -> None: ...
47
38
 
48
39
  def range_min_max(
49
40
  self, data, column_name: str
50
- ) -> Tuple[Optional[float], Optional[float]]:
51
- ...
41
+ ) -> Tuple[Optional[float], Optional[float]]: ...
52
42
 
53
43
 
54
44
  class CallableDataSourceAble(IDataSourceAble):
@@ -53,7 +53,6 @@ class rx_column(ui.column, rx_flex_box):
53
53
  vertical: TColumn_Vertical = "top",
54
54
  ) -> None:
55
55
  super().__init__()
56
- self.tailwind.align_items
57
56
  self.horizontal(horizontal)
58
57
  self.vertical(vertical)
59
58
 
@@ -1,4 +1,4 @@
1
- import 'echarts'
1
+ import { echarts } from "nicegui-echart";
2
2
  import { convertDynamicProperties } from "../../static/utils/dynamic_properties.js";
3
3
 
4
4
  const registerThemeTask = Promise.all(
@@ -11,18 +11,14 @@ from nicegui.events import handle_event, UiEventArguments, GenericEventArguments
11
11
  from nicegui.element import Element
12
12
  from nicegui.awaitable_response import AwaitableResponse
13
13
  from pathlib import Path
14
- import nicegui
15
14
 
16
15
  from .types import (
17
16
  _T_event_name,
18
17
  )
19
18
  from .utils import get_bound_event_args, create_event_handler_args
20
19
 
21
- NG_ROOT = Path(nicegui.__file__).parent / "elements"
22
- libraries = [NG_ROOT / "lib/echarts/echarts.min.js"]
23
20
 
24
-
25
- class echarts(Element, component="ECharts.js", dependencies=libraries): # type: ignore
21
+ class echarts(Element, component="ECharts.js"):
26
22
  def __init__(
27
23
  self,
28
24
  options: Optional[dict] = None,
@@ -125,9 +125,9 @@ class UseDraggable(Element, component="UseDraggable.js"):
125
125
  self.on("update", inner_handler, args=_Update_Args)
126
126
 
127
127
  def apply(self, target: Element):
128
- assert (
129
- self.__target_id is None
130
- ), "draggable can only be applied to one current element"
128
+ assert self.__target_id is None, (
129
+ "draggable can only be applied to one current element"
130
+ )
131
131
  self.__target_id = target.id
132
132
  self._props["elementId"] = self.__target_id
133
133
  # self.run_method("applyTargetId", str(self.__target_id))
@@ -63,7 +63,7 @@ from .officials.range import LazyRangeBindableUi as lazy_range
63
63
  from .officials.tree import TreeBindableUi as tree
64
64
  from .officials.spinner import SpinnerBindableUi as spinner
65
65
 
66
- from .local_file_picker import local_file_picker
66
+
67
67
  from .UseDraggable.UseDraggable import use_draggable
68
68
  from .useMouse.UseMouse import use_mouse
69
69
 
@@ -73,7 +73,7 @@ from .fileWatcher import FilesWatcher
73
73
  from .mermaid.mermaid import Mermaid as mermaid
74
74
  from .officials.dialog import DialogBindableUi as dialog
75
75
  from .vfor import vfor, VforStore
76
- from .vmodel import vmodel
76
+ from ._vmodel import vmodel
77
77
  from .view_model import ViewModel, var, cached_var, list_var, dict_var
78
78
 
79
79
  pagination = q_pagination
@@ -89,7 +89,6 @@ __all__ = [
89
89
  "circular_progress",
90
90
  "knob",
91
91
  "UploadResult",
92
- "local_file_picker",
93
92
  "use_draggable",
94
93
  "use_mouse",
95
94
  "use_pagination",
@@ -0,0 +1,150 @@
1
+ from __future__ import annotations
2
+ import warnings
3
+ from ex4nicegui.utils.signals import (
4
+ RefWrapper,
5
+ TRef,
6
+ to_raw,
7
+ to_ref_wrapper,
8
+ to_value,
9
+ is_reactive,
10
+ is_ref,
11
+ )
12
+ from typing import (
13
+ Any,
14
+ Union,
15
+ cast,
16
+ Tuple,
17
+ )
18
+
19
+ from ex4nicegui.reactive.systems.object_system import get_attribute, set_attribute
20
+ from ex4nicegui.utils.types import _TMaybeRef as TMaybeRef, Ref
21
+
22
+
23
+ def create_writeable_wrapper(ref_obj: TRef, attrs: Tuple[Union[str, int], ...]):
24
+ if not attrs:
25
+
26
+ def maybe_ref_getter():
27
+ return to_value(ref_obj)
28
+
29
+ def reactive_getter():
30
+ return to_raw(ref_obj)
31
+
32
+ def setter(value):
33
+ pass
34
+
35
+ wrapper = to_ref_wrapper(
36
+ reactive_getter if is_reactive(ref_obj) else maybe_ref_getter,
37
+ setter,
38
+ )
39
+ wrapper._is_readonly = False
40
+ return wrapper
41
+
42
+ def getter():
43
+ obj = get_attribute(to_value(ref_obj), attrs[0])
44
+ for attr in attrs[1:]:
45
+ obj = get_attribute(obj, attr)
46
+
47
+ return obj
48
+
49
+ def setter(value):
50
+ obj = to_value(ref_obj)
51
+
52
+ for attr in attrs[:-1]:
53
+ obj = get_attribute(obj, attr)
54
+
55
+ set_attribute(obj, attrs[-1], value)
56
+
57
+ wrapper = to_ref_wrapper(
58
+ getter,
59
+ setter,
60
+ )
61
+ wrapper._is_readonly = False
62
+ return wrapper
63
+
64
+
65
+ def vmodel(ref_obj: Union[TRef[Any], Any], *attrs: Union[str, int]) -> Any:
66
+ """Create a two-way binding on a form input element or a component.
67
+
68
+ @see - https://github.com/CrystalWindSnake/ex4nicegui/blob/main/README.en.md#vmodel
69
+ @中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#vmodel
70
+
71
+ Args:
72
+ expr (Any): _description_
73
+
74
+ ## Examples
75
+
76
+ .. code-block:: python
77
+ from ex4nicegui.reactive import rxui
78
+ from ex4nicegui import deep_ref
79
+
80
+ data = deep_ref({"a": 1, "b": [1, 2, 3, 4]})
81
+
82
+ rxui.label(lambda: f"{data.value=!s}")
83
+
84
+ # No binding effect
85
+ rxui.input(value=data.value["a"])
86
+
87
+ # readonly binding
88
+ rxui.input(value=lambda: data.value["a"])
89
+
90
+ # two-way binding
91
+ rxui.input(value=rxui.vmodel(data.value,'a'))
92
+
93
+
94
+ """
95
+
96
+ if not is_ref(ref_obj):
97
+ result = cast(Any, ref_obj)
98
+ for attr in attrs:
99
+ result = result[attr]
100
+
101
+ return result
102
+
103
+ if isinstance(ref_obj, RefWrapper):
104
+ ref_obj._is_readonly = False
105
+
106
+ if attrs:
107
+ wrapper = create_writeable_wrapper(ref_obj, attrs)
108
+
109
+ return cast(
110
+ TRef,
111
+ wrapper,
112
+ )
113
+ else:
114
+ warnings.warn(
115
+ """Maybe you don't need to use vmodel""",
116
+ stacklevel=2,
117
+ )
118
+
119
+ return cast(
120
+ TRef,
121
+ ref_obj,
122
+ )
123
+
124
+
125
+ def vmodel_with_index(ref: Ref, index: TMaybeRef[int], *keys: Union[str, int]) -> Ref:
126
+ proxy = ref.value
127
+
128
+ def getter():
129
+ item = proxy[to_value(index)]
130
+ result = item
131
+
132
+ for k in keys:
133
+ result = get_attribute(result, k)
134
+ return result
135
+
136
+ def setter(value):
137
+ item = proxy[to_value(index)]
138
+
139
+ if len(keys) == 1:
140
+ set_attribute(item, keys[0], value)
141
+ return
142
+
143
+ obj = get_attribute(item, keys[0])
144
+
145
+ for k in keys[1:-1]:
146
+ set_attribute(obj, k, get_attribute(obj, k))
147
+
148
+ set_attribute(obj, keys[-1], value)
149
+
150
+ return RefWrapper(getter, setter) # type: ignore
@@ -25,7 +25,7 @@ from ex4nicegui.utils.signals import (
25
25
  )
26
26
  from ex4nicegui.utils.clientScope import new_scope
27
27
  from ex4nicegui.utils.types import _TMaybeRef as TMaybeRef
28
- from nicegui import Tailwind, ui
28
+ from nicegui import ui
29
29
  from nicegui.elements.mixins.text_element import TextElement
30
30
  from nicegui.elements.mixins.value_element import ValueElement
31
31
  from ex4nicegui.reactive.services.reactive_service import inject_handle_delete
@@ -58,7 +58,6 @@ class BindableUi(Generic[TWidget]):
58
58
  def __init__(self, element: TWidget) -> None:
59
59
  self._element = element
60
60
  inject_handle_delete(self.element, self._on_element_delete)
61
- self.tailwind = Tailwind(cast(ui.element, self._element))
62
61
  self._effect_scope = new_scope()
63
62
  self.__used_scope_style = False
64
63
 
@@ -185,7 +184,7 @@ class BindableUi(Generic[TWidget]):
185
184
 
186
185
  def props_str():
187
186
  props_dict = (
188
- f"""{name if isinstance(raw_value,bool) else f"{name}='{raw_value}'"}"""
187
+ f"""{name if isinstance(raw_value, bool) else f"{name}='{raw_value}'"}"""
189
188
  for name, value in props.items()
190
189
  if (raw_value := to_value(value))
191
190
  )
@@ -15,11 +15,8 @@ class DeferredTask:
15
15
  task()
16
16
 
17
17
  # Avoid events becoming ineffective due to page refresh when sharing the client.
18
- if not client.shared:
19
- # In a shared page, execution is required with every refresh.
20
- # note:https://github.com/CrystalWindSnake/ex4nicegui/issues/227
21
- self._tasks.clear()
22
- client.connect_handlers.remove(on_client_connect) # type: ignore
18
+ self._tasks.clear()
19
+ client.connect_handlers.remove(on_client_connect) # type: ignore
23
20
 
24
21
  ui.context.client.on_connect(on_client_connect)
25
22
 
@@ -1,4 +1,4 @@
1
- import mermaid from "mermaid";
1
+ import { mermaid } from "nicegui-mermaid";
2
2
  import { select as d3Select } from "d3-selection";
3
3
  import { zoom as d3Zoom, zoomIdentity } from "d3-zoom";
4
4
 
@@ -1,19 +1,14 @@
1
1
  from typing import Callable, List, Optional
2
2
  from nicegui.elements.mixins.content_element import ContentElement
3
3
  from pathlib import Path
4
- import nicegui
5
4
  from dataclasses import dataclass
6
5
  from nicegui.events import UiEventArguments
7
6
  from nicegui.dataclasses import KWONLY_SLOTS
8
7
 
9
- NG_ROOT = Path(nicegui.__file__).parent / "elements"
10
-
11
8
  EX4_LIBS_ROOT = Path(__file__).parent.parent.parent / "libs"
12
9
 
13
10
  dependencies = [
14
- NG_ROOT / "lib/mermaid/mermaid.esm.min.mjs",
15
11
  EX4_LIBS_ROOT / "d3/*.js",
16
- NG_ROOT / "lib/mermaid/*.js",
17
12
  ]
18
13
 
19
14
 
@@ -24,7 +19,7 @@ class NodeClickEventArguments(UiEventArguments):
24
19
 
25
20
  class Mermaid( # type: ignore
26
21
  ContentElement,
27
- component="mermaid.js",
22
+ component="ex4ng_mermaid.js",
28
23
  dependencies=dependencies, # type: ignore
29
24
  ):
30
25
  CONTENT_PROP = "content"
@@ -13,11 +13,9 @@ class FlexWrapMixin(Protocol):
13
13
  _ui_signal_on: Callable[[Callable[..., Any]], signe.Effect[None]]
14
14
 
15
15
  @property
16
- def element(self) -> ui.element:
17
- ...
16
+ def element(self) -> ui.element: ...
18
17
 
19
- def bind_style(self, classes) -> Self:
20
- ...
18
+ def bind_style(self, classes) -> Self: ...
21
19
 
22
20
  def bind_wrap(self, value: TMaybeRef[bool]) -> Self:
23
21
  return self.bind_style(
@@ -35,11 +33,9 @@ class FlexAlignItemsMixin(Protocol):
35
33
  _ui_signal_on: Callable[[Callable[..., Any]], signe.Effect[None]]
36
34
 
37
35
  @property
38
- def element(self) -> ui.element:
39
- ...
36
+ def element(self) -> ui.element: ...
40
37
 
41
- def bind_classes(self, classes) -> Self:
42
- ...
38
+ def bind_classes(self, classes) -> Self: ...
43
39
 
44
40
  def bind_align_items(self, value: TMaybeRef[str]):
45
41
  return self.bind_classes(lambda: f"items-{to_value(value)}")
@@ -10,8 +10,7 @@ class ValueElementMixin(Protocol, Generic[T]):
10
10
  _ui_signal_on: Callable[[Callable[..., Any]], signe.Effect[None]]
11
11
 
12
12
  @property
13
- def element(self) -> ValueElement:
14
- ...
13
+ def element(self) -> ValueElement: ...
15
14
 
16
15
  def bind_value(self, value: TMaybeRef[T]):
17
16
  @self._ui_signal_on(value) # type: ignore
@@ -140,7 +140,7 @@ class EChartsBindableUi(BindableUi[echarts]):
140
140
  const value = {{
141
141
  src: '{src}',
142
142
  type: '{type}',
143
- specialAreas: {json.dumps(special_areas).decode('utf-8')}
143
+ specialAreas: {json.dumps(special_areas).decode("utf-8")}
144
144
  }}
145
145
 
146
146
  window.ex4ngEchartsMapTasks.set('{map_name}', value);
@@ -8,14 +8,12 @@ from typing import (
8
8
 
9
9
  @runtime_checkable
10
10
  class GetItemProtocol(Protocol):
11
- def __getitem__(self, key):
12
- ...
11
+ def __getitem__(self, key): ...
13
12
 
14
13
 
15
14
  @runtime_checkable
16
15
  class SetItemProtocol(Protocol):
17
- def __setitem__(self, key, value):
18
- ...
16
+ def __setitem__(self, key, value): ...
19
17
 
20
18
 
21
19
  def get_attribute(obj: Union[object, GetItemProtocol], name: Union[str, int]) -> Any:
@@ -17,11 +17,9 @@ def _clamp(value, min_v, max_v) -> int:
17
17
 
18
18
 
19
19
  class _SourceProtocol(Protocol):
20
- def __len__(self) -> int:
21
- ...
20
+ def __len__(self) -> int: ...
22
21
 
23
- def __getitem__(self, __idx: slice) -> Any:
24
- ...
22
+ def __getitem__(self, __idx: slice) -> Any: ...
25
23
 
26
24
 
27
25
  class PaginationRef:
@@ -68,6 +68,7 @@ def wrapper_getter_setter(ref: Ref, index: Ref[int], *keys: Union[str, int]):
68
68
 
69
69
  if len(keys) == 0:
70
70
  proxy[index.value] = value
71
+ return
71
72
 
72
73
  if len(keys) == 1:
73
74
  item[keys[0]] = value
@@ -1,4 +1,4 @@
1
- from typing import Any, Callable, Dict, List, Optional
1
+ from typing import Any, Callable, List, Optional
2
2
  from nicegui.element import Element
3
3
  from collections import defaultdict
4
4
 
@@ -52,8 +52,7 @@ def ui_effect(
52
52
  debug_trigger: Optional[Callable] = None,
53
53
  debug_name: Optional[str] = None,
54
54
  scope: Optional[Scope] = None,
55
- ) -> signe.Effect[None]:
56
- ...
55
+ ) -> signe.Effect[None]: ...
57
56
 
58
57
 
59
58
  def ui_effect(
@@ -34,14 +34,11 @@ class NgClientScopeManager:
34
34
  if client.id not in self._client_scope_map:
35
35
  self._client_scope_map[client.id] = NgScopeSuite()
36
36
 
37
- # shared clients always not dispose their scope
38
- if not client.shared:
39
-
40
- @client.on_disconnect
41
- def _(e: Client):
42
- if e.id in self._client_scope_map:
43
- self._client_scope_map[e.id]._top_scope.dispose() # type: ignore
44
- del self._client_scope_map[e.id]
37
+ @client.on_disconnect
38
+ def _(e: Client):
39
+ if e.id in self._client_scope_map:
40
+ self._client_scope_map[e.id]._top_scope.dispose() # type: ignore
41
+ del self._client_scope_map[e.id]
45
42
 
46
43
  return self._client_scope_map[client.id]
47
44
 
@@ -47,8 +47,7 @@ def effect(
47
47
  priority_level=1,
48
48
  debug_trigger: Optional[Callable] = None,
49
49
  debug_name: Optional[str] = None,
50
- ) -> signe.Effect[None]:
51
- ...
50
+ ) -> signe.Effect[None]: ...
52
51
 
53
52
 
54
53
  def effect(
@@ -0,0 +1,70 @@
1
+ from typing import ClassVar
2
+ from typing_extensions import Self
3
+ from contextvars import ContextVar
4
+
5
+
6
+ class PageState:
7
+ """
8
+ A base class for defining per-page reactive state containers.
9
+
10
+ Each subclass of `PageState` provides an isolated state context that is automatically
11
+ managed through a `ContextVar`. When used inside different `ui.page` definitions,
12
+ each page will maintain its own independent instance of the subclass, ensuring
13
+ states do not interfere across pages.
14
+
15
+ Subclasses should define reactive variables (e.g., created via `to_ref`) within
16
+ their `__init__` method. The instance can then be accessed anywhere in the same
17
+ page context using `MyState.get()` — without needing to pass it through function parameters.
18
+
19
+ Typical use cases include sharing state across multiple components or functions
20
+ within the same page.
21
+
22
+ @see - https://github.com/CrystalWindSnake/ex4nicegui/blob/main/README.en.md#PageState
23
+ @中文文档 - https://gitee.com/carson_add/ex4nicegui/tree/main/#PageState
24
+
25
+ # Example:
26
+ .. code-block:: python
27
+ from ex4nicegui import rxui, to_ref, PageState
28
+ from nicegui import ui
29
+
30
+ class MyState(PageState):
31
+ def __init__(self):
32
+ self.a = to_ref(1.0)
33
+
34
+ def sub_view():
35
+ state = MyState.get()
36
+ rxui.label(lambda: f"{state.a.value=}")
37
+
38
+ @ui.page('/')
39
+ def _():
40
+ state = MyState.get()
41
+ rxui.number(value=state.a)
42
+ sub_view()
43
+ """
44
+
45
+ _ctx: ClassVar[ContextVar["PageState"]]
46
+
47
+ def __init_subclass__(cls, **kwargs):
48
+ super().__init_subclass__(**kwargs)
49
+ cls._ctx = ContextVar(f"{cls.__name__}_ctx")
50
+
51
+ @classmethod
52
+ def get(cls) -> Self:
53
+ """
54
+ Retrieves the current instance of the page state for this subclass.
55
+
56
+ If no instance exists yet in the current context, a new one is created
57
+ and stored. Subsequent calls within the same page will return the same instance.
58
+
59
+ This method allows components and functions within the same page
60
+ to share reactive state seamlessly without passing references explicitly.
61
+
62
+ Returns:
63
+ Self: The current `PageState` subclass instance associated with this page.
64
+ """
65
+ inst = cls._ctx.get(None)
66
+
67
+ if inst is None:
68
+ inst = cls()
69
+ cls._ctx.set(inst)
70
+ return inst # type: ignore
@@ -7,7 +7,6 @@ from .string import StringProxy
7
7
  from .float import FloatProxy
8
8
  from .bool import BoolProxy
9
9
  from .date import DateProxy
10
- from .dict import DictProxy
11
10
  from ex4nicegui.utils.signals import deep_ref
12
11
  import datetime
13
12
  import warnings
@@ -64,8 +64,7 @@ def ref_computed(
64
64
  debug_trigger: Optional[Callable[..., None]] = None,
65
65
  priority_level: int = 1,
66
66
  debug_name: Optional[str] = None,
67
- ) -> Callable[[Callable[..., T]], ReadonlyRef[T]]:
68
- ...
67
+ ) -> Callable[[Callable[..., T]], ReadonlyRef[T]]: ...
69
68
 
70
69
 
71
70
  def ref_computed(