Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
2fcd3a9
downgrade view
NeloBlivion Sep 3, 2025
027527d
Merge branch 'master' into cv2_fixes
NeloBlivion Sep 3, 2025
b319d13
skeleton for actionrow
NeloBlivion Sep 3, 2025
fcb0849
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 3, 2025
d1f33ea
cleanup
NeloBlivion Sep 3, 2025
8efedd3
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 3, 2025
54afda3
Merge branch 'master' into cv2_fixes
Lulalaby Sep 3, 2025
6d6ce3e
eh..... going through it... don't judge yet................
NeloBlivion Sep 3, 2025
f13f5e0
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 3, 2025
b322b42
correct
NeloBlivion Sep 3, 2025
c0022f1
v2
NeloBlivion Sep 3, 2025
e830df4
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 3, 2025
be236bf
Merge branch 'master' into cv2_fixes
NeloBlivion Sep 22, 2025
23f296d
better separation of views
NeloBlivion Sep 22, 2025
57a0d98
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 22, 2025
eaa2c30
import
NeloBlivion Sep 22, 2025
c6c4fbf
)
NeloBlivion Sep 22, 2025
57282b8
))
NeloBlivion Sep 22, 2025
1da73f6
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 22, 2025
2b981d3
attempt modal conversion
NeloBlivion Sep 22, 2025
c929c67
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 22, 2025
e61064d
types
NeloBlivion Sep 22, 2025
d8e3d4f
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 22, 2025
057a26b
container uses actionrow instead of button/select, remove decorator s…
NeloBlivion Sep 22, 2025
b7a8f6b
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 22, 2025
166f540
types
NeloBlivion Sep 22, 2025
a7346cd
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 22, 2025
836998d
imports
NeloBlivion Sep 22, 2025
16b4886
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 22, 2025
5255da1
remove Select.label & Select.description
NeloBlivion Sep 22, 2025
a1068fb
update refresh logic
NeloBlivion Sep 22, 2025
e199c8c
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 22, 2025
0dc7522
typext
NeloBlivion Sep 22, 2025
bfd4aed
reduce further
NeloBlivion Sep 22, 2025
1223dea
fix __all__
NeloBlivion Sep 22, 2025
3a82843
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 22, 2025
25fbd2a
typecheck
NeloBlivion Sep 22, 2025
3ee00e0
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 22, 2025
61a1433
__
NeloBlivion Sep 22, 2025
edb4951
dispatch
NeloBlivion Sep 22, 2025
d9c465b
ComponentType
NeloBlivion Sep 22, 2025
1ae1c77
types ?
NeloBlivion Sep 22, 2025
bd60e9b
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 22, 2025
090af2e
typecheck again
NeloBlivion Sep 22, 2025
00faca4
attrs
NeloBlivion Sep 22, 2025
5c7a02b
fixes
NeloBlivion Sep 22, 2025
1975f6d
adjust
NeloBlivion Sep 22, 2025
2816b1b
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 22, 2025
e615190
actionrow components
NeloBlivion Sep 22, 2025
93fb09a
arc
NeloBlivion Sep 22, 2025
0517d2e
button priority
NeloBlivion Sep 22, 2025
e32fdad
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 22, 2025
bdb6d21
maybe?
NeloBlivion Sep 22, 2025
20aae0f
no
NeloBlivion Sep 22, 2025
a846b43
update examples
NeloBlivion Sep 23, 2025
7caf4f5
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 23, 2025
0060e0f
remove v2 from weights
NeloBlivion Sep 23, 2025
8308615
misc
NeloBlivion Sep 23, 2025
5410e00
adjust Label args (first arg label)
NeloBlivion Sep 23, 2025
d4dc1c5
add select overloads
NeloBlivion Sep 23, 2025
b619025
actionrow clarification
NeloBlivion Sep 23, 2025
f90a3bc
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 23, 2025
35e4cb1
all items in actionrow have the disabled attribute
NeloBlivion Sep 23, 2025
65a1116
fixes
NeloBlivion Sep 24, 2025
e453e01
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 24, 2025
e9ac8bc
more imports
NeloBlivion Sep 25, 2025
b2ed0d1
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 25, 2025
9ca373a
docs and typing cleanup
NeloBlivion Sep 25, 2025
0acb103
update _component_to_item
NeloBlivion Sep 26, 2025
43df60f
fix to_component_dict
NeloBlivion Oct 3, 2025
0b58904
attempt splitting into ViewItem and ModalItem
NeloBlivion Oct 3, 2025
3a8bb10
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 3, 2025
64f3c4f
more
NeloBlivion Oct 3, 2025
45a5c44
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 3, 2025
4ee4b70
guys don't you love typing changes
NeloBlivion Oct 3, 2025
fd67ea2
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 3, 2025
148535a
types
NeloBlivion Oct 3, 2025
bcc1a7a
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 3, 2025
712cd3c
this is required apparently
NeloBlivion Oct 3, 2025
ef78980
self.modal
NeloBlivion Oct 3, 2025
9cf2886
set modal
NeloBlivion Oct 3, 2025
62ffcdf
no weights in basemodal
NeloBlivion Oct 3, 2025
a961d49
undo on_modal_error change
NeloBlivion Oct 3, 2025
8664272
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 3, 2025
fbac652
define Item.type
NeloBlivion Oct 3, 2025
c2bd6b5
further consolidate
NeloBlivion Oct 3, 2025
9255c39
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 3, 2025
8fdc3f1
oops
NeloBlivion Oct 3, 2025
8a9fe8f
fix modal init
NeloBlivion Oct 3, 2025
38352c2
actual fix
NeloBlivion Oct 3, 2025
1d790da
forgot modal children thing
NeloBlivion Oct 3, 2025
846b14d
clarify
NeloBlivion Oct 3, 2025
157d8c7
adjust item docs
NeloBlivion Oct 4, 2025
fb01cfe
copyright
NeloBlivion Oct 5, 2025
340b5d5
children
NeloBlivion Oct 5, 2025
3f3ae31
Update discord/ui/action_row.py
NeloBlivion Oct 6, 2025
be9eb51
add Webhook.parent
NeloBlivion Oct 6, 2025
cf49aa3
style(pre-commit): auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 6, 2025
017f15f
add Webhook.from_interaction
NeloBlivion Oct 6, 2025
bba29af
slots
NeloBlivion Oct 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion discord/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def __init__(self, data: ComponentPayload):

