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.
Files changed (103) hide show
  1. nicegui_bootstrap_components/__init__.py +7 -0
  2. nicegui_bootstrap_components/_base.py +590 -0
  3. nicegui_bootstrap_components/_host.py +259 -0
  4. nicegui_bootstrap_components/assets.py +371 -0
  5. nicegui_bootstrap_components/bs/__init__.py +267 -0
  6. nicegui_bootstrap_components/bs/_actions.py +232 -0
  7. nicegui_bootstrap_components/bs/_alert_spinner.py +423 -0
  8. nicegui_bootstrap_components/bs/_carousel.py +312 -0
  9. nicegui_bootstrap_components/bs/_choices.py +779 -0
  10. nicegui_bootstrap_components/bs/_content.py +1343 -0
  11. nicegui_bootstrap_components/bs/_forms.py +378 -0
  12. nicegui_bootstrap_components/bs/_inputs.py +560 -0
  13. nicegui_bootstrap_components/bs/_layout.py +341 -0
  14. nicegui_bootstrap_components/bs/_nav.py +650 -0
  15. nicegui_bootstrap_components/bs/_navbar.py +445 -0
  16. nicegui_bootstrap_components/bs/_overlay_root.py +73 -0
  17. nicegui_bootstrap_components/bs/_overlays.py +2633 -0
  18. nicegui_bootstrap_components/bs/_scope.py +28 -0
  19. nicegui_bootstrap_components/bs/_tabs.py +605 -0
  20. nicegui_bootstrap_components/bs/_toast.py +137 -0
  21. nicegui_bootstrap_components/bs/_tooltip_popover.py +537 -0
  22. nicegui_bootstrap_components/contracts/dbc-2.0.4.json +16811 -0
  23. nicegui_bootstrap_components/dbc/__init__.py +108 -0
  24. nicegui_bootstrap_components/icons.py +29 -0
  25. nicegui_bootstrap_components/persist.py +201 -0
  26. nicegui_bootstrap_components/py.typed +0 -0
  27. nicegui_bootstrap_components/static/dist/fonts/bootstrap-icons.woff +0 -0
  28. nicegui_bootstrap_components/static/dist/fonts/bootstrap-icons.woff2 +0 -0
  29. nicegui_bootstrap_components/static/dist/ngbs-bootstrap-unscoped.css +12047 -0
  30. nicegui_bootstrap_components/static/dist/ngbs-bootstrap.css +12057 -0
  31. nicegui_bootstrap_components/static/dist/ngbs-cerulean-unscoped.css +12338 -0
  32. nicegui_bootstrap_components/static/dist/ngbs-cerulean.css +12348 -0
  33. nicegui_bootstrap_components/static/dist/ngbs-compat-unscoped.css +12 -0
  34. nicegui_bootstrap_components/static/dist/ngbs-compat.css +12 -0
  35. nicegui_bootstrap_components/static/dist/ngbs-cosmo-unscoped.css +11825 -0
  36. nicegui_bootstrap_components/static/dist/ngbs-cosmo.css +11835 -0
  37. nicegui_bootstrap_components/static/dist/ngbs-cyborg-unscoped.css +12213 -0
  38. nicegui_bootstrap_components/static/dist/ngbs-cyborg.css +12223 -0
  39. nicegui_bootstrap_components/static/dist/ngbs-darkly-unscoped.css +12143 -0
  40. nicegui_bootstrap_components/static/dist/ngbs-darkly.css +12153 -0
  41. nicegui_bootstrap_components/static/dist/ngbs-flatly-unscoped.css +12129 -0
  42. nicegui_bootstrap_components/static/dist/ngbs-flatly.css +12139 -0
  43. nicegui_bootstrap_components/static/dist/ngbs-grid-unscoped.css +4084 -0
  44. nicegui_bootstrap_components/static/dist/ngbs-grid.css +4094 -0
  45. nicegui_bootstrap_components/static/dist/ngbs-host-unscoped.css +9 -0
  46. nicegui_bootstrap_components/static/dist/ngbs-host.css +9 -0
  47. nicegui_bootstrap_components/static/dist/ngbs-icons-bootstrap.css +5 -0
  48. nicegui_bootstrap_components/static/dist/ngbs-icons-fontawesome.css +9 -0
  49. nicegui_bootstrap_components/static/dist/ngbs-journal-unscoped.css +12112 -0
  50. nicegui_bootstrap_components/static/dist/ngbs-journal.css +12122 -0
  51. nicegui_bootstrap_components/static/dist/ngbs-litera-unscoped.css +12214 -0
  52. nicegui_bootstrap_components/static/dist/ngbs-litera.css +12224 -0
  53. nicegui_bootstrap_components/static/dist/ngbs-lumen-unscoped.css +12377 -0
  54. nicegui_bootstrap_components/static/dist/ngbs-lumen.css +12387 -0
  55. nicegui_bootstrap_components/static/dist/ngbs-lux-unscoped.css +12048 -0
  56. nicegui_bootstrap_components/static/dist/ngbs-lux.css +12058 -0
  57. nicegui_bootstrap_components/static/dist/ngbs-materia-unscoped.css +13197 -0
  58. nicegui_bootstrap_components/static/dist/ngbs-materia.css +13207 -0
  59. nicegui_bootstrap_components/static/dist/ngbs-minty-unscoped.css +12184 -0
  60. nicegui_bootstrap_components/static/dist/ngbs-minty.css +12194 -0
  61. nicegui_bootstrap_components/static/dist/ngbs-morph-unscoped.css +12829 -0
  62. nicegui_bootstrap_components/static/dist/ngbs-morph.css +12839 -0
  63. nicegui_bootstrap_components/static/dist/ngbs-pulse-unscoped.css +11901 -0
  64. nicegui_bootstrap_components/static/dist/ngbs-pulse.css +11911 -0
  65. nicegui_bootstrap_components/static/dist/ngbs-quartz-unscoped.css +12625 -0
  66. nicegui_bootstrap_components/static/dist/ngbs-quartz.css +12635 -0
  67. nicegui_bootstrap_components/static/dist/ngbs-sandstone-unscoped.css +12214 -0
  68. nicegui_bootstrap_components/static/dist/ngbs-sandstone.css +12224 -0
  69. nicegui_bootstrap_components/static/dist/ngbs-simplex-unscoped.css +12199 -0
  70. nicegui_bootstrap_components/static/dist/ngbs-simplex.css +12209 -0
  71. nicegui_bootstrap_components/static/dist/ngbs-sketchy-unscoped.css +12518 -0
  72. nicegui_bootstrap_components/static/dist/ngbs-sketchy.css +12528 -0
  73. nicegui_bootstrap_components/static/dist/ngbs-slate-unscoped.css +12527 -0
  74. nicegui_bootstrap_components/static/dist/ngbs-slate.css +12537 -0
  75. nicegui_bootstrap_components/static/dist/ngbs-solar-unscoped.css +12152 -0
  76. nicegui_bootstrap_components/static/dist/ngbs-solar.css +12162 -0
  77. nicegui_bootstrap_components/static/dist/ngbs-spacelab-unscoped.css +12279 -0
  78. nicegui_bootstrap_components/static/dist/ngbs-spacelab.css +12289 -0
  79. nicegui_bootstrap_components/static/dist/ngbs-superhero-unscoped.css +12223 -0
  80. nicegui_bootstrap_components/static/dist/ngbs-superhero.css +12233 -0
  81. nicegui_bootstrap_components/static/dist/ngbs-united-unscoped.css +12090 -0
  82. nicegui_bootstrap_components/static/dist/ngbs-united.css +12100 -0
  83. nicegui_bootstrap_components/static/dist/ngbs-vapor-unscoped.css +12556 -0
  84. nicegui_bootstrap_components/static/dist/ngbs-vapor.css +12566 -0
  85. nicegui_bootstrap_components/static/dist/ngbs-yeti-unscoped.css +12333 -0
  86. nicegui_bootstrap_components/static/dist/ngbs-yeti.css +12343 -0
  87. nicegui_bootstrap_components/static/dist/ngbs-zephyr-unscoped.css +12296 -0
  88. nicegui_bootstrap_components/static/dist/ngbs-zephyr.css +12306 -0
  89. nicegui_bootstrap_components/static/prototype-ngbs.css +541 -0
  90. nicegui_bootstrap_components/static/webfonts/fa-brands-400.ttf +0 -0
  91. nicegui_bootstrap_components/static/webfonts/fa-brands-400.woff2 +0 -0
  92. nicegui_bootstrap_components/static/webfonts/fa-regular-400.ttf +0 -0
  93. nicegui_bootstrap_components/static/webfonts/fa-regular-400.woff2 +0 -0
  94. nicegui_bootstrap_components/static/webfonts/fa-solid-900.ttf +0 -0
  95. nicegui_bootstrap_components/static/webfonts/fa-solid-900.woff2 +0 -0
  96. nicegui_bootstrap_components/static/webfonts/fa-v4compatibility.ttf +0 -0
  97. nicegui_bootstrap_components/static/webfonts/fa-v4compatibility.woff2 +0 -0
  98. nicegui_bootstrap_components/theme.py +238 -0
  99. nicegui_bootstrap_components/themes.py +131 -0
  100. nicegui_bootstrap_components-0.1.0.dist-info/METADATA +234 -0
  101. nicegui_bootstrap_components-0.1.0.dist-info/RECORD +103 -0
  102. nicegui_bootstrap_components-0.1.0.dist-info/WHEEL +4 -0
  103. nicegui_bootstrap_components-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,312 @@
