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,1343 @@
1
+ """Content family: Card, ListGroup, Table, Badge, Progress, Placeholder."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable, Sequence
6
+ from contextvars import ContextVar, Token
7
+ from typing import Any, cast
8
+
9
+ from nicegui import ui
10
+
11
+ from .._base import (
12
+ BootstrapElement,
13
+ ChildrenError,
14
+ PropConflictError,
15
+ UnsupportedPropError,
16
+ make_surface_classes,
17
+ normalize_style,
18
+ resolve_class_name,
19
+ style_to_css,
20
+ )
21
+ from .._host import set_element_text
22
+
23
+ __all__ = [
24
+ "Badge",
25
+ "Card",
26
+ "CardBody",
27
+ "CardFooter",
28
+ "CardGroup",
29
+ "CardHeader",
30
+ "CardImg",
31
+ "CardImgOverlay",
32
+ "CardLink",
33
+ "DbcBadge",
34
+ "DbcCard",
35
+ "DbcCardBody",
36
+ "DbcCardFooter",
37
+ "DbcCardGroup",
38
+ "DbcCardHeader",
39
+ "DbcCardImg",
40
+ "DbcCardImgOverlay",
41
+ "DbcCardLink",
42
+ "DbcListGroup",
43
+ "DbcListGroupItem",
44
+ "DbcPlaceholder",
45
+ "DbcProgress",
46
+ "DbcTable",
47
+ "ListGroup",
48
+ "ListGroupItem",
49
+ "Placeholder",
50
+ "Progress",
51
+ "Table",
52
+ "badge",
53
+ "badge_structural_classes",
54
+ "card",
55
+ "card_body",
56
+ "card_footer",
57
+ "card_group",
58
+ "card_header",
59
+ "card_img",
60
+ "card_img_overlay",
61
+ "card_img_structural_classes",
62
+ "card_link",
63
+ "card_structural_classes",
64
+ "format_table_cell",
65
+ "grouped_spans",
66
+ "list_group",
67
+ "list_group_item",
68
+ "list_group_item_structural_classes",
69
+ "list_group_structural_classes",
70
+ "placeholder",
71
+ "placeholder_structural_classes",
72
+ "progress",
73
+ "progress_bar_structural_classes",
74
+ "progress_percent",
75
+ "progress_structural_classes",
76
+ "resolve_list_group_item_tag",
77
+ "resolve_list_group_tag",
78
+ "table",
79
+ "table_responsive_class",
80
+ "table_structural_classes",
81
+ ]
82
+
83
+ _BOOTSTRAP_CONTEXT_COLORS = frozenset(
84
+ {
85
+ "primary",
86
+ "secondary",
87
+ "success",
88
+ "danger",
89
+ "warning",
90
+ "info",
91
+ "light",
92
+ "dark",
93
+ }
94
+ )
95
+ _TEXT_COLORS = _BOOTSTRAP_CONTEXT_COLORS | frozenset(
96
+ {
97
+ "white",
98
+ "black",
99
+ "muted",
100
+ "body",
101
+ "body-secondary",
102
+ "body-tertiary",
103
+ }
104
+ )
105
+ _BREAKPOINTS = frozenset({"sm", "md", "lg", "xl", "xxl"})
106
+ _PLACEHOLDER_SIZES = frozenset({"xs", "sm", "lg"})
107
+ _PLACEHOLDER_ANIMATIONS = frozenset({"glow", "wave"})
108
+ _WIDTH_UTILITIES = frozenset({25, 50, 75, 100})
109
+ _DISPLAY_UTILITIES = frozenset(
110
+ {"inline", "inline-block", "block", "flex", "grid", "table", "inline-flex"}
111
+ )
112
+ _UNITLESS_STYLE = frozenset(
113
+ {
114
+ "opacity",
115
+ "flex",
116
+ "grow",
117
+ "shrink",
118
+ "order",
119
+ "z-index",
120
+ "font-weight",
121
+ "line-height",
122
+ }
123
+ )
124
+
125
+ _LIST_GROUP_ITEM_DEFAULT_TAG: ContextVar[str | None] = ContextVar(
126
+ "_list_group_item_default_tag",
127
+ default=None,
128
+ )
129
+ _PROGRESS_NESTED: ContextVar[bool] = ContextVar("_progress_nested", default=False)
130
+
131
+
132
+ def _validate_color(color: str, *, allowed: frozenset[str] | None = None) -> str:
133
+ palette = _BOOTSTRAP_CONTEXT_COLORS if allowed is None else allowed
134
+ if color not in palette:
135
+ raise ValueError(f"Unknown color {color!r}")
136
+ return color
137
+
138
+
139
+ def _col_width_classes(breakpoint: str, value: int | bool | None) -> list[str]:
140
+ if value is None or value is False:
141
+ return []
142
+ if value is True:
143
+ return ["col"] if breakpoint == "xs" else [f"col-{breakpoint}"]
144
+ if isinstance(value, bool) or not isinstance(value, int):
145
+ raise ValueError(f"Unknown column width {value!r}")
146
+ if value < 1 or value > 12:
147
+ raise ValueError(f"Unknown column width {value!r}")
148
+ if breakpoint == "xs":
149
+ return [f"col-{value}"]
150
+ return [f"col-{breakpoint}-{value}"]
151
+
152
+
153
+ def _children_present(children: object) -> bool:
154
+ if children is None:
155
+ return False
156
+ return not (children == "" or children == [] or children == ())
157
+
158
+
159
+ def _declared_children(children: object) -> list[object]:
160
+ if children is None:
161
+ return []
162
+ if isinstance(children, (list, tuple)):
163
+ return list(children)
164
+ return [children]
165
+
166
+
167
+ def _is_float_like(value: object) -> bool:
168
+ if isinstance(value, (bool, int)):
169
+ return False
170
+ if isinstance(value, float):
171
+ return True
172
+ return "float" in type(value).__name__.lower()
173
+
174
+
175
+ def _format_number(value: float) -> str:
176
+ if value == int(value):
177
+ return str(int(value))
178
+ return str(value)
179
+
180
+
181
+ def _merge_width_style(width_pct: float, bar_style: dict[str, Any] | str | None) -> str:
182
+ if isinstance(bar_style, str) and bar_style:
183
+ return f"width: {width_pct}%; {bar_style}"
184
+ style_dict: dict[str, Any] = {"width": f"{width_pct}%"}
185
+ if isinstance(bar_style, dict):
186
+ style_dict.update(bar_style)
187
+ normalized: dict[str, str] | str = normalize_style(style_dict)
188
+ if isinstance(normalized, str):
189
+ return normalized
190
+ converted = style_to_css(normalized)
191
+ return converted if isinstance(converted, str) else str(converted)
192
+
193
+
194
+ def _merge_style_kwarg(kwargs: dict[str, Any], extra: dict[str, Any]) -> None:
195
+ style = kwargs.get("style")
196
+ if style is None:
197
+ kwargs["style"] = dict(extra)
198
+ return
199
+ if isinstance(style, dict):
200
+ merged = dict(extra)
201
+ merged.update(style)
202
+ kwargs["style"] = merged
203
+ return
204
+ suffix = "; ".join(f"{key}: {value}" for key, value in extra.items())
205
+ kwargs["style"] = f"{style}; {suffix}"
206
+
207
+
208
+ def _require_pandas() -> Any:
209
+ try:
210
+ import pandas as pd
211
+ except ImportError as exc:
212
+ raise ImportError(
213
+ "pandas is required for Table.from_dataframe. "
214
+ "Install the pandas extra to use this helper."
215
+ ) from exc
216
+ return pd
217
+
218
+
219
+ def _apply_interactive_props(
220
+ element: Any,
221
+ *,
222
+ tag: str,
223
+ href: str | None = None,
224
+ disabled: bool = False,
225
+ external_link: bool = False,
226
+ ) -> None:
227
+ if tag == "a":
228
+ if href is not None:
229
+ element._props["href"] = href
230
+ if external_link:
231
+ element._props["target"] = "_blank"
232
+ element._props["rel"] = "noopener noreferrer"
233
+ if disabled:
234
+ element._props["aria-disabled"] = "true"
235
+ element._props["tabindex"] = "-1"
236
+ return
237
+ if tag == "button":
238
+ element._props.setdefault("type", "button")
239
+ if disabled:
240
+ element._props["disabled"] = True
241
+ return
242
+ if disabled:
243
+ element._props["aria-disabled"] = "true"
244
+
245
+
246
+ def _place_children(host: Any, children: object) -> None:
247
+ if children is None:
248
+ return
249
+ if isinstance(children, str):
250
+ raise ChildrenError("Table does not accept text children; wrap text in a component")
251
+ for child in _declared_children(children):
252
+ if isinstance(child, str):
253
+ raise ChildrenError("Table does not accept text children; wrap text in a component")
254
+ move = getattr(child, "move", None)
255
+ if callable(move):
256
+ move(host)
257
+
258
+
259
+ def _configure_progress_bar(
260
+ target: Any,
261
+ *,
262
+ classes: list[str],
263
+ value: float,
264
+ min_value: float,
265
+ max_value: float,
266
+ label: str | None,
267
+ bar_style: dict[str, Any] | str | None,
268
+ ) -> None:
269
+ if classes and hasattr(target, "classes"):
270
+ target.classes(" ".join(classes))
271
+ target._props["role"] = "progressbar"
272
+ target._props["aria-valuenow"] = _format_number(value)
273
+ target._props["aria-valuemin"] = _format_number(min_value)
274
+ target._props["aria-valuemax"] = _format_number(max_value)
275
+ target._props["style"] = _merge_width_style(
276
+ progress_percent(value, min_value, max_value),
277
+ bar_style,
278
+ )
279
+ if label is not None:
280
+ set_element_text(target, str(label))
281
+
282
+
283
+ def card_structural_classes(
284
+ *,
285
+ color: str | None = None,
286
+ outline: bool = False,
287
+ inverse: bool = False,
288
+ body: bool = False,
289
+ ) -> list[str]:
290
+ """Return Bootstrap ``card`` classes."""
291
+ classes = ["card"]
292
+ if body:
293
+ classes.append("card-body")
294
+ if color is not None:
295
+ _validate_color(color)
296
+ if outline:
297
+ classes.append(f"border-{color}")
298
+ else:
299
+ classes.append(f"text-bg-{color}")
300
+ if inverse:
301
+ classes.append("text-white")
302
+ return classes
303
+
304
+
305
+ def card_img_structural_classes(
306
+ *,
307
+ top: bool = False,
308
+ bottom: bool = False,
309
+ overlay: bool = False,
310
+ ) -> list[str]:
311
+ """Return Bootstrap ``card-img`` position classes."""
312
+ selected = [
313
+ name for name, flag in (("top", top), ("bottom", bottom), ("overlay", overlay)) if flag
314
+ ]
315
+ if len(selected) > 1:
316
+ raise PropConflictError("top")
317
+ if top:
318
+ return ["card-img-top"]
319
+ if bottom:
320
+ return ["card-img-bottom"]
321
+ return ["card-img"]
322
+
323
+
324
+ def list_group_structural_classes(
325
+ *,
326
+ flush: bool = False,
327
+ numbered: bool = False,
328
+ horizontal: bool | str = False,
329
+ ) -> list[str]:
330
+ """Return Bootstrap ``list-group`` classes."""
331
+ classes = ["list-group"]
332
+ if flush:
333
+ classes.append("list-group-flush")
334
+ if numbered:
335
+ classes.append("list-group-numbered")
336
+ if horizontal is True:
337
+ classes.append("list-group-horizontal")
338
+ elif horizontal is False:
339
+ pass
340
+ elif isinstance(horizontal, str) and horizontal in _BREAKPOINTS:
341
+ classes.append(f"list-group-horizontal-{horizontal}")
342
+ else:
343
+ raise ValueError(f"Unknown horizontal breakpoint {horizontal!r}")
344
+ return classes
345
+
346
+
347
+ def list_group_item_structural_classes(
348
+ *,
349
+ active: bool = False,
350
+ disabled: bool = False,
351
+ color: str | None = None,
352
+ action: bool = False,
353
+ href: str | None = None,
354
+ ) -> list[str]:
355
+ """Return Bootstrap ``list-group-item`` classes."""
356
+ classes = ["list-group-item"]
357
+ if color is not None:
358
+ _validate_color(color)
359
+ classes.append(f"list-group-item-{color}")
360
+ if action or href is not None:
361
+ classes.append("list-group-item-action")
362
+ if active:
363
+ classes.append("active")
364
+ if disabled:
365
+ classes.append("disabled")
366
+ return classes
367
+
368
+
369
+ def resolve_list_group_tag(*, tag: str | None = None, numbered: bool = False) -> str:
370
+ """Return the ListGroup tag fixed at construction time."""
371
+ if tag is not None:
372
+ return tag
373
+ if numbered:
374
+ return "ol"
375
+ return "div"
376
+
377
+
378
+ def resolve_list_group_item_tag(
379
+ *,
380
+ tag: str | None = None,
381
+ href: str | None = None,
382
+ action: bool = False,
383
+ context_default: str | None = None,
384
+ ) -> str:
385
+ """Return the ListGroupItem tag from explicit tag, href, action, then context."""
386
+ if tag is not None:
387
+ return tag
388
+ if href is not None:
389
+ return "a"
390
+ if action:
391
+ return "button"
392
+ if context_default is not None:
393
+ return context_default
394
+ return "div"
395
+
396
+
397
+ def table_structural_classes(
398
+ *,
399
+ bordered: bool = False,
400
+ borderless: bool = False,
401
+ striped: bool = False,
402
+ hover: bool = False,
403
+ small: bool = False,
404
+ size: str | None = None,
405
+ color: str | None = None,
406
+ striped_columns: bool = False,
407
+ ) -> list[str]:
408
+ """Return Bootstrap ``table`` classes."""
409
+ if bordered and borderless:
410
+ raise PropConflictError("bordered")
411
+ if small and size is not None and size != "sm":
412
+ raise PropConflictError("small")
413
+ classes = ["table"]
414
+ if bordered:
415
+ classes.append("table-bordered")
416
+ if borderless:
417
+ classes.append("table-borderless")
418
+ if striped:
419
+ classes.append("table-striped")
420
+ if striped_columns:
421
+ classes.append("table-striped-columns")
422
+ if hover:
423
+ classes.append("table-hover")
424
+ if small or size == "sm":
425
+ classes.append("table-sm")
426
+ elif size is not None:
427
+ raise ValueError(f"Unknown table size {size!r}")
428
+ if color is not None:
429
+ _validate_color(color)
430
+ classes.append(f"table-{color}")
431
+ return classes
432
+
433
+
434
+ def table_responsive_class(responsive: bool | str) -> str | None:
435
+ """Return the wrapper class for a responsive table, or ``None``."""
436
+ if responsive is False:
437
+ return None
438
+ if responsive is True:
439
+ return "table-responsive"
440
+ if isinstance(responsive, str) and responsive in _BREAKPOINTS:
441
+ return f"table-responsive-{responsive}"
442
+ raise ValueError(f"Unknown responsive breakpoint {responsive!r}")
443
+
444
+
445
+ def grouped_spans(values: list[object] | tuple[object, ...]) -> list[tuple[object, int]]:
446
+ """Group consecutive equal values into ``(value, colspan)`` pairs."""
447
+ if not values:
448
+ return []
449
+ grouped: list[tuple[object, int]] = []
450
+ current = values[0]
451
+ count = 1
452
+ for item in values[1:]:
453
+ if item == current:
454
+ count += 1
455
+ else:
456
+ grouped.append((current, count))
457
+ current = item
458
+ count = 1
459
+ grouped.append((current, count))
460
+ return grouped
461
+
462
+
463
+ def format_table_cell(
464
+ value: object,
465
+ *,
466
+ date_format: str | None = None,
467
+ float_format: str | Callable[..., Any] | None = None,
468
+ ) -> str:
469
+ """Format a table cell the way DBC ``from_dataframe`` does."""
470
+ if value is None:
471
+ return ""
472
+ if isinstance(value, tuple):
473
+ return " ".join(
474
+ format_table_cell(part, date_format=date_format, float_format=float_format)
475
+ for part in value
476
+ )
477
+ if date_format is not None:
478
+ strftime = getattr(value, "strftime", None)
479
+ if callable(strftime):
480
+ try:
481
+ return str(strftime(date_format))
482
+ except (TypeError, ValueError):
483
+ pass
484
+ if float_format is not None and _is_float_like(value):
485
+ number = float(cast(float, value))
486
+ if callable(float_format):
487
+ return str(float_format(number))
488
+ if "%" in float_format:
489
+ return float_format % number
490
+ try:
491
+ return format(number, float_format)
492
+ except ValueError:
493
+ return str(value)
494
+ return str(value)
495
+
496
+
497
+ def badge_structural_classes(
498
+ *,
499
+ color: str = "secondary",
500
+ pill: bool = False,
501
+ text_color: str | None = None,
502
+ ) -> list[str]:
503
+ """Return Bootstrap ``badge`` classes."""
504
+ _validate_color(color)
505
+ classes = ["badge", f"text-bg-{color}"]
506
+ if text_color is not None:
507
+ _validate_color(text_color, allowed=_TEXT_COLORS)
508
+ classes.append(f"text-{text_color}")
509
+ if pill:
510
+ classes.append("rounded-pill")
511
+ return classes
512
+
513
+
514
+ def progress_structural_classes() -> list[str]:
515
+ """Return Bootstrap ``progress`` wrapper classes."""
516
+ return ["progress"]
517
+
518
+
519
+ def progress_bar_structural_classes(
520
+ *,
521
+ color: str | None = None,
522
+ striped: bool = False,
523
+ animated: bool = False,
524
+ ) -> list[str]:
525
+ """Return Bootstrap ``progress-bar`` classes."""
526
+ classes = ["progress-bar"]
527
+ if color is not None:
528
+ _validate_color(color)
529
+ classes.append(f"bg-{color}")
530
+ if striped or animated:
531
+ classes.append("progress-bar-striped")
532
+ if animated:
533
+ classes.append("progress-bar-animated")
534
+ return classes
535
+
536
+
537
+ def progress_percent(value: float, min_value: float, max_value: float) -> float:
538
+ """Return a clamped 0-100 width percentage for a determinate bar."""
539
+ if max_value == min_value:
540
+ return 0.0
541
+ percent = (value - min_value) / (max_value - min_value) * 100.0
542
+ if percent < 0.0:
543
+ return 0.0
544
+ if percent > 100.0:
545
+ return 100.0
546
+ return round(percent, 4)
547
+
548
+
549
+ def placeholder_structural_classes(
550
+ *,
551
+ animation: str | None = None,
552
+ size: str | None = None,
553
+ color: str | None = None,
554
+ button: bool = False,
555
+ display: bool | str | None = None,
556
+ width: int | str | None = None,
557
+ xs: int | bool | None = None,
558
+ sm: int | bool | None = None,
559
+ md: int | bool | None = None,
560
+ lg: int | bool | None = None,
561
+ xl: int | bool | None = None,
562
+ xxl: int | bool | None = None,
563
+ ) -> list[str]:
564
+ """Return Bootstrap ``placeholder`` classes."""
565
+ classes = ["placeholder"]
566
+ if button:
567
+ classes.append("btn")
568
+ classes.append("disabled")
569
+ if color is not None:
570
+ _validate_color(color)
571
+ classes.append(f"btn-{color}")
572
+ elif color is not None:
573
+ _validate_color(color)
574
+ classes.append(f"bg-{color}")
575
+ if animation is not None:
576
+ if animation not in _PLACEHOLDER_ANIMATIONS:
577
+ raise ValueError(f"Unknown placeholder animation {animation!r}")
578
+ classes.append(f"placeholder-{animation}")
579
+ if size is not None:
580
+ if size not in _PLACEHOLDER_SIZES:
581
+ raise ValueError(f"Unknown placeholder size {size!r}")
582
+ classes.append(f"placeholder-{size}")
583
+ for breakpoint, value in (
584
+ ("xs", xs),
585
+ ("sm", sm),
586
+ ("md", md),
587
+ ("lg", lg),
588
+ ("xl", xl),
589
+ ("xxl", xxl),
590
+ ):
591
+ classes.extend(_col_width_classes(breakpoint, value))
592
+ if isinstance(width, bool) or (width is not None and not isinstance(width, (int, str))):
593
+ raise ValueError(f"Unknown placeholder width {width!r}")
594
+ if isinstance(width, int):
595
+ if width in _WIDTH_UTILITIES:
596
+ classes.append(f"w-{width}")
597
+ elif 1 <= width <= 12:
598
+ classes.append(f"col-{width}")
599
+ else:
600
+ raise ValueError(f"Unknown placeholder width {width!r}")
601
+ if display is False or display == "none":
602
+ classes.append("d-none")
603
+ elif display is True or display in {None, "show", "auto"}:
604
+ pass
605
+ elif isinstance(display, str) and display in _DISPLAY_UTILITIES:
606
+ classes.append(f"d-{display}")
607
+ elif display is not None:
608
+ raise ValueError(f"Unknown display {display!r}")
609
+ return classes
610
+
611
+
612
+ def _index_header_label(df: Any, index_label: str | None) -> str:
613
+ if index_label is not None:
614
+ return index_label
615
+ name = getattr(df.index, "name", None)
616
+ if name is not None:
617
+ return str(name)
618
+ names = getattr(df.index, "names", None)
619
+ if names:
620
+ parts = [str(part) for part in names if part is not None]
621
+ return " ".join(parts)
622
+ return ""
623
+
624
+
625
+ def _write_dataframe_header(df: Any, *, index: bool, index_label: str | None) -> None:
626
+ index_label_text = _index_header_label(df, index_label) if index else ""
627
+ columns = df.columns
628
+ nlevels = int(getattr(columns, "nlevels", 1))
629
+ if nlevels > 1 and hasattr(columns, "get_level_values"):
630
+ for level in range(nlevels):
631
+ with ui.element("tr"):
632
+ if index:
633
+ th = ui.element("th")
634
+ set_element_text(th, index_label_text if level == nlevels - 1 else "")
635
+ values = list(columns.get_level_values(level))
636
+ for value, span in grouped_spans(values):
637
+ th = ui.element("th")
638
+ set_element_text(th, "" if value is None else str(value))
639
+ if span > 1:
640
+ th._props["colspan"] = span
641
+ return
642
+ with ui.element("tr"):
643
+ if index:
644
+ th = ui.element("th")
645
+ set_element_text(th, index_label_text)
646
+ for col in columns:
647
+ th = ui.element("th")
648
+ set_element_text(th, "" if col is None else str(col))
649
+
650
+
651
+ def _write_dataframe_body(
652
+ df: Any,
653
+ *,
654
+ index: bool,
655
+ date_format: str | None,
656
+ float_format: str | Callable[..., Any] | None,
657
+ ) -> None:
658
+ for idx, row in df.iterrows():
659
+ with ui.element("tr"):
660
+ if index:
661
+ th = ui.element("th")
662
+ set_element_text(
663
+ th, format_table_cell(idx, date_format=date_format, float_format=float_format)
664
+ )
665
+ th._props["scope"] = "row"
666
+ for col in df.columns:
667
+ td = ui.element("td")
668
+ set_element_text(
669
+ td,
670
+ format_table_cell(row[col], date_format=date_format, float_format=float_format),
671
+ )
672
+
673
+
674
+ class _CardImpl(BootstrapElement):
675
+ """Private Card implementation."""
676
+
677
+ component_name = "Card"
678
+ children_kind = "auto"
679
+ reserved_classes = ("card",)
680
+
681
+ def __init__(
682
+ self,
683
+ children: object = None,
684
+ *,
685
+ _surface: str,
686
+ color: str | None = None,
687
+ outline: bool = False,
688
+ inverse: bool = False,
689
+ body: bool = False,
690
+ **kwargs: Any,
691
+ ) -> None:
692
+ self._surface = _surface
693
+ self._structural_classes = tuple(
694
+ card_structural_classes(color=color, outline=outline, inverse=inverse, body=body)
695
+ )
696
+ tag = kwargs.pop("tag", "div")
697
+ super().__init__(children, tag=tag, **kwargs)
698
+
699
+
700
+ Card, DbcCard = make_surface_classes("Card", globals())
701
+ card = Card
702
+
703
+
704
+ class _CardBodyImpl(BootstrapElement):
705
+ """Private CardBody implementation."""
706
+
707
+ component_name = "CardBody"
708
+ children_kind = "auto"
709
+ reserved_classes = ("card-body",)
710
+
711
+ def __init__(self, children: object = None, *, _surface: str, **kwargs: Any) -> None:
712
+ self._surface = _surface
713
+ self._structural_classes = ("card-body",)
714
+ tag = kwargs.pop("tag", "div")
715
+ super().__init__(children, tag=tag, **kwargs)
716
+
717
+
718
+ CardBody, DbcCardBody = make_surface_classes("CardBody", globals())
719
+ card_body = CardBody
720
+
721
+
722
+ class _CardHeaderImpl(BootstrapElement):
723
+ """Private CardHeader implementation."""
724
+
725
+ component_name = "CardHeader"
726
+ children_kind = "auto"
727
+ reserved_classes = ("card-header",)
728
+
729
+ def __init__(self, children: object = None, *, _surface: str, **kwargs: Any) -> None:
730
+ self._surface = _surface
731
+ self._structural_classes = ("card-header",)
732
+ tag = kwargs.pop("tag", "div")
733
+ super().__init__(children, tag=tag, **kwargs)
734
+
735
+
736
+ CardHeader, DbcCardHeader = make_surface_classes("CardHeader", globals())
737
+ card_header = CardHeader
738
+
739
+
740
+ class _CardFooterImpl(BootstrapElement):
741
+ """Private CardFooter implementation."""
742
+
743
+ component_name = "CardFooter"
744
+ children_kind = "auto"
745
+ reserved_classes = ("card-footer",)
746
+
747
+ def __init__(self, children: object = None, *, _surface: str, **kwargs: Any) -> None:
748
+ self._surface = _surface
749
+ self._structural_classes = ("card-footer",)
750
+ tag = kwargs.pop("tag", "div")
751
+ super().__init__(children, tag=tag, **kwargs)
752
+
753
+
754
+ CardFooter, DbcCardFooter = make_surface_classes("CardFooter", globals())
755
+ card_footer = CardFooter
756
+
757
+
758
+ class _CardGroupImpl(BootstrapElement):
759
+ """Private CardGroup implementation."""
760
+
761
+ component_name = "CardGroup"
762
+ children_kind = "grouping"
763
+ reserved_classes = ("card-group",)
764
+
765
+ def __init__(self, children: object = None, *, _surface: str, **kwargs: Any) -> None:
766
+ self._surface = _surface
767
+ self._structural_classes = ("card-group",)
768
+ tag = kwargs.pop("tag", "div")
769
+ super().__init__(children, tag=tag, **kwargs)
770
+
771
+
772
+ CardGroup, DbcCardGroup = make_surface_classes("CardGroup", globals())
773
+ card_group = CardGroup
774
+
775
+
776
+ class _CardImgImpl(BootstrapElement):
777
+ """Private CardImg implementation."""
778
+
779
+ component_name = "CardImg"
780
+ children_kind = "auto"
781
+ reserved_classes = ()
782
+
783
+ def __init__(
784
+ self,
785
+ children: object = None,
786
+ *,
787
+ _surface: str,
788
+ src: str | None = None,
789
+ alt: str = "",
790
+ top: bool = False,
791
+ bottom: bool = False,
792
+ overlay: bool = False,
793
+ **kwargs: Any,
794
+ ) -> None:
795
+ self._surface = _surface
796
+ self._structural_classes = tuple(
797
+ card_img_structural_classes(top=top, bottom=bottom, overlay=overlay)
798
+ )
799
+ tag = kwargs.pop("tag", "img")
800
+ super().__init__(children, tag=tag, **kwargs)
801
+ if src is not None:
802
+ self._props["src"] = src
803
+ self._props["alt"] = alt
804
+
805
+
806
+ CardImg, DbcCardImg = make_surface_classes("CardImg", globals())
807
+ card_img = CardImg
808
+
809
+
810
+ class _CardImgOverlayImpl(BootstrapElement):
811
+ """Private CardImgOverlay implementation."""
812
+
813
+ component_name = "CardImgOverlay"
814
+ children_kind = "auto"
815
+ reserved_classes = ("card-img-overlay",)
816
+
817
+ def __init__(self, children: object = None, *, _surface: str, **kwargs: Any) -> None:
818
+ self._surface = _surface
819
+ self._structural_classes = ("card-img-overlay",)
820
+ tag = kwargs.pop("tag", "div")
821
+ super().__init__(children, tag=tag, **kwargs)
822
+
823
+
824
+ CardImgOverlay, DbcCardImgOverlay = make_surface_classes("CardImgOverlay", globals())
825
+ card_img_overlay = CardImgOverlay
826
+
827
+
828
+ class _CardLinkImpl(BootstrapElement):
829
+ """Private CardLink implementation. Click listener is registered in ``__init__`` only."""
830
+
831
+ component_name = "CardLink"
832
+ children_kind = "phrasing"
833
+ reserved_classes = ("card-link",)
834
+
835
+ def __init__(
836
+ self,
837
+ children: object = None,
838
+ *,
839
+ _surface: str,
840
+ href: str | None = None,
841
+ external_link: bool = False,
842
+ n_clicks: int = 0,
843
+ on_click: Callable[..., Any] | None = None,
844
+ **kwargs: Any,
845
+ ) -> None:
846
+ self._surface = _surface
847
+ self._structural_classes = ("card-link",)
848
+ self._n_clicks = int(n_clicks)
849
+ self._on_click = on_click
850
+ kwargs.pop("tag", None)
851
+ super().__init__(children, tag="a", **kwargs)
852
+ _apply_interactive_props(self, tag="a", href=href, external_link=external_link)
853
+ self.on("click", self._handle_click)
854
+
855
+ def _handle_click(self, *_args: Any, **_kwargs: Any) -> None:
856
+ self._n_clicks += 1
857
+ if self._on_click is not None:
858
+ self._on_click()
859
+
860
+
861
+ CardLink, DbcCardLink = make_surface_classes("CardLink", globals())
862
+ card_link = CardLink
863
+
864
+
865
+ class _ListGroupImpl(BootstrapElement):
866
+ """Private ListGroup implementation. Tag is fixed in ``__init__``."""
867
+
868
+ component_name = "ListGroup"
869
+ children_kind = "grouping"
870
+ reserved_classes = ("list-group",)
871
+
872
+ def __init__(
873
+ self,
874
+ children: object = None,
875
+ *,
876
+ _surface: str,
877
+ flush: bool = False,
878
+ numbered: bool = False,
879
+ horizontal: bool | str = False,
880
+ **kwargs: Any,
881
+ ) -> None:
882
+ self._surface = _surface
883
+ self._structural_classes = tuple(
884
+ list_group_structural_classes(flush=flush, numbered=numbered, horizontal=horizontal)
885
+ )
886
+ explicit_tag = kwargs.pop("tag", None)
887
+ self._resolved_tag = resolve_list_group_tag(tag=explicit_tag, numbered=numbered)
888
+ super().__init__(children, tag=self._resolved_tag, **kwargs)
889
+
890
+ def __enter__(self) -> Any:
891
+ if self._surface == "native":
892
+ default = "li" if self._resolved_tag in {"ul", "ol"} else "div"
893
+ self._item_tag_token: Token[str | None] | None = _LIST_GROUP_ITEM_DEFAULT_TAG.set(
894
+ default
895
+ )
896
+ return super().__enter__()
897
+
898
+ def __exit__(self, *args: Any) -> Any:
899
+ try:
900
+ return super().__exit__(*args)
901
+ finally:
902
+ token = getattr(self, "_item_tag_token", None)
903
+ if token is not None:
904
+ _LIST_GROUP_ITEM_DEFAULT_TAG.reset(token)
905
+ self._item_tag_token = None
906
+
907
+
908
+ ListGroup, DbcListGroup = make_surface_classes("ListGroup", globals())
909
+ list_group = ListGroup
910
+
911
+
912
+ class _ListGroupItemImpl(BootstrapElement):
913
+ """Private ListGroupItem implementation. Click listener is registered in ``__init__`` only."""
914
+
915
+ component_name = "ListGroupItem"
916
+ children_kind = "auto"
917
+ reserved_classes = ("list-group-item",)
918
+
919
+ def __init__(
920
+ self,
921
+ children: object = None,
922
+ *,
923
+ _surface: str,
924
+ active: bool = False,
925
+ disabled: bool = False,
926
+ color: str | None = None,
927
+ action: bool = False,
928
+ href: str | None = None,
929
+ external_link: bool = False,
930
+ n_clicks: int = 0,
931
+ on_click: Callable[..., Any] | None = None,
932
+ **kwargs: Any,
933
+ ) -> None:
934
+ self._surface = _surface
935
+ self._structural_classes = tuple(
936
+ list_group_item_structural_classes(
937
+ active=active,
938
+ disabled=disabled,
939
+ color=color,
940
+ action=action,
941
+ href=href,
942
+ )
943
+ )
944
+ explicit_tag = kwargs.pop("tag", None)
945
+ self._resolved_tag = resolve_list_group_item_tag(
946
+ tag=explicit_tag,
947
+ href=href,
948
+ action=action,
949
+ context_default=_LIST_GROUP_ITEM_DEFAULT_TAG.get(),
950
+ )
951
+ self._n_clicks = int(n_clicks)
952
+ self._on_click = on_click
953
+ super().__init__(children, tag=self._resolved_tag, **kwargs)
954
+ _apply_interactive_props(
955
+ self,
956
+ tag=self._resolved_tag,
957
+ href=href,
958
+ disabled=disabled,
959
+ external_link=external_link,
960
+ )
961
+ self.on("click", self._handle_click)
962
+
963
+ def _handle_click(self, *_args: Any, **_kwargs: Any) -> None:
964
+ self._n_clicks += 1
965
+ if self._on_click is not None:
966
+ self._on_click()
967
+
968
+
969
+ ListGroupItem, DbcListGroupItem = make_surface_classes("ListGroupItem", globals())
970
+ list_group_item = ListGroupItem
971
+
972
+
973
+ class _TableImpl(BootstrapElement):
974
+ """Private Table implementation."""
975
+
976
+ component_name = "Table"
977
+ children_kind = "grouping"
978
+ reserved_classes = ()
979
+
980
+ def __init__(
981
+ self,
982
+ children: object = None,
983
+ *,
984
+ _surface: str,
985
+ bordered: bool = False,
986
+ borderless: bool = False,
987
+ striped: bool = False,
988
+ hover: bool = False,
989
+ size: str | None = None,
990
+ responsive: bool | str = False,
991
+ color: str | None = None,
992
+ striped_columns: bool = False,
993
+ **kwargs: Any,
994
+ ) -> None:
995
+ self._surface = _surface
996
+ self._inner: ui.element | None = None
997
+ if "dark" in kwargs:
998
+ kwargs.pop("dark")
999
+ if _surface == "compat":
1000
+ raise UnsupportedPropError("dark")
1001
+ raise ValueError("Table dark was removed; color mode comes from theme")
1002
+ small = False
1003
+ if "small" in kwargs:
1004
+ if _surface == "compat":
1005
+ raise UnsupportedPropError("small")
1006
+ small = bool(kwargs.pop("small"))
1007
+ tbl_classes = table_structural_classes(
1008
+ bordered=bordered,
1009
+ borderless=borderless,
1010
+ striped=striped,
1011
+ hover=hover,
1012
+ small=small,
1013
+ size=size,
1014
+ color=color,
1015
+ striped_columns=striped_columns,
1016
+ )
1017
+ resp_class = table_responsive_class(responsive)
1018
+ if resp_class is not None:
1019
+ user_class: str | None = None
1020
+ class_name = kwargs.pop("class_name", None)
1021
+ class_name_camel = kwargs.pop("className", None)
1022
+ if class_name is not None or class_name_camel is not None:
1023
+ user_class = resolve_class_name(class_name, class_name_camel, self._surface)
1024
+ self._structural_classes: tuple[str, ...] = (resp_class,)
1025
+ super().__init__(None, tag="div", **kwargs)
1026
+ inner = ui.element("table")
1027
+ inner.classes(" ".join(tbl_classes))
1028
+ if user_class:
1029
+ inner.classes(user_class)
1030
+ inner.move(self)
1031
+ self._inner = inner
1032
+ _place_children(inner, children)
1033
+ return
1034
+ self._structural_classes = tuple(tbl_classes)
1035
+ super().__init__(children, tag="table", **kwargs)
1036
+
1037
+ def __enter__(self) -> Any:
1038
+ if self._inner is not None:
1039
+ return self._inner.__enter__()
1040
+ return super().__enter__()
1041
+
1042
+ def __exit__(self, *args: Any) -> Any:
1043
+ if self._inner is not None:
1044
+ return self._inner.__exit__(*args)
1045
+ return super().__exit__(*args)
1046
+
1047
+ @classmethod
1048
+ def from_dataframe(
1049
+ cls,
1050
+ df: object,
1051
+ *,
1052
+ columns: str | Sequence[str] | None = None,
1053
+ header: bool = True,
1054
+ index: bool = True,
1055
+ index_label: str | None = None,
1056
+ date_format: str | None = None,
1057
+ float_format: str | Callable[..., Any] | None = None,
1058
+ caption: str | None = None,
1059
+ **table_props: Any,
1060
+ ) -> _TableImpl:
1061
+ """Build a Table from a pandas DataFrame.
1062
+
1063
+ pandas is an optional extra. Missing pandas raises ImportError.
1064
+ """
1065
+ pd_mod = _require_pandas()
1066
+ frame = df if isinstance(df, pd_mod.DataFrame) else pd_mod.DataFrame(df)
1067
+ if columns is not None:
1068
+ col_list = [columns] if isinstance(columns, str) else list(columns)
1069
+ frame = frame.loc[:, col_list]
1070
+ table = cls(**table_props)
1071
+ with table:
1072
+ if caption is not None:
1073
+ cap_el = ui.element("caption")
1074
+ set_element_text(cap_el, str(caption))
1075
+ if header:
1076
+ with ui.element("thead"):
1077
+ _write_dataframe_header(frame, index=index, index_label=index_label)
1078
+ with ui.element("tbody"):
1079
+ _write_dataframe_body(
1080
+ frame,
1081
+ index=index,
1082
+ date_format=date_format,
1083
+ float_format=float_format,
1084
+ )
1085
+ return table
1086
+
1087
+
1088
+ Table, DbcTable = make_surface_classes("Table", globals())
1089
+ table = Table
1090
+
1091
+
1092
+ class _BadgeImpl(BootstrapElement):
1093
+ """Private Badge implementation. Click listener is registered in ``__init__`` only."""
1094
+
1095
+ component_name = "Badge"
1096
+ children_kind = "phrasing"
1097
+ reserved_classes = ("badge",)
1098
+
1099
+ def __init__(
1100
+ self,
1101
+ children: object = None,
1102
+ *,
1103
+ _surface: str,
1104
+ color: str = "secondary",
1105
+ pill: bool = False,
1106
+ text_color: str | None = None,
1107
+ href: str | None = None,
1108
+ external_link: bool = False,
1109
+ n_clicks: int = 0,
1110
+ on_click: Callable[..., Any] | None = None,
1111
+ **kwargs: Any,
1112
+ ) -> None:
1113
+ self._surface = _surface
1114
+ self._structural_classes = tuple(
1115
+ badge_structural_classes(color=color, pill=pill, text_color=text_color)
1116
+ )
1117
+ explicit_tag = kwargs.pop("tag", None)
1118
+ resolved_tag = "a" if href is not None else (explicit_tag or "span")
1119
+ self._n_clicks = int(n_clicks)
1120
+ self._on_click = on_click
1121
+ super().__init__(children, tag=resolved_tag, **kwargs)
1122
+ _apply_interactive_props(self, tag=resolved_tag, href=href, external_link=external_link)
1123
+ self.on("click", self._handle_click)
1124
+
1125
+ def _handle_click(self, *_args: Any, **_kwargs: Any) -> None:
1126
+ self._n_clicks += 1
1127
+ if self._on_click is not None:
1128
+ self._on_click()
1129
+
1130
+
1131
+ Badge, DbcBadge = make_surface_classes("Badge", globals())
1132
+ badge = Badge
1133
+
1134
+
1135
+ class _ProgressImpl(BootstrapElement):
1136
+ """Private Progress implementation."""
1137
+
1138
+ component_name = "Progress"
1139
+ children_kind = "auto"
1140
+ reserved_classes = ()
1141
+
1142
+ def __init__(
1143
+ self,
1144
+ children: object = None,
1145
+ *,
1146
+ _surface: str,
1147
+ value: float = 0,
1148
+ min: float = 0,
1149
+ max: float = 100,
1150
+ label: str | None = None,
1151
+ animated: bool = False,
1152
+ striped: bool = False,
1153
+ color: str | None = None,
1154
+ height: int | float | str | None = None,
1155
+ bar: bool = False,
1156
+ bar_class_name: str | None = None,
1157
+ barClassName: str | None = None,
1158
+ bar_style: dict[str, Any] | str | None = None,
1159
+ **kwargs: Any,
1160
+ ) -> None:
1161
+ self._surface = _surface
1162
+ min_value = float(min)
1163
+ max_value = float(max)
1164
+ value_n = float(value)
1165
+ self._value = value_n
1166
+ self._min = min_value
1167
+ self._max = max_value
1168
+ self._label = label
1169
+ self._bar_style = bar_style
1170
+ extra_bar = None
1171
+ if bar_class_name is not None or barClassName is not None:
1172
+ extra_bar = resolve_class_name(bar_class_name, barClassName, self._surface)
1173
+ self._bar_classes = progress_bar_structural_classes(
1174
+ color=color, striped=striped, animated=animated
1175
+ )
1176
+ if extra_bar:
1177
+ self._bar_classes = [*self._bar_classes, extra_bar]
1178
+ self._nested = bool(bar) or _PROGRESS_NESTED.get()
1179
+ kwargs.pop("tag", None)
1180
+ if height is not None and not self._nested:
1181
+ if isinstance(height, bool):
1182
+ raise ValueError("height must be a CSS length or number of pixels")
1183
+ height_css = f"{height}px" if isinstance(height, (int, float)) else str(height)
1184
+ _merge_style_kwarg(kwargs, {"height": height_css})
1185
+ if self._nested:
1186
+ self._structural_classes = tuple(self._bar_classes)
1187
+ super().__init__(children, tag="div", **kwargs)
1188
+ _configure_progress_bar(
1189
+ self,
1190
+ classes=[],
1191
+ value=value_n,
1192
+ min_value=min_value,
1193
+ max_value=max_value,
1194
+ label=label,
1195
+ bar_style=bar_style,
1196
+ )
1197
+ self._bar = None
1198
+ return
1199
+ self._structural_classes = tuple(progress_structural_classes())
1200
+ super().__init__(children, tag="div", **kwargs)
1201
+ bar_el = ui.element("div")
1202
+ _configure_progress_bar(
1203
+ bar_el,
1204
+ classes=self._bar_classes,
1205
+ value=value_n,
1206
+ min_value=min_value,
1207
+ max_value=max_value,
1208
+ label=label,
1209
+ bar_style=bar_style,
1210
+ )
1211
+ bar_el.move(self, target_index=0)
1212
+ self._bar = bar_el
1213
+ self._convert_nested_progress_children(children)
1214
+
1215
+ def _convert_nested_progress_children(self, children: object) -> None:
1216
+ if children is None or isinstance(children, str):
1217
+ return
1218
+ for child in _declared_children(children):
1219
+ convert = getattr(child, "_become_nested_bar", None)
1220
+ if callable(convert):
1221
+ convert()
1222
+
1223
+ def _become_nested_bar(self) -> None:
1224
+ if self._nested:
1225
+ return
1226
+ self._nested = True
1227
+ self.classes(remove="progress")
1228
+ extra = " ".join(self._bar_classes)
1229
+ if extra:
1230
+ self.classes(add=extra)
1231
+ label = self._label
1232
+ bar = getattr(self, "_bar", None)
1233
+ if bar is not None:
1234
+ bar_text = getattr(bar, "text", None)
1235
+ if bar_text:
1236
+ label = str(bar_text)
1237
+ delete = getattr(bar, "delete", None)
1238
+ if callable(delete):
1239
+ delete()
1240
+ self._bar = None
1241
+ _configure_progress_bar(
1242
+ self,
1243
+ classes=[],
1244
+ value=self._value,
1245
+ min_value=self._min,
1246
+ max_value=self._max,
1247
+ label=label,
1248
+ bar_style=self._bar_style,
1249
+ )
1250
+
1251
+ def __enter__(self) -> Any:
1252
+ self._nested_token: Token[bool] | None = _PROGRESS_NESTED.set(True)
1253
+ try:
1254
+ return super().__enter__()
1255
+ except BaseException:
1256
+ _PROGRESS_NESTED.reset(self._nested_token)
1257
+ raise
1258
+
1259
+ def __exit__(self, *args: Any) -> Any:
1260
+ try:
1261
+ return super().__exit__(*args)
1262
+ finally:
1263
+ token = getattr(self, "_nested_token", None)
1264
+ if token is not None:
1265
+ _PROGRESS_NESTED.reset(token)
1266
+ self._nested_token = None
1267
+
1268
+
1269
+ Progress, DbcProgress = make_surface_classes("Progress", globals())
1270
+ progress = Progress
1271
+
1272
+
1273
+ class _PlaceholderImpl(BootstrapElement):
1274
+ """Private Placeholder implementation."""
1275
+
1276
+ component_name = "Placeholder"
1277
+ children_kind = "auto"
1278
+ reserved_classes = ("placeholder",)
1279
+
1280
+ def __init__(
1281
+ self,
1282
+ children: object = None,
1283
+ *,
1284
+ _surface: str,
1285
+ animation: str | None = None,
1286
+ size: str | None = None,
1287
+ color: str | None = None,
1288
+ button: bool = False,
1289
+ loading: bool = False,
1290
+ display: bool | str | None = None,
1291
+ width: int | str | None = None,
1292
+ xs: int | bool | None = None,
1293
+ sm: int | bool | None = None,
1294
+ md: int | bool | None = None,
1295
+ lg: int | bool | None = None,
1296
+ xl: int | bool | None = None,
1297
+ xxl: int | bool | None = None,
1298
+ **kwargs: Any,
1299
+ ) -> None:
1300
+ self._surface = _surface
1301
+ width_for_classes: int | None
1302
+ if isinstance(width, bool):
1303
+ raise ValueError(f"Unknown placeholder width {width!r}")
1304
+ width_for_classes = width if isinstance(width, int) else None
1305
+ self._structural_classes = tuple(
1306
+ placeholder_structural_classes(
1307
+ animation=animation,
1308
+ size=size,
1309
+ color=color,
1310
+ button=button,
1311
+ display=display,
1312
+ width=width_for_classes,
1313
+ xs=xs,
1314
+ sm=sm,
1315
+ md=md,
1316
+ lg=lg,
1317
+ xl=xl,
1318
+ xxl=xxl,
1319
+ )
1320
+ )
1321
+ explicit_tag = kwargs.pop("tag", None)
1322
+ if explicit_tag:
1323
+ resolved_tag = explicit_tag
1324
+ elif button:
1325
+ resolved_tag = "button"
1326
+ else:
1327
+ resolved_tag = "span"
1328
+ if isinstance(width, str):
1329
+ _merge_style_kwarg(kwargs, {"width": width})
1330
+ super().__init__(children, tag=resolved_tag, **kwargs)
1331
+ if resolved_tag == "button":
1332
+ self._props.setdefault("type", "button")
1333
+ self._props["disabled"] = True
1334
+ elif button:
1335
+ self._props["disabled"] = True
1336
+ if loading:
1337
+ self._props["aria-busy"] = "true"
1338
+ if not _children_present(children):
1339
+ self._props["aria-hidden"] = "true"
1340
+
1341
+
1342
+ Placeholder, DbcPlaceholder = make_surface_classes("Placeholder", globals())
1343
+ placeholder = Placeholder