instaui 0.1.9__py3-none-any.whl → 0.1.11__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.
@@ -46,7 +46,7 @@
46
46
 
47
47
  <script type="module">
48
48
  import { createApp } from 'vue'
49
- import installInsta from 'instaui'
49
+ import {install as installInsta} from 'instaui'
50
50
  const True = true;
51
51
  const False = false;
52
52
  const None = undefined;
@@ -43,7 +43,7 @@
43
43
 
44
44
  <script type="module">
45
45
  import { createApp } from 'vue'
46
- import installInsta from 'instaui'
46
+ import {install as installInsta} from 'instaui'
47
47
  const True = true;
48
48
  const False = false;
49
49
  const None = undefined;
@@ -43,7 +43,7 @@
43
43
 
44
44
  <script type="module">
45
45
  import { createApp } from 'vue'
46
- import installInsta from 'instaui'
46
+ import {install as installInsta} from 'instaui'
47
47
  const True = true;
48
48
  const False = false;
49
49
  const None = undefined;
@@ -35,6 +35,21 @@ def get_fn_params_infos(fn: Callable) -> List[Tuple[str, type]]:
35
35
  ]
36
36
 
37
37
 
38
+ def is_last_param_args(fn):
39
+ """
40
+ Returns:
41
+ bool: True if the last parameter of the function is `*args`, False otherwise.
42
+ """
43
+ sig = inspect.signature(fn)
44
+ params = list(sig.parameters.values())
45
+
46
+ if not params:
47
+ return False
48
+
49
+ last_param = params[-1]
50
+ return last_param.kind == inspect.Parameter.VAR_POSITIONAL
51
+
52
+
38
53
  def get_required_param_count(fn: Callable):
39
54
  signature = inspect.signature(fn)
40
55
  params = signature.parameters
