pycascadeui 3.1.0__tar.gz → 3.3.0__tar.gz
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.
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/PKG-INFO +62 -47
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/README.md +58 -44
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/__init__.py +24 -4
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/components/__init__.py +6 -0
- pycascadeui-3.3.0/cascadeui/components/base.py +402 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/components/inputs.py +114 -57
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/components/patterns/__init__.py +2 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/components/patterns/v2.py +99 -21
- pycascadeui-3.3.0/cascadeui/components/selects.py +270 -0
- pycascadeui-3.3.0/cascadeui/components/types.py +43 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/components/wrappers.py +3 -1
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/devtools.py +13 -2
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/persistence/__init__.py +5 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/persistence/backends/__init__.py +9 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/persistence/backends/memory.py +40 -1
- pycascadeui-3.3.0/cascadeui/persistence/backends/postgres.py +847 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/persistence/backends/sqlite.py +204 -7
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/persistence/manager.py +17 -4
- pycascadeui-3.3.0/cascadeui/persistence/protocols.py +301 -0
- pycascadeui-3.3.0/cascadeui/persistence/schema_postgres.py +125 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/state/middleware/persistence.py +22 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/state/middleware/undo.py +34 -1
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/utils/__init__.py +3 -1
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/utils/coercion.py +41 -1
- pycascadeui-3.3.0/cascadeui/utils/fetch.py +73 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/views/_navigation.py +205 -93
- pycascadeui-3.3.0/cascadeui/views/_placement.py +438 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/views/base.py +75 -4
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/views/layout.py +56 -18
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/views/patterns/__init__.py +3 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/views/patterns/form.py +4 -1
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/views/patterns/leaderboard.py +49 -10
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/views/patterns/menu.py +6 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/views/patterns/paginated.py +124 -27
- pycascadeui-3.3.0/cascadeui/views/patterns/roles.py +538 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/views/patterns/tabs.py +3 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/views/patterns/types.py +85 -5
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/views/patterns/wizard.py +9 -7
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/views/persistent.py +38 -36
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/views/view.py +23 -1
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/pycascadeui.egg-info/PKG-INFO +62 -47
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/pycascadeui.egg-info/SOURCES.txt +15 -1
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/pycascadeui.egg-info/requires.txt +3 -2
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/pyproject.toml +4 -3
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_auto_defer.py +9 -6
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_backends.py +32 -21
- pycascadeui-3.3.0/tests/test_backends_postgres.py +426 -0
- pycascadeui-3.3.0/tests/test_backends_raw_sql.py +442 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_components.py +116 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_computed.py +1 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_devtools.py +26 -2
- pycascadeui-3.3.0/tests/test_dynamic_persistent_button.py +330 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_ephemeral_refresh.py +9 -8
- pycascadeui-3.3.0/tests/test_fetch.py +146 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_five_pillars.py +4 -11
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_form_patterns.py +23 -8
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_hooks.py +1 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_input_coercion.py +70 -1
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_inputs.py +100 -15
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_layout_view.py +102 -11
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_leaderboard_patterns.py +164 -10
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_menu_patterns.py +11 -1
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_message_cleanup.py +5 -1
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_navigation.py +171 -3
- pycascadeui-3.3.0/tests/test_navigation_artifact_restore.py +231 -0
- pycascadeui-3.3.0/tests/test_paginated_patterns.py +506 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_pagination.py +52 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_persistence.py +120 -23
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_persistence_middleware.py +8 -21
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_persistent_views.py +46 -4
- pycascadeui-3.3.0/tests/test_placement.py +534 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_rebuild_callback.py +29 -14
- pycascadeui-3.3.0/tests/test_roles_patterns.py +505 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_scoping.py +26 -39
- pycascadeui-3.3.0/tests/test_selects.py +226 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_state_slots.py +10 -30
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_theming.py +3 -1
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_undo.py +100 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_v2_helpers.py +115 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_validation.py +3 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_view_init.py +91 -2
- pycascadeui-3.1.0/cascadeui/components/base.py +0 -199
- pycascadeui-3.1.0/cascadeui/components/selects.py +0 -106
- pycascadeui-3.1.0/cascadeui/persistence/protocols.py +0 -171
- pycascadeui-3.1.0/tests/test_paginated_patterns.py +0 -246
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/LICENSE +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/__main__.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/components/buttons.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/components/patterns/v1.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/components/v1_composition.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/exceptions.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/persistence/config.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/persistence/migrations.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/persistence/schema.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/py.typed +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/setup.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/state/__init__.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/state/actions.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/state/computed.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/state/middleware/__init__.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/state/middleware/logging.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/state/reducers.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/state/singleton.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/state/slots.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/state/store.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/state/types.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/theming/__init__.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/theming/context.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/theming/core.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/theming/themes.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/tracing.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/utils/decorators.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/utils/errors.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/utils/logging.py +0 -0
- pycascadeui-3.1.0/cascadeui/utils/helpers.py → pycascadeui-3.3.0/cascadeui/utils/strings.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/utils/tasks.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/validation.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/views/__init__.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/cascadeui/views/_interaction.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/pycascadeui.egg-info/dependency_links.txt +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/pycascadeui.egg-info/top_level.txt +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/setup.cfg +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_batching.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_button_grid.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_emoji_grid.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_instance_limit.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_layout_pagination.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_layout_patterns.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_layout_persistent.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_middleware.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_nav_version.py +1 -1
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_owner_only.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_pillar_isolation.py +1 -1
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_reducers.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_state_store.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_tab_patterns.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_update_coalescing.py +2 -2
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_wizard_patterns.py +0 -0
- {pycascadeui-3.1.0 → pycascadeui-3.3.0}/tests/test_wrappers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pycascadeui
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: Redux-inspired UI framework for discord.py
|
|
5
5
|
Author-email: HollowTheSilver <hollow@users.noreply.github.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -30,14 +30,15 @@ Requires-Dist: pytest>=7.0; extra == "dev"
|
|
|
30
30
|
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
|
|
31
31
|
Requires-Dist: black>=26.3; extra == "dev"
|
|
32
32
|
Requires-Dist: isort>=8.0; extra == "dev"
|
|
33
|
+
Requires-Dist: testcontainers[postgres]>=4.0; extra == "dev"
|
|
33
34
|
Provides-Extra: docs
|
|
34
35
|
Requires-Dist: mkdocs>=1.5; extra == "docs"
|
|
35
36
|
Requires-Dist: mkdocs-material>=9.0; extra == "docs"
|
|
36
37
|
Requires-Dist: pymdown-extensions>=10.0; extra == "docs"
|
|
37
38
|
Provides-Extra: sqlite
|
|
38
39
|
Requires-Dist: aiosqlite>=0.19; extra == "sqlite"
|
|
39
|
-
Provides-Extra:
|
|
40
|
-
Requires-Dist:
|
|
40
|
+
Provides-Extra: postgres
|
|
41
|
+
Requires-Dist: asyncpg>=0.30; extra == "postgres"
|
|
41
42
|
Dynamic: license-file
|
|
42
43
|
|
|
43
44
|
<p align="center">
|
|
@@ -151,7 +152,8 @@ pip install pycascadeui
|
|
|
151
152
|
Optional dependencies:
|
|
152
153
|
|
|
153
154
|
```bash
|
|
154
|
-
pip install pycascadeui[sqlite]
|
|
155
|
+
pip install pycascadeui[sqlite] # single-process persistence
|
|
156
|
+
pip install pycascadeui[postgres] # multi-process persistence with LISTEN/NOTIFY
|
|
155
157
|
```
|
|
156
158
|
|
|
157
159
|
Requirements:
|
|
@@ -226,22 +228,25 @@ class NotificationPanel(StatefulLayoutView):
|
|
|
226
228
|
> Define `build_ui()` once. The library calls it on every relevant state change and ships the edit for you. No `on_state_changed()` override, no manual `refresh()`, no `message.edit()` plumbing.
|
|
227
229
|
|
|
228
230
|
```python
|
|
229
|
-
class
|
|
231
|
+
class MyFleetView(StatefulLayoutView):
|
|
230
232
|
state_scope = "user"
|
|
231
|
-
subscribed_actions = {"
|
|
233
|
+
subscribed_actions = {"FLEET_REROLLED"}
|
|
232
234
|
|
|
233
235
|
def build_ui(self):
|
|
234
236
|
self.clear_items()
|
|
235
|
-
|
|
236
|
-
self.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
237
|
+
grid = emoji_grid(10, 10, fill="\U0001f7e6", row_labels="alpha", col_labels="numeric")
|
|
238
|
+
for row, col in self.ship_cells():
|
|
239
|
+
grid[row, col] = "\U0001f6a2"
|
|
240
|
+
|
|
241
|
+
self.add_item(card("## My Fleet", divider(), grid, color=discord.Color.blue()))
|
|
242
|
+
self.add_item(ActionRow(
|
|
243
|
+
StatefulButton(label="Regenerate", emoji="\U0001f3b2", callback=self._reroll),
|
|
242
244
|
))
|
|
243
245
|
|
|
244
|
-
|
|
246
|
+
async def _reroll(self, interaction):
|
|
247
|
+
await self.dispatch("FLEET_REROLLED", {"cells": random_placement()})
|
|
248
|
+
|
|
249
|
+
# build_ui() runs automatically on every FLEET_REROLLED dispatch --
|
|
245
250
|
# no manual refresh() or on_state_changed() override needed.
|
|
246
251
|
```
|
|
247
252
|
|
|
@@ -300,18 +305,14 @@ class BattleshipView(StatefulLayoutView):
|
|
|
300
305
|
|
|
301
306
|
### Lifecycle Control
|
|
302
307
|
|
|
303
|
-
>
|
|
308
|
+
> Cap active sessions per user, guild, or globally. Pick how a collision resolves and how the old view cleans up when a new one opens.
|
|
304
309
|
|
|
305
310
|
```python
|
|
306
|
-
class
|
|
311
|
+
class SettingsHubView(MenuLayoutView):
|
|
307
312
|
instance_limit = 1 # Only one open at a time
|
|
308
313
|
instance_scope = "user_guild" # Per user per guild
|
|
309
314
|
instance_policy = "replace" # Exit the old one, open the new one
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
class GameView(StatefulLayoutView):
|
|
313
|
-
participant_limit = 8 # Owner + 7 joiners maximum
|
|
314
|
-
auto_register_participants = True # Claim slots from allowed_users on send()
|
|
315
|
+
exit_policy = "disable" # Old view's buttons grey out, message stays
|
|
315
316
|
```
|
|
316
317
|
|
|
317
318
|

