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.
Files changed (152) hide show
  1. instaui/__init__.py +9 -0
  2. instaui/_helper/observable_helper.py +35 -0
  3. instaui/boot_info.py +43 -0
  4. instaui/common/jsonable.py +37 -0
  5. instaui/components/__init__.py +0 -0
  6. instaui/components/column.py +18 -0
  7. instaui/components/component.py +47 -0
  8. instaui/components/content.py +34 -0
  9. instaui/components/directive.py +55 -0
  10. instaui/components/element.py +462 -0
  11. instaui/components/grid.py +80 -0
  12. instaui/components/html/__init__.py +36 -0
  13. instaui/components/html/_mixins.py +34 -0
  14. instaui/components/html/button.py +38 -0
  15. instaui/components/html/checkbox.py +42 -0
  16. instaui/components/html/date.py +28 -0
  17. instaui/components/html/div.py +7 -0
  18. instaui/components/html/form.py +7 -0
  19. instaui/components/html/input.py +28 -0
  20. instaui/components/html/label.py +21 -0
  21. instaui/components/html/li.py +17 -0
  22. instaui/components/html/link.py +31 -0
  23. instaui/components/html/number.py +34 -0
  24. instaui/components/html/paragraph.py +19 -0
  25. instaui/components/html/range.py +45 -0
  26. instaui/components/html/select.py +93 -0
  27. instaui/components/html/span.py +19 -0
  28. instaui/components/html/ul.py +20 -0
  29. instaui/components/match.py +106 -0
  30. instaui/components/row.py +19 -0
  31. instaui/components/slot.py +82 -0
  32. instaui/components/transition_group.py +9 -0
  33. instaui/components/value_element.py +48 -0
  34. instaui/components/vfor.py +140 -0
  35. instaui/components/vif.py +38 -0
  36. instaui/consts.py +18 -0
  37. instaui/dependencies/__init__.py +15 -0
  38. instaui/dependencies/component_registrar.py +82 -0
  39. instaui/dependencies/installer.py +5 -0
  40. instaui/event/event_mixin.py +12 -0
  41. instaui/event/js_event.py +57 -0
  42. instaui/event/web_event.py +108 -0
  43. instaui/experimental/__init__.py +4 -0
  44. instaui/experimental/debug.py +48 -0
  45. instaui/fastapi_server/_utils.py +42 -0
  46. instaui/fastapi_server/_uvicorn.py +37 -0
  47. instaui/fastapi_server/config_router.py +60 -0
  48. instaui/fastapi_server/debug_mode_router.py +61 -0
  49. instaui/fastapi_server/event_router.py +58 -0
  50. instaui/fastapi_server/middlewares.py +19 -0
  51. instaui/fastapi_server/request_context.py +19 -0
  52. instaui/fastapi_server/server.py +246 -0
  53. instaui/fastapi_server/watch_router.py +53 -0
  54. instaui/handlers/_utils.py +66 -0
  55. instaui/handlers/computed_handler.py +42 -0
  56. instaui/handlers/config_handler.py +13 -0
  57. instaui/handlers/event_handler.py +58 -0
  58. instaui/handlers/watch_handler.py +57 -0
  59. instaui/html_tools.py +139 -0
  60. instaui/inject.py +33 -0
  61. instaui/js/__init__.py +4 -0
  62. instaui/js/js_output.py +15 -0
  63. instaui/js/lambda_func.py +35 -0
  64. instaui/launch_collector.py +52 -0
  65. instaui/page_info.py +23 -0
  66. instaui/runtime/__init__.py +29 -0
  67. instaui/runtime/_app.py +206 -0
  68. instaui/runtime/_inner_helper.py +9 -0
  69. instaui/runtime/context.py +47 -0
  70. instaui/runtime/dataclass.py +30 -0
  71. instaui/runtime/resource.py +87 -0
  72. instaui/runtime/scope.py +107 -0
  73. instaui/runtime/ui_state_scope.py +15 -0
  74. instaui/settings/__init__.py +4 -0
  75. instaui/settings/__settings.py +13 -0
  76. instaui/skip.py +12 -0
  77. instaui/spa_router/__init__.py +26 -0
  78. instaui/spa_router/_components.py +35 -0
  79. instaui/spa_router/_file_base_utils.py +264 -0
  80. instaui/spa_router/_functions.py +122 -0
  81. instaui/spa_router/_install.py +11 -0
  82. instaui/spa_router/_route_model.py +139 -0
  83. instaui/spa_router/_router_box.py +40 -0
  84. instaui/spa_router/_router_output.py +22 -0
  85. instaui/spa_router/_router_param_var.py +51 -0
  86. instaui/spa_router/_types.py +4 -0
  87. instaui/spa_router/templates/page_routes +59 -0
  88. instaui/static/insta-ui.css +1 -0
  89. instaui/static/insta-ui.esm-browser.prod.js +3663 -0
  90. instaui/static/insta-ui.iife.js +29 -0
  91. instaui/static/insta-ui.iife.js.map +1 -0
  92. instaui/static/insta-ui.js.map +1 -0
  93. instaui/static/tailwindcss.min.js +62 -0
  94. instaui/static/templates/debug/sse.html +117 -0
  95. instaui/static/templates/web.html +118 -0
  96. instaui/static/templates/zero.html +55 -0
  97. instaui/static/vue.esm-browser.prod.js +9 -0
  98. instaui/static/vue.global.prod.js +9 -0
  99. instaui/static/vue.runtime.esm-browser.prod.js +5 -0
  100. instaui/systems/file_system.py +17 -0
  101. instaui/systems/func_system.py +104 -0
  102. instaui/systems/js_system.py +22 -0
  103. instaui/systems/pydantic_system.py +27 -0
  104. instaui/systems/string_system.py +10 -0
  105. instaui/template/__init__.py +4 -0
  106. instaui/template/env.py +7 -0
  107. instaui/template/web_template.py +55 -0
  108. instaui/template/zero_template.py +24 -0
  109. instaui/ui/__init__.py +121 -0
  110. instaui/ui/events.py +25 -0
  111. instaui/ui_functions/input_slient_data.py +16 -0
  112. instaui/ui_functions/server.py +13 -0
  113. instaui/ui_functions/str_format.py +36 -0
  114. instaui/ui_functions/ui_page.py +31 -0
  115. instaui/ui_functions/ui_types.py +13 -0
  116. instaui/ui_functions/url_location.py +33 -0
  117. instaui/vars/__init__.py +13 -0
  118. instaui/vars/_types.py +8 -0
  119. instaui/vars/_utils.py +12 -0
  120. instaui/vars/data.py +68 -0
  121. instaui/vars/element_ref.py +42 -0
  122. instaui/vars/event_context.py +45 -0
  123. instaui/vars/event_extend.py +0 -0
  124. instaui/vars/js_computed.py +95 -0
  125. instaui/vars/mixin_types/common_type.py +5 -0
  126. instaui/vars/mixin_types/element_binding.py +10 -0
  127. instaui/vars/mixin_types/observable.py +7 -0
  128. instaui/vars/mixin_types/pathable.py +14 -0
  129. instaui/vars/mixin_types/py_binding.py +13 -0
  130. instaui/vars/mixin_types/str_format_binding.py +8 -0
  131. instaui/vars/mixin_types/var_type.py +5 -0
  132. instaui/vars/path_var.py +89 -0
  133. instaui/vars/ref.py +103 -0
  134. instaui/vars/slot_prop.py +46 -0
  135. instaui/vars/state.py +82 -0
  136. instaui/vars/types.py +24 -0
  137. instaui/vars/vfor_item.py +204 -0
  138. instaui/vars/vue_computed.py +82 -0
  139. instaui/vars/web_computed.py +157 -0
  140. instaui/vars/web_view_computed.py +1 -0
  141. instaui/version.py +3 -0
  142. instaui/watch/_types.py +4 -0
  143. instaui/watch/_utils.py +3 -0
  144. instaui/watch/js_watch.py +74 -0
  145. instaui/watch/vue_watch.py +61 -0
  146. instaui/watch/web_watch.py +123 -0
  147. instaui/zero/__init__.py +3 -0
  148. instaui/zero/scope.py +9 -0
  149. instaui-0.1.0.dist-info/LICENSE +21 -0
  150. instaui-0.1.0.dist-info/METADATA +154 -0
  151. instaui-0.1.0.dist-info/RECORD +152 -0
  152. 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