1
+ """Carousel family (native + compat sibling types)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from typing import Any
7
+
8
+ from nicegui.element import Element
9
+
10
+ from .._base import BootstrapElement, make_surface_classes, normalize_style, style_to_css
11
+ from .._host import set_element_text
12
+
13
+ __all__ = [
14
+ "Carousel",
15
+ "DbcCarousel",
16
+ "carousel",
17
+ "carousel_structural_classes",
18
+ "clamp_carousel_index",
19
+ "normalize_carousel_item",
20
+ "resolve_carousel_interval",
21
+ "wrap_carousel_index",
22
+ ]
23
+
24
+ _KNOWN_ITEM_KEYS: tuple[str, ...] = (
25
+ "src",
26
+ "alt",
27
+ "header",
28
+ "caption",
29
+ "img_style",
30
+ "img_class_name",
31
+ "caption_class_name",
32
+ "href",
33
+ "target",
34
+ "external_link",
35
+ )
36
+ _DEFAULT_IMG_CLASS_NAME = "d-block w-100"
37
+ _PERSIST_MODES: tuple[str, ...] = ("off", "local", "session")
38
+
39
+
40
+ def carousel_structural_classes(
41
+ *,
42
+ slide: bool = True,
43
+ variant: str | None = None,
44
+ ) -> list[str]:
45
+ """Return Bootstrap ``carousel`` classes. Unknown variants raise ``ValueError``."""
46
+ if variant is not None and variant != "dark":
47
+ raise ValueError(f"variant must be None or 'dark', got {variant!r}")
48
+ classes = ["carousel"]
49
+ if slide:
50
+ classes.append("slide")
51
+ if variant == "dark":
52
+ classes.append("carousel-dark")
53
+ return classes
54
+
55
+
56
+ def normalize_carousel_item(item: dict[str, Any]) -> dict[str, Any]:
57
+ """Return a copy with known keys only and default ``img_class_name``."""
58
+ normalized: dict[str, Any] = {key: item[key] for key in _KNOWN_ITEM_KEYS if key in item}
59
+ normalized.setdefault("img_class_name", _DEFAULT_IMG_CLASS_NAME)
60
+ return normalized
61
+
62
+
63
+ def wrap_carousel_index(index: int, length: int) -> int:
64
+ """Wrap ``index`` into ``[0, length)``. Empty length yields ``0``."""
65
+ if length <= 0:
66
+ return 0
67
+ return index % length
68
+
69
+
70
+ def clamp_carousel_index(index: int, length: int) -> int:
71
+ """Return ``index`` if in range, otherwise ``0``. Empty length yields ``0``."""
72
+ if length <= 0 or index < 0 or index >= length:
73
+ return 0
74
+ return index
75
+
76
+
77
+ def resolve_carousel_interval(interval: int | bool | None) -> int | None:
78
+ """Return autoplay milliseconds, or ``None`` when autoplay is disabled."""
79
+ if interval is False or interval is None:
80
+ return None
81
+ return int(interval)
82
+
83
+
84
+ def _validate_persist(persist: str, public_id: str | None) -> None:
85
+ if persist not in _PERSIST_MODES:
86
+ raise ValueError(f"persist must be 'off', 'local', or 'session', got {persist!r}")
87
+ if persist != "off" and not public_id:
88
+ raise ValueError("persist requires a public id when not 'off'")
89
+
90
+
91
+ class _CarouselImpl(BootstrapElement):
92
+ """Private Carousel implementation driven by an ``items`` dict list."""
93
+
94
+ component_name = "Carousel"
95
+ children_kind = "grouping"
96
+ reserved_classes = ("carousel",)
97
+
98
+ def __init__(
99
+ self,
100
+ children: object = None,
101
+ *,
102
+ _surface: str,
103
+ items: list[dict[str, Any]] | None = None,
104
+ active_index: int = 0,
105
+ controls: bool = True,
106
+ indicators: bool = True,
107
+ interval: int | bool | None = None,
108
+ slide: bool | None = True,
109
+ variant: str | None = None,
110
+ persist: str = "off",
111
+ on_change: Callable[..., Any] | None = None,
112
+ **kwargs: Any,
113
+ ) -> None:
114
+ if "ride" in kwargs:
115
+ raise TypeError("Carousel.__init__() got an unexpected keyword argument 'ride'")
116
+ if not isinstance(items, list):
117
+ raise TypeError("items must be a list of dicts")
118
+ for item in items:
119
+ if not isinstance(item, dict):
120
+ raise TypeError("each carousel item must be a dict")
121
+
122
+ self._surface = _surface
123
+ raw_id: Any = kwargs.get("id")
124
+ public_id = raw_id if isinstance(raw_id, str) else None
125
+ _validate_persist(persist, public_id)
126
+
127
+ slide_enabled = True if slide is None else bool(slide)
128
+ self._structural_classes = tuple(
129
+ carousel_structural_classes(slide=slide_enabled, variant=variant)
130
+ )
131
+ self._items = [normalize_carousel_item(item) for item in items]
132
+ self._active_index = clamp_carousel_index(active_index, len(self._items))
133
+ self._show_controls = bool(controls)
134
+ self._show_indicators = bool(indicators)
135
+ self._on_change = on_change
136
+ self._persist = persist
137
+ self._interval = resolve_carousel_interval(interval)
138
+ self._public_id = public_id
139
+ self._item_elements: list[Element] = []
140
+ self._indicator_elements: list[Element] = []
141
+
142
+ super().__init__(None, tag="div", **kwargs)
143
+
144
+ if self._interval is None:
145
+ self._props["data-bs-interval"] = "false"
146
+ else:
147
+ self._props["data-bs-interval"] = str(self._interval)
148
+ self._props["data-bs-ride"] = "carousel"
149
+
150
+ self._build()
151
+
152
+ @property
153
+ def active_index(self) -> int:
154
+ """Zero-based index of the visible slide."""
155
+ return self._active_index
156
+
157
+ def _target_selector(self) -> str:
158
+ return f"#{self._public_id}" if self._public_id else ""
159
+
160
+ def _build(self) -> None:
161
+ with self:
162
+ self._build_indicators()
163
+ self._build_inner()
164
+ self._build_controls()
165
+
166
+ def _build_indicators(self) -> None:
167
+ if not self._show_indicators or not self._items:
168
+ return
169
+ indicators = Element("div")
170
+ indicators.classes("carousel-indicators")
171
+ with indicators:
172
+ for index, _item in enumerate(self._items):
173
+ button = Element("button")
174
+ button._props["type"] = "button"
175
+ button._props["data-bs-target"] = self._target_selector()
176
+ button._props["data-bs-slide-to"] = str(index)
177
+ button._props["aria-label"] = f"Slide {index + 1}"
178
+ if index == self._active_index:
179
+ button.classes(add="active")
180
+ button._props["aria-current"] = "true"
181
+ button.on("click", self._make_index_handler(index))
182
+ self._indicator_elements.append(button)
183
+
184
+ def _build_inner(self) -> None:
185
+ inner = Element("div")
186
+ inner.classes("carousel-inner")
187
+ with inner:
188
+ for index, item in enumerate(self._items):
189
+ self._build_item(item, is_active=index == self._active_index)
190
+
191
+ def _build_item(self, item: dict[str, Any], *, is_active: bool) -> None:
192
+ item_el = Element("div")
193
+ item_el.classes("carousel-item")
194
+ if is_active:
195
+ item_el.classes(add="active")
196
+ with item_el:
197
+ href = item.get("href")
198
+ if href:
199
+ link = Element("a")
200
+ self._apply_link_props(link, item)
201
+ with link:
202
+ self._add_image(item)
203
+ self._add_caption(item)
204
+ else:
205
+ self._add_image(item)
206
+ self._add_caption(item)
207
+ self._item_elements.append(item_el)
208
+
209
+ def _apply_link_props(self, link: Element, item: dict[str, Any]) -> None:
210
+ link._props["href"] = str(item["href"])
211
+ target = item.get("target")
212
+ if item.get("external_link"):
213
+ link._props["target"] = str(target) if target else "_blank"
214
+ link._props["rel"] = "noopener noreferrer"
215
+ elif target:
216
+ link._props["target"] = str(target)
217
+
218
+ def _add_image(self, item: dict[str, Any]) -> None:
219
+ image = Element("img")
220
+ image.classes(str(item.get("img_class_name") or _DEFAULT_IMG_CLASS_NAME))
221
+ src = item.get("src")
222
+ if src is not None:
223
+ image._props["src"] = str(src)
224
+ alt = item.get("alt")
225
+ image._props["alt"] = "" if alt is None else str(alt)
226
+ raw_style = item.get("img_style")
227
+ if raw_style is not None:
228
+ if isinstance(raw_style, dict):
229
+ image.style(style_to_css(normalize_style(raw_style)))
230
+ else:
231
+ image.style(str(raw_style))
232
+
233
+ def _add_caption(self, item: dict[str, Any]) -> None:
234
+ header = item.get("header")
235
+ caption = item.get("caption")
236
+ if not header and not caption:
237
+ return
238
+ caption_el = Element("div")
239
+ caption_el.classes("carousel-caption")
240
+ extra = item.get("caption_class_name")
241
+ if extra:
242
+ caption_el.classes(str(extra))
243
+ with caption_el:
244
+ if header:
245
+ heading = Element("h5")
246
+ set_element_text(heading, str(header))
247
+ if caption:
248
+ paragraph = Element("p")
249
+ set_element_text(paragraph, str(caption))
250
+
251
+ def _build_controls(self) -> None:
252
+ if not self._show_controls:
253
+ return
254
+ self._build_control("prev")
255
+ self._build_control("next")
256
+
257
+ def _build_control(self, direction: str) -> None:
258
+ button = Element("button")
259
+ button.classes(f"carousel-control-{direction}")
260
+ button._props["type"] = "button"
261
+ button._props["data-bs-target"] = self._target_selector()
262
+ button._props["data-bs-slide"] = direction
263
+ with button:
264
+ icon = Element("span")
265
+ icon.classes(f"carousel-control-{direction}-icon")
266
+ icon._props["aria-hidden"] = "true"
267
+ label = Element("span")
268
+ label.classes("visually-hidden")
269
+ set_element_text(label, "Previous" if direction == "prev" else "Next")
270
+ if direction == "prev":
271
+ button.on("click", self._handle_prev)
272
+ else:
273
+ button.on("click", self._handle_next)
274
+
275
+ def _make_index_handler(self, index: int) -> Callable[..., None]:
276
+ def _handler(*_args: Any, **_kwargs: Any) -> None:
277
+ self._go_to(index, notify=True)
278
+
279
+ return _handler
280
+
281
+ def _handle_prev(self, *_args: Any, **_kwargs: Any) -> None:
282
+ self._go_to(wrap_carousel_index(self._active_index - 1, len(self._items)), notify=True)
283
+
284
+ def _handle_next(self, *_args: Any, **_kwargs: Any) -> None:
285
+ self._go_to(wrap_carousel_index(self._active_index + 1, len(self._items)), notify=True)
286
+
287
+ def _go_to(self, index: int, *, notify: bool) -> None:
288
+ length = len(self._item_elements)
289
+ if length == 0:
290
+ self._active_index = 0
291
+ return
292
+ new_index = wrap_carousel_index(index, length)
293
+ changed = new_index != self._active_index
294
+ self._active_index = new_index
295
+ for i, item_el in enumerate(self._item_elements):
296
+ if i == new_index:
297
+ item_el.classes(add="active")
298
+ else:
299
+ item_el.classes(remove="active")
300
+ for i, indicator_el in enumerate(self._indicator_elements):
301
+ if i == new_index:
302
+ indicator_el.classes(add="active")
303
+ indicator_el._props["aria-current"] = "true"
304
+ else:
305
+ indicator_el.classes(remove="active")
306
+ indicator_el._props["aria-current"] = "false"
307
+ if notify and changed and self._on_change is not None:
308
+ self._on_change()
309
+
310
+
311
+ Carousel, DbcCarousel = make_surface_classes("Carousel", globals())
312
+ carousel = Carousel