@property
def width(self):
"""Return the sum of the children's widths."""
"""Return the sum of the item's widths."""
t = 0
for item in self.children:
t += 1 if item.type is ComponentType.button else 5
Expand Down
346 changes: 346 additions & 0 deletions discord/ui/action_row.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,346 @@
from __future__ import annotations

from functools import partial
from typing import TYPE_CHECKING, ClassVar, Iterator, TypeVar

from ..components import ActionRow as ActionRowComponent
from ..components import SelectOption, _component_factory
from ..enums import ButtonStyle, ChannelType, ComponentType
from ..utils import find, get
from .button import Button
from .file import File
from .item import Item, ItemCallbackType
from .select import Select

__all__ = ("ActionRow",)

if TYPE_CHECKING:
from typing_extensions import Self

from ..emoji import AppEmoji, GuildEmoji
from ..partial_emoji import PartialEmoji, _EmojiTag
from ..types.components import ActionRow as ActionRowPayload
from .view import View


A = TypeVar("A", bound="ActionRow")
V = TypeVar("V", bound="View", covariant=True)


class ActionRow(Item[V]):
"""Represents a UI Action Row used in :class:`discord.ui.View`.

The items supported are as follows:

- :class:`discord.ui.Select`
- :class:`discord.ui.Button`

.. versionadded:: 2.7

Parameters
----------
*items: :class:`Item`
The initial items in this action row.
id: Optional[:class:`int`]
The action's ID.
"""

