Coverage for src / lexigram / ui / layouts / head.py: 100%
41 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 04:11 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 04:11 +0800
1"""Head section renderer.
3Renders the HTML head section with CSS and meta tags.
4"""
6from __future__ import annotations
8from markupsafe import escape
10from lexigram.ui.config import HeadConfig
13class HeadRenderer:
14 """Renders the head section content."""
16 CSS_FRAMEWORKS = {
17 "tailwind": "https://cdn.tailwindcss.com",
18 "pico": "https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css",
19 }
21 def __init__(self, config: HeadConfig | None = None):
22 """Initialize the renderer.
24 Args:
25 config: Head configuration
26 """
27 self.config = config or HeadConfig()
29 def render(self, extra_css: str = "") -> str:
30 """Render the head content.
32 Args:
33 extra_css: Additional inline CSS to include
35 Returns:
36 HTML string for head section
37 """
38 parts: list[str] = []
40 # CSS Framework
41 parts.append(self._render_css_framework())
43 # Font
44 if self.config.font_url:
45 parts.append('<link rel="preconnect" href="https://fonts.googleapis.com">')
46 parts.append(
47 f'<link rel="stylesheet" href="{escape(self.config.font_url)}">',
48 )
50 # Icon library
51 parts.append(self._render_icon_library())
53 # Custom CSS files
54 for css_file in self.config.css_files:
55 parts.append(f'<link rel="stylesheet" href="{escape(css_file)}">')
57 # HTMX
58 parts.append(f'<script src="{escape(self.config.htmx_url)}"></script>')
60 # Hyperscript
61 if self.config.include_hyperscript:
62 parts.append(
63 f'<script src="{escape(self.config.hyperscript_url)}"></script>',
64 )
66 # Inline CSS
67 if self.config.inline_css or extra_css:
68 parts.append("<style>")
69 if self.config.inline_css:
70 parts.append(self.config.inline_css)
71 if extra_css:
72 parts.append(extra_css)
73 parts.append("</style>")
75 return "\n".join(parts)
77 def _render_css_framework(self) -> str:
78 """Render CSS framework link."""
79 if self.config.css_framework_url:
80 url = self.config.css_framework_url
81 else:
82 url = self.CSS_FRAMEWORKS.get(self.config.css_framework, "")
84 if not url:
85 return ""
87 if self.config.css_framework == "tailwind":
88 return f'<script src="{escape(url)}"></script>'
89 return f'<link rel="stylesheet" href="{escape(url)}">'
91 def _render_icon_library(self) -> str:
92 """Render icon library script."""
93 if self.config.icon_library == "lucide" and self.config.icon_library_url:
94 return f"""
95<script src="{escape(self.config.icon_library_url)}"></script>
96<script>
97 document.addEventListener('DOMContentLoaded', function() {{
98 if (window.lucide) lucide.createIcons();
99 }});
100</script>"""
101 return ""
104__all__ = ["HeadConfig", "HeadRenderer"]