|
|
@@ -332,12 +333,18 @@ async def setup_hook(self):
|
|
|
332
333
|
)
|
|
333
334
|
```
|
|
334
335
|
|
|
335
|
-
|
|
336
|
+
Subclass `PersistentRolesLayoutView` and declare your categories. The pattern handles button rendering, cardinality enforcement, and restart re-attachment. A stable `persistence_key` is the match identity the middleware uses to find this panel after restart:
|
|
336
337
|
|
|
337
338
|
```python
|
|
338
|
-
class
|
|
339
|
-
|
|
340
|
-
|
|
339
|
+
class GuildRoles(PersistentRolesLayoutView):
|
|
340
|
+
categories = [
|
|
341
|
+
RoleCategory(name="Color Roles", exclusive=True, roles={"Red": 123, "Blue": 456}),
|
|
342
|
+
RoleCategory(name="Pronouns", required=True, roles={"He/Him": 789, "She/Her": 12}),
|
|
343
|
+
]
|
|
344
|
+
|
|
345
|
+
# Same key in, same panel out -- across bot restarts.
|
|
346
|
+
panel = GuildRoles(context=ctx, persistence_key=f"roles:{ctx.guild.id}")
|
|
347
|
+
await panel.send()
|
|
341
348
|
```
|
|
342
349
|
|
|
343
350
|

|
|
@@ -349,8 +356,9 @@ class BattleshipView(StatefulLayoutView):
|
|
|
349
356
|
> Snapshot-based state history per session with built-in undo and redo support.
|
|
350
357
|
|
|
351
358
|
```python
|
|
352
|
-
class
|
|
359
|
+
class NotificationsView(StatefulLayoutView):
|
|
353
360
|
enable_undo = True # Every dispatch captures a snapshot
|
|
361
|
+
undo_limit = 10 # Stack depth cap (self.undo_depth / self.redo_depth read live)
|
|
354
362
|
|
|
355
363
|
async def _undo(self, interaction):
|
|
356
364
|
await self.undo() # Restore previous snapshot
|
|
@@ -490,7 +498,13 @@ await view.send()
|
|
|
490
498
|
# Persistent variant -- admin-posted panel that survives bot restarts
|
|
491
499
|
# and re-fetches live data on every restore.
|
|
492
500
|
class PersistentBoard(PersistentLeaderboardLayoutView):
|
|
493
|
-
|
|
501
|
+
pass
|
|
502
|
+
|
|
503
|
+
panel = PersistentBoard(
|
|
504
|
+
context=ctx,
|
|
505
|
+
persistence_key="battleship-leaderboard-main",
|
|
506
|
+
)
|
|
507
|
+
await panel.send()
|
|
494
508
|
```
|
|
495
509
|
|
|
496
510
|

|
|
@@ -602,8 +616,6 @@ for row in rows:
|
|
|
602
616
|
view.add_item(row)
|
|
603
617
|
```
|
|
604
618
|
|
|
605
|
-

