Source code for duck.html.components.select
"""
Select HTML Component.
This module provides reusable `Select` and `Option` components for creating dropdown menus in HTML.
"""
from duck.html.components import ComponentError, InnerComponent
from duck.html.components.button import Clickable
from duck.html.components.theme import Theme
[docs]
class Option(InnerComponent):
"""
Represents an individual option within a `Select` dropdown.
This component is used to define selectable items inside a `Select` component.
Args:
text (str): The option's display text (or pass `inner_html` directly).
value (str): Optional. The option's `value` attribute, if it differs from its text.
selected (bool): Optional. Whether this option is selected by default.
**Example Usage:**
```py
option = Option(inner_html="Option 1")
select.add_child(option)
```
This generates:
```html
<option>Option 1</option>
```
**Returns:**
- An `<option>` HTML element.
"""
[docs]
def get_element(self) -> str:
"""
Returns the HTML tag for the component.
"""
return "option"
[docs]
def on_create(self) -> None:
super().on_create()
# Get optional fields, no need for handling text (already handled by default)
value = self.kwargs.get("value")
selected = self.kwargs.get("selected")
if value:
self.props["value"] = value
if selected:
self.props["selected"] = True
[docs]
class Select(Clickable, InnerComponent):
"""
A reusable HTML `<select>` component for creating dropdown menus.
This component generates a customizable `<select>` dropdown with options.
Args:
name (str): Optional. The `name` attribute for the select field.
options (list): Optional. A list of options, where each item is either
a string/int/float (used as the option text), a dict of Option
constructor kwargs, or an `Option` component instance.
**Styling:**
- Uses default styling based on the `Theme.current` class, falling back to sensible
defaults when the active theme doesn't define them.
- Can be overridden per-instance via the `style` kwarg, or with CSS.
"""
DEFAULT_STYLE = {
"padding": "10px",
"border": "1px solid #ccc",
"border-radius": getattr(Theme.current, "border_radius", "6px"),
"font-size": getattr(Theme.current, "font_size", "1rem"),
}
[docs]
def get_element(self) -> str:
"""
Returns the HTML tag for the component.
"""
return "select"
[docs]
def on_create(self) -> None:
"""
Initializes the component with default styles and options.
"""
super().on_create()
# Fill in theme defaults without clobbering any style already set
self.style.setdefaults(self.DEFAULT_STYLE)
# Get the name
name = self.kwargs.get("name")
if name:
self.props["name"] = name
for option in self.kwargs.get("options", []):
self.add_child(self.to_option(option))
[docs]
def to_option(self, option) -> Option:
"""
Normalizes a raw option value into an `Option` component.
Args:
option: A string/int/float, a two-item tuple/list of
`(value, text)`, a dict of Option kwargs, or an
existing `Option` component.
Returns:
An `Option` component.
Raises:
ComponentError: If `option` isn't one of the supported types,
or a tuple/list isn't exactly two items.
"""
if isinstance(option, Option):
return option
if isinstance(option, (str, int, float)):
return Option(text=option)
if isinstance(option, (tuple, list)):
if len(option) != 2:
raise ComponentError(
f"Option tuple/list must have exactly 2 items (value, text), got {len(option)}"
)
value, text = option
return Option(text=text, value=value)
if isinstance(option, dict):
return Option(**option)
raise ComponentError(
f"Option must be a string, number, tuple/list, dictionary, or Option component, not {type(option)}"
)