JS: Viewport Height

JavaScript generator for dynamic viewport height calculation.

generate_viewport_height_js

Delegates to cjm-fasthtml-viewport-fit for the core measurement algorithm.

The card stack uses the library’s individual generator functions (not the top-level IIFE compositor) because the card stack has its own IIFE in core.py that coordinates resize across multiple subsystems (viewport height, width, scale, scroll, touch, auto-adjust).

What the card stack coordinator handles (not delegated to viewport-fit): - IIFE wrapper — the card stack has its own in core.py - HTMX settle — coordinator’s _afterSettleHandler with card-stack-specific filtering

What viewport-fit provides: - Debug helpers (conditional logging via window.viewportFitDebug_{prefix}) - Space-below DOM walker - Collapse-measure-apply height calculation (with box-sizing awareness) - Debounced resize handler with auto-adjust callback - Sibling observation via ResizeObserver (auto-recalculates when siblings resize) - Initialization with scroll-to-top for consistent HTMX SPA measurements


source

generate_viewport_height_js


def generate_viewport_height_js(
    ids:CardStackHtmlIds, # HTML IDs for this card stack instance
    container_id:str='', # Unused — kept for API compatibility
)->str: # JavaScript code fragment for viewport height calculation

Generate JS for dynamic viewport height calculation via cjm-fasthtml-viewport-fit.

Delegates to the viewport-fit library’s individual generator functions. The card stack coordinator handles HTMX settle events separately.

# Test that the generator produces valid JS referencing correct IDs
ids = CardStackHtmlIds(prefix="cs0")
js = generate_viewport_height_js(ids, container_id="my-container")
assert ids.card_stack in js
assert ids.card_stack_inner in js
assert "ns.recalculateHeight" in js
assert "_calculateAndSetHeight" in js

# Verify position-based measurement approach
assert "spaceAbove" in js
assert "spaceBelow" in js
assert "_calculateSpaceBelow" in js

# Verify temporary collapse approach for accurate measurement
assert "'0px'" in js
assert "offsetHeight" in js  # Force reflow

# Verify position-based sibling detection (beside vs below)
assert "siblingRect.top < currentRect.bottom" in js

# Verify debug mode uses viewport-fit convention
assert f"viewportFitDebug_{ids.prefix}" in js
assert f"[vf:{ids.prefix}]" in js

# Verify auto-adjust trigger in resize handler
assert "ns.triggerAutoAdjust" in js

# Verify resize listener uses remove-and-replace
handler_key = f"_vfResizeHandler_{ids.prefix.replace('-', '_')}"
assert f"window.{handler_key}" in js
assert "removeEventListener" in js

# Verify sibling observer
assert "_setupSiblingObserver" in js
assert "ResizeObserver" in js
assert "ns._setupSiblingObserver" in js

# Verify init with scroll-to-top
assert "window.scrollTo(0, 0)" in js

# Verify section comment for core.py composition
assert "Viewport Height" in js

print("Viewport height JS generator tests passed!")
Viewport height JS generator tests passed!
# container_id is unused — both paths produce identical output
js_no_container = generate_viewport_height_js(ids)
assert js == js_no_container
# Verify position-based measurement is present
assert "spaceAbove" in js_no_container
assert "_calculateSpaceBelow" in js_no_container
print("Viewport height JS container_id compatibility test passed!")
Viewport height JS container_id compatibility test passed!