|
|
606
|
-
|
|
607
619
|
<div align="center">
|
|
608
620
|
<img src="https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/pngs/v2-emoji-grid.PNG" width="30%" alt="Emoji Grid" style="border-radius: 8px; margin: 5px;" />
|
|
609
621
|
<img src="https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/pngs/v2-button-grid.PNG" width="30%" alt="Button Grid" style="border-radius: 8px; margin: 5px;" />
|
|
@@ -630,7 +642,7 @@ for row in rows:
|
|
|
630
642
|
### Views
|
|
631
643
|
- V2 layout-based system for structured, container-driven interfaces
|
|
632
644
|
- Full support for traditional discord.py Views (V1)
|
|
633
|
-
- Pre-built patterns: menus, tabs, wizards, forms, pagination, leaderboards
|
|
645
|
+
- Pre-built patterns: menus, tabs, wizards, forms, pagination, leaderboards, roles
|
|
634
646
|
- `PaginatedView.from_cursor()` for lazy cursor-driven pagination with LRU page cache
|
|
635
647
|
- `DisplayLayoutView` for one-shot V2 sends from a pre-built container
|
|
636
648
|
- Automatic state-driven rebuilds: define `build_ui()`, the library handles the edit (≈ React `render()`)
|
|
@@ -639,7 +651,7 @@ for row in rows:
|
|
|
639
651
|
### Components
|
|
640
652
|
- Stateful buttons, selects, and modals with state integration
|
|
641
653
|
- Select callbacks can opt into a `values` second parameter
|
|
642
|
-
- V2 builders: `card()`, `stats_card()`, `action_section()`, `toggle_section()`, `image_section()`, `link_section()`, `confirm_section()`, `button_row()`, `cycle_button()`, `toggle_button()`, `tab_nav()`, `key_value()`, `alert()`, `progress_bar()`, `divider()`, `gap()`, `gallery()`
|
|
654
|
+
- V2 builders: `card()`, `stats_card()`, `action_section()`, `toggle_section()`, `image_section()`, `link_section()`, `confirm_section()`, `button_row()`, `cycle_button()`, `toggle_button()`, `tab_nav()`, `key_value()`, `alert()`, `progress_bar()`, `divider()`, `gap()`, `gallery()`, `file_attachment()`
|
|
643
655
|
- Grid helpers: `emoji_grid()` and `button_grid()`
|
|
644
656
|
- Typed modal fields (`text`, `integer`, `float`, `date`) with per-field validation
|
|
645
657
|
- Declarative `FormSchema` and `WizardSchema` base classes
|
|
@@ -666,7 +678,8 @@ for row in rows:
|
|
|
666
678
|
### Persistence
|
|
667
679
|
- Persistent views that survive bot restarts with automatic message re-attachment
|
|
668
680
|
- Opt-in per slot via `persistent_slots = (...)` (≈ `redux-persist`)
|
|
669
|
-
- Built-in SQLite and in-memory backends; custom backends via capability-flag `Protocol`
|
|
681
|
+
- Built-in SQLite, PostgreSQL, and in-memory backends; custom backends via capability-flag `Protocol`
|
|
682
|
+
- Cross-process scoped invalidation through PostgreSQL `LISTEN`/`NOTIFY` (multi-worker bots)
|
|
670
683
|
- Named scoped buckets via `scoped_slot` for per-subsystem persistence
|
|
671
684
|
- Two-namespace model (`registry` and `application`) with per-namespace debounce and retry backoff
|
|
672
685
|
- Undo and redo via snapshot-based state history (opt in with `enable_undo`)
|
|
@@ -677,20 +690,6 @@ for row in rows:
|
|
|
677
690
|
|
|
678
691
|
---
|
|
679
692
|
|
|
680
|
-
## V1 Components
|
|
681
|
-
|
|
682
|
-
> CascadeUI supports traditional discord.py Views and embeds.
|
|
683
|
-
|
|
684
|
-
Use V1 when you need:
|
|
685
|
-
- Embed-specific features such as fields or timestamps
|
|
686
|
-
- Simpler layouts without containers
|
|
687
|
-
|
|
688
|
-
All core features such as navigation, persistence, and undo/redo are supported.
|
|
689
|
-
|
|
690
|
-

