ex4nicegui 0.2.17__py3-none-any.whl → 0.3.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.
ex4nicegui/__init__.py CHANGED
@@ -16,4 +16,4 @@ from signe import batch
16
16
  from ex4nicegui.experimental_ import gridLayout as exp_ui
17
17
 
18
18
 
19
- __version__ = "0.2.17"
19
+ __version__ = "0.3.0"
@@ -0,0 +1,3 @@
1
+ from .index import data_source
2
+
3
+ __all__ = ["data_source"]
@@ -0,0 +1,174 @@
1
+ from typing import Dict, List, Optional, cast
2
+ from ex4nicegui import to_ref, ref_computed, on
3
+ from nicegui import globals, Client
4
+
5
+ from dataclasses import dataclass
6
+ from . import types
7
+ from .protocols import IDataSourceAble
8
+
9
+
10
+ @dataclass
11
+ class DataSourceInfo:
12
+ source: "DataSource"
13
+ update_callback: types._TSourceBuildFn
14
+
15
+
16
+ @dataclass
17
+ class Filter:
18
+ callback: types._TFilterCallback
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class ComponentInfoKey:
23
+ client_id: types._TNgClientID
24
+ element_id: types._TElementID
25
+
26
+
27
+ @dataclass
28
+ class ComponentInfo:
29
+ key: ComponentInfoKey
30
+ update_callback: types._TComponentUpdateCallback
31
+ filter: Optional[Filter] = None
32
+
33
+
34
+ class ComponentMap:
35
+ def __init__(self) -> None:
36
+ self._client_map: Dict[
37
+ types._TNgClientID, Dict[types._TElementID, ComponentInfo]
38
+ ] = {}
39
+
40
+ def has_client_record(self, client_id: types._TNgClientID):
41
+ return client_id in self._client_map
42
+
43
+ def add_info(self, info: ComponentInfo):
44
+ client_id = info.key.client_id
45
+ element_id = info.key.element_id
46
+
47
+ if client_id not in self._client_map:
48
+ self._client_map[client_id] = {element_id: info}
49
+
50
+ element_map = self._client_map[client_id]
51
+
52
+ if element_id not in element_map:
53
+ element_map[element_id] = info
54
+
55
+ def remove_client(self, client_id: types._TNgClientID):
56
+ if client_id in self._client_map:
57
+ del self._client_map[client_id]
58
+
59
+ def has_record(self, client_id: types._TNgClientID, element_id: types._TElementID):
60
+ return (
61
+ client_id in self._client_map and element_id in self._client_map[client_id]
62
+ )
63
+
64
+ def set_filter(
65
+ self,
66
+ client_id: types._TNgClientID,
67
+ element_id: types._TElementID,
68
+ filter: Filter,
69
+ ):
70
+ self._client_map[client_id][element_id].filter = filter
71
+
72
+ def get_all_info(self):
73
+ return (
74
+ info for ele_map in self._client_map.values() for info in ele_map.values()
75
+ )
76
+
77
+
78
+ class DataSource:
79
+ _global_id_count: types._TDataSourceId = 0
80
+
81
+ def __init__(self, data: IDataSourceAble) -> None:
82
+ DataSource._global_id_count += 1
83
+ self.__id = DataSource._global_id_count
84
+
85
+ self._idataSource = data
86
+
87
+ data_fn = lambda: data.get_data()
88
+ data_fn = ref_computed(data_fn)
89
+
90
+ self.__filters = to_ref(cast(List[Filter], []))
91
+
92
+ @ref_computed
93
+ def apply_filters():
94
+ df = data_fn.value
95
+ for f in self.__filters.value:
96
+ df = f.callback(df)
97
+
98
+ return df
99
+
100
+ self.__filtered_data = apply_filters
101
+
102
+ self.__data = data_fn
103
+ self._component_map = ComponentMap()
104
+
105
+ @on(data_fn)
106
+ def _():
107
+ self.__notify_update()
108
+
109
+ @property
110
+ def data(self):
111
+ return self.__data.value
112
+
113
+ @property
114
+ def filtered_data(self):
115
+ return self.__filtered_data.value
116
+
117
+ @property
118
+ def id(self):
119
+ return self.__id
120
+
121
+ def _register_component(
122
+ self,
123
+ element_id: types._TElementID,
124
+ update_callback: types._TComponentUpdateCallback,
125
+ ):
126
+ ng_client = globals.get_client()
127
+ client_id = ng_client.id
128
+
129
+ if not self._component_map.has_client_record(client_id):
130
+
131
+ @ng_client.on_disconnect
132
+ def _(e: Client):
133
+ if not e.shared:
134
+ self._component_map.remove_client(e.id)
135
+
136
+ self._component_map.add_info(
137
+ ComponentInfo(ComponentInfoKey(client_id, element_id), update_callback)
138
+ )
139
+
140
+ return self
141
+
142
+ def send_filter(self, element_id: types._TElementID, filter: Filter):
143
+ client_id = globals.get_client().id
144
+
145
+ if not self._component_map.has_record(client_id, element_id):
146
+ raise ValueError("element not register")
147
+
148
+ self._component_map.set_filter(client_id, element_id, filter)
149
+
150
+ self.__notify_update([ComponentInfoKey(client_id, element_id)])
151
+ return self
152
+
153
+ def __notify_update(self, ignore_keys: Optional[List[ComponentInfoKey]] = None):
154
+ ignore_keys = ignore_keys or []
155
+ ignore_ids_set = set(ignore_keys)
156
+
157
+ # nodify every component
158
+ for current_info in self._component_map.get_all_info():
159
+ if current_info.key in ignore_ids_set:
160
+ continue
161
+
162
+ # apply filters ,except current target
163
+ filters = [
164
+ info.filter.callback
165
+ for info in self._component_map.get_all_info()
166
+ if (info.key != current_info.key) and info.filter
167
+ ]
168
+
169
+ new_data = self._idataSource.apply_filters(self.__data.value, filters)
170
+ current_info.update_callback(new_data)
171
+
172
+ self.__filters.value = [
173
+ info.filter for info in self._component_map.get_all_info() if info.filter
174
+ ]
@@ -0,0 +1,263 @@
1
+ from __future__ import annotations
2
+ from typing import Any, Callable, Dict, TypeVar, Generic, Union, cast
3
+ from nicegui import ui
4
+ from ex4nicegui import ref_computed
5
+ from ex4nicegui.reactive import rxui
6
+ from .dataSource import DataSource, Filter
7
+ from ex4nicegui.reactive.EChartsComponent.ECharts import echarts
8
+
9
+
10
+ _TData = TypeVar("_TData")
11
+
12
+
13
+ class DataSourceFacade(Generic[_TData]):
14
+ def __init__(self, ds: DataSource) -> None:
15
+ self._dataSource = ds
16
+
17
+ @property
18
+ def data(self) -> _TData:
19
+ """Data without any filtering"""
20
+ return cast(_TData, self._dataSource.data)
21
+
22
+ @property
23
+ def filtered_data(self) -> _TData:
24
+ """Data after filtering"""
25
+ return cast(_TData, self._dataSource.filtered_data)
26
+
27
+ def ui_select(
28
+ self, column: str, *, clearable=True, multiple=True, **kwargs
29
+ ) -> ui.select:
30
+ """
31
+ Creates a user interface select box.
32
+
33
+ Parameters:
34
+ column (str): The column name of the data source.
35
+ clearable (bool, optional): Whether to allow clearing the content of the select box. Default is True.
36
+ multiple (bool, optional): Whether to allow multiple selections.
37
+ **kwargs: Additional optional parameters that will be passed to the ui.select constructor.
38
+
39
+ Returns:
40
+ ui.select: An instance of a user interface select box.
41
+ """
42
+ options = self._dataSource._idataSource.duplicates_column_values(
43
+ self.data, column
44
+ )
45
+ kwargs.update(
46
+ {
47
+ "options": options,
48
+ "multiple": multiple,
49
+ "clearable": clearable,
50
+ "label": column,
51
+ }
52
+ )
53
+
54
+ cp = ui.select(**kwargs).props("use-chips outlined")
55
+
56
+ def onchange(e):
57
+ value = None
58
+ if e.args:
59
+ if isinstance(e.args, list):
60
+ value = [arg["label"] for arg in e.args]
61
+ else:
62
+ value = e.args["label"]
63
+
64
+ cp.value = value
65
+
66
+ def data_filter(data):
67
+ if cp.value is None or not cp.value:
68
+ return data
69
+
70
+ cond = None
71
+ if isinstance(cp.value, list):
72
+ cond = data[column].isin(cp.value)
73
+ else:
74
+ cond = data[column] == cp.value
75
+ return data[cond]
76
+
77
+ self._dataSource.send_filter(cp.id, Filter(data_filter))
78
+
79
+ cp.on("update:modelValue", onchange)
80
+
81
+ def on_source_update(data):
82
+ options = self._dataSource._idataSource.duplicates_column_values(
83
+ data, column
84
+ )
85
+ value = cp.value
86
+
87
+ # Make the value within the options
88
+ if isinstance(value, list):
89
+ value = list(set(value) & set(options))
90
+ else:
91
+ if value not in options:
92
+ value = ""
93
+
94
+ cp.set_options(options, value=value)
95
+
96
+ self._dataSource._register_component(cp.id, on_source_update)
97
+
98
+ return cp
99
+
100
+ def ui_aggrid(self, **kwargs):
101
+ """
102
+ Creates aggrid table.
103
+
104
+ Parameters:
105
+ **kwargs: Additional optional parameters that will be passed to the ui.aggrid constructor.
106
+
107
+ Returns:
108
+ ui.aggrid: aggrid table.
109
+ """
110
+ kwargs.update(
111
+ {"options": self._dataSource._idataSource.get_aggrid_options(self.data)}
112
+ )
113
+
114
+ cp = ui.aggrid(**kwargs)
115
+
116
+ def on_source_update(data):
117
+ cp._props["options"] = self._dataSource._idataSource.get_aggrid_options(
118
+ data
119
+ )
120
+ cp.update()
121
+
122
+ on_source_update(self.filtered_data)
123
+
124
+ self._dataSource._register_component(cp.id, on_source_update)
125
+
126
+ return cp
127
+
128
+ def ui_radio(self, column: str, **kwargs):
129
+ """
130
+ Creates radio Selection.
131
+
132
+ Parameters:
133
+ column (str): The column name of the data source.
134
+ **kwargs: Additional optional parameters that will be passed to the ui.radio constructor.
135
+
136
+ Returns:
137
+ ui.radio: An radio Selection.
138
+ """
139
+ options = self._dataSource._idataSource.duplicates_column_values(
140
+ self.data, column
141
+ )
142
+ kwargs.update({"options": options})
143
+
144
+ cp = ui.radio(**kwargs)
145
+
146
+ def onchange(e):
147
+ cp.value = cp.options[e.args]
148
+
149
+ def data_filter(data):
150
+ if cp.value not in cp.options:
151
+ return data
152
+ cond = data[column] == cp.value
153
+ return data[cond]
154
+
155
+ self._dataSource.send_filter(cp.id, Filter(data_filter))
156
+
157
+ cp.on("update:modelValue", onchange)
158
+
159
+ def on_source_update(data):
160
+ options = self._dataSource._idataSource.duplicates_column_values(
161
+ data, column
162
+ )
163
+ value = cp.value
164
+ if value not in options:
165
+ value = ""
166
+
167
+ cp.set_options(options, value=value)
168
+
169
+ self._dataSource._register_component(cp.id, on_source_update)
170
+
171
+ return cp
172
+
173
+ def ui_slider(self, column: str, **kwargs):
174
+ """
175
+ Creates Slider.
176
+
177
+ Parameters:
178
+ column (str): The column name of the data source.
179
+ **kwargs: Additional optional parameters that will be passed to the ui.slider constructor.
180
+
181
+ Returns:
182
+ ui.radio: An Slider.
183
+ """
184
+ self._dataSource._idataSource.slider_check(self.data, column)
185
+
186
+ min, max = self._dataSource._idataSource.slider_min_max(self.data, column)
187
+ kwargs.update({"min": min, "max": max})
188
+
189
+ cp = ui.slider(**kwargs).props("label label-always switch-label-side")
190
+
191
+ def onchange():
192
+ def data_filter(data):
193
+ if cp.value is None or cp.value < min:
194
+ return data
195
+ cond = data[column] == cp.value
196
+ return data[cond]
197
+
198
+ self._dataSource.send_filter(cp.id, Filter(data_filter))
199
+
200
+ cp.on("change", onchange)
201
+
202
+ def on_source_update(data):
203
+ min, max = self._dataSource._idataSource.slider_min_max(data, column)
204
+ if min is None or max is None:
205
+ cp.value = None
206
+ else:
207
+ cp._props["min"] = min
208
+ cp._props["max"] = max
209
+
210
+ self._dataSource._register_component(cp.id, on_source_update)
211
+
212
+ return cp
213
+
214
+ def ui_echarts(
215
+ self, fn: Callable[[Any], Union[Dict, "pyecharts.Base"]] # pyright: ignore
216
+ ) -> echarts:
217
+ """Create charts
218
+
219
+ Args:
220
+ fn (Callable[[Any], Union[Dict, "pyecharts.Base"]]): builder function.
221
+
222
+ ## Examples
223
+
224
+ Support pyecharts
225
+
226
+ ```py
227
+ import pandas as pd
228
+ from ex4nicegui import bi
229
+ from pyecharts.charts import Bar
230
+
231
+ df = pd.DataFrame({"name": list("abcdc"), "value": range(5)})
232
+ ds = bi.data_source(df)
233
+
234
+ @ds.ui_echarts
235
+ def bar(data: pd.DataFrame):
236
+ c = (
237
+ Bar()
238
+ .add_xaxis(data["name"].tolist())
239
+ .add_yaxis("value", data["value"].tolist())
240
+ )
241
+
242
+ return c
243
+
244
+ bar.classes("h-[20rem]")
245
+ ```
246
+
247
+ """
248
+
249
+ @ref_computed
250
+ def chart_options():
251
+ options = fn(self.filtered_data)
252
+ if isinstance(options, Dict):
253
+ return options
254
+
255
+ import simplejson as json
256
+ from pyecharts.charts.chart import Base
257
+
258
+ if isinstance(options, Base):
259
+ return cast(Dict, json.loads(options.dump_options()))
260
+
261
+ cp = rxui.echarts(chart_options) # type: ignore
262
+
263
+ return cp.element # type: ignore
ex4nicegui/bi/index.py ADDED
@@ -0,0 +1,76 @@
1
+ from .dataSourceFacade import DataSourceFacade
2
+ from .dataSource import DataSource
3
+ from .protocols import CallableDataSourceAble, DataFrameDataSourceAble
4
+ from typing import Callable, cast, TypeVar, Union
5
+
6
+
7
+ _TData = TypeVar("_TData")
8
+
9
+
10
+ def data_source(data: Union[Callable[..., _TData], _TData]) -> DataSourceFacade[_TData]:
11
+ """Create a data source
12
+
13
+ Args:
14
+ data (_TData): Any supported data
15
+
16
+ Raises:
17
+ TypeError: Throw an error when the data type is not supported
18
+
19
+ Returns:
20
+ DataSourceFacade[_TData]: _description_
21
+
22
+ ## Examples
23
+
24
+ ---
25
+
26
+ pandas dataframe source:
27
+ ```python
28
+ df = pd.DataFrame({"name": list("abcdc"), "value": range(5)})
29
+ ds = bi.data_source(df)
30
+ ```
31
+
32
+ ---
33
+
34
+ Link multiple data sources
35
+
36
+ ---
37
+ ```python
38
+ df = pd.DataFrame({"name": list("abcdc"), "value": range(5)})
39
+ ds = bi.data_source(df)
40
+
41
+ @bi.data_source
42
+ def ds_other():
43
+ # ds.filtered_data is DataFrame after filtering
44
+ where = ds.filtered_data[''].isin(['b','c','d'])
45
+ return ds.filtered_data[where]
46
+
47
+ ```
48
+
49
+ ---
50
+
51
+ Now, when `ds` changes, it will trigger changes to `ds_other` and thus drive the related interface components to change.
52
+
53
+ ```python
54
+ # select box of data source 'ds'
55
+ # it change will trigger changes to table
56
+ ds.ui_select('name')
57
+
58
+ # table of data 'ds_other'
59
+ ds_other.ui_aggrid()
60
+ ```
61
+
62
+
63
+ """
64
+ ds_protocol = None
65
+
66
+ if type(data).__name__ == "DataFrame":
67
+ ds_protocol = DataFrameDataSourceAble(data)
68
+
69
+ if isinstance(data, Callable):
70
+ ds_protocol = CallableDataSourceAble(data)
71
+
72
+ if ds_protocol is None:
73
+ raise TypeError(f"not support type[{type(data)}]")
74
+
75
+ ds = DataSource(ds_protocol)
76
+ return cast(DataSourceFacade[_TData], DataSourceFacade(ds))
@@ -0,0 +1,111 @@
1
+ from typing_extensions import Protocol
2
+ from typing import Callable, Dict, List, Optional, Tuple
3
+
4
+ from ex4nicegui.bi.types import _TFilterCallback
5
+ from .types import _TFilterCallback
6
+ from ex4nicegui.utils import common as utils_common
7
+
8
+
9
+ class IDataSourceAble(Protocol):
10
+ def get_data(self):
11
+ ...
12
+
13
+ def apply_filters(self, data, filters: List[_TFilterCallback]):
14
+ ...
15
+
16
+ def duplicates_column_values(self, data, column_name: str) -> List:
17
+ ...
18
+
19
+ def get_aggrid_options(self, data) -> Dict:
20
+ ...
21
+
22
+ def slider_check(self, data, column_name: str) -> None:
23
+ ...
24
+
25
+ def slider_min_max(
26
+ self, data, column_name: str
27
+ ) -> Tuple[Optional[float], Optional[float]]:
28
+ ...
29
+
30
+
31
+ class DataFrameDataSourceAble(IDataSourceAble):
32
+ def __init__(self, df) -> None:
33
+ self.data = df
34
+
35
+ def get_data(self):
36
+ return self.data
37
+
38
+ def apply_filters(self, data, filters: List[_TFilterCallback]):
39
+ new_data = data
40
+ for f in filters:
41
+ new_data = f(new_data)
42
+
43
+ return new_data
44
+
45
+ def duplicates_column_values(self, data, column_name: str) -> List:
46
+ return data[column_name].drop_duplicates().tolist()
47
+
48
+ def get_aggrid_options(self, data) -> Dict:
49
+ df = utils_common.convert_dataframe(data)
50
+ return {
51
+ "columnDefs": [{"field": col} for col in df.columns],
52
+ "rowData": df.to_dict("records"),
53
+ }
54
+
55
+ def slider_check(self, data, column_name: str) -> None:
56
+ from pandas.api.types import is_numeric_dtype
57
+
58
+ if not is_numeric_dtype(data[column_name]):
59
+ raise ValueError(f"column[{column_name}] must be numeric type")
60
+
61
+ def slider_min_max(
62
+ self, data, column_name: str
63
+ ) -> Tuple[Optional[float], Optional[float]]:
64
+ import numpy as np
65
+
66
+ min, max = data[column_name].min(), data[column_name].max()
67
+
68
+ if np.isnan(min) or np.isnan(max):
69
+ return None, None
70
+
71
+ return min, max
72
+
73
+
74
+ class CallableDataSourceAble(IDataSourceAble):
75
+ def __init__(self, fn: Callable) -> None:
76
+ self.data_fn = fn
77
+
78
+ def get_data(self):
79
+ return self.data_fn()
80
+
81
+ def apply_filters(self, data, filters: List[_TFilterCallback]):
82
+ new_data = data
83
+ for f in filters:
84
+ new_data = f(new_data)
85
+
86
+ return new_data
87
+
88
+ def duplicates_column_values(self, data, column_name: str) -> List:
89
+ return data[column_name].drop_duplicates().tolist()
90
+
91
+ def get_aggrid_options(self, data) -> Dict:
92
+ df = data
93
+ return {
94
+ "columnDefs": [{"field": col} for col in df.columns],
95
+ "rowData": df.to_dict("records"),
96
+ }
97
+
98
+ def slider_check(self, data, column_name: str) -> None:
99
+ pass
100
+
101
+ def slider_min_max(
102
+ self, data, column_name: str
103
+ ) -> Tuple[Optional[float], Optional[float]]:
104
+ import numpy as np
105
+
106
+ min, max = data[column_name].min(), data[column_name].max()
107
+
108
+ if np.isnan(min) or np.isnan(max):
109
+ return None, None
110
+
111
+ return min, max
ex4nicegui/bi/types.py ADDED
@@ -0,0 +1,12 @@
1
+ from typing import Callable, TypeVar
2
+
3
+
4
+ _TData = TypeVar("_TData")
5
+
6
+ _TElementID = int
7
+ _TNgClientID = str
8
+ _TComponentUpdateCallback = Callable[[_TData], None]
9
+ _TFilterCallback = Callable[[_TData], _TData]
10
+
11
+ _TDataSourceId = int
12
+ _TSourceBuildFn = Callable[..., _TData]
@@ -1,2 +1,2 @@
1
- # from .gridbox import grid_box
2
1
  from .gridFlex import *
