nicegui-bootstrap-components 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.
- nicegui_bootstrap_components/__init__.py +7 -0
- nicegui_bootstrap_components/_base.py +590 -0
- nicegui_bootstrap_components/_host.py +259 -0
- nicegui_bootstrap_components/assets.py +371 -0
- nicegui_bootstrap_components/bs/__init__.py +267 -0
- nicegui_bootstrap_components/bs/_actions.py +232 -0
- nicegui_bootstrap_components/bs/_alert_spinner.py +423 -0
- nicegui_bootstrap_components/bs/_carousel.py +312 -0
- nicegui_bootstrap_components/bs/_choices.py +779 -0
- nicegui_bootstrap_components/bs/_content.py +1343 -0
- nicegui_bootstrap_components/bs/_forms.py +378 -0
- nicegui_bootstrap_components/bs/_inputs.py +560 -0
- nicegui_bootstrap_components/bs/_layout.py +341 -0
- nicegui_bootstrap_components/bs/_nav.py +650 -0
- nicegui_bootstrap_components/bs/_navbar.py +445 -0
- nicegui_bootstrap_components/bs/_overlay_root.py +73 -0
- nicegui_bootstrap_components/bs/_overlays.py +2633 -0
- nicegui_bootstrap_components/bs/_scope.py +28 -0
- nicegui_bootstrap_components/bs/_tabs.py +605 -0
- nicegui_bootstrap_components/bs/_toast.py +137 -0
- nicegui_bootstrap_components/bs/_tooltip_popover.py +537 -0
- nicegui_bootstrap_components/contracts/dbc-2.0.4.json +16811 -0
- nicegui_bootstrap_components/dbc/__init__.py +108 -0
- nicegui_bootstrap_components/icons.py +29 -0
- nicegui_bootstrap_components/persist.py +201 -0
- nicegui_bootstrap_components/py.typed +0 -0
- nicegui_bootstrap_components/static/dist/fonts/bootstrap-icons.woff +0 -0
- nicegui_bootstrap_components/static/dist/fonts/bootstrap-icons.woff2 +0 -0
- nicegui_bootstrap_components/static/dist/ngbs-bootstrap-unscoped.css +12047 -0
- nicegui_bootstrap_components/static/dist/ngbs-bootstrap.css +12057 -0
- nicegui_bootstrap_components/static/dist/ngbs-cerulean-unscoped.css +12338 -0
- nicegui_bootstrap_components/static/dist/ngbs-cerulean.css +12348 -0
- nicegui_bootstrap_components/static/dist/ngbs-compat-unscoped.css +12 -0
- nicegui_bootstrap_components/static/dist/ngbs-compat.css +12 -0
- nicegui_bootstrap_components/static/dist/ngbs-cosmo-unscoped.css +11825 -0
- nicegui_bootstrap_components/static/dist/ngbs-cosmo.css +11835 -0
- nicegui_bootstrap_components/static/dist/ngbs-cyborg-unscoped.css +12213 -0
- nicegui_bootstrap_components/static/dist/ngbs-cyborg.css +12223 -0
- nicegui_bootstrap_components/static/dist/ngbs-darkly-unscoped.css +12143 -0
- nicegui_bootstrap_components/static/dist/ngbs-darkly.css +12153 -0
- nicegui_bootstrap_components/static/dist/ngbs-flatly-unscoped.css +12129 -0
- nicegui_bootstrap_components/static/dist/ngbs-flatly.css +12139 -0
- nicegui_bootstrap_components/static/dist/ngbs-grid-unscoped.css +4084 -0
- nicegui_bootstrap_components/static/dist/ngbs-grid.css +4094 -0
- nicegui_bootstrap_components/static/dist/ngbs-host-unscoped.css +9 -0
- nicegui_bootstrap_components/static/dist/ngbs-host.css +9 -0
- nicegui_bootstrap_components/static/dist/ngbs-icons-bootstrap.css +5 -0
- nicegui_bootstrap_components/static/dist/ngbs-icons-fontawesome.css +9 -0
- nicegui_bootstrap_components/static/dist/ngbs-journal-unscoped.css +12112 -0
- nicegui_bootstrap_components/static/dist/ngbs-journal.css +12122 -0
- nicegui_bootstrap_components/static/dist/ngbs-litera-unscoped.css +12214 -0
- nicegui_bootstrap_components/static/dist/ngbs-litera.css +12224 -0
- nicegui_bootstrap_components/static/dist/ngbs-lumen-unscoped.css +12377 -0
- nicegui_bootstrap_components/static/dist/ngbs-lumen.css +12387 -0
- nicegui_bootstrap_components/static/dist/ngbs-lux-unscoped.css +12048 -0
- nicegui_bootstrap_components/static/dist/ngbs-lux.css +12058 -0
- nicegui_bootstrap_components/static/dist/ngbs-materia-unscoped.css +13197 -0
- nicegui_bootstrap_components/static/dist/ngbs-materia.css +13207 -0
- nicegui_bootstrap_components/static/dist/ngbs-minty-unscoped.css +12184 -0
- nicegui_bootstrap_components/static/dist/ngbs-minty.css +12194 -0
- nicegui_bootstrap_components/static/dist/ngbs-morph-unscoped.css +12829 -0
- nicegui_bootstrap_components/static/dist/ngbs-morph.css +12839 -0
- nicegui_bootstrap_components/static/dist/ngbs-pulse-unscoped.css +11901 -0
- nicegui_bootstrap_components/static/dist/ngbs-pulse.css +11911 -0
- nicegui_bootstrap_components/static/dist/ngbs-quartz-unscoped.css +12625 -0
- nicegui_bootstrap_components/static/dist/ngbs-quartz.css +12635 -0
- nicegui_bootstrap_components/static/dist/ngbs-sandstone-unscoped.css +12214 -0
- nicegui_bootstrap_components/static/dist/ngbs-sandstone.css +12224 -0
- nicegui_bootstrap_components/static/dist/ngbs-simplex-unscoped.css +12199 -0
- nicegui_bootstrap_components/static/dist/ngbs-simplex.css +12209 -0
- nicegui_bootstrap_components/static/dist/ngbs-sketchy-unscoped.css +12518 -0
- nicegui_bootstrap_components/static/dist/ngbs-sketchy.css +12528 -0
- nicegui_bootstrap_components/static/dist/ngbs-slate-unscoped.css +12527 -0
- nicegui_bootstrap_components/static/dist/ngbs-slate.css +12537 -0
- nicegui_bootstrap_components/static/dist/ngbs-solar-unscoped.css +12152 -0
- nicegui_bootstrap_components/static/dist/ngbs-solar.css +12162 -0
- nicegui_bootstrap_components/static/dist/ngbs-spacelab-unscoped.css +12279 -0
- nicegui_bootstrap_components/static/dist/ngbs-spacelab.css +12289 -0
- nicegui_bootstrap_components/static/dist/ngbs-superhero-unscoped.css +12223 -0
- nicegui_bootstrap_components/static/dist/ngbs-superhero.css +12233 -0
- nicegui_bootstrap_components/static/dist/ngbs-united-unscoped.css +12090 -0
- nicegui_bootstrap_components/static/dist/ngbs-united.css +12100 -0
- nicegui_bootstrap_components/static/dist/ngbs-vapor-unscoped.css +12556 -0
- nicegui_bootstrap_components/static/dist/ngbs-vapor.css +12566 -0
- nicegui_bootstrap_components/static/dist/ngbs-yeti-unscoped.css +12333 -0
- nicegui_bootstrap_components/static/dist/ngbs-yeti.css +12343 -0
- nicegui_bootstrap_components/static/dist/ngbs-zephyr-unscoped.css +12296 -0
- nicegui_bootstrap_components/static/dist/ngbs-zephyr.css +12306 -0
- nicegui_bootstrap_components/static/prototype-ngbs.css +541 -0
- nicegui_bootstrap_components/static/webfonts/fa-brands-400.ttf +0 -0
- nicegui_bootstrap_components/static/webfonts/fa-brands-400.woff2 +0 -0
- nicegui_bootstrap_components/static/webfonts/fa-regular-400.ttf +0 -0
- nicegui_bootstrap_components/static/webfonts/fa-regular-400.woff2 +0 -0
- nicegui_bootstrap_components/static/webfonts/fa-solid-900.ttf +0 -0
- nicegui_bootstrap_components/static/webfonts/fa-solid-900.woff2 +0 -0
- nicegui_bootstrap_components/static/webfonts/fa-v4compatibility.ttf +0 -0
- nicegui_bootstrap_components/static/webfonts/fa-v4compatibility.woff2 +0 -0
- nicegui_bootstrap_components/theme.py +238 -0
- nicegui_bootstrap_components/themes.py +131 -0
- nicegui_bootstrap_components-0.1.0.dist-info/METADATA +234 -0
- nicegui_bootstrap_components-0.1.0.dist-info/RECORD +103 -0
- nicegui_bootstrap_components-0.1.0.dist-info/WHEEL +4 -0
- nicegui_bootstrap_components-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from nicegui_bootstrap_components import bs, dbc, icons, themes
|
|
2
|
+
from nicegui_bootstrap_components.assets import StyleMode, setup
|
|
3
|
+
from nicegui_bootstrap_components.theme import ThemeController
|
|
4
|
+
|
|
5
|
+
__version__ = "0.1.0"
|
|
6
|
+
|
|
7
|
+
__all__ = ["StyleMode", "ThemeController", "bs", "dbc", "icons", "setup", "themes", "__version__"]
|
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
"""Shared Bootstrap element contract, dual-surface factory, and normalization."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
import warnings
|
|
7
|
+
from collections.abc import Callable, Mapping, MutableMapping
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Any, Literal, cast
|
|
10
|
+
|
|
11
|
+
from ._host import (
|
|
12
|
+
Element,
|
|
13
|
+
ValueElement,
|
|
14
|
+
client_store,
|
|
15
|
+
create_text_span,
|
|
16
|
+
get_parent,
|
|
17
|
+
get_slot_parent,
|
|
18
|
+
is_deleted,
|
|
19
|
+
is_ui_element,
|
|
20
|
+
move_element,
|
|
21
|
+
set_element_text,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"BootstrapElement",
|
|
26
|
+
"BootstrapElementMixin",
|
|
27
|
+
"BootstrapValueElement",
|
|
28
|
+
"ChildrenError",
|
|
29
|
+
"OverlayElement",
|
|
30
|
+
"PropConflictError",
|
|
31
|
+
"SharedProps",
|
|
32
|
+
"UnsupportedPropError",
|
|
33
|
+
"_normalize_children",
|
|
34
|
+
"lookup_public_id",
|
|
35
|
+
"make_surface_classes",
|
|
36
|
+
"normalize_style",
|
|
37
|
+
"prepare_shared_props",
|
|
38
|
+
"reject_or_warn",
|
|
39
|
+
"resolve_class_name",
|
|
40
|
+
"style_to_css",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
SurfaceName = Literal["native", "compat"]
|
|
44
|
+
|
|
45
|
+
_UNITLESS = frozenset(
|
|
46
|
+
{
|
|
47
|
+
"animation-iteration-count",
|
|
48
|
+
"flex",
|
|
49
|
+
"flex-grow",
|
|
50
|
+
"flex-shrink",
|
|
51
|
+
"font-weight",
|
|
52
|
+
"line-height",
|
|
53
|
+
"opacity",
|
|
54
|
+
"order",
|
|
55
|
+
"z-index",
|
|
56
|
+
"zoom",
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
_KEY_WARNED = False
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class PropConflictError(ValueError):
|
|
64
|
+
"""Conflicting aliases that would map to different nodes."""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ChildrenError(ValueError):
|
|
68
|
+
"""Illegal children, adoption, or mixed-content combination."""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class UnsupportedPropError(TypeError):
|
|
72
|
+
"""Raised on the compat surface for Dash-only or wrong-surface props."""
|
|
73
|
+
|
|
74
|
+
def __init__(self, prop: str, *, component: str | None = None) -> None:
|
|
75
|
+
self.prop = prop
|
|
76
|
+
self.component = component
|
|
77
|
+
if component:
|
|
78
|
+
msg = f"{component}: unsupported prop {prop!r}"
|
|
79
|
+
else:
|
|
80
|
+
msg = f"Unsupported prop {prop!r}"
|
|
81
|
+
super().__init__(msg)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass(frozen=True)
|
|
85
|
+
class SharedProps:
|
|
86
|
+
"""Normalized shared constructor fields."""
|
|
87
|
+
|
|
88
|
+
public_id: str | None
|
|
89
|
+
class_name: str | None
|
|
90
|
+
style: dict[str, str] | None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _reset_process_warnings() -> None:
|
|
94
|
+
global _KEY_WARNED
|
|
95
|
+
_KEY_WARNED = False
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _kebab(name: str) -> str:
|
|
99
|
+
if "-" in name:
|
|
100
|
+
return name.lower()
|
|
101
|
+
chars: list[str] = []
|
|
102
|
+
for i, ch in enumerate(name):
|
|
103
|
+
if ch.isupper() and i > 0:
|
|
104
|
+
chars.append("-")
|
|
105
|
+
chars.append(ch.lower())
|
|
106
|
+
return "".join(chars)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def normalize_style(style: Mapping[str, Any]) -> dict[str, str]:
|
|
110
|
+
"""Px-ify numeric CSS values except the unitless allow-list. Keys are preserved."""
|
|
111
|
+
out: dict[str, str] = {}
|
|
112
|
+
for key, value in style.items():
|
|
113
|
+
if value is None:
|
|
114
|
+
continue
|
|
115
|
+
kebab = _kebab(str(key))
|
|
116
|
+
if isinstance(value, bool):
|
|
117
|
+
out[str(key)] = "true" if value else "false"
|
|
118
|
+
continue
|
|
119
|
+
if isinstance(value, (int, float)) and kebab not in _UNITLESS:
|
|
120
|
+
if isinstance(value, float) and value.is_integer():
|
|
121
|
+
out[str(key)] = f"{int(value)}px"
|
|
122
|
+
else:
|
|
123
|
+
out[str(key)] = f"{value}px"
|
|
124
|
+
continue
|
|
125
|
+
out[str(key)] = str(value)
|
|
126
|
+
return out
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def style_to_css(style: Mapping[str, str]) -> str:
|
|
130
|
+
"""Serialize a normalized style dict to an inline CSS string."""
|
|
131
|
+
return "; ".join(f"{key}: {value}" for key, value in style.items())
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def resolve_class_name(
|
|
135
|
+
class_name: str | None,
|
|
136
|
+
className: str | None,
|
|
137
|
+
surface: str,
|
|
138
|
+
) -> str | None:
|
|
139
|
+
"""Resolve ``class_name`` / ``className``. ``class_name`` wins when both nonempty."""
|
|
140
|
+
preferred = class_name if class_name else None
|
|
141
|
+
alias = className if className else None
|
|
142
|
+
if preferred and alias:
|
|
143
|
+
if preferred != alias and surface == "native":
|
|
144
|
+
warnings.warn(
|
|
145
|
+
"Both class_name and className were provided; class_name wins. "
|
|
146
|
+
"className is deprecated.",
|
|
147
|
+
DeprecationWarning,
|
|
148
|
+
stacklevel=2,
|
|
149
|
+
)
|
|
150
|
+
return preferred
|
|
151
|
+
return preferred or alias
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def reject_or_warn(surface: str, prop: str, *, component: str | None = None) -> None:
|
|
155
|
+
"""Raise on compat; warn and ignore on native."""
|
|
156
|
+
if surface == "compat":
|
|
157
|
+
raise UnsupportedPropError(prop, component=component)
|
|
158
|
+
warnings.warn(
|
|
159
|
+
f"{prop} is not supported on the native surface and will be ignored",
|
|
160
|
+
UserWarning,
|
|
161
|
+
stacklevel=3,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def consume_key(surface: str, props: dict[str, Any]) -> None:
|
|
166
|
+
"""Drop Dash/React ``key``. Compat raises; native warns once per process."""
|
|
167
|
+
global _KEY_WARNED
|
|
168
|
+
if "key" not in props:
|
|
169
|
+
return
|
|
170
|
+
value = props.pop("key")
|
|
171
|
+
if value is None:
|
|
172
|
+
return
|
|
173
|
+
if surface == "compat":
|
|
174
|
+
raise UnsupportedPropError("key")
|
|
175
|
+
if not _KEY_WARNED:
|
|
176
|
+
_KEY_WARNED = True
|
|
177
|
+
warnings.warn(
|
|
178
|
+
"`key` is unsupported and ignored on the native surface",
|
|
179
|
+
UserWarning,
|
|
180
|
+
stacklevel=3,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def prepare_shared_props(
|
|
185
|
+
surface: str,
|
|
186
|
+
props: dict[str, Any],
|
|
187
|
+
*,
|
|
188
|
+
component: str | None = None,
|
|
189
|
+
) -> SharedProps:
|
|
190
|
+
"""Pop and normalize shared fields from ``props`` (mutates ``props``)."""
|
|
191
|
+
consume_key(surface, props)
|
|
192
|
+
if "loading_state" in props:
|
|
193
|
+
props.pop("loading_state")
|
|
194
|
+
reject_or_warn(surface, "loading_state", component=component)
|
|
195
|
+
public_id = props.pop("id", None)
|
|
196
|
+
if public_id is not None and not isinstance(public_id, str):
|
|
197
|
+
if isinstance(public_id, (dict, tuple, list)):
|
|
198
|
+
raise TypeError("Pattern-matching ids are not supported")
|
|
199
|
+
public_id = str(public_id)
|
|
200
|
+
class_name = props.pop("class_name", None)
|
|
201
|
+
className = props.pop("className", None)
|
|
202
|
+
style = props.pop("style", None)
|
|
203
|
+
resolved = resolve_class_name(
|
|
204
|
+
class_name if isinstance(class_name, str) or class_name is None else str(class_name),
|
|
205
|
+
className if isinstance(className, str) or className is None else str(className),
|
|
206
|
+
surface,
|
|
207
|
+
)
|
|
208
|
+
if style is None:
|
|
209
|
+
norm_style: dict[str, str] | None = None
|
|
210
|
+
elif isinstance(style, Mapping):
|
|
211
|
+
norm_style = normalize_style(dict(style))
|
|
212
|
+
else:
|
|
213
|
+
raise TypeError(f"style must be a dict, got {type(style)!r}")
|
|
214
|
+
return SharedProps(public_id=public_id, class_name=resolved, style=norm_style)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _normalize_children(children: object) -> list[object]:
|
|
218
|
+
"""Flatten children recursively; drop ``None``; reject unknown types."""
|
|
219
|
+
if children is None:
|
|
220
|
+
return []
|
|
221
|
+
if isinstance(children, bool):
|
|
222
|
+
raise TypeError(f"Unsupported child type: {type(children)!r}")
|
|
223
|
+
if isinstance(children, (str, int, float)) or is_ui_element(children):
|
|
224
|
+
return [children]
|
|
225
|
+
if isinstance(children, (list, tuple)):
|
|
226
|
+
out: list[object] = []
|
|
227
|
+
for child in children:
|
|
228
|
+
out.extend(_normalize_children(child))
|
|
229
|
+
return out
|
|
230
|
+
raise TypeError(f"Unsupported child type: {type(children)!r}")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _would_cycle(child: Any, new_parent: Any) -> bool:
|
|
234
|
+
if child is new_parent:
|
|
235
|
+
return True
|
|
236
|
+
current: Any | None = new_parent
|
|
237
|
+
seen: set[int] = set()
|
|
238
|
+
while current is not None:
|
|
239
|
+
ident = id(current)
|
|
240
|
+
if ident in seen:
|
|
241
|
+
break
|
|
242
|
+
if current is child:
|
|
243
|
+
return True
|
|
244
|
+
seen.add(ident)
|
|
245
|
+
current = get_parent(current)
|
|
246
|
+
return False
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _to_snake(name: str) -> str:
|
|
250
|
+
out: list[str] = []
|
|
251
|
+
for i, ch in enumerate(name):
|
|
252
|
+
if ch.isupper() and i > 0:
|
|
253
|
+
out.append("_")
|
|
254
|
+
out.append(ch.lower())
|
|
255
|
+
return "".join(out)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _strip_impl(name: str) -> str:
|
|
259
|
+
trimmed = name[1:] if name.startswith("_") else name
|
|
260
|
+
if trimmed.endswith("Impl"):
|
|
261
|
+
trimmed = trimmed[: -len("Impl")]
|
|
262
|
+
return trimmed
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _make_surface_init(impl_cls: type, surface: str) -> Callable[..., None]:
|
|
266
|
+
impl_init = cast(Any, impl_cls.__init__) # type: ignore[misc]
|
|
267
|
+
params = list(inspect.signature(impl_init).parameters)
|
|
268
|
+
first = params[1] if len(params) > 1 and params[0] == "self" else (params[0] if params else "")
|
|
269
|
+
value_first = first == "value"
|
|
270
|
+
|
|
271
|
+
def __init__(self: Any, children: object = None, *args: Any, **props: Any) -> None:
|
|
272
|
+
init_fn = impl_init
|
|
273
|
+
if value_first:
|
|
274
|
+
init_fn(self, props.pop("value", None), children=children, _surface=surface, **props)
|
|
275
|
+
else:
|
|
276
|
+
init_fn(self, children, _surface=surface, **props)
|
|
277
|
+
|
|
278
|
+
__init__.__doc__ = getattr(impl_init, "__doc__", None)
|
|
279
|
+
return __init__
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def make_surface_classes(
|
|
283
|
+
impl_name: str | type,
|
|
284
|
+
native_ns: MutableMapping[str, Any] | None = None,
|
|
285
|
+
compat_ns: MutableMapping[str, Any] | None = None,
|
|
286
|
+
component_name: str | None = None,
|
|
287
|
+
) -> tuple[type, type]:
|
|
288
|
+
"""Create native/compat sibling subclasses of a private impl class.
|
|
289
|
+
|
|
290
|
+
``self._surface`` is set in each public subclass ``__init__`` (via the
|
|
291
|
+
impl's ``_surface`` argument) before shared validation runs.
|
|
292
|
+
|
|
293
|
+
When ``impl_name`` is a string, the impl is ``native_ns['_{impl_name}Impl']``.
|
|
294
|
+
The native class is stored under PascalCase and snake_case; the compat
|
|
295
|
+
class is stored as ``Dbc{Name}`` on ``native_ns`` and as ``{Name}`` on
|
|
296
|
+
``compat_ns`` when provided.
|
|
297
|
+
"""
|
|
298
|
+
if isinstance(impl_name, str):
|
|
299
|
+
if native_ns is None:
|
|
300
|
+
raise TypeError("native_ns is required when impl_name is a string")
|
|
301
|
+
name = impl_name
|
|
302
|
+
impl_cls = native_ns[f"_{name}Impl"]
|
|
303
|
+
else:
|
|
304
|
+
impl_cls = impl_name
|
|
305
|
+
name = str(
|
|
306
|
+
component_name
|
|
307
|
+
or getattr(impl_cls, "component_name", None)
|
|
308
|
+
or _strip_impl(impl_cls.__name__)
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
native_init = _make_surface_init(impl_cls, "native")
|
|
312
|
+
compat_init = _make_surface_init(impl_cls, "compat")
|
|
313
|
+
native_cls = type(
|
|
314
|
+
name,
|
|
315
|
+
(impl_cls,),
|
|
316
|
+
{
|
|
317
|
+
"__init__": native_init,
|
|
318
|
+
"__module__": impl_cls.__module__,
|
|
319
|
+
"__doc__": impl_cls.__doc__,
|
|
320
|
+
"__qualname__": name,
|
|
321
|
+
"component_name": getattr(impl_cls, "component_name", name),
|
|
322
|
+
},
|
|
323
|
+
)
|
|
324
|
+
compat_cls = type(
|
|
325
|
+
name,
|
|
326
|
+
(impl_cls,),
|
|
327
|
+
{
|
|
328
|
+
"__init__": compat_init,
|
|
329
|
+
"__module__": impl_cls.__module__,
|
|
330
|
+
"__doc__": impl_cls.__doc__,
|
|
331
|
+
"__qualname__": f"Dbc{name}",
|
|
332
|
+
"component_name": getattr(impl_cls, "component_name", name),
|
|
333
|
+
},
|
|
334
|
+
)
|
|
335
|
+
if native_ns is not None:
|
|
336
|
+
native_ns[name] = native_cls
|
|
337
|
+
native_ns[_to_snake(name)] = native_cls
|
|
338
|
+
native_ns[f"Dbc{name}"] = compat_cls
|
|
339
|
+
if compat_ns is not None:
|
|
340
|
+
compat_ns[name] = compat_cls
|
|
341
|
+
return native_cls, compat_cls
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def lookup_public_id(public_id: str, *, client: Any | None = None) -> Any | None:
|
|
345
|
+
"""Resolve a public (DBC) id via the per-client registry."""
|
|
346
|
+
store = client_store(client)
|
|
347
|
+
registry = store.get("public_ids", {})
|
|
348
|
+
if not isinstance(registry, dict):
|
|
349
|
+
return None
|
|
350
|
+
return registry.get(public_id)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class BootstrapElementMixin:
|
|
354
|
+
"""Contract, adoption, public ids, reserved classes. Not an element by itself."""
|
|
355
|
+
|
|
356
|
+
component_name: str = ""
|
|
357
|
+
styling_target: str = "root"
|
|
358
|
+
reserved_classes: tuple[str, ...] = ()
|
|
359
|
+
children_kind: str = "auto"
|
|
360
|
+
_surface: str
|
|
361
|
+
|
|
362
|
+
def __enter__(self) -> Any:
|
|
363
|
+
if getattr(self, "_children_explicit", False):
|
|
364
|
+
raise ChildrenError(
|
|
365
|
+
"Passing children= is not supported when the element is used as a context manager"
|
|
366
|
+
)
|
|
367
|
+
enter = getattr(super(), "__enter__", None)
|
|
368
|
+
if enter is None:
|
|
369
|
+
return self
|
|
370
|
+
return enter()
|
|
371
|
+
|
|
372
|
+
def __exit__(self, *args: Any) -> Any:
|
|
373
|
+
exit_ = getattr(super(), "__exit__", None)
|
|
374
|
+
if exit_ is None:
|
|
375
|
+
return None
|
|
376
|
+
return exit_(*args)
|
|
377
|
+
|
|
378
|
+
def _run_bootstrap_init(
|
|
379
|
+
self,
|
|
380
|
+
children: object,
|
|
381
|
+
props: dict[str, Any],
|
|
382
|
+
*,
|
|
383
|
+
init: Callable[[], None],
|
|
384
|
+
) -> None:
|
|
385
|
+
surface = props.pop("_surface", None)
|
|
386
|
+
if surface is not None:
|
|
387
|
+
self._surface = surface
|
|
388
|
+
elif not hasattr(self, "_surface"):
|
|
389
|
+
self._surface = "native"
|
|
390
|
+
self._ambient_parent = get_slot_parent()
|
|
391
|
+
self._children_explicit = children is not None
|
|
392
|
+
shared = prepare_shared_props(
|
|
393
|
+
self._surface,
|
|
394
|
+
props,
|
|
395
|
+
component=getattr(self, "component_name", None) or None,
|
|
396
|
+
)
|
|
397
|
+
if props:
|
|
398
|
+
raise TypeError(
|
|
399
|
+
f"{self.component_name or type(self).__name__}: unexpected keyword argument(s): "
|
|
400
|
+
+ ", ".join(sorted(str(k) for k in props))
|
|
401
|
+
)
|
|
402
|
+
init()
|
|
403
|
+
self._public_id = shared.public_id
|
|
404
|
+
self._user_class_name = shared.class_name
|
|
405
|
+
self._user_style = shared.style
|
|
406
|
+
self._apply_visuals()
|
|
407
|
+
self._set_public_id(self._public_id)
|
|
408
|
+
if children is not None:
|
|
409
|
+
self._adopt(children)
|
|
410
|
+
from .theme import ensure_theme_bound
|
|
411
|
+
|
|
412
|
+
ensure_theme_bound()
|
|
413
|
+
|
|
414
|
+
def _apply_visuals(self) -> None:
|
|
415
|
+
classes_fn = getattr(self, "classes", None)
|
|
416
|
+
style_fn = getattr(self, "style", None)
|
|
417
|
+
structural = tuple(getattr(self, "_structural_classes", ()))
|
|
418
|
+
if callable(classes_fn):
|
|
419
|
+
for cls in structural:
|
|
420
|
+
if cls:
|
|
421
|
+
classes_fn(cls)
|
|
422
|
+
# ``reserved_classes`` is a protection set (classes owned by the
|
|
423
|
+
# library that must survive user class mutations); it is never
|
|
424
|
+
# applied eagerly — variants like ``is-invalid`` or
|
|
425
|
+
# ``offcanvas-end`` would otherwise all be added at once.
|
|
426
|
+
user = getattr(self, "_user_class_name", None)
|
|
427
|
+
if user:
|
|
428
|
+
classes_fn(user)
|
|
429
|
+
if callable(style_fn):
|
|
430
|
+
user_style = getattr(self, "_user_style", None)
|
|
431
|
+
if user_style:
|
|
432
|
+
style_fn(style_to_css(user_style))
|
|
433
|
+
|
|
434
|
+
def _set_public_id(self, public_id: str | None) -> None:
|
|
435
|
+
self._public_id = public_id
|
|
436
|
+
if not public_id:
|
|
437
|
+
return
|
|
438
|
+
props = getattr(self, "_props", None)
|
|
439
|
+
if props is not None:
|
|
440
|
+
props["data-ngbs-public-id"] = public_id
|
|
441
|
+
try:
|
|
442
|
+
store = client_store(getattr(self, "client", None))
|
|
443
|
+
except Exception:
|
|
444
|
+
return
|
|
445
|
+
registry = store.setdefault("public_ids", {})
|
|
446
|
+
registry[public_id] = self
|
|
447
|
+
|
|
448
|
+
def _validate_adoptee(self, child: Any) -> None:
|
|
449
|
+
if is_deleted(child):
|
|
450
|
+
raise ChildrenError("Cannot adopt a deleted element")
|
|
451
|
+
self_client = getattr(self, "client", None)
|
|
452
|
+
child_client = getattr(child, "client", None)
|
|
453
|
+
if self_client is not None and child_client is not None and child_client is not self_client:
|
|
454
|
+
raise ChildrenError("Cannot adopt an element from a different client")
|
|
455
|
+
if _would_cycle(child, self):
|
|
456
|
+
raise ChildrenError("Adopting this element would create a cycle")
|
|
457
|
+
parent = get_parent(child)
|
|
458
|
+
ambient = getattr(self, "_ambient_parent", None)
|
|
459
|
+
if parent is not None and parent is not ambient and parent is not self:
|
|
460
|
+
raise ChildrenError(
|
|
461
|
+
"Cannot steal an element from an unrelated parent; "
|
|
462
|
+
"only nested-expression adoption is allowed"
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
def _adopt(self, children: object) -> None:
|
|
466
|
+
items = _normalize_children(children)
|
|
467
|
+
if not items:
|
|
468
|
+
return
|
|
469
|
+
coalesced = _coalesce_children(items)
|
|
470
|
+
grouping = getattr(self, "children_kind", "auto") == "grouping"
|
|
471
|
+
if grouping:
|
|
472
|
+
for item in coalesced:
|
|
473
|
+
if isinstance(item, str):
|
|
474
|
+
raise ChildrenError(
|
|
475
|
+
f"{self.component_name}: text children are not allowed. "
|
|
476
|
+
"Wrap text in a component; this parent only allows element children."
|
|
477
|
+
)
|
|
478
|
+
text_only = all(isinstance(item, str) for item in coalesced)
|
|
479
|
+
if text_only and coalesced and not grouping:
|
|
480
|
+
set_element_text(self, "".join(coalesced)) # type: ignore[arg-type]
|
|
481
|
+
return
|
|
482
|
+
seen: set[int] = set()
|
|
483
|
+
for item in coalesced:
|
|
484
|
+
if isinstance(item, str):
|
|
485
|
+
span = create_text_span(item)
|
|
486
|
+
move_element(span, self)
|
|
487
|
+
continue
|
|
488
|
+
ident = id(item)
|
|
489
|
+
if ident in seen:
|
|
490
|
+
raise ChildrenError("Duplicate ownership")
|
|
491
|
+
seen.add(ident)
|
|
492
|
+
self._validate_adoptee(item)
|
|
493
|
+
move_element(item, self)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def _coalesce_children(items: list[object]) -> list[object]:
|
|
497
|
+
out: list[object] = []
|
|
498
|
+
buf: list[str] = []
|
|
499
|
+
for item in items:
|
|
500
|
+
if isinstance(item, bool):
|
|
501
|
+
raise TypeError(f"Unsupported child type: {type(item)!r}")
|
|
502
|
+
if isinstance(item, (str, int, float)):
|
|
503
|
+
piece = str(item)
|
|
504
|
+
if piece:
|
|
505
|
+
buf.append(piece)
|
|
506
|
+
continue
|
|
507
|
+
if buf:
|
|
508
|
+
out.append("".join(buf))
|
|
509
|
+
buf = []
|
|
510
|
+
out.append(item)
|
|
511
|
+
if buf:
|
|
512
|
+
out.append("".join(buf))
|
|
513
|
+
return out
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
class BootstrapElement(BootstrapElementMixin, Element):
|
|
517
|
+
"""Structural or hybrid Bootstrap element."""
|
|
518
|
+
|
|
519
|
+
def __init__(
|
|
520
|
+
self,
|
|
521
|
+
children: object = None,
|
|
522
|
+
*,
|
|
523
|
+
tag: str = "div",
|
|
524
|
+
id: str | None = None,
|
|
525
|
+
class_name: str | None = None,
|
|
526
|
+
className: str | None = None,
|
|
527
|
+
style: Mapping[str, Any] | None = None,
|
|
528
|
+
**compat_props: Any,
|
|
529
|
+
) -> None:
|
|
530
|
+
if id is not None:
|
|
531
|
+
compat_props.setdefault("id", id)
|
|
532
|
+
if class_name is not None:
|
|
533
|
+
compat_props.setdefault("class_name", class_name)
|
|
534
|
+
if className is not None:
|
|
535
|
+
compat_props.setdefault("className", className)
|
|
536
|
+
if style is not None:
|
|
537
|
+
compat_props.setdefault("style", style)
|
|
538
|
+
self._desired_tag = tag
|
|
539
|
+
self._run_bootstrap_init(
|
|
540
|
+
children,
|
|
541
|
+
compat_props,
|
|
542
|
+
init=lambda: Element.__init__(self, tag),
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
class BootstrapValueElement(BootstrapElementMixin, ValueElement):
|
|
547
|
+
"""Form control with ``.value`` and ``on_change``.
|
|
548
|
+
|
|
549
|
+
Frontend contract: accept prop ``model-value`` and emit ``update:modelValue``.
|
|
550
|
+
Inherits NiceGUI ValueElement (bind_value, loopback protection).
|
|
551
|
+
"""
|
|
552
|
+
|
|
553
|
+
def __init__(
|
|
554
|
+
self,
|
|
555
|
+
children: object = None,
|
|
556
|
+
*,
|
|
557
|
+
value: Any = None,
|
|
558
|
+
on_change: Callable[..., Any] | None = None,
|
|
559
|
+
tag: str = "input",
|
|
560
|
+
id: str | None = None,
|
|
561
|
+
class_name: str | None = None,
|
|
562
|
+
className: str | None = None,
|
|
563
|
+
style: Mapping[str, Any] | None = None,
|
|
564
|
+
**props: Any,
|
|
565
|
+
) -> None:
|
|
566
|
+
if id is not None:
|
|
567
|
+
props.setdefault("id", id)
|
|
568
|
+
if class_name is not None:
|
|
569
|
+
props.setdefault("class_name", class_name)
|
|
570
|
+
if className is not None:
|
|
571
|
+
props.setdefault("className", className)
|
|
572
|
+
if style is not None:
|
|
573
|
+
props.setdefault("style", style)
|
|
574
|
+
self._desired_tag = tag
|
|
575
|
+
|
|
576
|
+
def _init() -> None:
|
|
577
|
+
kwargs: dict[str, Any] = {"value": value}
|
|
578
|
+
if on_change is not None:
|
|
579
|
+
kwargs["on_value_change"] = on_change
|
|
580
|
+
ValueElement.__init__(self, **kwargs)
|
|
581
|
+
|
|
582
|
+
self._run_bootstrap_init(children, props, init=_init)
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
class OverlayElement(BootstrapElement):
|
|
586
|
+
"""Desired vs actual visibility; overlay-root registration is opt-in per subclass."""
|
|
587
|
+
|
|
588
|
+
uses_overlay_root: bool = True
|
|
589
|
+
is_open: bool = False
|
|
590
|
+
phase: str = "closed"
|