|
|
691
|
-
|
|
692
|
-
---
|
|
693
|
-
|
|
694
693
|
## Examples
|
|
695
694
|
|
|
696
695
|
> The <a href="https://hollowthesilver.github.io/CascadeUI/examples/"><strong>documentation</strong></a> includes full implementations demonstrating practical usage:
|
|
@@ -703,6 +702,22 @@ All core features such as navigation, persistence, and undo/redo are supported.
|
|
|
703
702
|
- Multi-user games with shared state, hidden information, and challenge flows (TicTacToe, Battleship)
|
|
704
703
|
- Open-join lobbies with capacity caps and host-vs-participant authority (Werewolf-style)
|
|
705
704
|
|
|
705
|
+

|
|
706
|
+
|
|
707
|
+
---
|
|
708
|
+
|
|
709
|
+
## V1 Components
|
|
710
|
+
|
|
711
|
+
> CascadeUI supports traditional discord.py Views and embeds.
|
|
712
|
+
|
|
713
|
+
Use V1 when you need:
|
|
714
|
+
- Embed-specific features such as fields or timestamps
|
|
715
|
+
- Simpler layouts without containers
|
|
716
|
+
|
|
717
|
+
All core features such as navigation, persistence, and undo/redo are supported.
|
|
718
|
+
|
|
719
|
+