2
+ from .rxFlex import *
@@ -147,7 +147,7 @@ class GridFlex(Element, component="GridFlex.js"):
147
147
  break_point: Optional[TBreakpoint] = None,
148
148
  **kws,
149
149
  ):
150
- if area is not None:
150
+ if area is not None and area.strip() != "":
151
151
  areas_list = utils.areas_str2array(area)
152
152
  area = utils.areas_array2str(areas_list)
153
153
  areas_cols_len = max(map(len, areas_list))
@@ -0,0 +1 @@
1
+ from .index import rx_column, rx_row
@@ -0,0 +1,121 @@
1
+ from typing import Callable, List, Optional, Union, TypeVar, Generic
2
+ from typing_extensions import Literal
3
+
4
+ from ex4nicegui.layout import grid_box, mark_area
5
+ from nicegui import ui, app
6
+ from .types import *
7
+
8
+ _T_itemWraper_add_var = TypeVar("_T_itemWraper_add_var")
9
+
10
+
11
+ class ItemWraper:
12
+ def __init__(self, fn: Callable[[ui.element], ui.element]):
13
+ self.fn = fn
14
+
15
+ def __radd__(self, other: _T_itemWraper_add_var) -> _T_itemWraper_add_var:
16
+ return self.fn(other) # type: ignore
17
+
18
+
19
+ class rx_flex_box(ui.element):
20
+ def space(self):
21
+ return ui.element("q-space")
22
+
23
+ def gap(self, value: Union[int, float, str]):
24
+ if isinstance(value, (int, float)):
25
+ value = f"{value}rem"
26
+ self._style["gap"] = str(value)
27
+ self.update()
28
+ return self
29
+
30
+ def all_items_grow(self):
31
+ self._props["ex4ng-rx-flex-auto-grow"] = ""
32
+ return self
33
+
34
+
35
+ def _q_space():
36
+ return ui.element("q-space")
37
+
38
+
39
+ class rx_column(ui.column, rx_flex_box):
40
+ def __init__(
41
+ self,
42
+ horizontal: TColumn_Horizontal = "left",
43
+ vertical: TColumn_Vertical = "top",
44
+ ) -> None:
45
+ super().__init__()
46
+ self.tailwind.align_items
47
+ self.horizontal(horizontal)
48
+ self.vertical(vertical)
49
+
50
+ self._props["ex4ng-rx-column"] = ""
51
+
52
+ def item_horizontal(self, value: TColumn_Item_Horizontal):
53
+ def fn(ele: ui.element):
54
+ ele._style["align-self"] = Column_Item_Horizontal_map.get(value, value)
55
+ # ele.update()
56
+ return ele
57
+
58
+ return ItemWraper(fn)
59
+
60
+ def horizontal(self, value: TColumn_Horizontal):
61
+ self._style["align-items"] = Column_Horizontal_map.get(value, value)
62
+ self.update()
63
+ return self
64
+
65
+ def vertical(self, value: TColumn_Vertical):
66
+ self._style["justify-content"] = Column_Vertical_map.get(value, value)
67
+ self.update()
68
+ return self
69
+
70
+ def space(self):
71
+ return _q_space()
72
+
73
+
74
+ class rx_row(ui.row, rx_flex_box):
75
+ def __init__(
76
+ self,
77
+ horizontal: TRow_Horizontal = "left",
78
+ vertical: TRow_Vertical = "top",
79
+ ) -> None:
80
+ super().__init__()
81
+ self.horizontal(horizontal)
82
+ self.vertical(vertical)
83
+ self._props["ex4ng-rx-row"] = ""
84
+
85
+ def item_vertical(self, value: TRow_Item_Vertical):
86
+ def fn(ele: ui.element):
87
+ ele._style["align-self"] = Row_Vertical_map.get(value, value)
88
+ return ele
89
+
90
+ return ItemWraper(fn)
91
+
92
+ def horizontal(self, value: TRow_Horizontal):
93
+ self._style["justify-content"] = Row_Horizontal_map.get(value, value)
94
+ self.update()
95
+ return self
96
+
97
+ def vertical(self, value: TRow_Vertical):
98
+ self._style["align-items"] = Row_Vertical_map.get(value, value)
99
+ self.update()
100
+ return
101
+
102
+ def space(self):
103
+ return _q_space()
104
+
105
+
106
+ class page_view(rx_column):
107
+ def __init__(
108
+ self,
109
+ horizontal: TColumn_Horizontal = "left",
110
+ vertical: TColumn_Vertical = "top",
111
+ ) -> None:
112
+ super().__init__(horizontal, vertical)
113
+ self.classes("w-full h-full no-wrap")
114
+ ui.query("main.q-page").classes("flex")
115
+ ui.query(".nicegui-content").classes("grow p-0")
116
+
117
+ def all_center(self):
118
+ return self.horizontal("center").vertical("center")
119
+
120
+ def full_screen(self):
121
+ return self.classes("fullscreen")
@@ -0,0 +1,80 @@
1
+ # region type
2
+
3
+ from typing_extensions import Literal
4
+
5
+
6
+ Space_map = {
7
+ "between": "space-between",
8
+ "around": "space-around",
9
+ "evenly": "space-evenly",
10
+ }
11
+
12
+ TColumn_Item_Horizontal = Literal[
13
+ "auto",
14
+ "left",
15
+ "right",
16
+ "center",
17
+ "stretch",
18
+ "baseline",
19
+ ]
20
+ Column_Item_Horizontal_map = {"left": "flex-start", "right": "flex-end"}
21
+
22
+
23
+ TColumn_Horizontal = Literal[
24
+ "left",
25
+ "right",
26
+ "center",
27
+ "baseline",
28
+ "stretch",
29
+ ]
30
+
31
+ Column_Horizontal_map = {"left": "flex-start", "right": "flex-end"}
32
+
33
+ TColumn_Vertical = Literal[
34
+ "normal",
35
+ "top",
36
+ "bottom",
37
+ "center",
38
+ "between",
39
+ "around",
40
+ "evenly",
41
+ "stretch",
42
+ ]
43
+
44
+ Column_Vertical_map = {"top": "flex-start", "bottom": "flex-end", **Space_map}
45
+
46
+
47
+ TRow_Horizontal = Literal[
48
+ "normal",
49
+ "left",
50
+ "right",
51
+ "center",
52
+ "between",
53
+ "around",
54
+ "evenly",
55
+ "stretch",
56
+ ]
57
+
58
+ Row_Horizontal_map = {"left": "flex-start", "right": "flex-end", **Space_map}
59
+
60
+ TRow_Vertical = Literal[
61
+ "top",
62
+ "bottom",
63
+ "center",
64
+ "baseline",
65
+ "stretch",
66
+ ]
67
+
68
+ Row_Vertical_map = {"top": "flex-start", "bottom": "flex-end"}
69
+
70
+
71
+ TRow_Item_Vertical = Literal[
72
+ "auto",
73
+ "top",
74
+ "bottom",
75
+ "center",
76
+ "stretch",
77
+ "baseline",
78
+ ]
79
+ TRow_Item_Vertical_map = {"top": "flex-start", "bottom": "flex-end"}
80
+ # endregion
@@ -43967,8 +43967,7 @@ const TX = { class: "echart-container relative" }, CX = /* @__PURE__ */ GL({
43967
43967
  l.target || t("chartClickBlank");
43968
43968
  });
