cjm-fasthtml-card-stack 0.0.1__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.
@@ -0,0 +1,1123 @@
1
+ Metadata-Version: 2.4
2
+ Name: cjm-fasthtml-card-stack
3
+ Version: 0.0.1
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
+ Home-page: https://github.com/cj-mills/cjm-fasthtml-card-stack
6
+ Author: Christian J. Mills
7
+ Author-email: "Christian J. Mills" <9126128+cj-mills@users.noreply.github.com>
8
+ License: Apache-2.0
9
+ Project-URL: Repository, https://github.com/cj-mills/cjm-fasthtml-card-stack
10
+ Project-URL: Documentation, https://cj-mills.github.io/cjm-fasthtml-card-stack
11
+ Keywords: nbdev,jupyter,notebook,python
12
+ Classifier: Natural Language :: English
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Requires-Python: >=3.12
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: python-fasthtml
21
+ Requires-Dist: cjm-fasthtml-tailwind
22
+ Requires-Dist: cjm-fasthtml-daisyui
23
+ Requires-Dist: cjm-fasthtml-keyboard-navigation
24
+ Dynamic: author
25
+ Dynamic: home-page
26
+ Dynamic: license-file
27
+ Dynamic: requires-python
28
+
29
+ # cjm-fasthtml-card-stack
30
+
31
+
32
+ <!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->
33
+
34
+ ## Install
35
+
36
+ ``` bash
37
+ pip install cjm_fasthtml_card_stack
38
+ ```
39
+
40
+ ## Project Structure
41
+
42
+ nbs/
43
+ ├── components/ (4)
44
+ │ ├── controls.ipynb # Width slider, scale slider, and card count selector components.
45
+ │ ├── progress.ipynb # Progress indicator showing the current position within the card stack.
46
+ │ ├── states.ipynb # Loading, empty, and placeholder card components for the card stack viewport.
47
+ │ └── viewport.ipynb # Card stack viewport with 3-section CSS Grid layout, slot rendering,
48
+ ├── core/ (5)
49
+ │ ├── button_ids.ipynb # Prefix-based IDs for hidden keyboard action buttons.
50
+ │ ├── config.ipynb # Configuration dataclass for card stack initialization.
51
+ │ ├── constants.ipynb # CSS class constants, type aliases, and default values for the card stack library.
52
+ │ ├── html_ids.ipynb # Prefix-based HTML ID generator for card stack DOM elements.
53
+ │ └── models.ipynb # Core dataclasses for card stack state, render context, and URL routing.
54
+ ├── helpers/ (1)
55
+ │ └── focus.ipynb # Focus position resolution, viewport window calculation, and OOB focus sync.
56
+ ├── js/ (4)
57
+ │ ├── core.ipynb # Master composer for card stack JavaScript. Combines viewport height,
58
+ │ ├── navigation.ipynb # JavaScript generator for page jump and first/last navigation helpers.
59
+ │ ├── scroll.ipynb # JavaScript generator for scroll-to-nav conversion.
60
+ │ └── viewport.ipynb # JavaScript generator for dynamic viewport height calculation.
61
+ ├── keyboard/ (1)
62
+ │ └── actions.ipynb # Keyboard navigation focus zone and action factories for the card stack.
63
+ └── routes/ (2)
64
+ ├── handlers.ipynb # Response builder functions for card stack operations (Tier 1 API).
65
+ └── router.ipynb # Convenience router factory that wires up standard card stack routes (Tier 2 API).
66
+
67
+ Total: 17 notebooks across 6 directories
68
+
69
+ ## Module Dependencies
70
+
71
+ ``` mermaid
72
+ graph LR
73
+ components_controls[components.controls<br/>Controls]
74
+ components_progress[components.progress<br/>Progress]
75
+ components_states[components.states<br/>States]
76
+ components_viewport[components.viewport<br/>Viewport]
77
+ core_button_ids[core.button_ids<br/>Button IDs]
78
+ core_config[core.config<br/>Config]
79
+ core_constants[core.constants<br/>Constants]
80
+ core_html_ids[core.html_ids<br/>HTML IDs]
81
+ core_models[core.models<br/>Models]
82
+ helpers_focus[helpers.focus<br/>Focus]
83
+ js_core[js.core<br/>JS: Core]
84
+ js_navigation[js.navigation<br/>JS: Page Navigation]
85
+ js_scroll[js.scroll<br/>JS: Scroll Navigation]
86
+ js_viewport[js.viewport<br/>JS: Viewport Height]
87
+ keyboard_actions[keyboard.actions<br/>Actions]
88
+ routes_handlers[routes.handlers<br/>Handlers]
89
+ routes_router[routes.router<br/>Router]
90
+
91
+ components_controls --> core_config
92
+ components_controls --> core_html_ids
93
+ components_progress --> core_html_ids
94
+ components_states --> core_html_ids
95
+ components_viewport --> core_models
96
+ components_viewport --> core_config
97
+ components_viewport --> core_constants
98
+ components_viewport --> helpers_focus
99
+ components_viewport --> core_html_ids
100
+ components_viewport --> components_states
101
+ helpers_focus --> core_html_ids
102
+ js_core --> core_constants
103
+ js_core --> core_button_ids
104
+ js_core --> core_models
105
+ js_core --> core_config
106
+ js_core --> js_scroll
107
+ js_core --> core_html_ids
108
+ js_core --> js_navigation
109
+ js_core --> js_viewport
110
+ js_navigation --> core_button_ids
111
+ js_scroll --> core_button_ids
112
+ js_scroll --> core_constants
113
+ js_scroll --> core_html_ids
114
+ js_viewport --> core_html_ids
115
+ keyboard_actions --> core_button_ids
116
+ keyboard_actions --> core_models
117
+ keyboard_actions --> core_config
118
+ keyboard_actions --> core_html_ids
119
+ keyboard_actions --> js_core
120
+ routes_handlers --> core_models
121
+ routes_handlers --> components_viewport
122
+ routes_handlers --> helpers_focus
123
+ routes_handlers --> core_config
124
+ routes_handlers --> components_progress
125
+ routes_handlers --> core_html_ids
126
+ routes_router --> routes_handlers
127
+ routes_router --> core_models
128
+ routes_router --> core_config
129
+ routes_router --> core_html_ids
130
+ ```
131
+
132
+ *39 cross-module dependencies detected*
133
+
134
+ ## CLI Reference
135
+
136
+ No CLI commands found in this project.
137
+
138
+ ## Module Overview
139
+
140
+ Detailed documentation for each module in the project:
141
+
142
+ ### Actions (`actions.ipynb`)
143
+
144
+ > Keyboard navigation focus zone and action factories for the card
145
+ > stack.
146
+
147
+ #### Import
148
+
149
+ ``` python
150
+ from cjm_fasthtml_card_stack.keyboard.actions import (
151
+ create_card_stack_focus_zone,
152
+ create_card_stack_nav_actions,
153
+ build_card_stack_url_map,
154
+ render_card_stack_action_buttons
155
+ )
156
+ ```
157
+
158
+ #### Functions
159
+
160
+ ``` python
161
+ def create_card_stack_focus_zone(
162
+ ids: CardStackHtmlIds, # HTML IDs for this card stack instance
163
+ on_focus_change: Optional[str] = None, # JS callback name on focus change
164
+ hidden_input_prefix: Optional[str] = None, # Prefix for keyboard nav hidden inputs
165
+ data_attributes: Tuple[str, ...] = (), # Data attributes to track on focused items
166
+ ) -> FocusZone: # Configured focus zone for the card stack
167
+ "Create a focus zone for a card stack viewport."
168
+ ```
169
+
170
+ ``` python
171
+ def create_card_stack_nav_actions(
172
+ zone_id: str, # Focus zone ID to restrict actions to
173
+ button_ids: CardStackButtonIds, # Button IDs for HTMX triggers
174
+ config: CardStackConfig, # Config (for prefix-unique callback names)
175
+ disable_in_modes: Tuple[str, ...] = (), # Mode names that disable navigation
176
+ ) -> Tuple[KeyAction, ...]: # Standard card stack navigation actions
177
+ "Create standard keyboard navigation actions for a card stack."
178
+ ```
179
+
180
+ ``` python
181
+ def build_card_stack_url_map(
182
+ button_ids: CardStackButtonIds, # Button IDs for this card stack instance
183
+ urls: CardStackUrls, # URL bundle for routing
184
+ ) -> Dict[str, str]: # Mapping of button ID -> route URL
185
+ """
186
+ Build url_map for render_keyboard_system with all card stack navigation buttons.
187
+
188
+ Returns a dict mapping button IDs to URLs for all navigation actions:
189
+ nav_up, nav_down, nav_first, nav_last, nav_page_up, nav_page_down.
190
+
191
+ Merge with consumer's own action URLs when building the keyboard system:
192
+ url_map = {**build_card_stack_url_map(btn_ids, urls), **my_action_urls}
193
+ """
194
+ ```
195
+
196
+ ``` python
197
+ def render_card_stack_action_buttons(
198
+ button_ids: CardStackButtonIds, # Button IDs for this card stack instance
199
+ urls: CardStackUrls, # URL bundle for routing
200
+ ids: CardStackHtmlIds, # HTML IDs (for hx-include of focused_index_input)
201
+ ) -> 'FT': # Div containing hidden action buttons
202
+ """
203
+ Render hidden HTMX buttons for JS-callback-triggered navigation actions.
204
+
205
+ Creates buttons for: page_up, page_down, first, last.
206
+ These are clicked programmatically by the card stack's JS functions.
207
+ Must be included in the DOM alongside the keyboard system's own buttons.
208
+ """
209
+ ```
210
+
211
+ ### Button IDs (`button_ids.ipynb`)
212
+
213
+ > Prefix-based IDs for hidden keyboard action buttons.
214
+
215
+ #### Import
216
+
217
+ ``` python
218
+ from cjm_fasthtml_card_stack.core.button_ids import (
219
+ CardStackButtonIds
220
+ )
221
+ ```
222
+
223
+ #### Classes
224
+
225
+ ``` python
226
+ @dataclass
227
+ class CardStackButtonIds:
228
+ "Prefix-based IDs for hidden keyboard action buttons."
229
+
230
+ prefix: str # ID prefix for this card stack instance
231
+
232
+ def nav_up(self) -> str: # Navigate to previous item
233
+ """Navigate up button."""
234
+ return f"{self.prefix}-btn-nav-up"
235
+
236
+ @property
237
+ def nav_down(self) -> str: # Navigate to next item
238
+ "Navigate up button."
239
+
240
+ def nav_down(self) -> str: # Navigate to next item
241
+ """Navigate down button."""
242
+ return f"{self.prefix}-btn-nav-down"
243
+
244
+ @property
245
+ def nav_first(self) -> str: # Navigate to first item
246
+ "Navigate down button."
247
+
248
+ def nav_first(self) -> str: # Navigate to first item
249
+ """Navigate to first item button."""
250
+ return f"{self.prefix}-btn-nav-first"
251
+
252
+ @property
253
+ def nav_last(self) -> str: # Navigate to last item
254
+ "Navigate to first item button."
255
+
256
+ def nav_last(self) -> str: # Navigate to last item
257
+ """Navigate to last item button."""
258
+ return f"{self.prefix}-btn-nav-last"
259
+
260
+ @property
261
+ def nav_page_up(self) -> str: # Page jump up
262
+ "Navigate to last item button."
263
+
264
+ def nav_page_up(self) -> str: # Page jump up
265
+ """Page up button."""
266
+ return f"{self.prefix}-btn-nav-page-up"
267
+
268
+ @property
269
+ def nav_page_down(self) -> str: # Page jump down
270
+ "Page up button."
271
+
272
+ def nav_page_down(self) -> str: # Page jump down
273
+ """Page down button."""
274
+ return f"{self.prefix}-btn-nav-page-down"
275
+
276
+ # --- Viewport control buttons ---
277
+
278
+ @property
279
+ def width_narrow(self) -> str: # Decrease viewport width
280
+ "Page down button."
281
+
282
+ def width_narrow(self) -> str: # Decrease viewport width
283
+ """Narrow viewport button."""
284
+ return f"{self.prefix}-btn-width-narrow"
285
+
286
+ @property
287
+ def width_widen(self) -> str: # Increase viewport width
288
+ "Narrow viewport button."
289
+
290
+ def width_widen(self) -> str: # Increase viewport width
291
+ """Widen viewport button."""
292
+ return f"{self.prefix}-btn-width-widen"
293
+
294
+ @property
295
+ def scale_decrease(self) -> str: # Decrease content scale
296
+ "Widen viewport button."
297
+
298
+ def scale_decrease(self) -> str: # Decrease content scale
299
+ """Decrease scale button."""
300
+ return f"{self.prefix}-btn-scale-decrease"
301
+
302
+ @property
303
+ def scale_increase(self) -> str: # Increase content scale
304
+ "Decrease scale button."
305
+
306
+ def scale_increase(self) -> str: # Increase content scale
307
+ "Increase scale button."
308
+ ```
309
+
310
+ ### Config (`config.ipynb`)
311
+
312
+ > Configuration dataclass for card stack initialization.
313
+
314
+ #### Import
315
+
316
+ ``` python
317
+ from cjm_fasthtml_card_stack.core.config import (
318
+ CardStackConfig
319
+ )
320
+ ```
321
+
322
+ #### Functions
323
+
324
+ ``` python
325
+ def _auto_prefix() -> str: # Unique prefix string (e.g., "cs0", "cs1")
326
+ """Generate an auto-incrementing unique prefix."""
327
+ global _prefix_counter
328
+ p = f"cs{_prefix_counter}"
329
+ _prefix_counter += 1
330
+ return p
331
+
332
+ def _reset_prefix_counter() -> None
333
+ "Generate an auto-incrementing unique prefix."
334
+ ```
335
+
336
+ ``` python
337
+ def _reset_prefix_counter() -> None
338
+ "Reset the prefix counter (for testing only)."
339
+ ```
340
+
341
+ #### Classes
342
+
343
+ ``` python
344
+ @dataclass
345
+ class CardStackConfig:
346
+ "Initialization-time settings for a card stack instance."
347
+
348
+ prefix: str = field(...) # HTML ID prefix (auto-generated if omitted)
349
+ visible_count_options: Tuple[int, ...] = (1, 3, 5, 7, 9) # Choices for card count dropdown
350
+ card_width_min: int = 30 # Width slider minimum (rem)
351
+ card_width_max: int = 120 # Width slider maximum (rem)
352
+ card_width_step: int = 5 # Width slider step (rem)
353
+ card_scale_min: int = 50 # Scale slider minimum (%)
354
+ card_scale_max: int = 200 # Scale slider maximum (%)
355
+ card_scale_step: int = 10 # Scale slider step (%)
356
+ click_to_focus: bool = False # Whether context cards get transparent click overlay
357
+ disable_scroll_in_modes: Tuple[str, ...] = () # Mode names where scroll-to-nav is suppressed
358
+ ```
359
+
360
+ #### Variables
361
+
362
+ ``` python
363
+ _prefix_counter: int = 0
364
+ ```
365
+
366
+ ### Constants (`constants.ipynb`)
367
+
368
+ > CSS class constants, type aliases, and default values for the card
369
+ > stack library.
370
+
371
+ #### Import
372
+
373
+ ``` python
374
+ from cjm_fasthtml_card_stack.core.constants import (
375
+ CardRole,
376
+ SCROLL_THRESHOLD,
377
+ NAVIGATION_COOLDOWN,
378
+ DEFAULT_VISIBLE_COUNT,
379
+ DEFAULT_CARD_WIDTH,
380
+ DEFAULT_CARD_SCALE,
381
+ width_storage_key,
382
+ scale_storage_key,
383
+ card_count_storage_key
384
+ )
385
+ ```
386
+
387
+ #### Functions
388
+
389
+ ``` python
390
+ def width_storage_key(
391
+ prefix: str # Card stack instance prefix
392
+ ) -> str: # localStorage key for card width
393
+ "Generate localStorage key for card width."
394
+ ```
395
+
396
+ ``` python
397
+ def scale_storage_key(
398
+ prefix: str # Card stack instance prefix
399
+ ) -> str: # localStorage key for card scale
400
+ "Generate localStorage key for card scale."
401
+ ```
402
+
403
+ ``` python
404
+ def card_count_storage_key(
405
+ prefix: str # Card stack instance prefix
406
+ ) -> str: # localStorage key for card count
407
+ "Generate localStorage key for card count."
408
+ ```
409
+
410
+ #### Variables
411
+
412
+ ``` python
413
+ SCROLL_THRESHOLD: int = 50
414
+ NAVIGATION_COOLDOWN: int = 100
415
+ DEFAULT_VISIBLE_COUNT: int = 3
416
+ DEFAULT_CARD_WIDTH: int = 80
417
+ DEFAULT_CARD_SCALE: int = 100
418
+ ```
419
+
420
+ ### Controls (`controls.ipynb`)
421
+
422
+ > Width slider, scale slider, and card count selector components.
423
+
424
+ #### Import
425
+
426
+ ``` python
427
+ from cjm_fasthtml_card_stack.components.controls import (
428
+ render_width_slider,
429
+ render_scale_slider,
430
+ render_card_count_select
431
+ )
432
+ ```
433
+
434
+ #### Functions
435
+
436
+ ``` python
437
+ def render_width_slider(
438
+ config: CardStackConfig, # Card stack configuration
439
+ ids: CardStackHtmlIds, # HTML IDs for this instance
440
+ card_width: int = 80, # Current card width in rem
441
+ ) -> Any: # Width slider component
442
+ "Render the card stack width slider control."
443
+ ```
444
+
445
+ ``` python
446
+ def render_scale_slider(
447
+ config: CardStackConfig, # Card stack configuration
448
+ ids: CardStackHtmlIds, # HTML IDs for this instance
449
+ card_scale: int = 100, # Current scale percentage
450
+ ) -> Any: # Scale slider component
451
+ "Render the card stack scale slider control."
452
+ ```
453
+
454
+ ``` python
455
+ def render_card_count_select(
456
+ config: CardStackConfig, # Card stack configuration
457
+ ids: CardStackHtmlIds, # HTML IDs for this instance
458
+ current_count: int = 3, # Currently selected card count
459
+ ) -> Any: # Card count dropdown component
460
+ "Render the card count dropdown selector."
461
+ ```
462
+
463
+ ### JS: Core (`core.ipynb`)
464
+
465
+ > Master composer for card stack JavaScript. Combines viewport height,
466
+
467
+ #### Import
468
+
469
+ ``` python
470
+ from cjm_fasthtml_card_stack.js.core import (
471
+ global_callback_name,
472
+ generate_card_stack_js
473
+ )
474
+ ```
475
+
476
+ #### Functions
477
+
478
+ ``` python
479
+ def _generate_width_mgmt_js(
480
+ ids: CardStackHtmlIds, # HTML IDs for this instance
481
+ config: CardStackConfig, # Config with slider bounds
482
+ urls: CardStackUrls, # URL bundle (save_width)
483
+ ) -> str: # JS code fragment for width management
484
+ "Generate JS for width slider management."
485
+ ```
486
+
487
+ ``` python
488
+ def _generate_scale_mgmt_js(
489
+ ids: CardStackHtmlIds, # HTML IDs for this instance
490
+ config: CardStackConfig, # Config with slider bounds
491
+ urls: CardStackUrls, # URL bundle (save_scale)
492
+ ) -> str: # JS code fragment for scale management
493
+ "Generate JS for scale slider management."
494
+ ```
495
+
496
+ ``` python
497
+ def _generate_card_count_mgmt_js(
498
+ ids: CardStackHtmlIds, # HTML IDs for this instance
499
+ config: CardStackConfig, # Config with count options
500
+ urls: CardStackUrls, # URL bundle (update_viewport)
501
+ ) -> str: # JS code fragment for card count management
502
+ "Generate JS for card count selector management."
503
+ ```
504
+
505
+ ``` python
506
+ def _generate_coordinator_js(
507
+ ids: CardStackHtmlIds, # HTML IDs for this instance
508
+ config: CardStackConfig, # Config for prefix-unique listener guards
509
+ ) -> str: # JS code fragment for master coordinator
510
+ "Generate JS for the master coordinator and HTMX listener."
511
+ ```
512
+
513
+ ``` python
514
+ def global_callback_name(
515
+ prefix: str, # Card stack instance prefix
516
+ callback: str, # Base callback name (e.g., "jumpPageUp")
517
+ ) -> str: # Global function name (e.g., "cs0_jumpPageUp")
518
+ "Generate a prefix-unique global callback name for keyboard navigation."
519
+ ```
520
+
521
+ ``` python
522
+ def _generate_global_callbacks_js(
523
+ config: CardStackConfig, # Config with prefix
524
+ ) -> str: # JS code fragment registering global wrappers
525
+ "Register global wrappers for keyboard navigation system."
526
+ ```
527
+
528
+ ``` python
529
+ def generate_card_stack_js(
530
+ "Compose all card stack JS into a single namespaced IIFE."
531
+ ```
532
+
533
+ #### Variables
534
+
535
+ ``` python
536
+ _GLOBAL_CALLBACKS
537
+ ```
538
+
539
+ ### Focus (`focus.ipynb`)
540
+
541
+ > Focus position resolution, viewport window calculation, and OOB focus
542
+ > sync.
543
+
544
+ #### Import
545
+
546
+ ``` python
547
+ from cjm_fasthtml_card_stack.helpers.focus import (
548
+ resolve_focus_slot,
549
+ calculate_viewport_window,
550
+ render_focus_oob
551
+ )
552
+ ```
553
+
554
+ #### Functions
555
+
556
+ ``` python
557
+ def resolve_focus_slot(
558
+ focus_position: Optional[int], # Slot offset (None=center, -1=bottom, 0=top)
559
+ visible_count: int, # Number of visible card slots
560
+ ) -> int: # Resolved 0-indexed slot position
561
+ "Resolve focus_position to an actual slot index within the viewport."
562
+ ```
563
+
564
+ ``` python
565
+ def calculate_viewport_window(
566
+ focused_index: int, # Index of the focused item
567
+ total_items: int, # Total number of items
568
+ visible_count: int, # Number of visible card slots
569
+ focus_position: Optional[int] = None, # Focus slot (None=center)
570
+ ) -> List[Optional[int]]: # Slot indices (None for placeholder slots)
571
+ "Calculate which item indices should be visible in each viewport slot."
572
+ ```
573
+
574
+ ``` python
575
+ def render_focus_oob(
576
+ focused_index: int, # The item index to focus
577
+ ids: CardStackHtmlIds, # HTML IDs for this card stack instance
578
+ form_input_name: str = "focused_index", # Field name for the form input
579
+ ) -> Tuple[Hidden, ...]: # Hidden inputs with OOB swap
580
+ "Render OOB hidden inputs to synchronize focus after HTMX swap."
581
+ ```
582
+
583
+ ### Handlers (`handlers.ipynb`)
584
+
585
+ > Response builder functions for card stack operations (Tier 1 API).
586
+
587
+ #### Import
588
+
589
+ ``` python
590
+ from cjm_fasthtml_card_stack.routes.handlers import (
591
+ build_slots_response,
592
+ build_nav_response,
593
+ card_stack_navigate,
594
+ card_stack_navigate_to_index,
595
+ card_stack_update_viewport,
596
+ card_stack_save_width,
597
+ card_stack_save_scale
598
+ )
599
+ ```
600
+
601
+ #### Functions
602
+
603
+ ``` python
604
+ def build_slots_response(
605
+ card_items: List[Any], # All data items
606
+ state: CardStackState, # Current card stack state
607
+ config: CardStackConfig, # Card stack configuration
608
+ ids: CardStackHtmlIds, # HTML IDs for this instance
609
+ urls: CardStackUrls, # URL bundle for navigation
610
+ render_card: Callable, # Card renderer callback
611
+ ) -> List[Any]: # OOB slot elements (3 viewport sections)
612
+ "Build OOB slot updates for the viewport sections only."
613
+ ```
614
+
615
+ ``` python
616
+ def build_nav_response(
617
+ card_items: List[Any], # All data items
618
+ state: CardStackState, # Current card stack state
619
+ config: CardStackConfig, # Card stack configuration
620
+ ids: CardStackHtmlIds, # HTML IDs for this instance
621
+ urls: CardStackUrls, # URL bundle for navigation
622
+ render_card: Callable, # Card renderer callback
623
+ progress_label: str = "Item", # Label for progress indicator
624
+ ) -> Tuple: # OOB elements (slots + progress + focus)
625
+ "Build full OOB response for navigation: slots + progress + focus inputs."
626
+ ```
627
+
628
+ ``` python
629
+ def card_stack_navigate(
630
+ direction: str, # "up", "down", "first", "last", "page_up", "page_down"
631
+ card_items: List[Any], # All data items
632
+ state: CardStackState, # Current card stack state (mutated in place)
633
+ config: CardStackConfig, # Card stack configuration
634
+ ids: CardStackHtmlIds, # HTML IDs for this instance
635
+ urls: CardStackUrls, # URL bundle for navigation
636
+ render_card: Callable, # Card renderer callback
637
+ progress_label: str = "Item", # Label for progress indicator
638
+ ) -> Tuple: # OOB elements (slots + progress + focus)
639
+ "Navigate to a different item. Mutates state.focused_index in place."
640
+ ```
641
+
642
+ ``` python
643
+ def card_stack_navigate_to_index(
644
+ target_index: int, # Target item index to navigate to
645
+ card_items: List[Any], # All data items
646
+ state: CardStackState, # Current card stack state (mutated in place)
647
+ config: CardStackConfig, # Card stack configuration
648
+ ids: CardStackHtmlIds, # HTML IDs for this instance
649
+ urls: CardStackUrls, # URL bundle for navigation
650
+ render_card: Callable, # Card renderer callback
651
+ progress_label: str = "Item", # Label for progress indicator
652
+ ) -> Tuple: # OOB elements (slots + progress + focus)
653
+ "Navigate to a specific item index. Mutates state.focused_index in place."
654
+ ```
655
+
656
+ ``` python
657
+ def card_stack_update_viewport(
658
+ visible_count: int, # New number of visible cards
659
+ card_items: List[Any], # All data items
660
+ state: CardStackState, # Current card stack state (mutated in place)
661
+ config: CardStackConfig, # Card stack configuration
662
+ ids: CardStackHtmlIds, # HTML IDs for this instance
663
+ urls: CardStackUrls, # URL bundle for navigation
664
+ render_card: Callable, # Card renderer callback
665
+ ) -> Any: # Full viewport component (outerHTML swap)
666
+ "Update viewport with new card count. Mutates state.visible_count in place."
667
+ ```
668
+
669
+ ``` python
670
+ def card_stack_save_width(
671
+ state: CardStackState, # Current card stack state (mutated in place)
672
+ card_width: int, # Card stack width in rem
673
+ config: CardStackConfig, # Card stack configuration (for clamping bounds)
674
+ ) -> None: # No response (swap=none on client)
675
+ "Save card stack width. Mutates state.card_width in place."
676
+ ```
677
+
678
+ ``` python
679
+ def card_stack_save_scale(
680
+ state: CardStackState, # Current card stack state (mutated in place)
681
+ card_scale: int, # Card stack scale percentage
682
+ config: CardStackConfig, # Card stack configuration (for clamping bounds)
683
+ ) -> None: # No response (swap=none on client)
684
+ "Save card stack scale. Mutates state.card_scale in place."
685
+ ```
686
+
687
+ ### HTML IDs (`html_ids.ipynb`)
688
+
689
+ > Prefix-based HTML ID generator for card stack DOM elements.
690
+
691
+ #### Import
692
+
693
+ ``` python
694
+ from cjm_fasthtml_card_stack.core.html_ids import (
695
+ CardStackHtmlIds
696
+ )
697
+ ```
698
+
699
+ #### Classes
700
+
701
+ ``` python
702
+ @dataclass
703
+ class CardStackHtmlIds:
704
+ "Prefix-based HTML ID generator for card stack DOM elements."
705
+
706
+ prefix: str # ID prefix for this card stack instance
707
+
708
+ def card_stack(self) -> str: # Full-width scroll capture container
709
+ """Outer card stack container."""
710
+ return f"{self.prefix}-card-stack"
711
+
712
+ @property
713
+ def card_stack_inner(self) -> str: # Width-constrained CSS Grid container
714
+ "Outer card stack container."
715
+
716
+ def card_stack_inner(self) -> str: # Width-constrained CSS Grid container
717
+ """Inner grid container for 3-section layout."""
718
+ return f"{self.prefix}-card-stack-inner"
719
+
720
+ @property
721
+ def card_stack_empty(self) -> str: # Empty state placeholder
722
+ "Inner grid container for 3-section layout."
723
+
724
+ def card_stack_empty(self) -> str: # Empty state placeholder
725
+ """Empty state container."""
726
+ return f"{self.prefix}-card-stack-empty"
727
+
728
+ # --- Viewport sections ---
729
+
730
+ @property
731
+ def viewport_section_before(self) -> str: # Cards before focused (1fr, justify-end)
732
+ "Empty state container."
733
+
734
+ def viewport_section_before(self) -> str: # Cards before focused (1fr, justify-end)
735
+ """Viewport section for context cards before focused card."""
736
+ return f"{self.prefix}-viewport-section-before"
737
+
738
+ @property
739
+ def viewport_section_focused(self) -> str: # Focused card (auto)
740
+ "Viewport section for context cards before focused card."
741
+
742
+ def viewport_section_focused(self) -> str: # Focused card (auto)
743
+ """Viewport section for the focused card."""
744
+ return f"{self.prefix}-viewport-section-focused"
745
+
746
+ @property
747
+ def viewport_section_after(self) -> str: # Cards after focused (1fr, justify-start)
748
+ "Viewport section for the focused card."
749
+
750
+ def viewport_section_after(self) -> str: # Cards after focused (1fr, justify-start)
751
+ """Viewport section for context cards after focused card."""
752
+ return f"{self.prefix}-viewport-section-after"
753
+
754
+ # --- Dynamic slot IDs ---
755
+
756
+ def viewport_slot(
757
+ self,
758
+ index: int # Slot index within the viewport
759
+ ) -> str: # Slot element ID
760
+ "Viewport section for context cards after focused card."
761
+
762
+ def viewport_slot(
763
+ self,
764
+ index: int # Slot index within the viewport
765
+ ) -> str: # Slot element ID
766
+ "ID for an individual viewport slot container."
767
+
768
+ def card_count_select(self) -> str: # Card count dropdown
769
+ """Card count selector dropdown."""
770
+ return f"{self.prefix}-card-count-select"
771
+
772
+ @property
773
+ def width_slider(self) -> str: # Width range slider
774
+ "Card count selector dropdown."
775
+
776
+ def width_slider(self) -> str: # Width range slider
777
+ """Card stack width slider."""
778
+ return f"{self.prefix}-width-slider"
779
+
780
+ @property
781
+ def scale_slider(self) -> str: # Scale range slider
782
+ "Card stack width slider."
783
+
784
+ def scale_slider(self) -> str: # Scale range slider
785
+ """Card stack scale slider."""
786
+ return f"{self.prefix}-scale-slider"
787
+
788
+ # --- Status elements ---
789
+
790
+ @property
791
+ def progress(self) -> str: # Progress indicator
792
+ "Card stack scale slider."
793
+
794
+ def progress(self) -> str: # Progress indicator
795
+ """Progress indicator element."""
796
+ return f"{self.prefix}-progress"
797
+
798
+ @property
799
+ def loading(self) -> str: # Loading state container
800
+ "Progress indicator element."
801
+
802
+ def loading(self) -> str: # Loading state container
803
+ """Loading state container."""
804
+ return f"{self.prefix}-loading"
805
+
806
+ # --- Hidden inputs ---
807
+
808
+ @property
809
+ def focused_index_input(self) -> str: # Hidden input for keyboard nav focus recovery
810
+ "Loading state container."
811
+
812
+ def focused_index_input(self) -> str: # Hidden input for keyboard nav focus recovery
813
+ "Hidden input storing the focused index for HTMX submissions."
814
+ ```
815
+
816
+ ### Models (`models.ipynb`)
817
+
818
+ > Core dataclasses for card stack state, render context, and URL
819
+ > routing.
820
+
821
+ #### Import
822
+
823
+ ``` python
824
+ from cjm_fasthtml_card_stack.core.models import (
825
+ CardStackState,
826
+ CardRenderContext,
827
+ CardStackUrls
828
+ )
829
+ ```
830
+
831
+ #### Classes
832
+
833
+ ``` python
834
+ @dataclass
835
+ class CardStackState:
836
+ "Viewport state for a card stack instance."
837
+
838
+ focused_index: int = 0 # Index of focused item in the items list
839
+ visible_count: int = 3 # Number of card slots visible in viewport
840
+ card_width: int = 80 # Max width of card stack inner container in rem
841
+ card_scale: int = 100 # Content scale percentage (50-200)
842
+ active_mode: Optional[str] # Current interaction mode name (consumer-defined)
843
+ focus_position: Optional[int] # Slot offset for focused card (None=center, -1=bottom)
844
+ ```
845
+
846
+ ``` python
847
+ @dataclass
848
+ class CardRenderContext:
849
+ "Context passed to the consumer's render_card callback."
850
+
851
+ card_role: str # "focused" or "context"
852
+ index: int # Item's position in the full items list
853
+ total_items: int # Total item count
854
+ is_first: bool # Whether this is the first item
855
+ is_last: bool # Whether this is the last item
856
+ active_mode: Optional[str] # Current interaction mode
857
+ card_scale: int # Scale percentage (50-200)
858
+ distance_from_focus: int # Signed slot offset from focused card (0=focused)
859
+ ```
860
+
861
+ ``` python
862
+ @dataclass
863
+ class CardStackUrls:
864
+ "URL bundle for card stack navigation and viewport operations."
865
+
866
+ nav_up: str = '' # Navigate to previous item
867
+ nav_down: str = '' # Navigate to next item
868
+ nav_first: str = '' # Navigate to first item
869
+ nav_last: str = '' # Navigate to last item
870
+ nav_page_up: str = '' # Page jump up
871
+ nav_page_down: str = '' # Page jump down
872
+ nav_to_index: str = '' # Navigate to specific index (click-to-focus)
873
+ update_viewport: str = '' # Change visible_count (full viewport re-render)
874
+ save_width: str = '' # Persist card_width
875
+ save_scale: str = '' # Persist card_scale
876
+ ```
877
+
878
+ ### JS: Page Navigation (`navigation.ipynb`)
879
+
880
+ > JavaScript generator for page jump and first/last navigation helpers.
881
+
882
+ #### Import
883
+
884
+ ``` python
885
+ from cjm_fasthtml_card_stack.js.navigation import (
886
+ generate_page_nav_js
887
+ )
888
+ ```
889
+
890
+ #### Functions
891
+
892
+ ``` python
893
+ def generate_page_nav_js(
894
+ button_ids: CardStackButtonIds, # Button IDs for navigation triggers
895
+ ) -> str: # JavaScript code fragment for page navigation
896
+ "Generate JS for page-based and first/last navigation functions."
897
+ ```
898
+
899
+ ### Progress (`progress.ipynb`)
900
+
901
+ > Progress indicator showing the current position within the card stack.
902
+
903
+ #### Import
904
+
905
+ ``` python
906
+ from cjm_fasthtml_card_stack.components.progress import (
907
+ render_progress_indicator
908
+ )
909
+ ```
910
+
911
+ #### Functions
912
+
913
+ ``` python
914
+ def render_progress_indicator(
915
+ focused_index: int, # Currently focused item index (0-based)
916
+ total_items: int, # Total number of items
917
+ ids: CardStackHtmlIds, # HTML IDs for this card stack instance
918
+ label: str = "Item", # Label prefix (e.g., "Item", "Segment", "Card")
919
+ oob: bool = False, # Whether to render as OOB swap
920
+ ) -> Any: # Progress indicator component
921
+ "Render position indicator showing current item in the collection."
922
+ ```
923
+
924
+ ### Router (`router.ipynb`)
925
+
926
+ > Convenience router factory that wires up standard card stack routes
927
+ > (Tier 2 API).
928
+
929
+ #### Import
930
+
931
+ ``` python
932
+ from cjm_fasthtml_card_stack.routes.router import (
933
+ init_card_stack_router
934
+ )
935
+ ```
936
+
937
+ #### Functions
938
+
939
+ ``` python
940
+ def init_card_stack_router(
941
+ config: CardStackConfig, # Card stack configuration
942
+ state_getter: Callable[[], CardStackState], # Function to get current state
943
+ state_setter: Callable[[CardStackState], None], # Function to save state
944
+ get_items: Callable[[], List[Any]], # Function to get current items list
945
+ render_card: Callable, # Card renderer callback: (item, CardRenderContext) -> FT
946
+ route_prefix: str = "/card-stack", # Route prefix for all card stack routes
947
+ progress_label: str = "Item", # Label for progress indicator
948
+ ) -> Tuple[APIRouter, CardStackUrls]: # (router, urls) tuple
949
+ "Initialize an APIRouter with all standard card stack routes."
950
+ ```
951
+
952
+ ### JS: Scroll Navigation (`scroll.ipynb`)
953
+
954
+ > JavaScript generator for scroll-to-nav conversion.
955
+
956
+ #### Import
957
+
958
+ ``` python
959
+ from cjm_fasthtml_card_stack.js.scroll import (
960
+ generate_scroll_nav_js
961
+ )
962
+ ```
963
+
964
+ #### Functions
965
+
966
+ ``` python
967
+ def generate_scroll_nav_js(
968
+ ids: CardStackHtmlIds, # HTML IDs for this card stack instance
969
+ button_ids: CardStackButtonIds, # Button IDs for navigation triggers
970
+ disable_in_modes: Tuple[str, ...] = (), # Mode names where scroll nav is suppressed
971
+ ) -> str: # JavaScript code fragment for scroll navigation
972
+ "Generate JS for scroll wheel to navigation conversion."
973
+ ```
974
+
975
+ ### States (`states.ipynb`)
976
+
977
+ > Loading, empty, and placeholder card components for the card stack
978
+ > viewport.
979
+
980
+ #### Import
981
+
982
+ ``` python
983
+ from cjm_fasthtml_card_stack.components.states import (
984
+ render_placeholder_card,
985
+ render_loading_state,
986
+ render_empty_state
987
+ )
988
+ ```
989
+
990
+ #### Functions
991
+
992
+ ``` python
993
+ def render_placeholder_card(
994
+ placeholder_type: Literal["start", "end"], # Which edge of the list
995
+ ) -> Any: # Placeholder card component
996
+ "Render a placeholder card for viewport edges."
997
+ ```
998
+
999
+ ``` python
1000
+ def render_loading_state(
1001
+ ids: CardStackHtmlIds, # HTML IDs for this card stack instance
1002
+ message: str = "Loading...", # Loading message text
1003
+ ) -> Any: # Loading component
1004
+ "Render loading state with spinner and message."
1005
+ ```
1006
+
1007
+ ``` python
1008
+ def render_empty_state(
1009
+ ids: CardStackHtmlIds, # HTML IDs for this card stack instance
1010
+ title: str = "No items available", # Main empty state message
1011
+ subtitle: str = "", # Optional subtitle text
1012
+ ) -> Any: # Empty state component
1013
+ "Render empty state when no items exist."
1014
+ ```
1015
+
1016
+ ### Viewport (`viewport.ipynb`)
1017
+
1018
+ > Card stack viewport with 3-section CSS Grid layout, slot rendering,
1019
+
1020
+ #### Import
1021
+
1022
+ ``` python
1023
+ from cjm_fasthtml_card_stack.components.viewport import (
1024
+ render_slot_card,
1025
+ render_all_slots_oob,
1026
+ render_viewport
1027
+ )
1028
+ ```
1029
+
1030
+ #### Functions
1031
+
1032
+ ``` python
1033
+ def _render_mode_sync_script(
1034
+ active_mode: Optional[str] = None, # Active keyboard mode name (None = navigation)
1035
+ ) -> Any: # Script element that syncs keyboard mode state
1036
+ "Generate script to sync keyboard navigation mode with rendered UI state."
1037
+ ```
1038
+
1039
+ ``` python
1040
+ def _render_click_overlay(
1041
+ item_index: int, # Index of the item this slot represents
1042
+ urls: CardStackUrls, # URL bundle for navigation
1043
+ ) -> Any: # Transparent click overlay element
1044
+ "Render transparent click-to-focus overlay for a context card slot."
1045
+ ```
1046
+
1047
+ ``` python
1048
+ def render_slot_card(
1049
+ slot_index: int, # Index of this slot in the viewport (0-based)
1050
+ focus_slot: int, # Which slot is the focused position
1051
+ card_items: List[Any], # Full items list
1052
+ item_index: Optional[int], # Item index for this slot (None for placeholder)
1053
+ render_card: Callable, # Callback: (item, CardRenderContext) -> FT
1054
+ state: CardStackState, # Current card stack state
1055
+ config: CardStackConfig, # Card stack configuration
1056
+ ids: CardStackHtmlIds, # HTML IDs for this instance
1057
+ urls: CardStackUrls, # URL bundle for navigation
1058
+ oob: bool = False, # Whether to render as OOB swap
1059
+ ) -> Any: # Slot content wrapper
1060
+ "Render a single card for a viewport slot."
1061
+ ```
1062
+
1063
+ ``` python
1064
+ def render_all_slots_oob(
1065
+ card_items: List[Any], # All data items
1066
+ state: CardStackState, # Current card stack state
1067
+ config: CardStackConfig, # Card stack configuration
1068
+ ids: CardStackHtmlIds, # HTML IDs for this instance
1069
+ urls: CardStackUrls, # URL bundle for navigation
1070
+ render_card: Callable, # Card renderer callback
1071
+ ) -> List[Any]: # List of OOB elements (3 sections)
1072
+ "Render all viewport sections with OOB swap for granular updates."
1073
+ ```
1074
+
1075
+ ``` python
1076
+ def _grid_template_rows(
1077
+ focus_slot: int, # Resolved focus slot position
1078
+ visible_count: int, # Number of visible slots
1079
+ ) -> str: # CSS grid-template-rows value
1080
+ "Compute CSS grid-template-rows based on focus slot position."
1081
+ ```
1082
+
1083
+ ``` python
1084
+ def render_viewport(
1085
+ card_items: List[Any], # All data items
1086
+ state: CardStackState, # Current card stack state
1087
+ config: CardStackConfig, # Card stack configuration
1088
+ ids: CardStackHtmlIds, # HTML IDs for this instance
1089
+ urls: CardStackUrls, # URL bundle for navigation
1090
+ render_card: Callable, # Card renderer callback
1091
+ form_input_name: str = "focused_index", # Name for the focused index hidden input
1092
+ ) -> Any: # Viewport component with 3-section layout
1093
+ "Render the card stack viewport with 3-section CSS Grid layout."
1094
+ ```
1095
+
1096
+ ### JS: Viewport Height (`viewport.ipynb`)
1097
+
1098
+ > JavaScript generator for dynamic viewport height calculation.
1099
+
1100
+ #### Import
1101
+
1102
+ ``` python
1103
+ from cjm_fasthtml_card_stack.js.viewport import (
1104
+ generate_viewport_height_js
1105
+ )
1106
+ ```
1107
+
1108
+ #### Functions
1109
+
1110
+ ``` python
1111
+ def generate_viewport_height_js(
1112
+ """
1113
+ Generate JS for dynamic viewport height calculation.
1114
+
1115
+ Uses the browser's layout engine to measure the space consumed by sibling
1116
+ elements rather than summing individual heights. This naturally handles
1117
+ margin collapsing regardless of the container's display type.
1118
+
1119
+ Strategy: temporarily collapse the card stack to 0 height, measure how
1120
+ much vertical space the remaining content occupies, then set the card
1121
+ stack height to fill the remaining viewport space.
1122
+ """
1123
+ ```