|
|
720
|
+
|
|
706
721
|
---
|
|
707
722
|
|
|
708
723
|
## Documentation
|
|
@@ -734,7 +749,7 @@ isort cascadeui/
|
|
|
734
749
|
|
|
735
750
|
## Developer's Note
|
|
736
751
|
|
|
737
|
-
> I built CascadeUI with over **ten years** of Python, and roughly fifteen years of
|
|
752
|
+
> I built CascadeUI with over **ten years** of Python, and roughly fifteen years of development experience. All documentation, docstrings and test modules are written and designed using custom **Anthropic Opus** sub-agents. I do not attempt to conceal this fact. I'm a proponent of efficient and responsible agent application in software design. That experience is what makes these tools effective. They're amplifiers, not substitutes.
|
|
738
753
|
>
|
|
739
754
|
> *-- Hollow*
|
|
740
755
|
|
|
@@ -109,7 +109,8 @@ pip install pycascadeui
|
|
|
109
109
|
Optional dependencies:
|
|
110
110
|
|
|
111
111
|
```bash
|
|
112
|
-
pip install pycascadeui[sqlite]
|
|
112
|
+
pip install pycascadeui[sqlite] # single-process persistence
|
|
113
|
+
pip install pycascadeui[postgres] # multi-process persistence with LISTEN/NOTIFY
|
|
113
114
|
```
|
|
114
115
|
|
|
115
116
|
Requirements:
|
|
@@ -184,22 +185,25 @@ class NotificationPanel(StatefulLayoutView):
|
|
|
184
185
|
> Define `build_ui()` once. The library calls it on every relevant state change and ships the edit for you. No `on_state_changed()` override, no manual `refresh()`, no `message.edit()` plumbing.
|
|
185
186
|
|
|
186
187
|
```python
|
|
187
|
-
class
|
|
188
|
+
class MyFleetView(StatefulLayoutView):
|
|
188
189
|
state_scope = "user"
|
|
189
|
-
subscribed_actions = {"
|
|
190
|
+
subscribed_actions = {"FLEET_REROLLED"}
|
|
190
191
|
|
|
191
192
|
def build_ui(self):
|
|
192
193
|
self.clear_items()
|
|
193
|
-
|
|
194
|
-
self.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
194
|
+
grid = emoji_grid(10, 10, fill="\U0001f7e6", row_labels="alpha", col_labels="numeric")
|
|
195
|
+
for row, col in self.ship_cells():
|
|
196
|
+
grid[row, col] = "\U0001f6a2"
|
|
197
|
+
|
|
198
|
+
self.add_item(card("## My Fleet", divider(), grid, color=discord.Color.blue()))
|
|
199
|
+
self.add_item(ActionRow(
|
|
200
|
+
StatefulButton(label="Regenerate", emoji="\U0001f3b2", callback=self._reroll),
|
|
200
201
|
))
|
|
201
202
|
|
|
202
|
-
|
|
203
|
+
async def _reroll(self, interaction):
|
|
204
|
+
await self.dispatch("FLEET_REROLLED", {"cells": random_placement()})
|
|
205
|
+
|
|
206
|
+
# build_ui() runs automatically on every FLEET_REROLLED dispatch --
|
|
203
207
|
# no manual refresh() or on_state_changed() override needed.
|
|
204
208
|
```
|
|
205
209
|
|
|
@@ -258,18 +262,14 @@ class BattleshipView(StatefulLayoutView):
|
|
|
258
262
|
|
|
259
263
|
### Lifecycle Control
|
|
260
264
|
|
|
261
|
-
>
|
|
265
|
+
> Cap active sessions per user, guild, or globally. Pick how a collision resolves and how the old view cleans up when a new one opens.
|
|
262
266
|
|
|
263
267
|
```python
|
|
264
|
-
class
|
|
268
|
+
class SettingsHubView(MenuLayoutView):
|
|
265
269
|
instance_limit = 1 # Only one open at a time
|
|
266
270
|
instance_scope = "user_guild" # Per user per guild
|
|
267
271
|
instance_policy = "replace" # Exit the old one, open the new one
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
class GameView(StatefulLayoutView):
|
|
271
|
-
participant_limit = 8 # Owner + 7 joiners maximum
|
|
272
|
-
auto_register_participants = True # Claim slots from allowed_users on send()
|
|
272
|
+
exit_policy = "disable" # Old view's buttons grey out, message stays
|
|
273
273
|
```
|
|
274
274
|
|
|
275
275
|

|
|
@@ -290,12 +290,18 @@ async def setup_hook(self):
|
|
|
290
290
|
)
|
|
291
291
|
```
|
|
292
292
|
|
|
293
|
-
|
|
293
|
+
Subclass `PersistentRolesLayoutView` and declare your categories. The pattern handles button rendering, cardinality enforcement, and restart re-attachment. A stable `persistence_key` is the match identity the middleware uses to find this panel after restart:
|
|
294
294
|
|
|
295
295
|
```python
|
|
296
|
-
class
|
|
297
|
-
|
|
298
|
-
|
|
296
|
+
class GuildRoles(PersistentRolesLayoutView):
|
|
297
|
+
categories = [
|
|
298
|
+
RoleCategory(name="Color Roles", exclusive=True, roles={"Red": 123, "Blue": 456}),
|
|
299
|
+
RoleCategory(name="Pronouns", required=True, roles={"He/Him": 789, "She/Her": 12}),
|
|
300
|
+
]
|
|
301
|
+
|
|
302
|
+
# Same key in, same panel out -- across bot restarts.
|
|
303
|
+
panel = GuildRoles(context=ctx, persistence_key=f"roles:{ctx.guild.id}")
|
|
304
|
+
await panel.send()
|
|
299
305
|
```
|
|
300
306
|
|
|
301
307
|

|
|
@@ -307,8 +313,9 @@ class BattleshipView(StatefulLayoutView):
|
|
|
307
313
|
> Snapshot-based state history per session with built-in undo and redo support.
|
|
308
314
|
|
|
309
315
|
```python
|
|
310
|
-
class
|
|
316
|
+
class NotificationsView(StatefulLayoutView):
|
|
311
317
|
enable_undo = True # Every dispatch captures a snapshot
|
|
318
|
+
undo_limit = 10 # Stack depth cap (self.undo_depth / self.redo_depth read live)
|
|
312
319
|
|
|
313
320
|
async def _undo(self, interaction):
|
|
314
321
|
await self.undo() # Restore previous snapshot
|
|
@@ -448,7 +455,13 @@ await view.send()
|
|
|
448
455
|
# Persistent variant -- admin-posted panel that survives bot restarts
|
|
449
456
|
# and re-fetches live data on every restore.
|
|
450
457
|
class PersistentBoard(PersistentLeaderboardLayoutView):
|
|
451
|
-
|
|
458
|
+
pass
|
|
459
|
+
|
|
460
|
+
panel = PersistentBoard(
|
|
461
|
+
context=ctx,
|
|
462
|
+
persistence_key="battleship-leaderboard-main",
|
|
463
|
+
)
|
|
464
|
+
await panel.send()
|
|
452
465
|
```
|
|
453
466
|
|
|
454
467
|

|
|
@@ -560,8 +573,6 @@ for row in rows:
|
|
|
560
573
|
view.add_item(row)
|
|
561
574
|
```
|
|
562
575
|
|
|
563
|
-

