cjm-fasthtml-card-stack 0.0.1__py3-none-any.whl → 0.0.2__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.
- cjm_fasthtml_card_stack/__init__.py +1 -1
- cjm_fasthtml_card_stack/components/__init__.py +0 -0
- cjm_fasthtml_card_stack/components/controls.py +115 -0
- cjm_fasthtml_card_stack/components/progress.py +44 -0
- cjm_fasthtml_card_stack/components/states.py +107 -0
- cjm_fasthtml_card_stack/components/viewport.py +320 -0
- cjm_fasthtml_card_stack/core/__init__.py +0 -0
- cjm_fasthtml_card_stack/core/button_ids.py +69 -0
- cjm_fasthtml_card_stack/core/config.py +48 -0
- cjm_fasthtml_card_stack/core/constants.py +41 -0
- cjm_fasthtml_card_stack/core/html_ids.py +94 -0
- cjm_fasthtml_card_stack/core/models.py +53 -0
- cjm_fasthtml_card_stack/helpers/__init__.py +0 -0
- cjm_fasthtml_card_stack/helpers/focus.py +72 -0
- cjm_fasthtml_card_stack/js/__init__.py +0 -0
- cjm_fasthtml_card_stack/js/core.py +303 -0
- cjm_fasthtml_card_stack/js/navigation.py +37 -0
- cjm_fasthtml_card_stack/js/scroll.py +76 -0
- cjm_fasthtml_card_stack/js/viewport.py +124 -0
- cjm_fasthtml_card_stack/keyboard/__init__.py +0 -0
- cjm_fasthtml_card_stack/keyboard/actions.py +200 -0
- cjm_fasthtml_card_stack/routes/__init__.py +0 -0
- cjm_fasthtml_card_stack/routes/handlers.py +158 -0
- cjm_fasthtml_card_stack/routes/router.py +150 -0
- {cjm_fasthtml_card_stack-0.0.1.dist-info → cjm_fasthtml_card_stack-0.0.2.dist-info}/METADATA +17 -9
- cjm_fasthtml_card_stack-0.0.2.dist-info/RECORD +36 -0
- {cjm_fasthtml_card_stack-0.0.1.dist-info → cjm_fasthtml_card_stack-0.0.2.dist-info}/top_level.txt +1 -0
- demos/__init__.py +0 -0
- demos/basic.py +115 -0
- demos/bottom.py +104 -0
- demos/data.py +29 -0
- demos/shared.py +153 -0
- cjm_fasthtml_card_stack-0.0.1.dist-info/RECORD +0 -8
- {cjm_fasthtml_card_stack-0.0.1.dist-info → cjm_fasthtml_card_stack-0.0.2.dist-info}/WHEEL +0 -0
- {cjm_fasthtml_card_stack-0.0.1.dist-info → cjm_fasthtml_card_stack-0.0.2.dist-info}/entry_points.txt +0 -0
- {cjm_fasthtml_card_stack-0.0.1.dist-info → cjm_fasthtml_card_stack-0.0.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Convenience router factory that wires up standard card stack routes (Tier 2 API)."""
|
|
2
|
+
|
|
3
|
+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/routes/router.ipynb.
|
|
4
|
+
|
|
5
|
+
# %% auto #0
|
|
6
|
+
__all__ = ['init_card_stack_router']
|
|
7
|
+
|
|
8
|
+
# %% ../../nbs/routes/router.ipynb #r1000003
|
|
9
|
+
from typing import Any, Callable, List, Optional, Tuple
|
|
10
|
+
|
|
11
|
+
from fasthtml.common import APIRouter
|
|
12
|
+
|
|
13
|
+
from ..core.config import CardStackConfig
|
|
14
|
+
from ..core.html_ids import CardStackHtmlIds
|
|
15
|
+
from ..core.models import CardStackState, CardStackUrls
|
|
16
|
+
from cjm_fasthtml_card_stack.routes.handlers import (
|
|
17
|
+
card_stack_navigate,
|
|
18
|
+
card_stack_navigate_to_index,
|
|
19
|
+
card_stack_update_viewport,
|
|
20
|
+
card_stack_save_width,
|
|
21
|
+
card_stack_save_scale,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# %% ../../nbs/routes/router.ipynb #r1000005
|
|
25
|
+
def init_card_stack_router(
|
|
26
|
+
config: CardStackConfig, # Card stack configuration
|
|
27
|
+
state_getter: Callable[[], CardStackState], # Function to get current state
|
|
28
|
+
state_setter: Callable[[CardStackState], None], # Function to save state
|
|
29
|
+
get_items: Callable[[], List[Any]], # Function to get current items list
|
|
30
|
+
render_card: Callable, # Card renderer callback: (item, CardRenderContext) -> FT
|
|
31
|
+
route_prefix: str = "/card-stack", # Route prefix for all card stack routes
|
|
32
|
+
progress_label: str = "Item", # Label for progress indicator
|
|
33
|
+
) -> Tuple[APIRouter, CardStackUrls]: # (router, urls) tuple
|
|
34
|
+
"""Initialize an APIRouter with all standard card stack routes."""
|
|
35
|
+
router = APIRouter(prefix=route_prefix)
|
|
36
|
+
ids = CardStackHtmlIds(prefix=config.prefix)
|
|
37
|
+
|
|
38
|
+
# -----------------------------------------------------------------
|
|
39
|
+
# Navigation Routes
|
|
40
|
+
# -----------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
def _nav(direction: str) -> Any:
|
|
43
|
+
"""Shared navigation handler."""
|
|
44
|
+
state = state_getter()
|
|
45
|
+
items = get_items()
|
|
46
|
+
result = card_stack_navigate(
|
|
47
|
+
direction=direction, card_items=items, state=state,
|
|
48
|
+
config=config, ids=ids, urls=urls,
|
|
49
|
+
render_card=render_card, progress_label=progress_label,
|
|
50
|
+
)
|
|
51
|
+
state_setter(state)
|
|
52
|
+
return result
|
|
53
|
+
|
|
54
|
+
@router
|
|
55
|
+
def nav_up() -> Any:
|
|
56
|
+
"""Navigate to previous item."""
|
|
57
|
+
return _nav("up")
|
|
58
|
+
|
|
59
|
+
@router
|
|
60
|
+
def nav_down() -> Any:
|
|
61
|
+
"""Navigate to next item."""
|
|
62
|
+
return _nav("down")
|
|
63
|
+
|
|
64
|
+
@router
|
|
65
|
+
def nav_first() -> Any:
|
|
66
|
+
"""Navigate to first item."""
|
|
67
|
+
return _nav("first")
|
|
68
|
+
|
|
69
|
+
@router
|
|
70
|
+
def nav_last() -> Any:
|
|
71
|
+
"""Navigate to last item."""
|
|
72
|
+
return _nav("last")
|
|
73
|
+
|
|
74
|
+
@router
|
|
75
|
+
def nav_page_up() -> Any:
|
|
76
|
+
"""Navigate up by page."""
|
|
77
|
+
return _nav("page_up")
|
|
78
|
+
|
|
79
|
+
@router
|
|
80
|
+
def nav_page_down() -> Any:
|
|
81
|
+
"""Navigate down by page."""
|
|
82
|
+
return _nav("page_down")
|
|
83
|
+
|
|
84
|
+
@router
|
|
85
|
+
def nav_to_index(target_index: int) -> Any:
|
|
86
|
+
"""Navigate to a specific item index (click-to-focus)."""
|
|
87
|
+
state = state_getter()
|
|
88
|
+
items = get_items()
|
|
89
|
+
result = card_stack_navigate_to_index(
|
|
90
|
+
target_index=target_index, card_items=items, state=state,
|
|
91
|
+
config=config, ids=ids, urls=urls,
|
|
92
|
+
render_card=render_card, progress_label=progress_label,
|
|
93
|
+
)
|
|
94
|
+
state_setter(state)
|
|
95
|
+
return result
|
|
96
|
+
|
|
97
|
+
# -----------------------------------------------------------------
|
|
98
|
+
# Viewport Route
|
|
99
|
+
# -----------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
@router
|
|
102
|
+
def update_viewport(visible_count: int) -> Any:
|
|
103
|
+
"""Update viewport with new card count (full outerHTML swap)."""
|
|
104
|
+
state = state_getter()
|
|
105
|
+
items = get_items()
|
|
106
|
+
result = card_stack_update_viewport(
|
|
107
|
+
visible_count=visible_count, card_items=items, state=state,
|
|
108
|
+
config=config, ids=ids, urls=urls, render_card=render_card,
|
|
109
|
+
)
|
|
110
|
+
state_setter(state)
|
|
111
|
+
return result
|
|
112
|
+
|
|
113
|
+
# -----------------------------------------------------------------
|
|
114
|
+
# Preference Persistence Routes
|
|
115
|
+
# -----------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
@router
|
|
118
|
+
def save_width(card_width: int) -> Any:
|
|
119
|
+
"""Save card stack width to server state."""
|
|
120
|
+
state = state_getter()
|
|
121
|
+
card_stack_save_width(state, card_width, config)
|
|
122
|
+
state_setter(state)
|
|
123
|
+
return ""
|
|
124
|
+
|
|
125
|
+
@router
|
|
126
|
+
def save_scale(card_scale: int) -> Any:
|
|
127
|
+
"""Save card stack scale to server state."""
|
|
128
|
+
state = state_getter()
|
|
129
|
+
card_stack_save_scale(state, card_scale, config)
|
|
130
|
+
state_setter(state)
|
|
131
|
+
return ""
|
|
132
|
+
|
|
133
|
+
# -----------------------------------------------------------------
|
|
134
|
+
# Build URL bundle from registered routes
|
|
135
|
+
# -----------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
urls = CardStackUrls(
|
|
138
|
+
nav_up=nav_up.to(),
|
|
139
|
+
nav_down=nav_down.to(),
|
|
140
|
+
nav_first=nav_first.to(),
|
|
141
|
+
nav_last=nav_last.to(),
|
|
142
|
+
nav_page_up=nav_page_up.to(),
|
|
143
|
+
nav_page_down=nav_page_down.to(),
|
|
144
|
+
nav_to_index=nav_to_index.to(),
|
|
145
|
+
update_viewport=update_viewport.to(),
|
|
146
|
+
save_width=save_width.to(),
|
|
147
|
+
save_scale=save_scale.to(),
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
return router, urls
|
{cjm_fasthtml_card_stack-0.0.1.dist-info → cjm_fasthtml_card_stack-0.0.2.dist-info}/METADATA
RENAMED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cjm-fasthtml-card-stack
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.2
|
|
4
4
|
Summary: A fixed-viewport card stack component for FastHTML with keyboard navigation, scroll-to-nav, configurable focus position, and HTMX-driven OOB updates.
|
|
5
5
|
Home-page: https://github.com/cj-mills/cjm-fasthtml-card-stack
|
|
6
6
|
Author: Christian J. Mills
|
|
7
|
-
Author-email:
|
|
7
|
+
Author-email: 9126128+cj-mills@users.noreply.github.com
|
|
8
8
|
License: Apache-2.0
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Keywords: nbdev,jupyter,notebook,python
|
|
12
|
-
Classifier: Natural Language :: English
|
|
9
|
+
Keywords: nbdev jupyter notebook python
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
11
|
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier:
|
|
15
|
-
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Natural Language :: English
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
15
|
Requires-Python: >=3.12
|
|
18
16
|
Description-Content-Type: text/markdown
|
|
19
17
|
License-File: LICENSE
|
|
@@ -21,10 +19,20 @@ Requires-Dist: python-fasthtml
|
|
|
21
19
|
Requires-Dist: cjm-fasthtml-tailwind
|
|
22
20
|
Requires-Dist: cjm-fasthtml-daisyui
|
|
23
21
|
Requires-Dist: cjm-fasthtml-keyboard-navigation
|
|
22
|
+
Provides-Extra: dev
|
|
24
23
|
Dynamic: author
|
|
24
|
+
Dynamic: author-email
|
|
25
|
+
Dynamic: classifier
|
|
26
|
+
Dynamic: description
|
|
27
|
+
Dynamic: description-content-type
|
|
25
28
|
Dynamic: home-page
|
|
29
|
+
Dynamic: keywords
|
|
30
|
+
Dynamic: license
|
|
26
31
|
Dynamic: license-file
|
|
32
|
+
Dynamic: provides-extra
|
|
33
|
+
Dynamic: requires-dist
|
|
27
34
|
Dynamic: requires-python
|
|
35
|
+
Dynamic: summary
|
|
28
36
|
|
|
29
37
|
# cjm-fasthtml-card-stack
|
|
30
38
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
cjm_fasthtml_card_stack/__init__.py,sha256=QvlVh4JTl3JL7jQAja76yKtT-IvF4631ASjWY1wS6AQ,22
|
|
2
|
+
cjm_fasthtml_card_stack/_modidx.py,sha256=jYNj6LPbD5I8yDghD3Lx9XWsDnWqc_oNCESguSF8blA,25105
|
|
3
|
+
cjm_fasthtml_card_stack/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
cjm_fasthtml_card_stack/components/controls.py,sha256=C3iRvaiRe0ti8S7ogTSKRRRdw-z6vz77mkuvKVmIz-Y,4093
|
|
5
|
+
cjm_fasthtml_card_stack/components/progress.py,sha256=BBeR4EWEikA54bVG41iLdlyyGRFwMJtEh8-cX9L9QLI,1500
|
|
6
|
+
cjm_fasthtml_card_stack/components/states.py,sha256=ClgpdS3e19ccO6VHU6ajulU_yb7c1bQVZf9nSsd5QPc,3797
|
|
7
|
+
cjm_fasthtml_card_stack/components/viewport.py,sha256=ikb35mGEUP2RpC-1B6F-JeGBrNaT6wpXv_op5T7zbgA,11699
|
|
8
|
+
cjm_fasthtml_card_stack/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
cjm_fasthtml_card_stack/core/button_ids.py,sha256=elKKLBrt-Hu6vgWhrcVlCXgdUeYXHQyfn3NyhhEmxW4,2123
|
|
10
|
+
cjm_fasthtml_card_stack/core/config.py,sha256=XYaptNnzZ8sRcCdxWA_RB1FwBiXU8Ko6mEsNSbxhtb0,1714
|
|
11
|
+
cjm_fasthtml_card_stack/core/constants.py,sha256=MaVTAInPk66VTHzmMtGkxvAgr4R2c7HEWvKDNvmv7hM,1673
|
|
12
|
+
cjm_fasthtml_card_stack/core/html_ids.py,sha256=N5ut7EkEMt2ynjnYv5naE_22gviPzLccGbWU29VsJWg,3099
|
|
13
|
+
cjm_fasthtml_card_stack/core/models.py,sha256=soivwLNBbSNQmcCb9EpFz09gaDhb9HesXbwfq9t_MTY,2448
|
|
14
|
+
cjm_fasthtml_card_stack/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
cjm_fasthtml_card_stack/helpers/focus.py,sha256=PTbsZagutEM8pwpLUKvM8Gjhv7rVNMbNbO27x5uC3YM,2626
|
|
16
|
+
cjm_fasthtml_card_stack/js/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
cjm_fasthtml_card_stack/js/core.py,sha256=fs6mwNOoqxw4K_Wo5X5zKEAXVz6ed7lvg95nYkE9XuQ,12485
|
|
18
|
+
cjm_fasthtml_card_stack/js/navigation.py,sha256=471F3NDxdH-BQPLPQuUvtyACqi6g6EOcELYs_sP1hyk,1288
|
|
19
|
+
cjm_fasthtml_card_stack/js/scroll.py,sha256=X6j5JMOjzgH57sDK1fazNRyGYH2GGEDQeZ0dURFEjC4,3062
|
|
20
|
+
cjm_fasthtml_card_stack/js/viewport.py,sha256=hFWr3zt9odyuaYdRdJmhKJcS_vsJyLRhz-HlZPvGh54,5653
|
|
21
|
+
cjm_fasthtml_card_stack/keyboard/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
cjm_fasthtml_card_stack/keyboard/actions.py,sha256=rfCD8lLVXXVPrML-8dj48bT_zOKN7g8VmPMNE21IAJI,7573
|
|
23
|
+
cjm_fasthtml_card_stack/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
cjm_fasthtml_card_stack/routes/handlers.py,sha256=GX1h_9t4-2G59IaDyzWTeWUaDFjrXYCqaaUT4UZ_Viw,6823
|
|
25
|
+
cjm_fasthtml_card_stack/routes/router.py,sha256=w6mTQGb-OE0X7t-6dIz7bXg5A0U4llSzNsec-PTkacs,5131
|
|
26
|
+
cjm_fasthtml_card_stack-0.0.2.dist-info/licenses/LICENSE,sha256=xV8xoN4VOL0uw9X8RSs2IMuD_Ss_a9yAbtGNeBWZwnw,11337
|
|
27
|
+
demos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
|
+
demos/basic.py,sha256=o93t_7O3PCnn-aGGCVKSVt09xcgs1oy-yAW2EDdPsY8,3813
|
|
29
|
+
demos/bottom.py,sha256=ylyYqUXhv97noIQF1-kdFeVM6E40POKd45r0WM0cabo,3339
|
|
30
|
+
demos/data.py,sha256=8yVV_E0nLcr0L1PcrTG-DaT-V36apF74ALKNIZuSQ70,4471
|
|
31
|
+
demos/shared.py,sha256=8AMZhgjyobY0pnt_7lmZ91gxb8X1UA1biK0KPfherjo,5384
|
|
32
|
+
cjm_fasthtml_card_stack-0.0.2.dist-info/METADATA,sha256=kMYvuotFt_cCjJf1TJa-g8YBRAQBbt4IeOoUCZW8Sxw,36832
|
|
33
|
+
cjm_fasthtml_card_stack-0.0.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
34
|
+
cjm_fasthtml_card_stack-0.0.2.dist-info/entry_points.txt,sha256=qTw6qaijkEUH1AcPQhpTqJ8TPiUSMnpn0Cvg4hXoX90,68
|
|
35
|
+
cjm_fasthtml_card_stack-0.0.2.dist-info/top_level.txt,sha256=ydBTJsY2ONaDryp85HkjhdGHEuuUOoMGdrOgFA2ddyg,30
|
|
36
|
+
cjm_fasthtml_card_stack-0.0.2.dist-info/RECORD,,
|
demos/__init__.py
ADDED
|
File without changes
|
demos/basic.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Basic centered card stack demo."""
|
|
2
|
+
|
|
3
|
+
from fasthtml.common import Div, P, Span
|
|
4
|
+
|
|
5
|
+
from cjm_fasthtml_daisyui.components.data_display.card import card, card_body
|
|
6
|
+
from cjm_fasthtml_daisyui.components.data_display.badge import badge, badge_colors
|
|
7
|
+
from cjm_fasthtml_daisyui.utilities.semantic_colors import bg_dui, text_dui
|
|
8
|
+
from cjm_fasthtml_tailwind.utilities.spacing import m
|
|
9
|
+
from cjm_fasthtml_tailwind.utilities.sizing import w
|
|
10
|
+
from cjm_fasthtml_tailwind.utilities.typography import font_size, font_weight
|
|
11
|
+
from cjm_fasthtml_tailwind.utilities.flexbox_and_grid import flex_display, items, gap
|
|
12
|
+
from cjm_fasthtml_tailwind.utilities.effects import opacity
|
|
13
|
+
from cjm_fasthtml_tailwind.core.base import combine_classes
|
|
14
|
+
|
|
15
|
+
from cjm_fasthtml_card_stack.core.config import CardStackConfig
|
|
16
|
+
from cjm_fasthtml_card_stack.core.models import CardStackState, CardRenderContext
|
|
17
|
+
from cjm_fasthtml_card_stack.core.html_ids import CardStackHtmlIds
|
|
18
|
+
from cjm_fasthtml_card_stack.core.button_ids import CardStackButtonIds
|
|
19
|
+
from cjm_fasthtml_card_stack.routes.router import init_card_stack_router
|
|
20
|
+
|
|
21
|
+
from demos.data import SAMPLE_ITEMS
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def render_card(item, context: CardRenderContext):
|
|
25
|
+
"""Render a card with index badge and text content."""
|
|
26
|
+
is_focused = context.card_role == "focused"
|
|
27
|
+
|
|
28
|
+
index_badge = Span(
|
|
29
|
+
f"#{context.index + 1}",
|
|
30
|
+
cls=combine_classes(
|
|
31
|
+
badge,
|
|
32
|
+
badge_colors.primary if is_focused else badge_colors.neutral,
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
text = P(
|
|
37
|
+
f'"{item}"',
|
|
38
|
+
cls=combine_classes(
|
|
39
|
+
font_weight.medium if is_focused else font_weight.normal,
|
|
40
|
+
text_dui.base_content,
|
|
41
|
+
),
|
|
42
|
+
style="font-size: calc(1rem * var(--card-stack-scale, 100) / 100)",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return Div(
|
|
46
|
+
Div(
|
|
47
|
+
Div(
|
|
48
|
+
index_badge,
|
|
49
|
+
Span(
|
|
50
|
+
f"Item {context.index + 1} of {context.total_items}",
|
|
51
|
+
cls=combine_classes(font_size.xs, text_dui.base_content, str(opacity(60))),
|
|
52
|
+
),
|
|
53
|
+
cls=combine_classes(flex_display, items.center, gap(2), m.b(2)),
|
|
54
|
+
),
|
|
55
|
+
text,
|
|
56
|
+
cls=card_body,
|
|
57
|
+
),
|
|
58
|
+
cls=combine_classes(
|
|
59
|
+
card,
|
|
60
|
+
bg_dui.base_100 if is_focused else bg_dui.base_200,
|
|
61
|
+
w.full,
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def setup(route_prefix="/basic"):
|
|
67
|
+
"""Set up the basic centered card stack demo.
|
|
68
|
+
|
|
69
|
+
Returns dict with config, ids, btn_ids, router, urls, state management,
|
|
70
|
+
and page rendering dependencies.
|
|
71
|
+
"""
|
|
72
|
+
config = CardStackConfig(prefix="basic", click_to_focus=True)
|
|
73
|
+
ids = CardStackHtmlIds(prefix=config.prefix)
|
|
74
|
+
btn_ids = CardStackButtonIds(prefix=config.prefix)
|
|
75
|
+
|
|
76
|
+
state = CardStackState(visible_count=5, card_width=60)
|
|
77
|
+
|
|
78
|
+
def get_state():
|
|
79
|
+
return state
|
|
80
|
+
|
|
81
|
+
def set_state(s):
|
|
82
|
+
nonlocal state
|
|
83
|
+
state.focused_index = s.focused_index
|
|
84
|
+
state.visible_count = s.visible_count
|
|
85
|
+
state.card_width = s.card_width
|
|
86
|
+
state.card_scale = s.card_scale
|
|
87
|
+
state.active_mode = s.active_mode
|
|
88
|
+
state.focus_position = s.focus_position
|
|
89
|
+
|
|
90
|
+
def get_items():
|
|
91
|
+
return SAMPLE_ITEMS
|
|
92
|
+
|
|
93
|
+
router, urls = init_card_stack_router(
|
|
94
|
+
config=config,
|
|
95
|
+
state_getter=get_state,
|
|
96
|
+
state_setter=set_state,
|
|
97
|
+
get_items=get_items,
|
|
98
|
+
render_card=render_card,
|
|
99
|
+
route_prefix=route_prefix,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return dict(
|
|
103
|
+
config=config,
|
|
104
|
+
ids=ids,
|
|
105
|
+
btn_ids=btn_ids,
|
|
106
|
+
router=router,
|
|
107
|
+
urls=urls,
|
|
108
|
+
get_state=get_state,
|
|
109
|
+
get_items=get_items,
|
|
110
|
+
render_card=render_card,
|
|
111
|
+
container_id="basic-demo-container",
|
|
112
|
+
title="Basic Card Stack",
|
|
113
|
+
description="Centered focus. Navigate with arrow keys, scroll wheel, or click any card.",
|
|
114
|
+
progress_label="Item",
|
|
115
|
+
)
|
demos/bottom.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Bottom-anchored card stack demo (chat-style)."""
|
|
2
|
+
|
|
3
|
+
from fasthtml.common import Div, P, Span
|
|
4
|
+
|
|
5
|
+
from cjm_fasthtml_daisyui.components.data_display.card import card, card_body
|
|
6
|
+
from cjm_fasthtml_daisyui.components.data_display.badge import badge, badge_colors
|
|
7
|
+
from cjm_fasthtml_daisyui.utilities.semantic_colors import bg_dui, text_dui
|
|
8
|
+
from cjm_fasthtml_tailwind.utilities.spacing import p, m
|
|
9
|
+
from cjm_fasthtml_tailwind.utilities.sizing import w
|
|
10
|
+
from cjm_fasthtml_tailwind.core.base import combine_classes
|
|
11
|
+
|
|
12
|
+
from cjm_fasthtml_card_stack.core.config import CardStackConfig
|
|
13
|
+
from cjm_fasthtml_card_stack.core.models import CardStackState, CardRenderContext
|
|
14
|
+
from cjm_fasthtml_card_stack.core.html_ids import CardStackHtmlIds
|
|
15
|
+
from cjm_fasthtml_card_stack.core.button_ids import CardStackButtonIds
|
|
16
|
+
from cjm_fasthtml_card_stack.routes.router import init_card_stack_router
|
|
17
|
+
|
|
18
|
+
from demos.data import SAMPLE_ITEMS
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def render_card(item, context: CardRenderContext):
|
|
22
|
+
"""Render a compact chat-style message card."""
|
|
23
|
+
is_focused = context.card_role == "focused"
|
|
24
|
+
|
|
25
|
+
index_badge = Span(
|
|
26
|
+
f"#{context.index + 1}",
|
|
27
|
+
cls=combine_classes(
|
|
28
|
+
badge,
|
|
29
|
+
badge_colors.secondary if is_focused else badge_colors.neutral,
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return Div(
|
|
34
|
+
Div(
|
|
35
|
+
Div(index_badge, cls=combine_classes(m.b(1))),
|
|
36
|
+
P(
|
|
37
|
+
item,
|
|
38
|
+
cls=combine_classes(text_dui.base_content),
|
|
39
|
+
style="font-size: calc(0.875rem * var(--card-stack-scale, 100) / 100)",
|
|
40
|
+
),
|
|
41
|
+
cls=combine_classes(card_body, p(3)),
|
|
42
|
+
),
|
|
43
|
+
cls=combine_classes(
|
|
44
|
+
card,
|
|
45
|
+
bg_dui.base_100 if is_focused else bg_dui.base_200,
|
|
46
|
+
w.full,
|
|
47
|
+
),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def setup(route_prefix="/bottom"):
|
|
52
|
+
"""Set up the bottom-anchored card stack demo.
|
|
53
|
+
|
|
54
|
+
Returns dict with config, ids, btn_ids, router, urls, state management,
|
|
55
|
+
and page rendering dependencies.
|
|
56
|
+
"""
|
|
57
|
+
config = CardStackConfig(prefix="bottom", click_to_focus=True)
|
|
58
|
+
ids = CardStackHtmlIds(prefix=config.prefix)
|
|
59
|
+
btn_ids = CardStackButtonIds(prefix=config.prefix)
|
|
60
|
+
|
|
61
|
+
state = CardStackState(
|
|
62
|
+
visible_count=5,
|
|
63
|
+
card_width=60,
|
|
64
|
+
focus_position=-1, # Bottom-anchored
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def get_state():
|
|
68
|
+
return state
|
|
69
|
+
|
|
70
|
+
def set_state(s):
|
|
71
|
+
nonlocal state
|
|
72
|
+
state.focused_index = s.focused_index
|
|
73
|
+
state.visible_count = s.visible_count
|
|
74
|
+
state.card_width = s.card_width
|
|
75
|
+
state.card_scale = s.card_scale
|
|
76
|
+
state.active_mode = s.active_mode
|
|
77
|
+
state.focus_position = s.focus_position
|
|
78
|
+
|
|
79
|
+
def get_items():
|
|
80
|
+
return SAMPLE_ITEMS
|
|
81
|
+
|
|
82
|
+
router, urls = init_card_stack_router(
|
|
83
|
+
config=config,
|
|
84
|
+
state_getter=get_state,
|
|
85
|
+
state_setter=set_state,
|
|
86
|
+
get_items=get_items,
|
|
87
|
+
render_card=render_card,
|
|
88
|
+
route_prefix=route_prefix,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return dict(
|
|
92
|
+
config=config,
|
|
93
|
+
ids=ids,
|
|
94
|
+
btn_ids=btn_ids,
|
|
95
|
+
router=router,
|
|
96
|
+
urls=urls,
|
|
97
|
+
get_state=get_state,
|
|
98
|
+
get_items=get_items,
|
|
99
|
+
render_card=render_card,
|
|
100
|
+
container_id="bottom-demo-container",
|
|
101
|
+
title="Bottom-Anchored Card Stack",
|
|
102
|
+
description="Chat-style layout with focused card at the bottom. Context cards appear above.",
|
|
103
|
+
progress_label="Message",
|
|
104
|
+
)
|
demos/data.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Sample data for card stack demos."""
|
|
2
|
+
|
|
3
|
+
SAMPLE_ITEMS = [
|
|
4
|
+
"Machine learning is a subset of artificial intelligence that enables systems to learn and improve from experience without being explicitly programmed.",
|
|
5
|
+
"The core idea is to feed data into an algorithm, let it find patterns, and then use those patterns to make predictions or decisions on new, unseen data.",
|
|
6
|
+
"There are three main paradigms: supervised learning, unsupervised learning, and reinforcement learning. Each addresses a fundamentally different type of problem.",
|
|
7
|
+
"In supervised learning, the algorithm is trained on labeled examples — input-output pairs where the correct answer is known. The goal is to learn a mapping from inputs to outputs.",
|
|
8
|
+
"Classification and regression are the two primary supervised learning tasks. Classification predicts discrete categories, while regression predicts continuous numerical values.",
|
|
9
|
+
"Common supervised algorithms include linear regression, logistic regression, decision trees, random forests, support vector machines, and neural networks.",
|
|
10
|
+
"Unsupervised learning works with unlabeled data. The algorithm must discover hidden structure, groupings, or patterns without any guidance about what the correct output should be.",
|
|
11
|
+
"Clustering is the most common unsupervised task — grouping similar data points together. K-means, DBSCAN, and hierarchical clustering are widely used approaches.",
|
|
12
|
+
"Dimensionality reduction is another key unsupervised technique. Methods like PCA and t-SNE compress high-dimensional data into fewer dimensions while preserving important structure.",
|
|
13
|
+
"Reinforcement learning trains an agent to make sequences of decisions by interacting with an environment. The agent receives rewards or penalties and learns to maximize cumulative reward.",
|
|
14
|
+
"The training process in supervised learning involves minimizing a loss function — a measure of how far the model's predictions are from the true values.",
|
|
15
|
+
"Gradient descent is the optimization algorithm most commonly used to minimize the loss. It iteratively adjusts model parameters in the direction that reduces the error.",
|
|
16
|
+
"Overfitting occurs when a model learns the training data too well, including its noise and outliers, and performs poorly on new data. It is one of the central challenges in machine learning.",
|
|
17
|
+
"Regularization techniques like L1, L2, and dropout help prevent overfitting by adding constraints that discourage overly complex models.",
|
|
18
|
+
"The bias-variance tradeoff is a fundamental concept. High bias means the model is too simple and underfits; high variance means it is too complex and overfits.",
|
|
19
|
+
"Cross-validation is a technique for evaluating model performance by splitting data into multiple train-test folds, reducing the risk of evaluation bias from a single split.",
|
|
20
|
+
"Feature engineering — the process of creating, selecting, and transforming input variables — often has more impact on model performance than the choice of algorithm.",
|
|
21
|
+
"Neural networks are composed of layers of interconnected nodes. Each connection has a learnable weight, and each node applies a nonlinear activation function to its inputs.",
|
|
22
|
+
"Deep learning refers to neural networks with many layers. These architectures can automatically learn hierarchical feature representations from raw data.",
|
|
23
|
+
"Convolutional neural networks (CNNs) are specialized for grid-like data such as images. They use learnable filters that detect local patterns like edges, textures, and shapes.",
|
|
24
|
+
"Recurrent neural networks (RNNs) and their variants like LSTMs are designed for sequential data. They maintain hidden state that carries information across time steps.",
|
|
25
|
+
"Transformers replaced RNNs for most sequence tasks by using self-attention mechanisms that can relate any position in a sequence to any other position in parallel.",
|
|
26
|
+
"Large language models like GPT and Claude are transformer-based models trained on massive text corpora. They learn to predict the next token and develop broad language understanding.",
|
|
27
|
+
"Transfer learning allows a model pretrained on a large dataset to be fine-tuned on a smaller, task-specific dataset. This dramatically reduces the data and compute needed for new tasks.",
|
|
28
|
+
"The field continues to evolve rapidly, with active research in areas like multimodal learning, efficient architectures, alignment, interpretability, and learning from human feedback.",
|
|
29
|
+
]
|
demos/shared.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Shared utilities for card stack demos."""
|
|
2
|
+
|
|
3
|
+
from fasthtml.common import Div, H1, P, Span
|
|
4
|
+
|
|
5
|
+
from cjm_fasthtml_daisyui.utilities.semantic_colors import bg_dui, text_dui
|
|
6
|
+
from cjm_fasthtml_tailwind.utilities.spacing import p, m
|
|
7
|
+
from cjm_fasthtml_tailwind.utilities.sizing import container, max_w
|
|
8
|
+
from cjm_fasthtml_tailwind.utilities.typography import font_size, font_weight
|
|
9
|
+
from cjm_fasthtml_tailwind.utilities.flexbox_and_grid import (
|
|
10
|
+
flex_display, items, justify, gap
|
|
11
|
+
)
|
|
12
|
+
from cjm_fasthtml_tailwind.utilities.borders import rounded
|
|
13
|
+
from cjm_fasthtml_tailwind.core.base import combine_classes
|
|
14
|
+
|
|
15
|
+
from cjm_fasthtml_keyboard_navigation.core.manager import ZoneManager
|
|
16
|
+
from cjm_fasthtml_keyboard_navigation.components.system import render_keyboard_system
|
|
17
|
+
|
|
18
|
+
from cjm_fasthtml_card_stack.components.viewport import render_viewport
|
|
19
|
+
from cjm_fasthtml_card_stack.components.controls import (
|
|
20
|
+
render_width_slider, render_scale_slider, render_card_count_select
|
|
21
|
+
)
|
|
22
|
+
from cjm_fasthtml_card_stack.components.progress import render_progress_indicator
|
|
23
|
+
from cjm_fasthtml_card_stack.js.core import generate_card_stack_js
|
|
24
|
+
from cjm_fasthtml_card_stack.keyboard.actions import (
|
|
25
|
+
create_card_stack_focus_zone, create_card_stack_nav_actions,
|
|
26
|
+
build_card_stack_url_map, render_card_stack_action_buttons
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def build_keyboard_system(config, btn_ids, ids, urls):
|
|
31
|
+
"""Build keyboard navigation system for a card stack."""
|
|
32
|
+
zone = create_card_stack_focus_zone(ids)
|
|
33
|
+
nav_actions = create_card_stack_nav_actions(zone.id, btn_ids, config)
|
|
34
|
+
manager = ZoneManager(zones=(zone,), actions=nav_actions)
|
|
35
|
+
|
|
36
|
+
url_map = build_card_stack_url_map(btn_ids, urls)
|
|
37
|
+
target_map = {btn_id: f"#{ids.card_stack}" for btn_id in url_map}
|
|
38
|
+
swap_map = {btn_id: "none" for btn_id in url_map}
|
|
39
|
+
include_map = {btn_id: f"#{ids.focused_index_input}" for btn_id in url_map}
|
|
40
|
+
|
|
41
|
+
return render_keyboard_system(
|
|
42
|
+
manager,
|
|
43
|
+
url_map=url_map,
|
|
44
|
+
target_map=target_map,
|
|
45
|
+
swap_map=swap_map,
|
|
46
|
+
include_map=include_map,
|
|
47
|
+
show_hints=False,
|
|
48
|
+
include_state_inputs=True,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def render_demo_page(
|
|
53
|
+
title,
|
|
54
|
+
description,
|
|
55
|
+
state_getter,
|
|
56
|
+
items_getter,
|
|
57
|
+
render_card,
|
|
58
|
+
config,
|
|
59
|
+
ids,
|
|
60
|
+
btn_ids,
|
|
61
|
+
urls,
|
|
62
|
+
container_id,
|
|
63
|
+
progress_label="Item",
|
|
64
|
+
):
|
|
65
|
+
"""Render a standard card stack demo page.
|
|
66
|
+
|
|
67
|
+
Returns a page_content() callable for use with handle_htmx_request.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def page_content():
|
|
71
|
+
state = state_getter()
|
|
72
|
+
card_items = items_getter()
|
|
73
|
+
|
|
74
|
+
kb_system = build_keyboard_system(config, btn_ids, ids, urls)
|
|
75
|
+
|
|
76
|
+
js_script = generate_card_stack_js(
|
|
77
|
+
ids=ids,
|
|
78
|
+
button_ids=btn_ids,
|
|
79
|
+
config=config,
|
|
80
|
+
urls=urls,
|
|
81
|
+
container_id=container_id,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return Div(
|
|
85
|
+
# Visual content container (used for viewport height calculation)
|
|
86
|
+
Div(
|
|
87
|
+
# Header
|
|
88
|
+
Div(
|
|
89
|
+
H1(title, cls=combine_classes(font_size._2xl, font_weight.bold)),
|
|
90
|
+
P(description,
|
|
91
|
+
cls=combine_classes(text_dui.base_content, font_size.sm, m.b(4))),
|
|
92
|
+
cls=m.b(2)
|
|
93
|
+
),
|
|
94
|
+
|
|
95
|
+
# Controls row
|
|
96
|
+
Div(
|
|
97
|
+
Div(
|
|
98
|
+
render_card_count_select(config, ids, state.visible_count),
|
|
99
|
+
cls=combine_classes(flex_display, items.center, gap(2)),
|
|
100
|
+
),
|
|
101
|
+
Div(
|
|
102
|
+
Span("Width:", cls=combine_classes(font_size.sm, text_dui.base_content)),
|
|
103
|
+
render_width_slider(config, ids, state.card_width),
|
|
104
|
+
cls=combine_classes(flex_display, items.center, gap(2)),
|
|
105
|
+
),
|
|
106
|
+
Div(
|
|
107
|
+
Span("Scale:", cls=combine_classes(font_size.sm, text_dui.base_content)),
|
|
108
|
+
render_scale_slider(config, ids, state.card_scale),
|
|
109
|
+
cls=combine_classes(flex_display, items.center, gap(2)),
|
|
110
|
+
),
|
|
111
|
+
cls=combine_classes(
|
|
112
|
+
flex_display, items.center, justify.between,
|
|
113
|
+
gap(4), m.b(2), p(2),
|
|
114
|
+
bg_dui.base_200, rounded.lg,
|
|
115
|
+
)
|
|
116
|
+
),
|
|
117
|
+
|
|
118
|
+
# Viewport
|
|
119
|
+
render_viewport(
|
|
120
|
+
card_items=card_items,
|
|
121
|
+
state=state,
|
|
122
|
+
config=config,
|
|
123
|
+
render_card=render_card,
|
|
124
|
+
ids=ids,
|
|
125
|
+
urls=urls,
|
|
126
|
+
),
|
|
127
|
+
|
|
128
|
+
# Footer
|
|
129
|
+
Div(
|
|
130
|
+
render_progress_indicator(
|
|
131
|
+
state.focused_index, len(card_items), ids, label=progress_label
|
|
132
|
+
),
|
|
133
|
+
cls=combine_classes(flex_display, justify.center, m.t(2))
|
|
134
|
+
),
|
|
135
|
+
|
|
136
|
+
id=container_id,
|
|
137
|
+
),
|
|
138
|
+
|
|
139
|
+
# Keyboard system
|
|
140
|
+
kb_system.script,
|
|
141
|
+
kb_system.hidden_inputs,
|
|
142
|
+
kb_system.action_buttons,
|
|
143
|
+
|
|
144
|
+
# Hidden buttons for JS-callback-triggered actions
|
|
145
|
+
render_card_stack_action_buttons(btn_ids, urls, ids),
|
|
146
|
+
|
|
147
|
+
# Card stack JS
|
|
148
|
+
js_script,
|
|
149
|
+
|
|
150
|
+
cls=combine_classes(container, max_w._6xl, m.x.auto, p(4))
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return page_content
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
cjm_fasthtml_card_stack/__init__.py,sha256=sXLh7g3KC4QCFxcZGBTpG2scR7hmmBsMjq6LqRptkRg,22
|
|
2
|
-
cjm_fasthtml_card_stack/_modidx.py,sha256=jYNj6LPbD5I8yDghD3Lx9XWsDnWqc_oNCESguSF8blA,25105
|
|
3
|
-
cjm_fasthtml_card_stack-0.0.1.dist-info/licenses/LICENSE,sha256=xV8xoN4VOL0uw9X8RSs2IMuD_Ss_a9yAbtGNeBWZwnw,11337
|
|
4
|
-
cjm_fasthtml_card_stack-0.0.1.dist-info/METADATA,sha256=PDmhCLd0lgo4dGhsEojEvH6TV0bguLQDC9YnRA3Zwzg,36798
|
|
5
|
-
cjm_fasthtml_card_stack-0.0.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
6
|
-
cjm_fasthtml_card_stack-0.0.1.dist-info/entry_points.txt,sha256=qTw6qaijkEUH1AcPQhpTqJ8TPiUSMnpn0Cvg4hXoX90,68
|
|
7
|
-
cjm_fasthtml_card_stack-0.0.1.dist-info/top_level.txt,sha256=y6h84kPxytZIWTD4ITK2Gsi40KivBsEfSe8131fIoR4,24
|
|
8
|
-
cjm_fasthtml_card_stack-0.0.1.dist-info/RECORD,,
|
|
File without changes
|
{cjm_fasthtml_card_stack-0.0.1.dist-info → cjm_fasthtml_card_stack-0.0.2.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{cjm_fasthtml_card_stack-0.0.1.dist-info → cjm_fasthtml_card_stack-0.0.2.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|