instaui 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.
- instaui/__init__.py +9 -0
- instaui/_helper/observable_helper.py +35 -0
- instaui/boot_info.py +43 -0
- instaui/common/jsonable.py +37 -0
- instaui/components/__init__.py +0 -0
- instaui/components/column.py +18 -0
- instaui/components/component.py +47 -0
- instaui/components/content.py +34 -0
- instaui/components/directive.py +55 -0
- instaui/components/element.py +462 -0
- instaui/components/grid.py +80 -0
- instaui/components/html/__init__.py +36 -0
- instaui/components/html/_mixins.py +34 -0
- instaui/components/html/button.py +38 -0
- instaui/components/html/checkbox.py +42 -0
- instaui/components/html/date.py +28 -0
- instaui/components/html/div.py +7 -0
- instaui/components/html/form.py +7 -0
- instaui/components/html/input.py +28 -0
- instaui/components/html/label.py +21 -0
- instaui/components/html/li.py +17 -0
- instaui/components/html/link.py +31 -0
- instaui/components/html/number.py +34 -0
- instaui/components/html/paragraph.py +19 -0
- instaui/components/html/range.py +45 -0
- instaui/components/html/select.py +93 -0
- instaui/components/html/span.py +19 -0
- instaui/components/html/ul.py +20 -0
- instaui/components/match.py +106 -0
- instaui/components/row.py +19 -0
- instaui/components/slot.py +82 -0
- instaui/components/transition_group.py +9 -0
- instaui/components/value_element.py +48 -0
- instaui/components/vfor.py +140 -0
- instaui/components/vif.py +38 -0
- instaui/consts.py +18 -0
- instaui/dependencies/__init__.py +15 -0
- instaui/dependencies/component_registrar.py +82 -0
- instaui/dependencies/installer.py +5 -0
- instaui/event/event_mixin.py +12 -0
- instaui/event/js_event.py +57 -0
- instaui/event/web_event.py +108 -0
- instaui/experimental/__init__.py +4 -0
- instaui/experimental/debug.py +48 -0
- instaui/fastapi_server/_utils.py +42 -0
- instaui/fastapi_server/_uvicorn.py +37 -0
- instaui/fastapi_server/config_router.py +60 -0
- instaui/fastapi_server/debug_mode_router.py +61 -0
- instaui/fastapi_server/event_router.py +58 -0
- instaui/fastapi_server/middlewares.py +19 -0
- instaui/fastapi_server/request_context.py +19 -0
- instaui/fastapi_server/server.py +246 -0
- instaui/fastapi_server/watch_router.py +53 -0
- instaui/handlers/_utils.py +66 -0
- instaui/handlers/computed_handler.py +42 -0
- instaui/handlers/config_handler.py +13 -0
- instaui/handlers/event_handler.py +58 -0
- instaui/handlers/watch_handler.py +57 -0
- instaui/html_tools.py +139 -0
- instaui/inject.py +33 -0
- instaui/js/__init__.py +4 -0
- instaui/js/js_output.py +15 -0
- instaui/js/lambda_func.py +35 -0
- instaui/launch_collector.py +52 -0
- instaui/page_info.py +23 -0
- instaui/runtime/__init__.py +29 -0
- instaui/runtime/_app.py +206 -0
- instaui/runtime/_inner_helper.py +9 -0
- instaui/runtime/context.py +47 -0
- instaui/runtime/dataclass.py +30 -0
- instaui/runtime/resource.py +87 -0
- instaui/runtime/scope.py +107 -0
- instaui/runtime/ui_state_scope.py +15 -0
- instaui/settings/__init__.py +4 -0
- instaui/settings/__settings.py +13 -0
- instaui/skip.py +12 -0
- instaui/spa_router/__init__.py +26 -0
- instaui/spa_router/_components.py +35 -0
- instaui/spa_router/_file_base_utils.py +264 -0
- instaui/spa_router/_functions.py +122 -0
- instaui/spa_router/_install.py +11 -0
- instaui/spa_router/_route_model.py +139 -0
- instaui/spa_router/_router_box.py +40 -0
- instaui/spa_router/_router_output.py +22 -0
- instaui/spa_router/_router_param_var.py +51 -0
- instaui/spa_router/_types.py +4 -0
- instaui/spa_router/templates/page_routes +59 -0
- instaui/static/insta-ui.css +1 -0
- instaui/static/insta-ui.esm-browser.prod.js +3663 -0
- instaui/static/insta-ui.iife.js +29 -0
- instaui/static/insta-ui.iife.js.map +1 -0
- instaui/static/insta-ui.js.map +1 -0
- instaui/static/tailwindcss.min.js +62 -0
- instaui/static/templates/debug/sse.html +117 -0
- instaui/static/templates/web.html +118 -0
- instaui/static/templates/zero.html +55 -0
- instaui/static/vue.esm-browser.prod.js +9 -0
- instaui/static/vue.global.prod.js +9 -0
- instaui/static/vue.runtime.esm-browser.prod.js +5 -0
- instaui/systems/file_system.py +17 -0
- instaui/systems/func_system.py +104 -0
- instaui/systems/js_system.py +22 -0
- instaui/systems/pydantic_system.py +27 -0
- instaui/systems/string_system.py +10 -0
- instaui/template/__init__.py +4 -0
- instaui/template/env.py +7 -0
- instaui/template/web_template.py +55 -0
- instaui/template/zero_template.py +24 -0
- instaui/ui/__init__.py +121 -0
- instaui/ui/events.py +25 -0
- instaui/ui_functions/input_slient_data.py +16 -0
- instaui/ui_functions/server.py +13 -0
- instaui/ui_functions/str_format.py +36 -0
- instaui/ui_functions/ui_page.py +31 -0
- instaui/ui_functions/ui_types.py +13 -0
- instaui/ui_functions/url_location.py +33 -0
- instaui/vars/__init__.py +13 -0
- instaui/vars/_types.py +8 -0
- instaui/vars/_utils.py +12 -0
- instaui/vars/data.py +68 -0
- instaui/vars/element_ref.py +42 -0
- instaui/vars/event_context.py +45 -0
- instaui/vars/event_extend.py +0 -0
- instaui/vars/js_computed.py +95 -0
- instaui/vars/mixin_types/common_type.py +5 -0
- instaui/vars/mixin_types/element_binding.py +10 -0
- instaui/vars/mixin_types/observable.py +7 -0
- instaui/vars/mixin_types/pathable.py +14 -0
- instaui/vars/mixin_types/py_binding.py +13 -0
- instaui/vars/mixin_types/str_format_binding.py +8 -0
- instaui/vars/mixin_types/var_type.py +5 -0
- instaui/vars/path_var.py +89 -0
- instaui/vars/ref.py +103 -0
- instaui/vars/slot_prop.py +46 -0
- instaui/vars/state.py +82 -0
- instaui/vars/types.py +24 -0
- instaui/vars/vfor_item.py +204 -0
- instaui/vars/vue_computed.py +82 -0
- instaui/vars/web_computed.py +157 -0
- instaui/vars/web_view_computed.py +1 -0
- instaui/version.py +3 -0
- instaui/watch/_types.py +4 -0
- instaui/watch/_utils.py +3 -0
- instaui/watch/js_watch.py +74 -0
- instaui/watch/vue_watch.py +61 -0
- instaui/watch/web_watch.py +123 -0
- instaui/zero/__init__.py +3 -0
- instaui/zero/scope.py +9 -0
- instaui-0.1.0.dist-info/LICENSE +21 -0
- instaui-0.1.0.dist-info/METADATA +154 -0
- instaui-0.1.0.dist-info/RECORD +152 -0
- instaui-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,462 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import ast
|
4
|
+
from copy import copy
|
5
|
+
import inspect
|
6
|
+
from pathlib import Path
|
7
|
+
import re
|
8
|
+
from typing import (
|
9
|
+
Any,
|
10
|
+
Dict,
|
11
|
+
List,
|
12
|
+
ClassVar,
|
13
|
+
Optional,
|
14
|
+
Tuple,
|
15
|
+
Union,
|
16
|
+
overload,
|
17
|
+
TYPE_CHECKING,
|
18
|
+
)
|
19
|
+
from typing_extensions import Self
|
20
|
+
from collections import defaultdict
|
21
|
+
from instaui.runtime._app import get_app_slot
|
22
|
+
from instaui.runtime._app import get_current_scope
|
23
|
+
from instaui.vars.element_ref import ElementRef
|
24
|
+
|
25
|
+
|
26
|
+
from instaui.vars.vfor_item import VForItem
|
27
|
+
from instaui.components.directive import Directive
|
28
|
+
from instaui.dependencies import ComponentRegistrationInfo, register_component
|
29
|
+
from .slot import SlotManager, Slot
|
30
|
+
from instaui import consts
|
31
|
+
from instaui.components.component import Component
|
32
|
+
|
33
|
+
from instaui.vars.mixin_types.element_binding import ElementBindingMixin
|
34
|
+
|
35
|
+
if TYPE_CHECKING:
|
36
|
+
from instaui.event.event_mixin import EventMixin
|
37
|
+
|
38
|
+
|
39
|
+
# Refer to the NiceGUI project.
|
40
|
+
# https://github.com/zauberzeug/nicegui/blob/main/nicegui/element.py
|
41
|
+
PROPS_PATTERN = re.compile(
|
42
|
+
r"""
|
43
|
+
# Match a key-value pair optionally followed by whitespace or end of string
|
44
|
+
([:\w\-]+) # Capture group 1: Key
|
45
|
+
(?: # Optional non-capturing group for value
|
46
|
+
= # Match the equal sign
|
47
|
+
(?: # Non-capturing group for value options
|
48
|
+
( # Capture group 2: Value enclosed in double quotes
|
49
|
+
" # Match double quote
|
50
|
+
[^"\\]* # Match any character except quotes or backslashes zero or more times
|
51
|
+
(?:\\.[^"\\]*)* # Match any escaped character followed by any character except quotes or backslashes zero or more times
|
52
|
+
" # Match the closing quote
|
53
|
+
)
|
54
|
+
|
|
55
|
+
( # Capture group 3: Value enclosed in single quotes
|
56
|
+
' # Match a single quote
|
57
|
+
[^'\\]* # Match any character except quotes or backslashes zero or more times
|
58
|
+
(?:\\.[^'\\]*)* # Match any escaped character followed by any character except quotes or backslashes zero or more times
|
59
|
+
' # Match the closing quote
|
60
|
+
)
|
61
|
+
| # Or
|
62
|
+
([\w\-.%:\/]+) # Capture group 4: Value without quotes
|
63
|
+
)
|
64
|
+
)? # End of optional non-capturing group for value
|
65
|
+
(?:$|\s) # Match end of string or whitespace
|
66
|
+
""",
|
67
|
+
re.VERBOSE,
|
68
|
+
)
|
69
|
+
|
70
|
+
|
71
|
+
class Element(Component):
|
72
|
+
component: ClassVar[Optional[ComponentRegistrationInfo]] = None
|
73
|
+
_default_props: ClassVar[Dict[str, Any]] = {}
|
74
|
+
_default_classes: ClassVar[List[str]] = []
|
75
|
+
_default_style: ClassVar[Dict[str, str]] = {}
|
76
|
+
|
77
|
+
def __init__(self, tag: Optional[Union[str, ElementBindingMixin]] = None):
|
78
|
+
if self.component:
|
79
|
+
tag = self.component.name
|
80
|
+
|
81
|
+
super().__init__(tag)
|
82
|
+
|
83
|
+
self._str_classes: List[str] = []
|
84
|
+
self._dict_classes: Dict[str, ElementBindingMixin[bool]] = {}
|
85
|
+
self._bind_str_classes: List[ElementBindingMixin[str]] = []
|
86
|
+
self._str_classes.extend(self._default_classes)
|
87
|
+
self._style: Dict[str, str] = {}
|
88
|
+
self._style.update(self._default_style)
|
89
|
+
self._style_str_binds: List[ElementBindingMixin[str]] = []
|
90
|
+
self._props: Dict[str, Any] = {}
|
91
|
+
self._props.update(self._default_props)
|
92
|
+
self._proxy_props: List[ElementBindingMixin] = []
|
93
|
+
|
94
|
+
self._js_events: defaultdict[str, List[EventMixin]] = defaultdict(list)
|
95
|
+
self._web_events: defaultdict[str, List[EventMixin]] = defaultdict(list)
|
96
|
+
self._directives: Dict[Directive, None] = {}
|
97
|
+
|
98
|
+
self._slot_manager = SlotManager()
|
99
|
+
self._element_ref: Optional[ElementRef] = None
|
100
|
+
|
101
|
+
def __init_subclass__(
|
102
|
+
cls,
|
103
|
+
*,
|
104
|
+
component: Union[str, Path, None] = None,
|
105
|
+
) -> None:
|
106
|
+
super().__init_subclass__()
|
107
|
+
|
108
|
+
if component:
|
109
|
+
if isinstance(component, str):
|
110
|
+
component = Path(component)
|
111
|
+
if not component.is_absolute():
|
112
|
+
component = Path(inspect.getfile(cls)).parent / component
|
113
|
+
|
114
|
+
# TODO: Lazy load component registration
|
115
|
+
cls.component = register_component(
|
116
|
+
component.stem, esm=component, shared=True
|
117
|
+
)
|
118
|
+
|
119
|
+
cls._default_props = copy(cls._default_props)
|
120
|
+
cls._default_classes = copy(cls._default_classes)
|
121
|
+
cls._default_style = copy(cls._default_style)
|
122
|
+
|
123
|
+
@staticmethod
|
124
|
+
def _update_classes(
|
125
|
+
classes: List[str],
|
126
|
+
add: str,
|
127
|
+
) -> List[str]:
|
128
|
+
return list(dict.fromkeys(classes + add.split()))
|
129
|
+
|
130
|
+
@staticmethod
|
131
|
+
def _parse_style(text: Union[str, Dict[str, str]]) -> Dict[str, str]:
|
132
|
+
if isinstance(text, dict):
|
133
|
+
return text
|
134
|
+
|
135
|
+
if not text:
|
136
|
+
return {}
|
137
|
+
|
138
|
+
result = {}
|
139
|
+
for item in text.split(";"):
|
140
|
+
item = item.strip()
|
141
|
+
if item:
|
142
|
+
key, value = item.split(":")
|
143
|
+
key = key.strip()
|
144
|
+
value = value.strip()
|
145
|
+
result[key] = value
|
146
|
+
|
147
|
+
return result
|
148
|
+
|
149
|
+
@staticmethod
|
150
|
+
def _parse_props(props: Union[str, Dict[str, Any]]) -> Dict[str, Any]:
|
151
|
+
if isinstance(props, dict):
|
152
|
+
return props
|
153
|
+
|
154
|
+
if not props:
|
155
|
+
return {}
|
156
|
+
|
157
|
+
dictionary = {}
|
158
|
+
for match in PROPS_PATTERN.finditer(props or ""):
|
159
|
+
key = match.group(1)
|
160
|
+
value = match.group(2) or match.group(3) or match.group(4)
|
161
|
+
if value is None:
|
162
|
+
dictionary[key] = True
|
163
|
+
else:
|
164
|
+
if (value.startswith("'") and value.endswith("'")) or (
|
165
|
+
value.startswith('"') and value.endswith('"')
|
166
|
+
):
|
167
|
+
value = ast.literal_eval(value)
|
168
|
+
dictionary[key] = value
|
169
|
+
return dictionary
|
170
|
+
|
171
|
+
def key(self, key: Any):
|
172
|
+
"""Set the key prop of the component.
|
173
|
+
|
174
|
+
Args:
|
175
|
+
key (str): The key prop value.
|
176
|
+
|
177
|
+
"""
|
178
|
+
self.props({"key": key})
|
179
|
+
return self
|
180
|
+
|
181
|
+
def vmodel(
|
182
|
+
self,
|
183
|
+
value: Any,
|
184
|
+
modifiers: Union[consts.TModifier, List[consts.TModifier], None] = None,
|
185
|
+
*,
|
186
|
+
prop_name: str = "value",
|
187
|
+
is_html_component=False,
|
188
|
+
):
|
189
|
+
if prop_name == "value":
|
190
|
+
prop_name = "modelValue"
|
191
|
+
|
192
|
+
modifiers = modifiers or []
|
193
|
+
if isinstance(modifiers, str):
|
194
|
+
modifiers = [modifiers]
|
195
|
+
|
196
|
+
self.directive(
|
197
|
+
Directive(
|
198
|
+
is_sys=is_html_component,
|
199
|
+
name="vmodel",
|
200
|
+
arg=prop_name,
|
201
|
+
modifiers=modifiers,
|
202
|
+
value=value, # type: ignore
|
203
|
+
)
|
204
|
+
)
|
205
|
+
|
206
|
+
if is_html_component is False:
|
207
|
+
model_modifiers = {m: True for m in modifiers}
|
208
|
+
self.props({"modelModifiers": model_modifiers})
|
209
|
+
|
210
|
+
return self
|
211
|
+
|
212
|
+
def add_slot(self, name: str) -> Slot:
|
213
|
+
return self._slot_manager.get_slot(name)
|
214
|
+
|
215
|
+
@overload
|
216
|
+
def classes(self, add: str) -> Self: ...
|
217
|
+
@overload
|
218
|
+
def classes(self, add: Dict[str, ElementBindingMixin[bool]]) -> Self: ...
|
219
|
+
|
220
|
+
@overload
|
221
|
+
def classes(self, add: ElementBindingMixin[str]) -> Self: ...
|
222
|
+
|
223
|
+
def classes(
|
224
|
+
self,
|
225
|
+
add: Union[
|
226
|
+
str,
|
227
|
+
Dict[str, ElementBindingMixin[bool]],
|
228
|
+
ElementBindingMixin[str],
|
229
|
+
VForItem,
|
230
|
+
],
|
231
|
+
) -> Self:
|
232
|
+
if isinstance(add, str):
|
233
|
+
self._str_classes = self._update_classes(self._str_classes, add)
|
234
|
+
|
235
|
+
if isinstance(add, dict):
|
236
|
+
self._dict_classes.update(**add)
|
237
|
+
|
238
|
+
if isinstance(add, ElementBindingMixin):
|
239
|
+
self._bind_str_classes.append(add) # type: ignore
|
240
|
+
|
241
|
+
return self
|
242
|
+
|
243
|
+
def style(self, add: Union[str, Dict[str, Any], ElementBindingMixin[str]]) -> Self:
|
244
|
+
if isinstance(add, dict):
|
245
|
+
add = {key: value for key, value in add.items()}
|
246
|
+
|
247
|
+
if isinstance(add, ElementBindingMixin):
|
248
|
+
self._style_str_binds.append(add)
|
249
|
+
return self
|
250
|
+
|
251
|
+
new_style = self._parse_style(add)
|
252
|
+
self._style.update(new_style)
|
253
|
+
return self
|
254
|
+
|
255
|
+
def props(self, add: Union[str, Dict[str, Any], ElementBindingMixin]) -> Self:
|
256
|
+
if isinstance(add, ElementBindingMixin):
|
257
|
+
self._proxy_props.append(add)
|
258
|
+
return self
|
259
|
+
|
260
|
+
if isinstance(add, dict):
|
261
|
+
add = {key: value for key, value in add.items() if value is not None}
|
262
|
+
new_props = self._parse_props(add)
|
263
|
+
self._props.update(new_props)
|
264
|
+
return self
|
265
|
+
|
266
|
+
@classmethod
|
267
|
+
def default_classes(cls, add: str) -> type[Self]:
|
268
|
+
cls._default_classes = cls._update_classes(cls._default_classes, add)
|
269
|
+
return cls
|
270
|
+
|
271
|
+
@classmethod
|
272
|
+
def default_style(cls, add: Union[str, Dict[str, str]]) -> type[Self]:
|
273
|
+
new_style = cls._parse_style(add)
|
274
|
+
cls._default_style.update(new_style)
|
275
|
+
return cls
|
276
|
+
|
277
|
+
@classmethod
|
278
|
+
def default_props(cls, add: Union[str, Dict[str, Any]]) -> type[Self]:
|
279
|
+
new_props = cls._parse_props(add)
|
280
|
+
cls._default_props.update(new_props)
|
281
|
+
return cls
|
282
|
+
|
283
|
+
def on(
|
284
|
+
self,
|
285
|
+
event_name: str,
|
286
|
+
handler: EventMixin,
|
287
|
+
*,
|
288
|
+
extends: Optional[List] = None,
|
289
|
+
):
|
290
|
+
if extends:
|
291
|
+
handler = handler.copy_with_extends(extends)
|
292
|
+
|
293
|
+
if handler.event_type() == "js":
|
294
|
+
self._js_events[event_name].append(handler)
|
295
|
+
|
296
|
+
if handler.event_type() == "web":
|
297
|
+
self._web_events[event_name].append(handler)
|
298
|
+
|
299
|
+
return self
|
300
|
+
|
301
|
+
def directive(self, directive: Directive) -> Self:
|
302
|
+
self._directives[directive] = None
|
303
|
+
return self
|
304
|
+
|
305
|
+
def display(self, value: Union[ElementBindingMixin[bool], bool]) -> Self:
|
306
|
+
return self.directive(Directive(is_sys=False, name="vshow", value=value))
|
307
|
+
|
308
|
+
def event_dataset(self, data: Any, name: str = "event-data") -> Self:
|
309
|
+
from instaui.vars.js_computed import JsComputed
|
310
|
+
|
311
|
+
value = JsComputed(inputs=[data], code="(data)=> JSON.stringify(data)")
|
312
|
+
self.props({f"data-{name}": value})
|
313
|
+
return self
|
314
|
+
|
315
|
+
def element_ref(self, ref: ElementRef):
|
316
|
+
self._element_ref = ref
|
317
|
+
return self
|
318
|
+
|
319
|
+
def _to_json_dict(self):
|
320
|
+
data = super()._to_json_dict()
|
321
|
+
|
322
|
+
if self._style or self._style_str_binds:
|
323
|
+
value_styles, bind_styles = _classifyBindableDict(self._style)
|
324
|
+
if value_styles:
|
325
|
+
data["style"] = value_styles
|
326
|
+
|
327
|
+
b_style_list = []
|
328
|
+
|
329
|
+
if bind_styles:
|
330
|
+
b_style_list.append(bind_styles)
|
331
|
+
|
332
|
+
if self._style_str_binds:
|
333
|
+
b_style_list.append(
|
334
|
+
[v._to_element_binding_config() for v in self._style_str_binds]
|
335
|
+
)
|
336
|
+
|
337
|
+
if b_style_list:
|
338
|
+
data["bStyle"] = b_style_list
|
339
|
+
|
340
|
+
if self._str_classes or self._dict_classes or self._bind_str_classes:
|
341
|
+
data["classes"] = _normalize_classes_data(
|
342
|
+
self._str_classes, self._dict_classes, self._bind_str_classes
|
343
|
+
)
|
344
|
+
|
345
|
+
if self._props:
|
346
|
+
value_props, bind_props = _classifyBindableDict(self._props)
|
347
|
+
|
348
|
+
if value_props:
|
349
|
+
data["props"] = value_props
|
350
|
+
|
351
|
+
if bind_props:
|
352
|
+
data["bProps"] = bind_props
|
353
|
+
|
354
|
+
if self._proxy_props:
|
355
|
+
data["proxyProps"] = [
|
356
|
+
v._to_element_binding_config() for v in self._proxy_props
|
357
|
+
]
|
358
|
+
|
359
|
+
if self._js_events or self._web_events:
|
360
|
+
data["events"] = _normalize_events(self._js_events, self._web_events)
|
361
|
+
|
362
|
+
if self._slot_manager.has_slot():
|
363
|
+
data["slots"] = self._slot_manager
|
364
|
+
|
365
|
+
if self._directives:
|
366
|
+
data["dir"] = list(self._directives.keys())
|
367
|
+
|
368
|
+
if self.component:
|
369
|
+
get_app_slot().register_component(self.component)
|
370
|
+
|
371
|
+
if self._element_ref:
|
372
|
+
scope = get_current_scope()
|
373
|
+
id = scope.generate_element_ref_id()
|
374
|
+
self._element_ref._set_id(id)
|
375
|
+
data["eRef"] = self._element_ref._to_element_config()
|
376
|
+
scope.register_element_ref(self._element_ref)
|
377
|
+
|
378
|
+
return data
|
379
|
+
|
380
|
+
|
381
|
+
def _normalize_events(
|
382
|
+
js_events: defaultdict[str, List[EventMixin]],
|
383
|
+
web_events: defaultdict[str, List[EventMixin]],
|
384
|
+
):
|
385
|
+
merged: defaultdict[str, List[EventMixin]] = defaultdict(list)
|
386
|
+
|
387
|
+
for name, events in js_events.items():
|
388
|
+
name = _normalize_event_name(name)
|
389
|
+
merged[name].extend(events)
|
390
|
+
|
391
|
+
for name, events in web_events.items():
|
392
|
+
name = _normalize_event_name(name)
|
393
|
+
merged[name].extend(events)
|
394
|
+
|
395
|
+
return dict(merged)
|
396
|
+
|
397
|
+
|
398
|
+
def _normalize_event_name(event_name: str):
|
399
|
+
"""'click' -> 'onClick' , 'press-enter' -> 'onPressEnter' , 'pressEnter' -> 'onPressEnter'"""
|
400
|
+
|
401
|
+
if event_name.startswith("on-"):
|
402
|
+
event_name = event_name[3:]
|
403
|
+
|
404
|
+
if event_name.startswith("on"):
|
405
|
+
event_name = event_name[2:]
|
406
|
+
|
407
|
+
parts = event_name.split("-")
|
408
|
+
formatted_parts = [part[0].upper() + part[1:] for part in parts]
|
409
|
+
|
410
|
+
return "".join(["on", *formatted_parts])
|
411
|
+
|
412
|
+
|
413
|
+
def _normalize_classes_data(
|
414
|
+
str_classes: List[str],
|
415
|
+
dict_classes: Dict[str, ElementBindingMixin[bool]],
|
416
|
+
bind_str_classes: List[ElementBindingMixin[str]],
|
417
|
+
):
|
418
|
+
_str_result = " ".join(str_classes)
|
419
|
+
|
420
|
+
_dict_classes = {k: v._to_element_binding_config() for k, v in dict_classes.items()}
|
421
|
+
|
422
|
+
_bind_str_classes = [v._to_element_binding_config() for v in bind_str_classes]
|
423
|
+
|
424
|
+
if _dict_classes or _bind_str_classes:
|
425
|
+
result = {}
|
426
|
+
|
427
|
+
if _str_result:
|
428
|
+
result["str"] = _str_result
|
429
|
+
|
430
|
+
if _dict_classes:
|
431
|
+
result["map"] = _dict_classes
|
432
|
+
|
433
|
+
if _bind_str_classes:
|
434
|
+
result["bind"] = _bind_str_classes
|
435
|
+
|
436
|
+
return result
|
437
|
+
else:
|
438
|
+
return _str_result
|
439
|
+
|
440
|
+
|
441
|
+
def _classifyBindableDict(
|
442
|
+
data: Dict[str, Any],
|
443
|
+
) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
444
|
+
"""Return value_data, bind_data
|
445
|
+
|
446
|
+
Args:
|
447
|
+
data (Dict[str, Any]): _description_
|
448
|
+
|
449
|
+
Returns:
|
450
|
+
Tuple[Dict[str, Any], Dict[str, Any]]: _description_
|
451
|
+
"""
|
452
|
+
|
453
|
+
value_data = {}
|
454
|
+
bind_data = {}
|
455
|
+
|
456
|
+
for key, value in data.items():
|
457
|
+
if isinstance(value, ElementBindingMixin):
|
458
|
+
bind_data[key] = value._to_element_binding_config()
|
459
|
+
else:
|
460
|
+
value_data[key] = value
|
461
|
+
|
462
|
+
return value_data, bind_data
|
@@ -0,0 +1,80 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import (
|
3
|
+
Literal,
|
4
|
+
Optional,
|
5
|
+
TypeVar,
|
6
|
+
Union,
|
7
|
+
)
|
8
|
+
from instaui.vars.types import TMaybeRef
|
9
|
+
from instaui.vars.js_computed import JsComputed
|
10
|
+
from instaui.components.element import Element
|
11
|
+
from instaui.vars.mixin_types.observable import ObservableMixin
|
12
|
+
|
13
|
+
_T = TypeVar("_T")
|
14
|
+
|
15
|
+
|
16
|
+
class Grid(Element):
|
17
|
+
def __init__(
|
18
|
+
self,
|
19
|
+
rows: Optional[TMaybeRef[Union[int, str]]] = None,
|
20
|
+
columns: Optional[TMaybeRef[Union[int, str]]] = None,
|
21
|
+
):
|
22
|
+
super().__init__("div")
|
23
|
+
self.style("display: grid;")
|
24
|
+
|
25
|
+
if rows is not None:
|
26
|
+
if isinstance(rows, int):
|
27
|
+
rows = f"repeat({rows}, 1fr)"
|
28
|
+
|
29
|
+
if isinstance(rows, ObservableMixin):
|
30
|
+
rows = _convert_to_repeat_computed(rows)
|
31
|
+
|
32
|
+
self.style({"grid-template-rows": rows})
|
33
|
+
|
34
|
+
if columns is not None:
|
35
|
+
if isinstance(columns, int):
|
36
|
+
columns = f"repeat({columns}, 1fr)"
|
37
|
+
|
38
|
+
if isinstance(columns, ObservableMixin):
|
39
|
+
columns = _convert_to_repeat_computed(columns)
|
40
|
+
|
41
|
+
self.style({"grid-template-columns": columns})
|
42
|
+
|
43
|
+
@classmethod
|
44
|
+
def auto_columns(
|
45
|
+
cls,
|
46
|
+
*,
|
47
|
+
min_width: TMaybeRef[str],
|
48
|
+
mode: TMaybeRef[Literal["auto-fill", "auto-fit"]] = "auto-fit",
|
49
|
+
) -> Grid:
|
50
|
+
if isinstance(min_width, ObservableMixin) or isinstance(mode, ObservableMixin):
|
51
|
+
template = JsComputed(
|
52
|
+
inputs=[min_width, mode],
|
53
|
+
code=r"(min_width, mode)=> `repeat(${mode}, minmax(min(${min_width},100%), 1fr))`",
|
54
|
+
)
|
55
|
+
|
56
|
+
else:
|
57
|
+
template = f"repeat({mode}, minmax(min({min_width},100%), 1fr))"
|
58
|
+
|
59
|
+
return cls(columns=template)
|
60
|
+
|
61
|
+
def row_gap(self, gap: TMaybeRef[str]) -> Grid:
|
62
|
+
return self.style({"row-gap": gap})
|
63
|
+
|
64
|
+
def column_gap(self, gap: TMaybeRef[str]) -> Grid:
|
65
|
+
return self.style({"column-gap": gap})
|
66
|
+
|
67
|
+
def gap(self, gap: TMaybeRef[str]) -> Grid:
|
68
|
+
return self.row_gap(gap).column_gap(gap)
|
69
|
+
|
70
|
+
|
71
|
+
def _convert_to_repeat_computed(value: ObservableMixin):
|
72
|
+
return JsComputed(
|
73
|
+
inputs=[value],
|
74
|
+
code=r"""(value)=> {
|
75
|
+
if (typeof value === "number"){
|
76
|
+
return `repeat(${value}, 1fr)`
|
77
|
+
}
|
78
|
+
return value
|
79
|
+
}""",
|
80
|
+
)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
from .span import Span as span
|
2
|
+
from .label import Label as label
|
3
|
+
from .paragraph import Paragraph as paragraph
|
4
|
+
from .input import Input as input
|
5
|
+
from .number import Number as number
|
6
|
+
from .button import Button as button
|
7
|
+
from .checkbox import Checkbox as checkbox
|
8
|
+
from .form import Form as form
|
9
|
+
from .select import Select as select
|
10
|
+
from .ul import Ul as ul
|
11
|
+
from .li import Li as li
|
12
|
+
from .div import Div as div
|
13
|
+
from .range import Range as range
|
14
|
+
from .date import Date as date
|
15
|
+
from .link import Link as link
|
16
|
+
|
17
|
+
option = select.Option
|
18
|
+
|
19
|
+
__all__ = [
|
20
|
+
"span",
|
21
|
+
"label",
|
22
|
+
"paragraph",
|
23
|
+
"input",
|
24
|
+
"number",
|
25
|
+
"button",
|
26
|
+
"checkbox",
|
27
|
+
"form",
|
28
|
+
"select",
|
29
|
+
"option",
|
30
|
+
"ul",
|
31
|
+
"li",
|
32
|
+
"div",
|
33
|
+
"range",
|
34
|
+
"date",
|
35
|
+
"link",
|
36
|
+
]
|
@@ -0,0 +1,34 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import abc
|
3
|
+
from typing import TYPE_CHECKING, List, Optional
|
4
|
+
from instaui.event.event_mixin import EventMixin
|
5
|
+
|
6
|
+
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from instaui.components.element import Element
|
9
|
+
|
10
|
+
|
11
|
+
class InputEventMixin:
|
12
|
+
@abc.abstractmethod
|
13
|
+
def _input_event_mixin_element(self) -> Element:
|
14
|
+
pass
|
15
|
+
|
16
|
+
def on_change(
|
17
|
+
self,
|
18
|
+
handler: EventMixin,
|
19
|
+
*,
|
20
|
+
extends: Optional[List] = None,
|
21
|
+
key: Optional[str] = None,
|
22
|
+
):
|
23
|
+
self._input_event_mixin_element().on("change", handler, extends=extends)
|
24
|
+
return self
|
25
|
+
|
26
|
+
def on_input(
|
27
|
+
self,
|
28
|
+
handler: EventMixin,
|
29
|
+
*,
|
30
|
+
extends: Optional[List] = None,
|
31
|
+
key: Optional[str] = None,
|
32
|
+
):
|
33
|
+
self._input_event_mixin_element().on("input", handler, extends=extends)
|
34
|
+
return self
|
@@ -0,0 +1,38 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import (
|
3
|
+
TYPE_CHECKING,
|
4
|
+
List,
|
5
|
+
Optional,
|
6
|
+
)
|
7
|
+
from instaui.components.element import Element
|
8
|
+
|
9
|
+
from instaui.event.event_mixin import EventMixin
|
10
|
+
from instaui.vars.types import TMaybeRef
|
11
|
+
|
12
|
+
if TYPE_CHECKING:
|
13
|
+
pass
|
14
|
+
|
15
|
+
|
16
|
+
class Button(Element):
|
17
|
+
def __init__(
|
18
|
+
self,
|
19
|
+
text: Optional[TMaybeRef[str]] = None,
|
20
|
+
):
|
21
|
+
super().__init__("button")
|
22
|
+
|
23
|
+
if text is not None:
|
24
|
+
self.props(
|
25
|
+
{
|
26
|
+
"innerText": text,
|
27
|
+
}
|
28
|
+
)
|
29
|
+
|
30
|
+
def on_click(
|
31
|
+
self,
|
32
|
+
handler: EventMixin,
|
33
|
+
*,
|
34
|
+
extends: Optional[List] = None,
|
35
|
+
key: Optional[str] = None,
|
36
|
+
):
|
37
|
+
self.on("click", handler, extends=extends)
|
38
|
+
return self
|
@@ -0,0 +1,42 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import (
|
3
|
+
Any,
|
4
|
+
Optional,
|
5
|
+
Union,
|
6
|
+
)
|
7
|
+
|
8
|
+
from instaui.components.element import Element
|
9
|
+
from instaui.components.value_element import ValueElement
|
10
|
+
|
11
|
+
|
12
|
+
from instaui.vars.types import TMaybeRef
|
13
|
+
from ._mixins import InputEventMixin
|
14
|
+
|
15
|
+
|
16
|
+
class Checkbox(InputEventMixin, ValueElement[Union[bool, str]]):
|
17
|
+
def __init__(
|
18
|
+
self,
|
19
|
+
value: Union[Union[bool, str], TMaybeRef[Union[bool, str]], None] = None,
|
20
|
+
*,
|
21
|
+
model_value: Optional[TMaybeRef[Union[bool, str]]] = None,
|
22
|
+
checked: Optional[TMaybeRef[bool]] = None,
|
23
|
+
id: Optional[Any] = None,
|
24
|
+
):
|
25
|
+
super().__init__("input", value, is_html_component=True)
|
26
|
+
self.props({"type": "checkbox"})
|
27
|
+
if id is not None:
|
28
|
+
self.props({"id": id})
|
29
|
+
if checked is not None:
|
30
|
+
self.props({"checked": checked})
|
31
|
+
if model_value is not None:
|
32
|
+
self.props({"value": model_value})
|
33
|
+
|
34
|
+
# def vmodel(
|
35
|
+
# self,
|
36
|
+
# value: Any,
|
37
|
+
# *modifiers: Literal["trim"] | Literal["number"] | Literal["lazy"],
|
38
|
+
# ):
|
39
|
+
# return super().vmodel(value, *modifiers) # type: ignore
|
40
|
+
|
41
|
+
def _input_event_mixin_element(self) -> Element:
|
42
|
+
return self
|