|
|
564
|
-
|
|
565
576
|
<div align="center">
|
|
566
577
|
<img src="https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/pngs/v2-emoji-grid.PNG" width="30%" alt="Emoji Grid" style="border-radius: 8px; margin: 5px;" />
|
|
567
578
|
<img src="https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/pngs/v2-button-grid.PNG" width="30%" alt="Button Grid" style="border-radius: 8px; margin: 5px;" />
|
|
@@ -588,7 +599,7 @@ for row in rows:
|
|
|
588
599
|
### Views
|
|
589
600
|
- V2 layout-based system for structured, container-driven interfaces
|
|
590
601
|
- Full support for traditional discord.py Views (V1)
|
|
591
|
-
- Pre-built patterns: menus, tabs, wizards, forms, pagination, leaderboards
|
|
602
|
+
- Pre-built patterns: menus, tabs, wizards, forms, pagination, leaderboards, roles
|
|
592
603
|
- `PaginatedView.from_cursor()` for lazy cursor-driven pagination with LRU page cache
|
|
593
604
|
- `DisplayLayoutView` for one-shot V2 sends from a pre-built container
|
|
594
605
|
- Automatic state-driven rebuilds: define `build_ui()`, the library handles the edit (≈ React `render()`)
|
|
@@ -597,7 +608,7 @@ for row in rows:
|
|
|
597
608
|
### Components
|
|
598
609
|
- Stateful buttons, selects, and modals with state integration
|
|
599
610
|
- Select callbacks can opt into a `values` second parameter
|
|
600
|
-
- V2 builders: `card()`, `stats_card()`, `action_section()`, `toggle_section()`, `image_section()`, `link_section()`, `confirm_section()`, `button_row()`, `cycle_button()`, `toggle_button()`, `tab_nav()`, `key_value()`, `alert()`, `progress_bar()`, `divider()`, `gap()`, `gallery()`
|
|
611
|
+
- V2 builders: `card()`, `stats_card()`, `action_section()`, `toggle_section()`, `image_section()`, `link_section()`, `confirm_section()`, `button_row()`, `cycle_button()`, `toggle_button()`, `tab_nav()`, `key_value()`, `alert()`, `progress_bar()`, `divider()`, `gap()`, `gallery()`, `file_attachment()`
|
|
601
612
|
- Grid helpers: `emoji_grid()` and `button_grid()`
|
|
602
613
|
- Typed modal fields (`text`, `integer`, `float`, `date`) with per-field validation
|
|
603
614
|
- Declarative `FormSchema` and `WizardSchema` base classes
|
|
@@ -624,7 +635,8 @@ for row in rows:
|
|
|
624
635
|
### Persistence
|
|
625
636
|
- Persistent views that survive bot restarts with automatic message re-attachment
|
|
626
637
|
- Opt-in per slot via `persistent_slots = (...)` (≈ `redux-persist`)
|
|
627
|
-
- Built-in SQLite and in-memory backends; custom backends via capability-flag `Protocol`
|
|
638
|
+
- Built-in SQLite, PostgreSQL, and in-memory backends; custom backends via capability-flag `Protocol`
|
|
639
|
+
- Cross-process scoped invalidation through PostgreSQL `LISTEN`/`NOTIFY` (multi-worker bots)
|
|
628
640
|
- Named scoped buckets via `scoped_slot` for per-subsystem persistence
|
|
629
641
|
- Two-namespace model (`registry` and `application`) with per-namespace debounce and retry backoff
|
|
630
642
|
- Undo and redo via snapshot-based state history (opt in with `enable_undo`)
|
|
@@ -635,20 +647,6 @@ for row in rows:
|
|
|
635
647
|
|
|
636
648
|
---
|
|
637
649
|
|
|
638
|
-
## V1 Components
|
|
639
|
-
|
|
640
|
-
> CascadeUI supports traditional discord.py Views and embeds.
|
|
641
|
-
|
|
642
|
-
Use V1 when you need:
|
|
643
|
-
- Embed-specific features such as fields or timestamps
|
|
644
|
-
- Simpler layouts without containers
|
|
645
|
-
|
|
646
|
-
All core features such as navigation, persistence, and undo/redo are supported.
|
|
647
|
-
|
|
648
|
-

