instaui 0.1.18__py2.py3-none-any.whl → 0.2.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 (46) hide show
  1. instaui/components/component.py +4 -7
  2. instaui/components/element.py +0 -16
  3. instaui/components/html/button.py +2 -2
  4. instaui/components/html/input.py +2 -2
  5. instaui/components/html/select.py +3 -2
  6. instaui/components/html/textarea.py +2 -2
  7. instaui/components/match.py +4 -6
  8. instaui/components/mixins.py +12 -0
  9. instaui/components/vfor.py +2 -4
  10. instaui/components/vif.py +2 -4
  11. instaui/event/js_event.py +0 -2
  12. instaui/event/vue_event.py +0 -3
  13. instaui/event/web_event.py +25 -5
  14. instaui/fastapi_server/middlewares.py +12 -0
  15. instaui/fastapi_server/server.py +5 -1
  16. instaui/js/fn.py +5 -1
  17. instaui/pre_setup.py +45 -0
  18. instaui/runtime/_app.py +7 -3
  19. instaui/runtime/scope.py +74 -33
  20. instaui/spa_router/_route_model.py +5 -6
  21. instaui/static/insta-ui.esm-browser.prod.js +1523 -1504
  22. instaui/static/insta-ui.js.map +1 -1
  23. instaui/static/templates/web.html +5 -3
  24. instaui/static/templates/webview.html +4 -2
  25. instaui/static/templates/zero.html +4 -2
  26. instaui/template/web_template.py +1 -0
  27. instaui/template/webview_template.py +1 -0
  28. instaui/template/zero_template.py +1 -0
  29. instaui/ui/__init__.py +2 -0
  30. instaui/ui/__init__.pyi +2 -0
  31. instaui/ui_functions/server.py +10 -1
  32. instaui/vars/data.py +5 -7
  33. instaui/vars/js_computed.py +6 -22
  34. instaui/vars/mixin_types/element_binding.py +1 -13
  35. instaui/vars/ref.py +5 -7
  36. instaui/vars/vue_computed.py +6 -21
  37. instaui/vars/web_computed.py +6 -23
  38. instaui/watch/js_watch.py +0 -2
  39. instaui/watch/web_watch.py +11 -2
  40. instaui/webview/resource.py +2 -0
  41. instaui/zero/func.py +2 -0
  42. {instaui-0.1.18.dist-info → instaui-0.2.0.dist-info}/METADATA +4 -3
  43. {instaui-0.1.18.dist-info → instaui-0.2.0.dist-info}/RECORD +45 -44
  44. instaui/webview/func.py +0 -114
  45. {instaui-0.1.18.dist-info → instaui-0.2.0.dist-info}/WHEEL +0 -0
  46. {instaui-0.1.18.dist-info → instaui-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -20,7 +20,7 @@ class Component(Jsonable):
20
20
  "Not allowed to create element outside of ui.page"
21
21
  )
22
22
 