__item_repr_attributes__: tuple[str, ...] = (
"items",
"id",
)

__row_children_items__: ClassVar[list[ItemCallbackType]] = []

def __init_subclass__(cls) -> None:
children: list[ItemCallbackType] = []
for base in reversed(cls.__mro__):
for member in base.__dict__.values():
if hasattr(member, "__discord_ui_model_type__"):
children.append(member)

cls.__row_children_items__ = children

def __init__(
self,
*items: Item,
id: int | None = None,
):
super().__init__()

self.items: list[Item] = []

self._underlying = ActionRowComponent._raw_construct(
type=ComponentType.action_row,
id=id,
components=[],
)

for func in self.__row_children_items__:
item: Item = func.__discord_ui_model_type__(
**func.__discord_ui_model_kwargs__
)
item.callback = partial(func, self, item)
self.add_item(item)
setattr(self, func.__name__, item)
for i in items:
self.add_item(i)

def _add_component_from_item(self, item: Item):
self._underlying.components.append(item._underlying)

def _set_components(self, items: list[Item]):
self._underlying.components.clear()
for item in items:
self._add_component_from_item(item)

def add_item(self, item: Item) -> Self:
"""Adds an item to the action row.

Parameters
----------
item: :class:`Item`
The item to add to the action row.

Raises
------
TypeError
An :class:`Item` was not passed.
"""

if not isinstance(item, Item):
raise TypeError(f"expected Item not {item.__class__!r}")

item._view = self.view
item.parent = self

self.items.append(item)
self._add_component_from_item(item)
return self

def remove_item(self, item: Item | str | int) -> Self:
"""Removes an item from the action row. If an int or str is passed, it will remove by Item :attr:`id` or ``custom_id`` respectively.

Parameters
----------
item: Union[:class:`Item`, :class:`int`, :class:`str`]
The item, ``id``, or item ``custom_id`` to remove from the action row.
"""

if isinstance(item, (str, int)):
item = self.get_item(item)
try:
self.items.remove(item)
except ValueError:
pass
return self

def get_item(self, id: str | int) -> Item | None:
"""Get an item from this action row. Roughly equivalent to `utils.get(row.items, ...)`.
If an ``int`` is provided, the item will be retrieved by ``id``, otherwise by ``custom_id``.

Parameters
----------
id: Union[:class:`str`, :class:`int`]
The id or custom_id of the item to get.

Returns
-------
Optional[:class:`Item`]
The item with the matching ``id`` or ``custom_id`` if it exists.
"""
if not id:
return None
attr = "id" if isinstance(id, int) else "custom_id"
child = find(lambda i: getattr(i, attr, None) == id, self.items)
return child

def add_button(
self,
*,
style: ButtonStyle = ButtonStyle.secondary,
label: str | None = None,
disabled: bool = False,
custom_id: str | None = None,
url: str | None = None,
emoji: str | GuildEmoji | AppEmoji | PartialEmoji | None = None,
sku_id: int | None = None,
id: int | None = None,
) -> Self:
"""Adds a :class:`Button` to the action row.

To append a pre-existing :class:`Button`, use the
:meth:`add_item` method, instead.

Parameters
----------
style: :class:`discord.ButtonStyle`
The style of the button.
custom_id: Optional[:class:`str`]
The custom ID of the button that gets received during an interaction.
If this button is for a URL, it does not have a custom ID.
url: Optional[:class:`str`]
The URL this button sends you to.
disabled: :class:`bool`
Whether the button is disabled or not.
label: Optional[:class:`str`]
The label of the button, if any. Maximum of 80 chars.
emoji: Optional[Union[:class:`.PartialEmoji`, :class:`GuildEmoji`, :class:`AppEmoji`, :class:`str`]]
The emoji of the button, if any.
sku_id: Optional[Union[:class:`int`]]
The ID of the SKU this button refers to.
id: Optional[:class:`int`]
The button's ID.
"""