|
|
649
|
-
|
|
650
|
-
---
|
|
651
|
-
|
|
652
650
|
## Examples
|
|
653
651
|
|
|
654
652
|
> The <a href="https://hollowthesilver.github.io/CascadeUI/examples/"><strong>documentation</strong></a> includes full implementations demonstrating practical usage:
|
|
@@ -661,6 +659,22 @@ All core features such as navigation, persistence, and undo/redo are supported.
|
|
|
661
659
|
- Multi-user games with shared state, hidden information, and challenge flows (TicTacToe, Battleship)
|
|
662
660
|
- Open-join lobbies with capacity caps and host-vs-participant authority (Werewolf-style)
|
|
663
661
|
|
|
662
|
+

|
|
663
|
+
|
|
664
|
+
---
|
|
665
|
+
|
|
666
|
+
## V1 Components
|
|
667
|
+
|
|
668
|
+
> CascadeUI supports traditional discord.py Views and embeds.
|
|
669
|
+
|
|
670
|
+
Use V1 when you need:
|
|
671
|
+
- Embed-specific features such as fields or timestamps
|
|
672
|
+
- Simpler layouts without containers
|
|
673
|
+
|
|
674
|
+
All core features such as navigation, persistence, and undo/redo are supported.
|
|
675
|
+
|
|
676
|
+