23
- self.tag = (
23
+ self._tag = (
24
24
  "div"
25
25
  if tag is None or tag == ""
26
26
  else (
@@ -29,8 +29,6 @@ class Component(Jsonable):
29
29
  else str(tag)
30
30
  )
31
31
  )
32
- if isinstance(tag, ElementBindingMixin):
33
- tag._mark_used()
34
32
  self._slot_manager = SlotManager()
35
33
 
36
34
  get_app_slot().append_component_to_container(self)
@@ -43,7 +41,6 @@ class Component(Jsonable):
43
41
  self._slot_manager.default.__exit__(*_)
44
42
 
45
43
  def _to_json_dict(self) -> Dict:
46
- data = super()._to_json_dict()
47
- data["tag"] = self.tag
48
-
49
- return data
44
+ return {
45
+ "tag": self._tag,
46
+ }
@@ -271,28 +271,18 @@ class Element(Component):
271
271
 
272
272
  if isinstance(add, dict):
273
273
  self._dict_classes.update(**add) # type: ignore
274
- for value in (
275
- v for v in add.values() if isinstance(v, ElementBindingMixin)
276
- ):
277
- value._mark_used()
278
274
 
279
275
  if isinstance(add, ElementBindingMixin):
280
276
  self._bind_str_classes.append(add) # type: ignore
281
- add._mark_used()
282
277
 
283
278
  return self
284
279
 
285
280
  def style(self, add: Union[str, Dict[str, Any], TMaybeRef[str]]) -> Self:
286
281
  if isinstance(add, dict):
287
282
  add = {key: value for key, value in add.items()}
288
- for value in (
289
- v for v in add.values() if isinstance(v, ElementBindingMixin)
290
- ):
291
- value._mark_used()
292
283
 
293
284
  if isinstance(add, ElementBindingMixin):
294
285
  self._style_str_binds.append(add)
295
- add._mark_used()
296
286
  return self
297
287
 
298
288
  new_style = self._parse_style(add)
@@ -302,17 +292,11 @@ class Element(Component):
302
292
  def props(self, add: Union[str, Dict[str, Any], TMaybeRef]) -> Self:
303
293
  if isinstance(add, ElementBindingMixin):
304
294
  self._proxy_props.append(add)
305
- add._mark_used()
306
295
  return self
307
296
 
308
297
  if isinstance(add, dict):
309
298
  add = {key: value for key, value in add.items() if value is not None}
310
299
 
311
- for value in (
312
- v for v in add.values() if isinstance(v, ElementBindingMixin)
313
- ):
314
- value._mark_used()
315
-
316
300
  new_props = self._parse_props(add)
317
301
  self._props.update(new_props)
318
302
  return self
@@ -5,15 +5,15 @@ from typing import (
5
5
  Optional,
6
6
  )
7
7
  from instaui.components.element import Element
8
-
9
8
  from instaui.event.event_mixin import EventMixin
10
9
  from instaui.vars.types import TMaybeRef
10
+ from instaui.components.mixins import CanDisabledMixin
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  pass
14
14
 
15
15
 
16
- class Button(Element):
16
+ class Button(Element, CanDisabledMixin):
17
17
  def __init__(
18
18
  self,
19
19
  text: Optional[TMaybeRef[str]] = None,
@@ -2,14 +2,14 @@ from __future__ import annotations
2
2
  from typing import TYPE_CHECKING, Optional, Union
3
3
  from instaui.components.element import Element
4
4
  from instaui.components.value_element import ValueElement
5
-
5
+ from instaui.components.mixins import CanDisabledMixin
6
6
  from ._mixins import InputEventMixin
7
7
 
8
8
  if TYPE_CHECKING:
9
9
  from instaui.vars.types import TMaybeRef
10
10
 
11
11
 
12
- class Input(InputEventMixin, ValueElement[str]):
12
+ class Input(InputEventMixin, CanDisabledMixin, ValueElement[str]):
13
13
  def __init__(
14
14
  self,
15
15
  value: Union[str, TMaybeRef[str], None] = None,
@@ -9,11 +9,12 @@ from instaui.components.element import Element
9
9
  from instaui.event.event_mixin import EventMixin
10
10
  from instaui.vars.types import TMaybeRef
11
11
  from instaui.components.vfor import VFor
12
+ from instaui.components.mixins import CanDisabledMixin
12
13
 
13
14
  _T_Select_Value = Union[List[str], str]
14
15
 
15
16
 
16
- class Select(ValueElement[Union[List[str], str]]):
17
+ class Select(CanDisabledMixin, ValueElement[Union[List[str], str]]):
17
18
  def __init__(
18
19
  self,
19
20
  value: Union[_T_Select_Value, TMaybeRef[_T_Select_Value], None] = None,
@@ -48,7 +49,7 @@ class Select(ValueElement[Union[List[str], str]]):
48
49
 
49
50
  return select
50
51
 
51
- class Option(Element):
52
+ class Option(Element, CanDisabledMixin):
52
53
  def __init__(
53
54
  self,
54
55
  text: Optional[TMaybeRef[str]] = None,
@@ -2,14 +2,14 @@ from __future__ import annotations
2
2
  from typing import TYPE_CHECKING, Optional, Union
3
3
  from instaui.components.element import Element
4
4
  from instaui.components.value_element import ValueElement
5
-
5
+ from instaui.components.mixins import CanDisabledMixin
6
6
  from ._mixins import InputEventMixin
7
7
 
8
8
  if TYPE_CHECKING:
9
9
  from instaui.vars.types import TMaybeRef
10
10
 
11
11
 
12
- class Textarea(InputEventMixin, ValueElement[str]):
12
+ class Textarea(InputEventMixin, CanDisabledMixin, ValueElement[str]):
13
13
  def __init__(
14
14
  self,
15
15
  value: Union[str, TMaybeRef[str], None] = None,
@@ -11,8 +11,6 @@ class Match(Component):
11
11
  def __init__(self, on: ElementBindingMixin):
12
12
  super().__init__("match")
13
13
  self._on = on
14
- if isinstance(on, ElementBindingMixin):
15
- on._mark_used()
16
14
  self._default_case = None
17
15
 
18
16
  def _to_json_dict(self):
@@ -52,7 +50,7 @@ class Case(Component):
52
50
  def __init__(self, value: typing.Any):
53
51
  super().__init__("case")
54
52
  self._value = value
55
- self.__scope_manager = new_scope(append_to_app=False)
53
+ self.__scope_manager = new_scope()
56
54
  self.__scope = None
57
55
 
58
56
  def __enter__(self):
@@ -70,7 +68,7 @@ class Case(Component):
70
68
  }
71
69
  props = data["props"]
72
70
 
73
- props["scope"] = self.__scope
71
+ props["scopeId"] = self.__scope.id # type: ignore
74
72
 
75
73
  if self._slot_manager.has_slot():
76
74
  props["items"] = self._slot_manager
@@ -83,7 +81,7 @@ class DefaultCase(Component):
83
81
  def __init__(self):
84
82
  super().__init__("default-case")
85
83
 
86
- self.__scope_manager = new_scope(append_to_app=False)
84
+ self.__scope_manager = new_scope()
87
85
  self.__scope = None
88
86
 
89
87
  def __enter__(self):
@@ -99,7 +97,7 @@ class DefaultCase(Component):
99
97
  data["props"] = {}
100
98
  props = data["props"]
101
99
 
102
- props["scope"] = self.__scope
100
+ props["scopeId"] = self.__scope.id # type: ignore
103
101
 
104
102
  if self._slot_manager.has_slot():
105
103
  props["items"] = self._slot_manager
@@ -0,0 +1,12 @@
1
+ from typing import Dict, Protocol
2
+ from typing_extensions import Self
3
+ from instaui.vars.types import TMaybeRef
4
+
5
+
6
+ class PropsProtocol(Protocol):
7
+ def props(self, props: Dict) -> Self: ...
8
+
9
+
10
+ class CanDisabledMixin:
11
+ def disabled(self: PropsProtocol, disabled: TMaybeRef[bool] = True):
12
+ return self.props({"disabled": disabled})
@@ -50,11 +50,9 @@ class VFor(Component, Generic[_T]):
50
50
 
51
51
  super().__init__("vfor")
52
52
  self._data = data
53
- if isinstance(self._data, ElementBindingMixin):
54
- self._data._mark_used()
55
53
  self._key = key
56
54
  self._fid = get_app_slot().generate_vfor_id()
57
- self.__scope_manager = new_scope(append_to_app=False)
55
+ self.__scope_manager = new_scope()
58
56
  self.__scope = None
59
57
  self._num = None
60
58
  self._transition_group_setting = None
@@ -104,7 +102,7 @@ class VFor(Component, Generic[_T]):
104
102
  k: v for k, v in self._transition_group_setting.items() if v is not None
105
103
  }
106
104
 
107
- props["scope"] = self.__scope
105
+ props["scopeId"] = self.__scope.id # type: ignore
108
106
 
109
107
  if self._slot_manager.has_slot():
110
108
  props["items"] = self._slot_manager
instaui/components/vif.py CHANGED
@@ -10,9 +10,7 @@ class VIf(Component):
10
10
  def __init__(self, on: TMaybeRef[bool]):
11
11
  super().__init__("vif")
12
12
  self._on = cast(ElementBindingMixin, on)
13
- if isinstance(on, ElementBindingMixin):
14
- on._mark_used()
15
- self.__scope_manager = new_scope(append_to_app=False)
13
+ self.__scope_manager = new_scope()
16
14
  self.__scope = None
17
15
 
18
16
  def __enter__(self):
@@ -32,7 +30,7 @@ class VIf(Component):
32
30
  }
33
31
  props: Dict = data["props"]
34
32
 
35
- props["scope"] = self.__scope
33
+ props["scopeId"] = self.__scope.id # type: ignore
36
34
 
37
35
  if self._slot_manager.has_slot():
38
36
  props["items"] = self._slot_manager
instaui/event/js_event.py CHANGED
@@ -1,6 +1,5 @@
1
1
  import typing
2
2
  from instaui.vars.mixin_types.py_binding import CanInputMixin, CanOutputMixin
3
- from instaui.vars.mixin_types.element_binding import _try_mark_inputs_used
4
3
  from instaui.common.jsonable import Jsonable
5
4
  from .event_mixin import EventMixin
6
5
 
@@ -27,7 +26,6 @@ class JsEvent(Jsonable, EventMixin):
27
26
  def _to_json_dict(self):
28
27
  data = super()._to_json_dict()
29
28
  data["type"] = self.event_type()
30
- _try_mark_inputs_used(self._org_inputs)
31
29
 
32
30
  if self._inputs:
33
31
  data["inputs"] = self._inputs
@@ -1,7 +1,6 @@
1
1
  import typing
2
2
  from instaui.common.jsonable import Jsonable
3
3
  from instaui.vars.mixin_types.observable import ObservableMixin
4
- from instaui.vars.mixin_types.element_binding import _try_mark_inputs_used
5
4
  from .event_mixin import EventMixin
6
5
 
7
6
 
@@ -60,8 +59,6 @@ class VueEvent(Jsonable, EventMixin):
60
59
 
61
60
  def _to_json_dict(self):
62
61
  data = super()._to_json_dict()
63
-
64
- _try_mark_inputs_used((self._bindings or {}).values())
65
62
  data["type"] = self.event_type()
66
63
  return data
67
64
 
@@ -4,17 +4,16 @@ from typing_extensions import ParamSpec
4
4
  from instaui.common.jsonable import Jsonable
5
5
  from instaui.runtime._app import get_current_scope, get_app_slot
6
6
  from instaui.vars.mixin_types.py_binding import CanInputMixin, CanOutputMixin
7
- from instaui.vars.mixin_types.element_binding import _try_mark_inputs_used
8
7
  from instaui.handlers import event_handler
8
+ from instaui import pre_setup as _pre_setup
9
9
  from .event_mixin import EventMixin
10
10
 
11
+
11
12
  _SYNC_TYPE = "sync"
12
13
  _ASYNC_TYPE = "async"
13
14
 
14
15
  P = ParamSpec("P")
15
16
  R = typing.TypeVar("R")
16
- _T_input = typing.TypeVar("_T_input")
17
- _T_output = typing.TypeVar("_T_output")
18
17
 
19
18
 
20
19
  class WebEvent(Jsonable, EventMixin, typing.Generic[P, R]):
@@ -23,10 +22,15 @@ class WebEvent(Jsonable, EventMixin, typing.Generic[P, R]):
23
22
  fn: typing.Callable[P, R],
24
23
  inputs: typing.Sequence[CanInputMixin],
25
24
  outputs: typing.Sequence[CanOutputMixin],
25
+ pre_setup: typing.Optional[typing.Dict] = None,
26
26
  ):
27
+ if pre_setup:
28
+ _pre_setup._check_args(pre_setup)
29
+
27
30
  self._inputs = inputs
28
31
  self._outputs = outputs
29
32
  self._fn = fn
33
+ self._pre_setup = pre_setup
30
34
 
31
35
  scope = get_current_scope()
32
36
  self._sid = scope.id
@@ -47,8 +51,6 @@ class WebEvent(Jsonable, EventMixin, typing.Generic[P, R]):
47
51
  def _to_json_dict(self):
48
52
  app = get_app_slot()
49
53
 
50
- _try_mark_inputs_used(self._inputs)
51
-
52
54
  hkey = event_handler.create_handler_key(
53
55
  page_path=app.page_path, handler=self._fn
54
56
  )
@@ -76,6 +78,9 @@ class WebEvent(Jsonable, EventMixin, typing.Generic[P, R]):
76
78
  if self._outputs:
77
79
  data["set"] = [ref._to_output_config() for ref in self._outputs]
78
80
 
81
+ if self._pre_setup:
82
+ data["preSetup"] = _pre_setup.convert_config(self._pre_setup)
83
+
79
84
  return data
80
85
 
81
86
 
@@ -83,6 +88,7 @@ def event(
83
88
  *,
84
89
  inputs: typing.Optional[typing.Sequence] = None,
85
90
  outputs: typing.Optional[typing.Sequence] = None,
91
+ pre_setup: typing.Optional[typing.Dict] = None,
86
92
  ):
87
93
  """
88
94
  Creates an event handler decorator for binding reactive logic to component events.
@@ -95,6 +101,8 @@ def event(
95
101
  outputs (typing.Optional[typing.Sequence], optional): Targets (state variables, UI elements) that should
96
102
  update when this handler executes. Used for coordinating
97
103
  interface updates after the event is processed.
104
+ pre_setup (typing.Optional[typing.Dict], optional): A dictionary of pre-setup actions to be executed before the event executes.
105
+
98
106
 
99
107
  # Example:
100
108
  .. code-block:: python
@@ -109,6 +117,17 @@ def event(
109
117
  html.button("click me").on_click(plus_one)
110
118
  html.paragraph(a)
111
119
 
120
+ use pre_setup:
121
+ .. code-block:: python
122
+ a = ui.state(0)
123
+ task_running = ui.state(False)
124
+
125
+ @ui.event(inputs=[a], outputs=[a], pre_setup={task_running: True})
126
+ async def long_running_task(a):
127
+ await asyncio.sleep(3)
128
+ return a + 1
129
+
130
+ html.button("click me").on_click(long_running_task).disabled(task_running)
112
131
  """
113
132
 
114
133
  def wrapper(func: typing.Callable[P, R]):
@@ -116,6 +135,7 @@ def event(
116
135
  func,
117
136
  inputs or [],
118
137
  outputs=outputs or [],
138
+ pre_setup=pre_setup,
119
139
  )
120
140
 
121
141
  return wrapper
@@ -17,3 +17,15 @@ class RequestContextMiddleware(BaseHTTPMiddleware):
17
17
  reset_app_slot(system_slot_token)
18
18
 
19
19
  return response
20
+
21
+
22
+ class NoCacheDebugModeMiddleware(BaseHTTPMiddleware):
23
+ async def dispatch(self, request: Request, call_next: Callable) -> Any:
24
+ response = await call_next(request)
25
+
26
+ if request.url.path.endswith((".js", ".css")):
27
+ response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
28
+ response.headers["Pragma"] = "no-cache"
29
+ response.headers["Expires"] = "0"
30
+
31
+ return response
@@ -22,6 +22,7 @@ from instaui.page_info import PageInfo
22
22
 
23
23
  from instaui import consts
24
24
  from instaui.runtime._app import get_app_slot, get_default_app_slot
25
+ from instaui.runtime.context import get_context
25
26
  from instaui.runtime.dataclass import JsLink, VueAppComponent
26
27
  from instaui.template import web_template
27
28
 
@@ -30,7 +31,7 @@ from . import dependency_router
30
31
  from . import event_router
31
32
  from . import watch_router
32
33
  from . import debug_mode_router
33
- from .middlewares import RequestContextMiddleware
34
+ from .middlewares import RequestContextMiddleware, NoCacheDebugModeMiddleware
34
35
  from ._uvicorn import UvicornServer
35
36
  from . import resource
36
37
  from instaui.version import __version__ as _INSTA_VERSION
@@ -55,6 +56,8 @@ class Server:
55
56
  ):
56
57
  self.app = FastAPI()
57
58
  self.app.add_middleware(RequestContextMiddleware)
59
+ if get_context().debug_mode:
60
+ self.app.add_middleware(NoCacheDebugModeMiddleware)
58
61
 
59
62
  if use_gzip:
60
63
  self.app.add_middleware(
@@ -130,6 +133,7 @@ class Server:
130
133
  favicon_url = resource.record_resource(default_html_resource.favicon)
131
134
 
132
135
  model = web_template.WebTemplateModel(
136
+ version=_INSTA_VERSION,
133
137
  vue_js_link=VUE_JS_HASH_LINK,
134
138
  instaui_js_link=INSTAUI_JS_HASH_LINK,
135
139
  css_links=[
instaui/js/fn.py CHANGED
@@ -21,12 +21,13 @@ class JsFn(Jsonable, CanInputMixin):
21
21
  ui.label(result)
22
22
  """
23
23
 
24
- def __init__(self, code: str):
24
+ def __init__(self, code: str, *, execute_immediately=False):
25
25
  self.code = code
26
26
  self.__type = "jsFn"
27
27
  app = get_app_slot()
28
28
  app.register_js_fn(self)
29
29
  self.__id = app.generate_js_fn_id()
30
+ self._execute_immediately = execute_immediately
30
31
 
31
32
  def _to_input_config(self):
32
33
  return {
@@ -39,4 +40,7 @@ class JsFn(Jsonable, CanInputMixin):
39
40
  data["type"] = self.__type
40
41
  data["id"] = self.__id
41
42
 
43
+ if self._execute_immediately is True:
44
+ data["immediately"] = 1
45
+
42
46
  return data
instaui/pre_setup.py ADDED
@@ -0,0 +1,45 @@
1
+ from typing import Dict, Sequence, cast
2
+ from instaui.common.jsonable import Jsonable
3
+ from instaui.vars.mixin_types.py_binding import CanOutputMixin, CanInputMixin
4
+
5
+
6
+ def _check_args(config: Dict):
7
+ for key in config.keys():
8
+ if not isinstance(key, CanOutputMixin):
9
+ raise TypeError(f"key {key} is not a CanOutputMixin")
10
+
11
+
12
+ def convert_config(config: Dict):
13
+ return [
14
+ {
15
+ "target": cast(CanInputMixin, key)._to_input_config(),
16
+ **value._to_json_dict(),
17
+ }
18
+ if isinstance(value, PreSetupAction)
19
+ else {
20
+ "type": "const",
21
+ "target": cast(CanInputMixin, key)._to_input_config(),
22
+ "value": value,
23
+ }
24
+ for key, value in config.items()
25
+ ]
26
+
27
+
28
+ class PreSetupAction(Jsonable):
29
+ def __init__(self, *, inputs: Sequence, code: str, reset: bool = True):
30
+ self.type = "action"
31
+ self._inputs = inputs
32
+ self.code = code
33
+ self.reset = reset
34
+
35
+ def _to_json_dict(self):
36
+ data = super()._to_json_dict()
37
+ if self._inputs:
38
+ data["inputs"] = [
39
+ binding._to_input_config()
40
+ if isinstance(binding, CanInputMixin)
41
+ else binding
42
+ for binding in self._inputs
43
+ ]
44
+
45
+ return data
instaui/runtime/_app.py CHANGED
@@ -30,7 +30,7 @@ class App(Jsonable):
30
30
  self._vfor_id_counter = 0
31
31
  self._slot_id_counter = 0
32
32
  self._js_fn_id_counter = 0
33
- self.mode: _T_App_Mode = mode
33
+ self._mode: _T_App_Mode = mode
34
34
  self.items: List[Component] = []
35
35
  self.meta = meta
36
36
  self._slots_stacks: List[Slot] = []
@@ -49,6 +49,10 @@ class App(Jsonable):
49
49
  self._route_collector: Optional[RouteCollector] = None
50
50
  self._js_fns: List[JsFn] = []
51
51
 
52
+ @property
53
+ def mode(self) -> _T_App_Mode:
54
+ return self._mode
55
+
52
56
  @property
53
57
  def page_path(self) -> str:
54
58
  assert self._page_path is not None, "Page path is not set"
@@ -126,8 +130,8 @@ class App(Jsonable):
126
130
 
127
131
  data["url"] = url_info
128
132
 
129
- assert len(self._scopes) == 1, "Only one scope is allowed"
130
- data["scope"] = self._scopes[0]
133
+ data["scopeId"] = self._scopes[0].id
134
+ data["scopes"] = self._scopes
131
135
 
132
136
  if self._route_collector is not None:
133
137
  data["router"] = self._route_collector.model_dump(
instaui/runtime/scope.py CHANGED
@@ -1,6 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, List
3
+ from typing import TYPE_CHECKING, Callable, List
4
+ import functools
5
+ import weakref
4
6
  from instaui.common.jsonable import Jsonable
5
7
 
6
8
 
@@ -58,21 +60,6 @@ class Scope(Jsonable):
58
60
  "on": [v._to_input_config() for v in on],
59
61
  }
60
62
 
61
- def register_ref(self, var: VarMixin) -> None:
62
- self._refs.append(var)
63
-
64
- def register_data(self, data: ConstData) -> None:
65
- self._const_data.append(data)
66
-
67
- def register_js_computed(self, computed: JsComputed) -> None:
68
- self._js_computeds.append(computed)
69
-
70
- def register_vue_computed(self, computed: VueComputed) -> None:
71
- self._vue_computeds.append(computed)
72
-
73
- def register_web_computed(self, computed: WebComputed) -> None:
74
- self._web_computeds.append(computed)
75
-
76
63
  def register_web_watch(self, watch: WebWatch) -> None:
77
64
  self._web_watchs.append(watch)
78
65
 
@@ -82,6 +69,51 @@ class Scope(Jsonable):
82
69
  def register_vue_watch(self, watch: VueWatch) -> None:
83
70
  self._vue_watchs.append(watch)
84
71
 
72
+ def register_data_task(self, data: ConstData):
73
+ weak_obj = weakref.ref(data)
74
+
75
+ def register_fn():
76
+ self._const_data.append(weak_obj()) # type: ignore
77
+ return self.generate_vars_id()
78
+
79
+ return VarRegisterTask(self.id, register_fn)
80
+
81
+ def register_ref_task(self, ref: VarMixin):
82
+ weak_obj = weakref.ref(ref)
83
+
84
+ def register_fn():
85
+ self._refs.append(weak_obj()) # type: ignore
86
+ return self.generate_vars_id()
87
+
88
+ return VarRegisterTask(self.id, register_fn)
89
+
90
+ def register_js_computed_task(self, computed: JsComputed):
91
+ weak_obj = weakref.ref(computed)
92
+
93
+ def register_fn():
94
+ self._js_computeds.append(weak_obj()) # type: ignore
95
+ return self.generate_vars_id()
96
+
97
+ return VarRegisterTask(self.id, register_fn)
98
+
99
+ def register_computed_task(self, computed: WebComputed):
100
+ weak_obj = weakref.ref(computed)
101
+
102
+ def register_fn():
103
+ self._web_computeds.append(weak_obj()) # type: ignore
104
+ return self.generate_vars_id()
105
+
106
+ return VarRegisterTask(self.id, register_fn)
107
+
108
+ def register_vue_computed_task(self, computed: VueComputed):
109
+ weak_obj = weakref.ref(computed)
110
+
111
+ def register_fn():
112
+ self._vue_computeds.append(weak_obj()) # type: ignore
113
+ return self.generate_vars_id()
114
+
115
+ return VarRegisterTask(self.id, register_fn)
116
+
85
117
  def _to_json_dict(self):
86
118
  data = super()._to_json_dict()
87
119
  if self._refs:
@@ -97,23 +129,12 @@ class Scope(Jsonable):
97
129
  if self._element_refs:
98
130
  data["eRefs"] = self._element_refs
99
131
 
100
- # web computeds
101
- _web_computeds = [
102
- computed for computed in self._web_computeds if computed._is_used()
103
- ]
104
-
105
- if _web_computeds:
106
- data["web_computed"] = _web_computeds
132
+ if self._web_computeds:
133
+ data["web_computed"] = self._web_computeds
107
134
 
108
- # js computeds
109
- _js_computeds = [
110
- computed for computed in self._js_computeds if computed._is_used()
111
- ]
135
+ if self._js_computeds:
136
+ data["js_computed"] = self._js_computeds
112
137
 
113
- if _js_computeds:
114
- data["js_computed"] = _js_computeds
115
-
116
- # vue computeds
117
138
  if self._vue_computeds:
118
139
  data["vue_computed"] = self._vue_computeds
119
140
  if self._const_data:
@@ -126,12 +147,18 @@ class GlobalScope(Scope):
126
147
  def __init__(self, id: str) -> None:
127
148
  super().__init__(id)
128
149
 
129
- def register_ref(self, var: VarMixin) -> None:
150
+ def register_ref_task(self, var: VarMixin) -> None:
130
151
  raise ValueError("Can not register ref in global scope")
131
152
 
132
- def register_web_computed(self, computed: WebComputed) -> None:
153
+ def register_computed_task(self, computed: WebComputed) -> None:
133
154
  raise ValueError("Can not register web_computeds in global scope")
134
155
 
156
+ def register_js_computed_task(self, computed: JsComputed):
157
+ raise ValueError("Can not register js_computeds in global scope")
158
+
159
+ def register_vue_computed_task(self, computed: VueComputed):
160
+ raise ValueError("Can not register vue_computeds in global scope")
161
+
135
162
  def register_web_watch(self, watch: WebWatch) -> None:
136
163
  raise ValueError("Can not register web_watchs in global scope")
137
164
 
@@ -140,3 +167,17 @@ class GlobalScope(Scope):
140
167
 
141
168
  def register_vue_watch(self, watch: VueWatch) -> None:
142
169
  raise ValueError("Can not register vue_watchs in global scope")
170
+
171
+
172
+ class VarRegisterTask:
173
+ def __init__(self, scope_id: str, register_fn: Callable[[], str]) -> None:
174
+ self._scope_id = scope_id
175
+ self._id_gen_fn = functools.lru_cache(maxsize=1)(register_fn)
176
+
177
+ @property
178
+ def scope_id(self) -> str:
179
+ return self._scope_id
180
+
181
+ @property
182
+ def var_id(self) -> str:
183
+ return self._id_gen_fn()