button = Button(
style=style,
label=label,
disabled=disabled,
custom_id=custom_id,
url=url,
emoji=emoji,
sku_id=sku_id,
id=id,
)

return self.add_item(button)

def add_select(
self,
select_type: ComponentType = ComponentType.string_select,
*,
custom_id: str | None = None,
placeholder: str | None = None,
min_values: int = 1,
max_values: int = 1,
options: list[SelectOption] | None = None,
channel_types: list[ChannelType] | None = None,
disabled: bool = False,
id: int | None = None,
) -> Self:
"""Adds a :class:`TextDisplay` to the container.

Parameters
----------
select_type: :class:`discord.ComponentType`
The type of select to create. Must be one of
:attr:`discord.ComponentType.string_select`, :attr:`discord.ComponentType.user_select`,
:attr:`discord.ComponentType.role_select`, :attr:`discord.ComponentType.mentionable_select`,
or :attr:`discord.ComponentType.channel_select`.
custom_id: :class:`str`
The custom ID of the select menu that gets received during an interaction.
If not given then one is generated for you.
placeholder: Optional[:class:`str`]
The placeholder text that is shown if nothing is selected, if any.
min_values: :class:`int`
The minimum number of items that must be chosen for this select menu.
Defaults to 1 and must be between 1 and 25.
max_values: :class:`int`
The maximum number of items that must be chosen for this select menu.
Defaults to 1 and must be between 1 and 25.
options: List[:class:`discord.SelectOption`]
A list of options that can be selected in this menu.
Only valid for selects of type :attr:`discord.ComponentType.string_select`.
channel_types: List[:class:`discord.ChannelType`]
A list of channel types that can be selected in this menu.
Only valid for selects of type :attr:`discord.ComponentType.channel_select`.
disabled: :class:`bool`
Whether the select is disabled or not. Defaults to ``False``.
id: Optional[:class:`int`]
The select menu's ID.
"""

select = Select(
select_type=select_type,
custom_id=custom_id,
placeholder=placeholder,
min_values=min_values,
max_values=max_values,
options=options or [],
channel_types=channel_types or [],
disabled=disabled,
id=id,
)

return self.add_item(select)

@Item.view.setter
def view(self, value):
self._view = value
for item in self.items:
item.parent = self
item._view = value

@property
def type(self) -> ComponentType:
return self._underlying.type

@property
def width(self) -> int:
return 5

def is_dispatchable(self) -> bool:
return any(item.is_dispatchable() for item in self.items)

def is_persistent(self) -> bool:
return all(item.is_persistent() for item in self.items)

def refresh_component(self, component: ActionRowComponent) -> None:
self._underlying = component
i = 0
for y in component.components:
x = self.items[i]
x.refresh_component(y)
i += 1

def disable_all_items(self, *, exclusions: list[Item] | None = None) -> Self:
"""
Disables all items in the row.

Parameters
----------
exclusions: Optional[List[:class:`Item`]]
A list of items in `self.items` to not disable.
"""
for item in self.walk_items():
if exclusions is None or item not in exclusions:
item.disabled = True
return self

def enable_all_items(self, *, exclusions: list[Item] | None = None) -> Self:
"""
Enables all items in the row.

Parameters
----------
exclusions: Optional[List[:class:`Item`]]
A list of items in `self.items` to not enable.
"""
for item in self.walk_items():
if hasattr(item, "disabled") and (
exclusions is None or item not in exclusions
):
item.disabled = False
return self

def walk_items(self) -> Iterator[Item]:
yield from self.items

def to_component_dict(self) -> ActionRowPayload:
self._set_components(self.items)
return self._underlying.to_dict()

@classmethod
def from_component(cls: type[A], component: ActionRowComponent) -> A:
from .view import _component_to_item, _walk_all_components

items = [
_component_to_item(c) for c in _walk_all_components(component.components)
]
return cls(
*items,
id=component.id,
)

callback = None
Loading
Loading