cjm-fasthtml-card-stack 0.0.3__py3-none-any.whl → 0.0.4__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.
@@ -1 +1 @@
1
- __version__ = "0.0.3"
1
+ __version__ = "0.0.4"
@@ -210,19 +210,17 @@ def render_all_slots_oob(
210
210
 
211
211
  # %% ../../nbs/components/viewport.ipynb #v1000031
212
212
  def _grid_template_rows(
213
- focus_slot: int, # Resolved focus slot position
214
- visible_count: int, # Number of visible slots
213
+ focus_position: Optional[int] = None, # Focus slot offset (None=center, -1=bottom, 0=top)
215
214
  ) -> str: # CSS grid-template-rows value
216
- """Compute CSS grid-template-rows based on focus slot position."""
217
- slots_before = focus_slot
218
- slots_after = visible_count - focus_slot - 1
219
-
220
- if slots_before == 0:
221
- return "auto 1fr" # No before section
222
- elif slots_after == 0:
223
- return "1fr auto" # No after section
215
+ """Compute CSS grid-template-rows based on focus position intent."""
216
+ if focus_position is None:
217
+ return "1fr auto 1fr" # Center: always 3-section
218
+ elif focus_position == 0:
219
+ return "auto 1fr" # Top: focused first
220
+ elif focus_position < 0:
221
+ return "1fr auto" # Bottom: focused last
224
222
  else:
225
- return "1fr auto 1fr" # Standard 3-section
223
+ return "1fr auto 1fr" # Custom positive: always 3-section
226
224
 
227
225
  # %% ../../nbs/components/viewport.ipynb #v1000041
228
226
  def render_viewport(
@@ -285,8 +283,8 @@ def render_viewport(
285
283
  cls=section_cls(justify.start)
286
284
  )
287
285
 
288
- # Grid template adapts to focus position
289
- grid_rows = _grid_template_rows(focus_slot, state.visible_count)
286
+ # Grid template based on focus position intent (stable across count changes)
287
+ grid_rows = _grid_template_rows(state.focus_position)
290
288
 
291
289
  inner_cls = combine_classes(grid_display, w.full, h.full, m.x.auto, gap(4))
292
290
  inner_style = f"grid-template-rows: {grid_rows}; max-width: {state.card_width}rem"
@@ -6,7 +6,7 @@
6
6
  __all__ = ['global_callback_name', 'generate_card_stack_js']
7
7
 
8
8
  # %% ../../nbs/js/core.ipynb #jc000003
9
- from typing import Any, Tuple
9
+ from typing import Any, Optional, Tuple
10
10
 
11
11
  from fasthtml.common import Script
12
12
 
@@ -166,7 +166,7 @@ def _generate_card_count_mgmt_js(
166
166
  if ('{urls.update_viewport}') {{
167
167
  htmx.ajax('POST', '{urls.update_viewport}', {{
168
168
  target: '#' + '{ids.card_stack}',
169
- swap: 'outerHTML',
169
+ swap: 'none',
170
170
  values: {{ visible_count: count }}
171
171
  }});
172
172
  }}
@@ -184,15 +184,36 @@ def _generate_card_count_mgmt_js(
184
184
  def _generate_coordinator_js(
185
185
  ids: CardStackHtmlIds, # HTML IDs for this instance
186
186
  config: CardStackConfig, # Config for prefix-unique listener guards
187
+ focus_position: Optional[int] = None, # Focus slot offset (None=center, -1=bottom, 0=top)
187
188
  ) -> str: # JS code fragment for master coordinator
188
189
  """Generate JS for the master coordinator and HTMX listener."""
189
190
  guard_var = f"_csMasterListener_{config.prefix.replace('-', '_')}"
191
+ js_focus_pos = "null" if focus_position is None else str(focus_position)
190
192
  return f"""
193
+ // === Grid Template Management ===
194
+ ns.applyGridTemplate = function() {{
195
+ const inner = document.getElementById('{ids.card_stack_inner}');
196
+ if (!inner) return;
197
+ const focusPosRaw = {js_focus_pos};
198
+ let tmpl;
199
+ if (focusPosRaw === null) {{
200
+ tmpl = '1fr auto 1fr';
201
+ }} else if (focusPosRaw === 0) {{
202
+ tmpl = 'auto 1fr';
203
+ }} else if (focusPosRaw < 0) {{
204
+ tmpl = '1fr auto';
205
+ }} else {{
206
+ tmpl = '1fr auto 1fr';
207
+ }}
208
+ inner.style.gridTemplateRows = tmpl;
209
+ }};
210
+
191
211
  // === Master Coordinator ===
192
212
  ns.applyAllViewportSettings = function() {{
193
213
  requestAnimationFrame(function() {{
194
214
  if (ns.applyWidth) ns.applyWidth();
195
215
  if (ns.applyScale) ns.applyScale();
216
+ if (ns.applyGridTemplate) ns.applyGridTemplate();
196
217
  if (ns.recalculateHeight) ns.recalculateHeight();
197
218
 
198
219
  const cs = document.getElementById('{ids.card_stack}');
@@ -272,6 +293,7 @@ def generate_card_stack_js(
272
293
  urls: CardStackUrls, # URL bundle for routing
273
294
  container_id: str = "", # Consumer's parent container ID (for height calc)
274
295
  extra_scripts: Tuple[str, ...] = (), # Additional JS to include in the IIFE
296
+ focus_position: Optional[int] = None, # Focus slot offset (None=center, -1=bottom, 0=top)
275
297
  ) -> Any: # Script element with all card stack JavaScript
276
298
  """Compose all card stack JS into a single namespaced IIFE."""
277
299
  prefix = config.prefix
@@ -285,7 +307,7 @@ def generate_card_stack_js(
285
307
  scale_js = _generate_scale_mgmt_js(ids, config, urls)
286
308
  count_js = _generate_card_count_mgmt_js(ids, config, urls)
287
309
  global_cbs_js = _generate_global_callbacks_js(config)
288
- coordinator_js = _generate_coordinator_js(ids, config)
310
+ coordinator_js = _generate_coordinator_js(ids, config, focus_position)
289
311
 
290
312
  return Script(f"""(function() {{
291
313
  window.cardStacks = window.cardStacks || {{}};
@@ -127,17 +127,17 @@ def card_stack_update_viewport(
127
127
  ids: CardStackHtmlIds, # HTML IDs for this instance
128
128
  urls: CardStackUrls, # URL bundle for navigation
129
129
  render_card: Callable, # Card renderer callback
130
- ) -> Any: # Full viewport component (outerHTML swap)
131
- """Update viewport with new card count. Mutates state.visible_count in place."""
130
+ ) -> Tuple: # OOB section elements (3 viewport sections)
131
+ """Update viewport with new card count via OOB section swaps. Mutates state.visible_count in place."""
132
132
  state.visible_count = visible_count
133
- return render_viewport(
133
+ return tuple(build_slots_response(
134
134
  card_items=card_items,
135
135
  state=state,
136
136
  config=config,
137
137
  ids=ids,
138
138
  urls=urls,
139
139
  render_card=render_card,
140
- )
140
+ ))
141
141
 
142
142
  # %% ../../nbs/routes/handlers.ipynb #h1000013
143
143
  def card_stack_save_width(
@@ -100,7 +100,7 @@ def init_card_stack_router(
100
100
 
101
101
  @router
102
102
  def update_viewport(visible_count: int) -> Any:
103
- """Update viewport with new card count (full outerHTML swap)."""
103
+ """Update viewport with new card count (OOB section swaps)."""
104
104
  state = state_getter()
105
105
  items = get_items()
106
106
  result = card_stack_update_viewport(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cjm-fasthtml-card-stack
3
- Version: 0.0.3
3
+ Version: 0.0.4
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
@@ -102,38 +102,38 @@ graph LR
102
102
  components_states --> core_html_ids
103
103
  components_viewport --> core_config
104
104
  components_viewport --> core_html_ids
105
- components_viewport --> components_states
106
- components_viewport --> core_constants
107
105
  components_viewport --> core_models
108
106
  components_viewport --> helpers_focus
107
+ components_viewport --> core_constants
108
+ components_viewport --> components_states
109
109
  helpers_focus --> core_html_ids
110
110
  js_core --> core_constants
111
111
  js_core --> core_config
112
112
  js_core --> core_html_ids
113
- js_core --> js_viewport
114
- js_core --> js_navigation
115
- js_core --> js_scroll
116
113
  js_core --> core_models
114
+ js_core --> js_scroll
115
+ js_core --> js_navigation
116
+ js_core --> js_viewport
117
117
  js_core --> core_button_ids
118
118
  js_navigation --> core_button_ids
119
119
  js_scroll --> core_constants
120
- js_scroll --> core_button_ids
121
120
  js_scroll --> core_html_ids
121
+ js_scroll --> core_button_ids
122
122
  js_viewport --> core_html_ids
123
+ keyboard_actions --> js_core
123
124
  keyboard_actions --> core_config
124
125
  keyboard_actions --> core_html_ids
125
- keyboard_actions --> js_core
126
126
  keyboard_actions --> core_models
127
127
  keyboard_actions --> core_button_ids
128
- routes_handlers --> core_models
129
128
  routes_handlers --> core_config
130
129
  routes_handlers --> core_html_ids
131
- routes_handlers --> components_progress
132
- routes_handlers --> helpers_focus
130
+ routes_handlers --> core_models
133
131
  routes_handlers --> components_viewport
132
+ routes_handlers --> helpers_focus
133
+ routes_handlers --> components_progress
134
+ routes_router --> routes_handlers
134
135
  routes_router --> core_config
135
136
  routes_router --> core_html_ids
136
- routes_router --> routes_handlers
137
137
  routes_router --> core_models
138
138
  ```
139
139
 
@@ -514,6 +514,7 @@ def _generate_card_count_mgmt_js(
514
514
  def _generate_coordinator_js(
515
515
  ids: CardStackHtmlIds, # HTML IDs for this instance
516
516
  config: CardStackConfig, # Config for prefix-unique listener guards
517
+ focus_position: Optional[int] = None, # Focus slot offset (None=center, -1=bottom, 0=top)
517
518
  ) -> str: # JS code fragment for master coordinator
518
519
  "Generate JS for the master coordinator and HTMX listener."
519
520
  ```
@@ -670,8 +671,8 @@ def card_stack_update_viewport(
670
671
  ids: CardStackHtmlIds, # HTML IDs for this instance
671
672
  urls: CardStackUrls, # URL bundle for navigation
672
673
  render_card: Callable, # Card renderer callback
673
- ) -> Any: # Full viewport component (outerHTML swap)
674
- "Update viewport with new card count. Mutates state.visible_count in place."
674
+ ) -> Tuple: # OOB section elements (3 viewport sections)
675
+ "Update viewport with new card count via OOB section swaps. Mutates state.visible_count in place."
675
676
  ```
676
677
 
677
678
  ``` python
@@ -1082,10 +1083,9 @@ def render_all_slots_oob(
1082
1083
 
1083
1084
  ``` python
1084
1085
  def _grid_template_rows(
1085
- focus_slot: int, # Resolved focus slot position
1086
- visible_count: int, # Number of visible slots
1086
+ focus_position: Optional[int] = None, # Focus slot offset (None=center, -1=bottom, 0=top)
1087
1087
  ) -> str: # CSS grid-template-rows value
1088
- "Compute CSS grid-template-rows based on focus slot position."
1088
+ "Compute CSS grid-template-rows based on focus position intent."
1089
1089
  ```
1090
1090
 
1091
1091
  ``` python
@@ -1,10 +1,10 @@
1
- cjm_fasthtml_card_stack/__init__.py,sha256=4GZKi13lDTD25YBkGakhZyEQZWTER_OWQMNPoH_UM2c,22
1
+ cjm_fasthtml_card_stack/__init__.py,sha256=1mptEzQihbdyqqzMgdns_j5ZGK9gz7hR2bsgA_TnjO4,22
2
2
  cjm_fasthtml_card_stack/_modidx.py,sha256=jYNj6LPbD5I8yDghD3Lx9XWsDnWqc_oNCESguSF8blA,25105
3
3
  cjm_fasthtml_card_stack/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  cjm_fasthtml_card_stack/components/controls.py,sha256=C3iRvaiRe0ti8S7ogTSKRRRdw-z6vz77mkuvKVmIz-Y,4093
5
5
  cjm_fasthtml_card_stack/components/progress.py,sha256=BBeR4EWEikA54bVG41iLdlyyGRFwMJtEh8-cX9L9QLI,1500
6
6
  cjm_fasthtml_card_stack/components/states.py,sha256=ClgpdS3e19ccO6VHU6ajulU_yb7c1bQVZf9nSsd5QPc,3797
7
- cjm_fasthtml_card_stack/components/viewport.py,sha256=vLH9oTbSXlkWZEDHTTKWs38Shd9_ajvSpq1mrvcuOGU,11741
7
+ cjm_fasthtml_card_stack/components/viewport.py,sha256=LOocpEh4V_pkTqpdOyWBX50ctil5rf6l7NQl8QBNcGs,11794
8
8
  cjm_fasthtml_card_stack/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  cjm_fasthtml_card_stack/core/button_ids.py,sha256=elKKLBrt-Hu6vgWhrcVlCXgdUeYXHQyfn3NyhhEmxW4,2123
10
10
  cjm_fasthtml_card_stack/core/config.py,sha256=XYaptNnzZ8sRcCdxWA_RB1FwBiXU8Ko6mEsNSbxhtb0,1714
@@ -14,23 +14,24 @@ cjm_fasthtml_card_stack/core/models.py,sha256=soivwLNBbSNQmcCb9EpFz09gaDhb9HesXb
14
14
  cjm_fasthtml_card_stack/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  cjm_fasthtml_card_stack/helpers/focus.py,sha256=PTbsZagutEM8pwpLUKvM8Gjhv7rVNMbNbO27x5uC3YM,2626
16
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
17
+ cjm_fasthtml_card_stack/js/core.py,sha256=k6s4sYMz19-5FzFk7MqmrrR8SUHtJnslqm_sfXE6qVs,13486
18
18
  cjm_fasthtml_card_stack/js/navigation.py,sha256=471F3NDxdH-BQPLPQuUvtyACqi6g6EOcELYs_sP1hyk,1288
19
19
  cjm_fasthtml_card_stack/js/scroll.py,sha256=X6j5JMOjzgH57sDK1fazNRyGYH2GGEDQeZ0dURFEjC4,3062
20
20
  cjm_fasthtml_card_stack/js/viewport.py,sha256=hFWr3zt9odyuaYdRdJmhKJcS_vsJyLRhz-HlZPvGh54,5653
21
21
  cjm_fasthtml_card_stack/keyboard/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  cjm_fasthtml_card_stack/keyboard/actions.py,sha256=rfCD8lLVXXVPrML-8dj48bT_zOKN7g8VmPMNE21IAJI,7573
23
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.3.dist-info/licenses/LICENSE,sha256=xV8xoN4VOL0uw9X8RSs2IMuD_Ss_a9yAbtGNeBWZwnw,11337
24
+ cjm_fasthtml_card_stack/routes/handlers.py,sha256=Rnfie_QcvyIA5P4PqXOX8FjZFA1ohzbLga4k9HoUGhY,6861
25
+ cjm_fasthtml_card_stack/routes/router.py,sha256=88a0NEnPX5agS_VsNUcxZ8pC8c0P-eaFy0ZZoIn0UP4,5129
26
+ cjm_fasthtml_card_stack-0.0.4.dist-info/licenses/LICENSE,sha256=xV8xoN4VOL0uw9X8RSs2IMuD_Ss_a9yAbtGNeBWZwnw,11337
27
27
  demos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
28
  demos/basic.py,sha256=8GVV7eSWOzcz67L6XiPqwKFy886AT5y7Ok-T2ZPX32Q,3815
29
29
  demos/bottom.py,sha256=ZLpgpUfOvCzM3rVf3ufvZnSAxuzOQ5gND7RsSUgNpJo,3341
30
+ demos/custom_position.py,sha256=wra57olAq-RuzcEIl4Jwl_-EeXHx0LW0YLqFB8-w-BI,3613
30
31
  demos/data.py,sha256=8yVV_E0nLcr0L1PcrTG-DaT-V36apF74ALKNIZuSQ70,4471
31
- demos/shared.py,sha256=8AMZhgjyobY0pnt_7lmZ91gxb8X1UA1biK0KPfherjo,5384
32
- cjm_fasthtml_card_stack-0.0.3.dist-info/METADATA,sha256=mv8IdrvEVlWeuYLBn5BKQBZpH8rlUXo9GQeRdhQEvds,36832
33
- cjm_fasthtml_card_stack-0.0.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
34
- cjm_fasthtml_card_stack-0.0.3.dist-info/entry_points.txt,sha256=qTw6qaijkEUH1AcPQhpTqJ8TPiUSMnpn0Cvg4hXoX90,68
35
- cjm_fasthtml_card_stack-0.0.3.dist-info/top_level.txt,sha256=ydBTJsY2ONaDryp85HkjhdGHEuuUOoMGdrOgFA2ddyg,30
36
- cjm_fasthtml_card_stack-0.0.3.dist-info/RECORD,,
32
+ demos/shared.py,sha256=88vNOVODcauWHoswzsZt9vQhEPsam8uWGujSgisVxhU,5433
33
+ cjm_fasthtml_card_stack-0.0.4.dist-info/METADATA,sha256=xHG3PiHNzISON6yxr3paljb6d1yLWFYF06oUyztc8CM,36946
34
+ cjm_fasthtml_card_stack-0.0.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
35
+ cjm_fasthtml_card_stack-0.0.4.dist-info/entry_points.txt,sha256=qTw6qaijkEUH1AcPQhpTqJ8TPiUSMnpn0Cvg4hXoX90,68
36
+ cjm_fasthtml_card_stack-0.0.4.dist-info/top_level.txt,sha256=ydBTJsY2ONaDryp85HkjhdGHEuuUOoMGdrOgFA2ddyg,30
37
+ cjm_fasthtml_card_stack-0.0.4.dist-info/RECORD,,
@@ -0,0 +1,114 @@
1
+ """Custom focus position demo (focus at slot 1)."""
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, badge_styles
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 card with position info for the custom focus demo."""
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.accent if is_focused else badge_colors.neutral,
30
+ )
31
+ )
32
+
33
+ distance_label = Span(
34
+ f"slot offset: {context.distance_from_focus:+d}",
35
+ cls=combine_classes(
36
+ badge, badge_styles.ghost,
37
+ )
38
+ ) if not is_focused else Span(
39
+ "focused",
40
+ cls=combine_classes(badge, badge_colors.accent),
41
+ )
42
+
43
+ return Div(
44
+ Div(
45
+ Div(index_badge, distance_label, cls=combine_classes(m.b(1))),
46
+ P(
47
+ item,
48
+ cls=combine_classes(text_dui.base_content),
49
+ style="font-size: calc(0.875rem * var(--card-stack-scale, 100) / 100)",
50
+ ),
51
+ cls=combine_classes(card_body, p(3)),
52
+ ),
53
+ cls=combine_classes(
54
+ card,
55
+ bg_dui.base_100,
56
+ w.full,
57
+ ),
58
+ )
59
+
60
+
61
+ def setup(route_prefix="/custom"):
62
+ """Set up the custom focus position demo.
63
+
64
+ Returns dict with config, ids, btn_ids, router, urls, state management,
65
+ and page rendering dependencies.
66
+ """
67
+ config = CardStackConfig(prefix="custom", click_to_focus=True)
68
+ ids = CardStackHtmlIds(prefix=config.prefix)
69
+ btn_ids = CardStackButtonIds(prefix=config.prefix)
70
+
71
+ state = CardStackState(
72
+ visible_count=5,
73
+ card_width=60,
74
+ focus_position=1, # Second slot from top
75
+ )
76
+
77
+ def get_state():
78
+ return state
79
+
80
+ def set_state(s):
81
+ nonlocal state
82
+ state.focused_index = s.focused_index
83
+ state.visible_count = s.visible_count
84
+ state.card_width = s.card_width
85
+ state.card_scale = s.card_scale
86
+ state.active_mode = s.active_mode
87
+ state.focus_position = s.focus_position
88
+
89
+ def get_items():
90
+ return SAMPLE_ITEMS
91
+
92
+ router, urls = init_card_stack_router(
93
+ config=config,
94
+ state_getter=get_state,
95
+ state_setter=set_state,
96
+ get_items=get_items,
97
+ render_card=render_card,
98
+ route_prefix=route_prefix,
99
+ )
100
+
101
+ return dict(
102
+ config=config,
103
+ ids=ids,
104
+ btn_ids=btn_ids,
105
+ router=router,
106
+ urls=urls,
107
+ get_state=get_state,
108
+ get_items=get_items,
109
+ render_card=render_card,
110
+ container_id="custom-demo-container",
111
+ title="Custom Focus Position",
112
+ description="Focus at slot 1 (second from top). One context card above, rest below.",
113
+ progress_label="Item",
114
+ )
demos/shared.py CHANGED
@@ -79,6 +79,7 @@ def render_demo_page(
79
79
  config=config,
80
80
  urls=urls,
81
81
  container_id=container_id,
82
+ focus_position=state.focus_position,
82
83
  )
83
84
 
84
85
  return Div(