|
|
677
|
+
|
|
664
678
|
---
|
|
665
679
|
|
|
666
680
|
## Documentation
|
|
@@ -692,7 +706,7 @@ isort cascadeui/
|
|
|
692
706
|
|
|
693
707
|
## Developer's Note
|
|
694
708
|
|
|
695
|
-
> I built CascadeUI with over **ten years** of Python, and roughly fifteen years of
|
|
709
|
+
> I built CascadeUI with over **ten years** of Python, and roughly fifteen years of development experience. All documentation, docstrings and test modules are written and designed using custom **Anthropic Opus** sub-agents. I do not attempt to conceal this fact. I'm a proponent of efficient and responsible agent application in software design. That experience is what makes these tools effective. They're amplifiers, not substitutes.
|
|
696
710
|
>
|
|
697
711
|
> *-- Hollow*
|
|
698
712
|
|
|
@@ -11,7 +11,7 @@ import logging as _logging
|
|
|
11
11
|
|
|
12
12
|
_logging.getLogger(__name__).addHandler(_logging.NullHandler())
|
|
13
13
|
|
|
14
|
-
from .components.base import StatefulButton, StatefulSelect
|
|
14
|
+
from .components.base import DynamicPersistentButton, StatefulButton, StatefulSelect
|
|
15
15
|
from .components.inputs import Checkbox, CheckboxGroup, FileUpload, Modal, RadioGroup, TextInput
|
|
16
16
|
from .components.patterns import (
|
|
17
17
|
ConfirmationButtons,
|
|
@@ -28,6 +28,7 @@ from .components.patterns import (
|
|
|
28
28
|
cycle_button,
|
|
29
29
|
divider,
|
|
30
30
|
emoji_grid,
|
|
31
|
+
file_attachment,
|
|
31
32
|
gallery,
|
|
32
33
|
gap,
|
|
33
34
|
image_section,
|
|
@@ -39,6 +40,7 @@ from .components.patterns import (
|
|
|
39
40
|
toggle_button,
|
|
40
41
|
toggle_section,
|
|
41
42
|
)
|
|
43
|
+
from .components.types import EmojiInput, MediaInput
|
|
42
44
|
from .components.v1_composition import CompositeComponent, get_component, register_component
|
|
43
45
|
from .components.wrappers import with_confirmation, with_cooldown, with_loading_state
|
|
44
46
|
from .devtools import DevToolsCog, InspectorView
|
|
@@ -81,8 +83,9 @@ from .theming.core import Theme, get_default_theme, get_theme, register_theme, s
|
|
|
81
83
|
from .theming.themes import dark_theme, default_theme, light_theme
|
|
82
84
|
from .utils.decorators import cascade_component, cascade_reducer
|
|
83
85
|
from .utils.errors import safe_execute, with_error_boundary, with_retry
|
|
84
|
-
from .utils.
|
|
86
|
+
from .utils.fetch import fetch_as_file
|
|
85
87
|
from .utils.logging import setup_logging
|
|
88
|
+
from .utils.strings import slugify
|
|
86
89
|
from .utils.tasks import get_task_manager
|
|
87
90
|
from .validation import (
|
|
88
91
|
ValidationResult,
|
|
@@ -105,12 +108,20 @@ from .views.patterns import (
|
|
|
105
108
|
PaginatedLayoutView,
|
|
106
109
|
PaginatedView,
|
|
107
110
|
PersistentLeaderboardLayoutView,
|
|
111
|
+
PersistentRolesLayoutView,
|
|
112
|
+
RolesLayoutView,
|
|
108
113
|
TabLayoutView,
|
|
109
114
|
TabView,
|
|
110
115
|
WizardLayoutView,
|
|
111
116
|
WizardView,
|
|
112
117
|
)
|
|
113
|
-
from .views.patterns.types import
|
|
118
|
+
from .views.patterns.types import (
|
|
119
|
+
FormField,
|
|
120
|
+
FormSchema,
|
|
121
|
+
RoleCategory,
|
|
122
|
+
WizardSchema,
|
|
123
|
+
WizardStep,
|
|
124
|
+
)
|
|
114
125
|
from .views.persistent import PersistentLayoutView, PersistentView
|
|
115
126
|
from .views.view import StatefulView
|
|
116
127
|
|
|
@@ -121,7 +132,7 @@ if "SQLiteBackend" in _persistence_all:
|
|
|
121
132
|
# // ========================================( Script )======================================== // #
|
|
122
133
|
|
|
123
134
|
|
|
124
|
-
__version__ = "3.
|
|
135
|
+
__version__ = "3.3.0"
|
|
125
136
|
|
|
126
137
|
# Export public API
|
|
127
138
|
__all__ = [
|
|
@@ -160,11 +171,14 @@ __all__ = [
|
|
|
160
171
|
"MenuLayoutView",
|
|
161
172
|
"PaginatedLayoutView",
|
|
162
173
|
"PersistentLeaderboardLayoutView",
|
|
174
|
+
"PersistentRolesLayoutView",
|
|
175
|
+
"RolesLayoutView",
|
|
163
176
|
"TabLayoutView",
|
|
164
177
|
"WizardLayoutView",
|
|
165
178
|
# Components
|
|
166
179
|
"StatefulButton",
|
|
167
180
|
"StatefulSelect",
|
|
181
|
+
"DynamicPersistentButton",
|
|
168
182
|
"CompositeComponent",
|
|
169
183
|
"ConfirmationButtons",
|
|
170
184
|
"PaginationControls",
|
|
@@ -183,6 +197,9 @@ __all__ = [
|
|
|
183
197
|
"with_loading_state",
|
|
184
198
|
"with_confirmation",
|
|
185
199
|
"with_cooldown",
|
|
200
|
+
# Type aliases
|
|
201
|
+
"EmojiInput",
|
|
202
|
+
"MediaInput",
|
|
186
203
|
# V2 Cards & Sections
|
|
187
204
|
"card",
|
|
188
205
|
"action_section",
|
|
@@ -206,6 +223,7 @@ __all__ = [
|
|
|
206
223
|
"tab_nav",
|
|
207
224
|
# V2 Media
|
|
208
225
|
"gallery",
|
|
226
|
+
"file_attachment",
|
|
209
227
|
# V2 Grids
|
|
210
228
|
"EmojiGrid",
|
|
211
229
|
"emoji_grid",
|
|
@@ -228,6 +246,7 @@ __all__ = [
|
|
|
228
246
|
# Typed schemas
|
|
229
247
|
"FormField",
|
|
230
248
|
"FormSchema",
|
|
249
|
+
"RoleCategory",
|
|
231
250
|
"WizardStep",
|
|
232
251
|
"WizardSchema",
|
|
233
252
|
# Validation
|
|
@@ -262,6 +281,7 @@ __all__ = [
|
|
|
262
281
|
"cascade_reducer",
|
|
263
282
|
"cascade_component",
|
|
264
283
|
"slugify",
|
|
284
|
+
"fetch_as_file",
|
|
265
285
|
# DevTools
|
|
266
286
|
"InspectorView",
|
|
267
287
|
"DevToolsCog",
|
|
@@ -23,6 +23,7 @@ from .patterns import (
|
|
|
23
23
|
confirm_section,
|
|
24
24
|
cycle_button,
|
|
25
25
|
divider,
|
|
26
|
+
file_attachment,
|
|
26
27
|
gallery,
|
|
27
28
|
gap,
|
|
28
29
|
image_section,
|
|
@@ -35,6 +36,7 @@ from .patterns import (
|
|
|
35
36
|
toggle_section,
|
|
36
37
|
)
|
|
37
38
|
from .selects import ChannelSelect, Dropdown, MentionableSelect, RoleSelect, UserSelect
|
|
39
|
+
from .types import EmojiInput, MediaInput
|
|
38
40
|
from .v1_composition import CompositeComponent, get_component, register_component
|
|
39
41
|
from .wrappers import with_confirmation, with_cooldown, with_loading_state
|
|
40
42
|
|
|
@@ -101,4 +103,8 @@ __all__ = [
|
|
|
101
103
|
"tab_nav",
|
|
102
104
|
# V2 media
|
|
103
105
|
"gallery",
|
|
106
|
+
"file_attachment",
|
|
107
|
+
# Type aliases
|
|
108
|
+
"EmojiInput",
|
|
109
|
+
"MediaInput",
|
|
104
110
|
]
|