43969
43969
  function s() {
43970
- const l = n.value.getBoundingClientRect();
43971
- i == null || i.resize({ width: l.width - 5, height: l.height - 5 });
43970
+ i == null || i.resize();
43972
43971
  }
43973
43972
  ZL("resize", () => {
43974
43973
  s();
@@ -26,7 +26,7 @@ class EChartsBindableUi(BindableUi[echarts]):
26
26
 
27
27
  value_kws = _convert_kws_ref2value(kws)
28
28
 
29
- element = echarts(**value_kws)
29
+ element = echarts(**value_kws).classes("grow self-stretch h-[16rem]")
30
30
 
31
31
  super().__init__(element)
32
32
 
@@ -0,0 +1,42 @@
1
+ from typing import Dict, List
2
+ from signe.core.effect import Effect
3
+ from signe.core.scope import IScope
4
+ from nicegui import globals as ng_globals, Client
5
+
6
+
7
+ _TClientID = str
8
+
9
+
10
+ class NgClientScope(IScope):
11
+ def __init__(self) -> None:
12
+ self._effects: List[Effect] = []
13
+
14
+ def add_effect(self, effect: Effect):
15
+ self._effects.append(effect)
16
+
17
+ def dispose(self):
18
+ for effect in self._effects:
19
+ effect.dispose()
20
+
21
+
22
+ class NgClientScopeManager:
23
+ def __init__(self) -> None:
24
+ self._client_scope_map: Dict[_TClientID, NgClientScope] = {}
25
+
26
+ def get_scope(self):
27
+ if len(ng_globals.get_slot_stack()) <= 0:
28
+ return
29
+
30
+ client = ng_globals.get_client()
31
+ if client.shared:
32
+ return
33
+
34
+ if client.id not in self._client_scope_map:
35
+ self._client_scope_map[client.id] = NgClientScope()
36
+
37
+ @client.on_disconnect
38
+ def _(e: Client):
39
+ if e.id in self._client_scope_map:
40
+ self._client_scope_map[e.id].dispose()
41
+
42
+ return self._client_scope_map[client.id]
@@ -1,9 +1,12 @@
1
- from signe import createSignal, effect, computed, on as signe_on
1
+ from signe import createSignal, effect as signe_effect, computed, on as signe_on
2
2
  from signe.core.signal import Signal, SignalOption
3
+ from signe.core.effect import Effect
3
4
  from signe import utils as signe_utils
5
+ from .clientScope import NgClientScopeManager
4
6
  from signe.types import TSetter, TGetter
5
7
  from typing import (
6
8
  Any,
9
+ Dict,
7
10
  TypeVar,
8
11
  Generic,
9
12
  overload,
@@ -17,6 +20,8 @@ from nicegui import ui
17
20
 
18
21
  T = TypeVar("T")
19
22
 
23
+ _CLIENT_SCOPE_MANAGER = NgClientScopeManager()
24
+
20
25
 
21
26
  class ReadonlyRef(Generic[T]):
22
27
  def __init__(self, getter: TGetter[T]) -> None:
@@ -115,6 +120,39 @@ def ref(value: T):
115
120
  return cast(Ref[T], Ref(s.getValue, s.setValue, s))
116
121
 
117
122
 
123
+ @overload
124
+ def effect(
125
+ fn: None = ...,
126
+ *,
127
+ priority_level=1,
128
+ debug_trigger: Optional[Callable] = None,
129
+ ) -> signe_utils._TEffect_Fn[None]:
130
+ ...
131
+
132
+
133
+ @overload
134
+ def effect(
135
+ fn: Callable[..., None],
136
+ *,
137
+ priority_level=1,
138
+ debug_trigger: Optional[Callable] = None,
139
+ ) -> Effect[None]:
140
+ ...
141
+
142
+
143
+ def effect(
144
+ fn: Optional[Callable[..., None]] = None,
145
+ *,
146
+ priority_level=1,
147
+ debug_trigger: Optional[Callable] = None,
148
+ ) -> Union[signe_utils._TEffect_Fn[None], Effect[None]]:
149
+ kws = {
150
+ "debug_trigger": debug_trigger,
151
+ "priority_level": priority_level,
152
+ }
153
+ return signe_effect(fn, **kws, scope=_CLIENT_SCOPE_MANAGER.get_scope())
154
+
155
+
118
156
  @overload
119
157
  def ref_computed(
120
158
  fn: Callable[[], T],
@@ -122,6 +160,7 @@ def ref_computed(
122
160
  desc="",
123
161
  debug_trigger: Optional[Callable[..., None]] = None,
124
162
  priority_level: int = 1,
163
+ debug_name: Optional[str] = None,
125
164
  ) -> ReadonlyRef[T]:
126
165
  ...
127
166
 
@@ -133,6 +172,7 @@ def ref_computed(
133
172
  desc="",
134
173
  debug_trigger: Optional[Callable[..., None]] = None,
135
174
  priority_level: int = 1,
175
+ debug_name: Optional[str] = None,
136
176
  ) -> Callable[[Callable[..., T]], ReadonlyRef[T]]:
137
177
  ...
138
178
 
@@ -143,14 +183,16 @@ def ref_computed(
143
183
  desc="",
144
184
  debug_trigger: Optional[Callable[..., None]] = None,
145
185
  priority_level: int = 1,
186
+ debug_name: Optional[str] = None,
146
187
  ) -> Union[ReadonlyRef[T], Callable[[Callable[..., T]], ReadonlyRef[T]]]:
147
188
  kws = {
148
189
  "debug_trigger": debug_trigger,
149
190
  "priority_level": priority_level,
191
+ "debug_name": debug_name,
150
192
  }
151
193
 
152
194
  if fn:
153
- getter = computed(fn, **kws)
195
+ getter = computed(fn, **kws, scope=_CLIENT_SCOPE_MANAGER.get_scope())
154
196
  return cast(DescReadonlyRef[T], DescReadonlyRef(getter, desc))
155
197
  else:
156
198
 
@@ -192,20 +234,23 @@ class effect_refreshable:
192
234
  re_func.refresh()
193
235
 
194
236
  if len(self._refs) == 0:
195
- runner = effect(runner)
237
+ runner = signe_effect(runner)
196
238
  else:
197
239
  runner = on(self._refs)(runner)
198
240
 
199
241
  return runner
200
242
 
201
243
 
202
- def on(refs: Union[ReadonlyRef, Sequence[ReadonlyRef]]):
244
+ def on(
245
+ refs: Union[ReadonlyRef, Sequence[ReadonlyRef]],
246
+ effect_kws: Optional[Dict[str, Any]] = None,
247
+ ):
203
248
  if not isinstance(refs, Sequence):
204
249
  refs = [refs]
205
250
 
206
251
  getters = [getattr(r, "_ReadonlyRef___getter") for r in refs]
207
252
 
208
253
  def wrap(fn: Callable):
209
- return signe_on(getters, fn)
254
+ return signe_on(getters, fn, effect_kws=effect_kws)
210
255
 
211
256
  return wrap
@@ -1,19 +1,19 @@
1
- Metadata-Version: 2.1
2
- Name: ex4nicegui
3
- Version: 0.2.17
4
- Summary: ...
5
- Home-page:
6
- Author: carson_jia
7
- Author-email: 568166495@qq.com
8
- License: MIT license
9
- Keywords: nicegui,ex4nicegui,webui
10
- Classifier: Intended Audience :: Developers
11
- Classifier: License :: OSI Approved :: MIT License
12
- Classifier: Natural Language :: English
13
- Classifier: Programming Language :: Python :: 3.8
14
- Requires-Python: >=3.8
15
- License-File: LICENSE
16
- Requires-Dist: signe (>=0.1.8)
17
- Requires-Dist: nicegui (>=1.3.13)
18
- Requires-Dist: typing-extensions
19
-
1
+ Metadata-Version: 2.1
2
+ Name: ex4nicegui
3
+ Version: 0.3.0
4
+ Summary: ...
5
+ Home-page:
6
+ Author: carson_jia
7
+ Author-email: 568166495@qq.com
8
+ License: MIT license
9
+ Keywords: nicegui,ex4nicegui,webui
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Natural Language :: English
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Requires-Python: >=3.8
15
+ License-File: LICENSE
16
+ Requires-Dist: signe (>=0.2.3)
17
+ Requires-Dist: nicegui (>=1.3.13)
18
+ Requires-Dist: typing-extensions
19
+
@@ -1,13 +1,21 @@
1
- ex4nicegui/__init__.py,sha256=9OkkZEITcLZ-h7lqMk4OHrHynnfSjlsBc2EeIexno34,397
1
+ ex4nicegui/__init__.py,sha256=r2UGFOv2DxiLcgASHLLVpmZJALQYsF2VTWGRSQQMoRw,396
2
+ ex4nicegui/bi/__init__.py,sha256=OXA3np4-3CaujOLAxza8vK0mrV0nIfNP-TQBlfbq9k8,61
3
+ ex4nicegui/bi/dataSource.py,sha256=yue_zPniAmjq8-z4gXg718kfCXU1CU5gCwj1AufWoEc,5039
4
+ ex4nicegui/bi/dataSourceFacade.py,sha256=S6D7h82o5loIFqHkDN4gtC1knDUuqzRF_O7t38qRibw,8024
5
+ ex4nicegui/bi/index.py,sha256=Zuo8V_7IZnayo8vxEMFbx-BkZTkEca-QhHVrmVTHjuc,1883
6
+ ex4nicegui/bi/protocols.py,sha256=YiLYC62nFb3UsGu4Ly9AkM-kj1TuG1vlqKLJb23xe90,3175
7
+ ex4nicegui/bi/types.py,sha256=R6mfDqD9dONJNq1_3uPcMFUsTBr6Rdpu5KFIfsvNSN4,277
2
8
  ex4nicegui/experimental_/__init__.py,sha256=LSDd_U6eQ9g9St9kC4daau3MFGlVCRHGZJC4E0JRH34,36
3
9
  ex4nicegui/experimental_/gridLayout/__init__.py,sha256=48y_Pm0xxgC_PRnixQB5R_5rPL4FuyeoeOao_W7pm7A,49
4
10
  ex4nicegui/experimental_/gridLayout/index.py,sha256=zFXuvFroo5EC1CFjt-b4hMiEy67hGP5J1GYTKH6kpUU,4737
5
- ex4nicegui/layout/__init__.py,sha256=j7Yb_p3YT69QHKVcn4dCjQ43JwVUzV5hDJ7cVTh_ZGA,58
6
- ex4nicegui/layout/_gridbox.py,sha256=CcupbaDVbKl2g2KVWvpaSPO73tuuRvfSNuyzH2koe4U,2454
11
+ ex4nicegui/layout/__init__.py,sha256=yYkdH0bC1nxIDr_OtvyTqR_yXkwnY5DmelaQOIwwjas,48
7
12
  ex4nicegui/layout/gridFlex/GridFlex.js,sha256=ljkxGFucBUIPksMAT5w_35sxGogC7OzxzXnOw21Z3_k,4468
8
13
  ex4nicegui/layout/gridFlex/__init__.py,sha256=98dcrOEROibl5fnzIj644CnELbUvYTid702IjhLdpfs,79
9
- ex4nicegui/layout/gridFlex/gridFlex.py,sha256=bAGpnLp-cjHK6p-SK8nrRQNTOylZ1HwmeGlSeWokoRs,7438
14
+ ex4nicegui/layout/gridFlex/gridFlex.py,sha256=8zMHKgh23wVr3TXVcvv8_KzyIZLk5zPUdgf7BWgy38k,7461
10
15
  ex4nicegui/layout/gridFlex/utils.py,sha256=hBuusveBRaHSubIr2q38AP033-VtXDFE_fDzZtg4h44,1236
16
+ ex4nicegui/layout/rxFlex/__init__.py,sha256=dllXV6cri1oOZkOCGJpI9AlUjIZ3oB99ckLIYRW8faM,38
17
+ ex4nicegui/layout/rxFlex/index.py,sha256=XeAsxfy35RJBE2g2WGzKgskf43K4We0d2mBzWb0kbB8,3509
18
+ ex4nicegui/layout/rxFlex/types.py,sha256=OQBo3kkmtXhMo3BDI0GjX36HPJLDV1Cm67hPZGb1k2Q,1411
11
19
  ex4nicegui/reactive/__index.py,sha256=OaN_mD2TJORxqTzh2JeKAqaOhIBqM5cBnm21RqKt7PQ,451
12
20
  ex4nicegui/reactive/__init__.py,sha256=NZUgvItxqqgzHKrt4oGZnxxV9dlEudGiv4J3fhJdvdQ,24
13
21
  ex4nicegui/reactive/drawer.py,sha256=NWMq2gnalpYAU8tT0DwGN5l8n7fuMxTIWxOfr2QvFIA,1356
@@ -16,7 +24,7 @@ ex4nicegui/reactive/local_file_picker.py,sha256=DWNzm_IP02sY-nZWN6WEWJxlwpABW6tN
16
24
  ex4nicegui/reactive/q_pagination.py,sha256=ITXBrjLnI1a5bz3Rbn7j8lZs9UJaFuMHrM9_FW_V7NA,1217
17
25
  ex4nicegui/reactive/rxui.py,sha256=NZUgvItxqqgzHKrt4oGZnxxV9dlEudGiv4J3fhJdvdQ,24
18
26
  ex4nicegui/reactive/usePagination.py,sha256=IP1NeLxaH3413KTEjtbyuzq0FVdtnKQsTZqM-W7iEgY,2468
19
- ex4nicegui/reactive/EChartsComponent/ECharts.js,sha256=zpB_m1dxK236_qZAiM8jzMkNCVTEPgeSV6tFpdgLZoo,1581763
27
+ ex4nicegui/reactive/EChartsComponent/ECharts.js,sha256=qj3HUngg-pcTjNDCiz7jgz-O2Lx3aW0HhvrcsDEb0a0,1581667
20
28
  ex4nicegui/reactive/EChartsComponent/ECharts.py,sha256=LhThgTo7TSjaXIaWcG6eJojuPWyhUkDkw3tJIuCkJTw,2968
21
29
  ex4nicegui/reactive/EChartsComponent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
30
  ex4nicegui/reactive/UseDraggable/UseDraggable.js,sha256=D2_4c64qYwkqG_JzL1ZAwoNZDoz6qtHfPA_Z5RIvmIw,5235
@@ -35,7 +43,7 @@ ex4nicegui/reactive/officials/color_picker.py,sha256=s6zUBkCAqAnBnoLWS3bXFIqmCK5
35
43
  ex4nicegui/reactive/officials/column.py,sha256=3RLvVKNaDtOb8df4uS3xRfwJJPuH1ndXk_Y4Gry0Tjo,413
36
44
  ex4nicegui/reactive/officials/date.py,sha256=4muMUxoXwV_OYBoe-ucB5g20msT2zjbiyfGNZbKPdoU,2700
37
45
  ex4nicegui/reactive/officials/drawer.py,sha256=7XqP8UmkvgxglV1-vUwbo5Sx2Fz7CPr1UqBfriEagDE,2443
38
- ex4nicegui/reactive/officials/echarts.py,sha256=kVlKT1PPjD7BDvI8LmOfyhvhCt4KyHQEVYD9M0fQu2I,2159
46
+ ex4nicegui/reactive/officials/echarts.py,sha256=HmQqzcvIFum6C80_b6IPmLLy-D16l_8y1IaHYFOsylk,2198
39
47
  ex4nicegui/reactive/officials/expansion.py,sha256=Z2aKsrtUpkO0Z4kO9kPwcu7piBcE_d62OAC2oVDFTGE,1528
40
48
  ex4nicegui/reactive/officials/grid.py,sha256=6brGijR9ZLqOhe5r2w4BF81R8I4kJPZxZVkbQjXwlOU,925
41
49
  ex4nicegui/reactive/officials/html.py,sha256=7CQWKu_t3MdDJX21fTC3xTMAOcg0gKZrKJsaSCpZ0e4,1687
@@ -59,10 +67,11 @@ ex4nicegui/reactive/useMouse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
59
67
  ex4nicegui/tools/__init__.py,sha256=Ue6ATQC9BuQlJEcs2JnuFXZh4DYh9twKc4F7zpIPhjE,40
60
68
  ex4nicegui/tools/debug.py,sha256=HCKlVzhHx5av-983ADgwgMkScKwTreSluLA7uikGYa0,4887
61
69
  ex4nicegui/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
70
+ ex4nicegui/utils/clientScope.py,sha256=plgHxcCY1y76bK31u5zfOTX8cnYC1Jlx-N3cAgCY2G4,1143
62
71
  ex4nicegui/utils/common.py,sha256=5fsaOkoj-Ild1LGsInZXra66gJLVoVcZGAIG6YOeM6E,430
63
- ex4nicegui/utils/signals.py,sha256=Dwvb0Anansa0N1ZH2Ko0yCX9o-QHKbXOYEtvdrtde9g,4807
64
- ex4nicegui-0.2.17.dist-info/LICENSE,sha256=0KDDElS2dl-HIsWvbpy8ywbLzJMBFzXLev57LnMIZXs,1094
65
- ex4nicegui-0.2.17.dist-info/METADATA,sha256=2PpOH1p1Ob0TY1r4xePjfy5lwtWuIpxPIMiPuXfsfYM,515
66
- ex4nicegui-0.2.17.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
67
- ex4nicegui-0.2.17.dist-info/top_level.txt,sha256=VFwMiO9AFjj5rfLMJwN1ipLRASk9fJXB8tM6DNrpvPQ,11
68
- ex4nicegui-0.2.17.dist-info/RECORD,,
72
+ ex4nicegui/utils/signals.py,sha256=S15wA6WPLBmohn1Z5EpFOxC1KvksOf5ir86PuhRM_Oc,5969
73
+ ex4nicegui-0.3.0.dist-info/LICENSE,sha256=0KDDElS2dl-HIsWvbpy8ywbLzJMBFzXLev57LnMIZXs,1094
74
+ ex4nicegui-0.3.0.dist-info/METADATA,sha256=WfHwYH3zUfTli4m0P_D8wDRdls-AMIZqwwcoggnjBKw,533
75
+ ex4nicegui-0.3.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
76
+ ex4nicegui-0.3.0.dist-info/top_level.txt,sha256=VFwMiO9AFjj5rfLMJwN1ipLRASk9fJXB8tM6DNrpvPQ,11
77
+ ex4nicegui-0.3.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.37.1)
2
+ Generator: bdist_wheel (0.40.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,87 +0,0 @@
1
- from typing import List, Optional, Union, cast
2
- from nicegui.client import Client
3
- from nicegui.element import Element
4
- from ex4nicegui.reactive.officials.base import BindableUi
5
-
6
-
7
- def _areas_str2array(areas: str) -> List[List[str]]:
8
- """
9
- >>> input='''
10
- sc1 sc2
11
- sc3
12
- table table table table
13
- '''
14
- >>> areas_str2array(input)
15
- >>> [
16
- ["sc1", "sc2"],
17
- ["sc3"],
18
- ["table", "table", "table", "table"]
19
- ]
20
- """
21
- pass
22
-
23
- lines = (line.strip() for line in areas.splitlines())
24
- remove_empty_rows = (line for line in lines if len(line) > 0)
25
- splie_space = (line.split() for line in remove_empty_rows)
26
- return list(splie_space)
27
-
28
-
29
- def _areas_array2str(areas_array: List[List[str]]):
30
- """
31
- >>> input = [
32
- ["sc1", "sc2"],
33
- ["sc3"],
34
- ["table"] * 4
35
- ]
36
- >>> areas_array2str(input)
37
- >>> '"sc1 sc2 . ." "sc3 . . ." "table table table table"'
38
- """
39
- max_len = max(map(len, areas_array))
40
-
41
- fix_empty = (
42
- [*line, *(["."] * (max_len - len(line)))] if len(line) < max_len else line
43
- for line in areas_array
44
- )
45
-
46
- line2str = (f'"{" ".join(line)}"' for line in fix_empty)
47
- return " ".join(line2str)
48
-
49
-
50
- class grid_box(Element):
51
- def __init__(
52
- self,
53
- areas_text: str,
54
- template_columns: Optional[str] = None,
55
- template_rows: Optional[str] = None,
56
- *,
57
- _client: Optional[Client] = None,
58
- ) -> None:
59
- super().__init__("div", _client=_client)
60
-
61
- areas_list = _areas_str2array(areas_text)
62
-
63
- areas = _areas_array2str(areas_list)
64
-
65
- areas_cols_len = max(map(len, areas_list))
66
- areas_rows_len = len(areas_list)
67
-
68
- template_columns = template_columns or f"repeat({areas_cols_len}, 1fr)"
69
- template_rows = template_rows or f"repeat({areas_rows_len}, 1fr)"
70
-
71
- self.style(
72
- f"""
73
- grid-template-areas: {areas};
74
- display: grid;
75
- grid-template-columns: {template_columns};
76
- grid-template-rows:{template_rows};"""
77
- )
78
-
79
- def areas_mark(self, element: Union[Element, BindableUi], mark: str):
80
- if isinstance(element, BindableUi):
81
- element = element.element
82
- element = cast(Element, element)
83
- element.style(self.mark_style(mark))
84
- return element
85
-
86
- def mark_style(self, mark: str):
87
- return f"grid-area:{mark}"