instaui/ui/__init__.py CHANGED
@@ -29,6 +29,7 @@ __all__ = [
29
29
  "element",
30
30
  "vfor",
31
31
  "content",
32
+ "label",
32
33
  "add_style",
33
34
  "add_css_link",
34
35
  "remove_css_link",
@@ -42,6 +43,7 @@ __all__ = [
42
43
  "page",
43
44
  "event",
44
45
  "js_event",
46
+ "vue_event",
45
47
  "event_context",
46
48
  "TEventFn",
47
49
  "server",
@@ -103,9 +105,11 @@ from instaui.components.vfor import VFor as vfor
103
105
  from instaui.components.vif import VIf as vif
104
106
  from instaui.components.match import Match as match
105
107
  from instaui.components.content import Content as content
108
+ from instaui.components.label import label
106
109
 
107
- from instaui.event.web_event import ui_event as event, WebEvent as TEventFn
110
+ from instaui.event.web_event import event, WebEvent as TEventFn
108
111
  from instaui.event.js_event import js_event
112
+ from instaui.event.vue_event import vue_event
109
113
  from instaui.vars.event_context import EventContext as event_context
110
114
  from instaui.runtime.context import get_context as context
111
115
 
instaui/ui/__init__.pyi CHANGED
@@ -29,6 +29,7 @@ __all__ = [
29
29
  "element",
30
30
  "vfor",
31
31
  "content",
32
+ "label",
32
33
  "add_style",
33
34
  "add_css_link",
34
35
  "remove_css_link",
@@ -42,6 +43,7 @@ __all__ = [
42
43
  "page",
43
44
  "event",
44
45
  "js_event",
46
+ "vue_event",
45
47
  "event_context",
46
48
  "TEventFn",
47
49
  "server",
@@ -103,9 +105,11 @@ from instaui.components.vfor import VFor as vfor
103
105
  from instaui.components.vif import VIf as vif
104
106
  from instaui.components.match import Match as match
105
107
  from instaui.components.content import Content as content
108
+ from instaui.components.label import label
106
109
 
107
- from instaui.event.web_event import ui_event as event, WebEvent as TEventFn
110
+ from instaui.event.web_event import event, WebEvent as TEventFn
108
111
  from instaui.event.js_event import js_event
112
+ from instaui.event.vue_event import vue_event
109
113
  from instaui.vars.event_context import EventContext as event_context
110
114
  from instaui.runtime.context import get_context as context
111
115
 
@@ -2,15 +2,15 @@ from typing import Callable
2
2
  from instaui.launch_collector import get_launch_collector, PageInfo
3
3
 
4
4
 
5
- def page(url: str = "/"):
5
+ def page(path: str = "/"):
6
6
  """Register a page route.
7
7
 
8
8
  Args:
9
- url (str): The route URL.
9
+ path (str): The route path.
10
10
  """
11
11
 
12
12
  def wrapper(func: Callable):
13
- get_launch_collector().register_page(PageInfo(url, func))
13
+ get_launch_collector().register_page(PageInfo(path, func))
14
14
  return func
15
15
 
16
16
  return wrapper
@@ -25,14 +25,34 @@ class JsComputed(
25
25
  StrFormatBindingMixin,
26
26
  ElementBindingMixin,
27
27
  ):
28
+ """
29
+ A client-side computed property that evaluates JavaScript code to derive reactive values.
30
+
31
+ Args:
32
+ inputs (typing.Optional[typing.Sequence[TObservableInput]], optional): Reactive data sources to monitor.
33
+ Changes to these values trigger re-computation.
34
+ code (str): JavaScript code to execute for computing the value.
35
+ async_init_value (typing.Optional[typing.Any], optional): Initial value to use before first successful async evaluation.
36
+
37
+ # Example:
38
+ .. code-block:: python
39
+ a = ui.state(0)
40
+
41
+ plus_one = ui.js_computed(inputs=[a], code="a=> a+1")
42
+
43
+ html.number(a)
44
+ ui.label(plus_one)
45
+ """
46
+
28
47
  BIND_TYPE = "var"
29
48
 
30
49
  def __init__(
31
50
  self,
32
51
  *,
33
52
  inputs: typing.Optional[typing.Sequence[TObservableInput]] = None,
34
- code: str = "",
53
+ code: str,
35
54
  async_init_value: typing.Optional[typing.Any] = None,
55
+ deep_compare_on_input: bool = False,
36
56
  ) -> None:
37
57
  self.code = code
38
58
 
@@ -46,6 +66,7 @@ class JsComputed(
46
66
  )
47
67
 
48
68
  self._async_init_value = async_init_value
69
+ self._deep_compare_on_input = deep_compare_on_input
49
70
 
50
71
  def __to_binding_config(self):
51
72
  return {
@@ -87,6 +108,9 @@ class JsComputed(
87
108
  if self._async_init_value is not None:
88
109
  data["asyncInit"] = self._async_init_value
89
110
 
111
+ if self._deep_compare_on_input is not False:
112
+ data["deepEqOnInput"] = 1
113
+
90
114
  return data
91
115
 
92
116
 
instaui/vars/state.py CHANGED
@@ -78,5 +78,20 @@ class StateModel(BaseModel, Jsonable):
78
78
 
79
79
 
80
80
  def state(value: _T) -> _T:
81
+ """
82
+ Creates a reactive state object that tracks changes and notifies dependencies.
83
+
84
+ Args:
85
+ value (_T): The initial value to wrap in a reactive state container.
86
+ Can be any type (primitive, object, or complex data structure).
87
+
88
+ # Example:
89
+ .. code-block:: python
90
+ from instaui import ui,html
91
+ count = ui.state(0)
92
+
93
+ html.number(count)
94
+ ui.label(count)
95
+ """
81
96
  obj = RefProxy(_ProxyModel(value)) # type: ignore
82
97
  return obj # type: ignore
@@ -50,6 +50,7 @@ class WebComputed(
50
50
  extend_outputs: Optional[Sequence[CanOutputMixin]] = None,
51
51
  init_value: Optional[R] = None,
52
52
  evaluating: Optional[CanOutputMixin] = None,
53
+ deep_compare_on_input: bool = False,
53
54
  debug_info: Optional[Dict] = None,
54
55
  ) -> None:
55
56
  scope = get_current_scope()
@@ -65,6 +66,7 @@ class WebComputed(
65
66
  self._fn = func
66
67
  self._init_value = init_value
67
68
  self._evaluating = evaluating
69
+ self._deep_compare_on_input = deep_compare_on_input
68
70
 
69
71
  if debug_info is not None:
70
72
  self.debug = debug_info
@@ -111,6 +113,9 @@ class WebComputed(
111
113
  if self._evaluating:
112
114
  data["running"] = self._evaluating._to_output_config()
113
115
 
116
+ if self._deep_compare_on_input is not False:
117
+ data["deepEqOnInput"] = 1
118
+
114
119
  return data
115
120
 
116
121
  # return {}
@@ -144,8 +149,38 @@ def web_computed(
144
149
  extend_outputs: Optional[Sequence] = None,
145
150
  init_value: Optional[Any] = None,
146
151
  evaluating: Optional[Any] = None,
152
+ deep_compare_on_input: bool = False,
147
153
  debug_info: Optional[Dict] = None,
148
154
  ):
155
+ """
156
+ Creates a computed property decorator for reactive programming with dependency tracking.
157
+
158
+ This decorator factory wraps functions to create reactive computed properties that:
159
+ - Automatically re-evaluate when dependencies (inputs) change
160
+ - Cache results for performance optimization
161
+ - Support both synchronous and asynchronous computation patterns
162
+
163
+ Args:
164
+ inputs (Optional[Sequence], optional): Collection of reactive sources that trigger recomputation
165
+ when changed. These can be state objects or other computed properties.
166
+ extend_outputs (Optional[Sequence], optional): Additional outputs to notify when this computed value updates.
167
+ init_value (Optional[Any], optional): Initial value to return before first successful evaluation.
168
+ evaluating (Optional[Any], optional): Temporary value returned during asynchronous computation.
169
+
170
+ # Example:
171
+ .. code-block:: python
172
+ from instaui import ui,html
173
+
174
+ a = ui.state(0)
175
+
176
+ @ui.computed(inputs=[a])
177
+ def plus_one(a):
178
+ return a + 1
179
+
180
+ html.number(a)
181
+ ui.label(plus_one)
182
+ """
183
+
149
184
  def wrapper(func: Callable[P, R]):
150
185
  return WebComputed(
151
186
  func,
@@ -153,6 +188,7 @@ def web_computed(
153
188
  extend_outputs=extend_outputs,
154
189
  init_value=init_value,
155
190
  evaluating=evaluating,
191
+ deep_compare_on_input=deep_compare_on_input,
156
192
  debug_info=debug_info,
157
193
  )
158
194
 
instaui/watch/js_watch.py CHANGED
@@ -21,6 +21,8 @@ class JsWatch(Jsonable):
21
21
  once: bool = False,
22
22
  flush: typing.Optional[_types.TFlush] = None,
23
23
  ) -> None:
24
+ inputs = observable_helper.auto_made_inputs_to_slient(inputs, outputs)
25
+
24
26
  get_current_scope().register_js_watch(self)
25
27
 
26
28
  self.code = code
@@ -65,10 +67,44 @@ def js_watch(
65
67
  *,
66
68
  inputs: typing.Optional[typing.Sequence] = None,
67
69
  outputs: typing.Optional[typing.Sequence] = None,
68
- code: str = "",
70
+ code: str,
69
71
  immediate: bool = True,
70
72
  deep: typing.Union[bool, int] = False,
71
73
  once: bool = False,
72
74
  flush: typing.Optional[_types.TFlush] = None,
73
75
  ):
76
+ """
77
+ Creates a client-side observer that executes JavaScript code in response to reactive source changes.
78
+
79
+ Args:
80
+ inputs (typing.Optional[typing.Sequence], optional): Reactive sources to observe. Changes to these sources
81
+ trigger the watcher's JavaScript execution.
82
+ outputs (typing.Optional[typing.Sequence], optional): Output targets associated with this watcher. Used for
83
+ coordination with other observers.
84
+ code (str, optional): JavaScript code to execute when changes are detected. The code has access
85
+ to the current values of observed inputs through the `args` parameter.
86
+ immediate (bool, optional):If True, executes the watcher immediately after creation with current values. Defaults to True.
87
+ deep (typing.Union[bool, int], optional): Controls depth of change detection:
88
+ - True: Recursively tracks nested properties
89
+ - False: Shallow comparison only
90
+ - int: Maximum depth level to track (for complex objects).
91
+ Defaults to False.
92
+ once (bool, optional): If True, automatically stops observation after first trigger. Defaults to False.
93
+ flush (typing.Optional[_types.TFlush], optional): Controls when to flush updates:
94
+ - 'sync': Execute immediately on change
95
+ - 'post': Batch updates and execute after current tick
96
+ - 'pre': Execute before render phase (if applicable)
97
+
98
+ # Example:
99
+ .. code-block:: python
100
+ from instaui import ui, html
101
+
102
+ num = ui.state(0)
103
+ msg = ui.state('')
104
+ ui.js_watch(inputs=[num], outputs=[msg], code="num => `The number is ${num}`")
105
+
106
+ html.number(num)
107
+ ui.label(msg)
108
+ """
109
+
74
110
  return JsWatch(code, inputs, outputs, immediate, deep, once, flush)
@@ -32,6 +32,8 @@ class WebWatch(Jsonable, typing.Generic[P, R]):
32
32
  flush: typing.Optional[_types.TFlush] = None,
33
33
  _debug: typing.Optional[typing.Any] = None,
34
34
  ) -> None:
35
+ inputs = observable_helper.auto_made_inputs_to_slient(inputs, outputs)
36
+
35
37
  get_current_scope().register_web_watch(self)
36
38
 
37
39
  self._inputs, self._is_slient_inputs, self._is_data = (
@@ -107,6 +109,57 @@ def watch(
107
109
  flush: typing.Optional[_types.TFlush] = None,
108
110
  _debug: typing.Optional[typing.Any] = None,
109
111
  ):
112
+ """
113
+ Creates an observer that tracks changes in reactive sources and triggers callbacks.
114
+
115
+ Args:
116
+ inputs (typing.Optional[typing.Sequence], optional): Reactive sources to observe (state objects or computed properties).
117
+ Changes to these sources trigger the watcher callback.
118
+ outputs (typing.Optional[typing.Sequence], optional): Output targets associated with this watcher.
119
+ Used for coordination with computed properties or other observers.
120
+ immediate (bool, optional): If True, executes callback immediately after creation with current values. Defaults to True.
121
+ deep (typing.Union[bool, int], optional): Controls depth of change detection:
122
+ - True: Recursively tracks nested properties
123
+ - False: Shallow comparison only
124
+ - int: Maximum depth level to track (for complex objects).
125
+ Defaults to True.
126
+ once (bool, optional): If True, automatically stops observation after first trigger. Defaults to False.
127
+ flush (typing.Optional[_types.TFlush], optional): Controls when to flush updates:
128
+ - 'sync': Execute immediately on change
129
+ - 'post': Batch updates and execute after current tick
130
+ - 'pre': Execute before render phase (if applicable)
131
+
132
+ # Example:
133
+ .. code-block:: python
134
+ from instaui import ui, html
135
+
136
+ num = ui.state(0)
137
+ msg = ui.state('')
138
+
139
+ @ui.watch(inputs=[num], outputs=[msg])
140
+ def when_num_change(num):
141
+ return f"The number is {num}"
142
+
143
+ html.number(num)
144
+ ui.label(msg)
145
+
146
+ list append:
147
+ .. code-block:: python
148
+ from instaui import ui, html
149
+
150
+ num = ui.state(0)
151
+ msg = ui.state([])
152
+
153
+ @ui.watch(inputs=[num, msg], outputs=[msg])
154
+ def when_num_change(num, msg:list):
155
+ msg.append(f"The number changed to {num}")
156
+ return msg
157
+
158
+ html.number(num)
159
+ ui.label(msg)
160
+
161
+ """
162
+
110
163
  def wrapper(func: typing.Callable[P, R]):
111
164
  return WebWatch(
112
165
  func,
@@ -0,0 +1,153 @@
1
+ Metadata-Version: 2.4
2
+ Name: instaui
3
+ Version: 0.1.11
4
+ Summary: insta-ui is a Python-based UI library for rapidly building user interfaces.
5
+ Author-email: CrystalWindSnake <568166495@qq.com>
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Keywords: gui,ui,web
9
+ Requires-Python: <3.13,>=3.8
10
+ Requires-Dist: jinja2>=3.1.6
11
+ Requires-Dist: orjson>=3.10.15
12
+ Requires-Dist: pydantic>=2.10.6
13
+ Requires-Dist: typing-extensions>=4.13.2
14
+ Provides-Extra: all
15
+ Requires-Dist: fastapi[standard]; extra == 'all'
16
+ Requires-Dist: pywebview; extra == 'all'
17
+ Requires-Dist: uvicorn; extra == 'all'
18
+ Provides-Extra: web
19
+ Requires-Dist: fastapi[standard]; extra == 'web'
20
+ Requires-Dist: pydantic<3.0.0,>=2.10.6; extra == 'web'
21
+ Requires-Dist: uvicorn; extra == 'web'
22
+ Provides-Extra: webview
23
+ Requires-Dist: pydantic<3.0.0,>=2.10.6; extra == 'webview'
24
+ Requires-Dist: pywebview; extra == 'webview'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # insta-ui
28
+
29
+ <div align="center">
30
+
31
+ English| [简体中文](./README.md)
32
+
33
+ </div>
34
+
35
+ ## 📖 Introduction
36
+ insta-ui is a Python-based UI library for quickly building user interfaces.
37
+
38
+ ## ⚙️ Features
39
+ Three modes:
40
+
41
+ - Web mode: Generate web (stateless) applications.
42
+ - Web View mode: Generate web view applications that can be packaged as native apps (no need to start a web server).
43
+ - Zero mode: Generate pure HTML files that run directly in browsers without any dependencies.
44
+
45
+
46
+ ## 📦 Installation
47
+
48
+ Zero mode:
49
+
50
+ ```
51
+ pip install instaui -U
52
+ ```
53
+
54
+ web mode
55
+
56
+ ```
57
+ pip install instaui[web] -U
58
+ ```
59
+
60
+ Web View mode
61
+ ```
62
+ pip install instaui[webview] -U
63
+ ```
64
+
65
+
66
+ ## 🖥️ Quick Start
67
+ Below is a simple example of number summation. The result color changes dynamically based on the sum:
68
+
69
+ ```python
70
+ from instaui import ui, arco
71
+ arco.use()
72
+
73
+ @ui.page('/')
74
+ def home():
75
+ num1 = ui.state(0)
76
+ num2 = ui.state(0)
77
+
78
+ # Automatically recalculates result when num1 or num2 changes
79
+ @ui.computed(inputs=[num1, num2])
80
+ def result(num1: int, num2: int):
81
+ return num1 + num2
82
+
83
+ # Automatically updates text_color when result changes
84
+ @ui.computed(inputs=[result])
85
+ def text_color(result: int):
86
+ return "red" if result % 2 == 0 else "blue"
87
+
88
+ # UI components
89
+ arco.input_number(num1)
90
+ ui.label("+")
91
+ arco.input_number(num2)
92
+ ui.label("=")
93
+ ui.label(result).style({"color": text_color})
94
+
95
+ ui.server().run()
96
+ ```
97
+
98
+ Replace `ui.server().run()` with `ui.webview().run()` to switch to Web View mode:
99
+
100
+ ```python
101
+ ...
102
+
103
+ # ui.server().run()
104
+ ui.webview().run()
105
+ ```
106
+
107
+ To execute computations on the client side instead of the server, use `ui.js_computed` instead of `ui.computed`:
108
+
109
+ ```python
110
+ from instaui import ui, arco
111
+ arco.use()
112
+
113
+ @ui.page('/')
114
+ def home():
115
+ num1 = ui.state(0)
116
+ num2 = ui.state(0)
117
+
118
+ result = ui.js_computed(inputs=[num1, num2], code="(num1, num2) => num1 + num2")
119
+ text_color = ui.js_computed(inputs=[result], code="(result) => result % 2 === 0? 'red' : 'blue'")
120
+
121
+ # UI components
122
+ ...
123
+
124
+ ...
125
+
126
+ ```
127
+
128
+ In this case, all interactions will run on the client side. Use `Zero mode` to generate a standalone HTML file:
129
+
130
+ ```python
131
+ from instaui import ui, arco,zero
132
+ arco.use()
133
+
134
+ @ui.page('/')
135
+ def home():
136
+ num1 = ui.state(0)
137
+ num2 = ui.state(0)
138
+
139
+ result = ui.js_computed(inputs=[num1, num2], code="(num1, num2) => num1 + num2")
140
+ text_color = ui.js_computed(inputs=[result], code="(result) => result % 2 === 0? 'red' : 'blue'")
141
+
142
+ # UI components
143
+ arco.input_number(num1)
144
+ ui.label("+")
145
+ arco.input_number(num2)
146
+ ui.label("=")
147
+ ui.label(result).style({"color": text_color})
148
+
149
+ with zero() as z:
150
+ home()
151
+ z.to_html('index.html')
152
+
153
+ ```