feat: navigation
This commit is contained in:
@@ -19,7 +19,7 @@ from uframe.ir import (
|
||||
Gauge, Sparkline, Status, Table, TextSpan,
|
||||
Form, Field, Password, Radio, Checkbox, FormButton,
|
||||
Let, Source, IfBlock, ForLoop, CacheControl, OnSubmit, StateDecl,
|
||||
BigTitle, ImageNode,
|
||||
BigTitle, ImageNode, HNav, VNav, NavItem,
|
||||
ComponentDef, ComponentUse,
|
||||
SourceType,
|
||||
BorderWeight, HeadingLevel, DividerStyle, ListStyle, Align, Style,
|
||||
@@ -222,9 +222,19 @@ def _parse_line(keyword: str, args: list[str], line_num: int) -> IRNode:
|
||||
return ListNode(list_style=style, source_line=line_num)
|
||||
|
||||
elif keyword == "item":
|
||||
# Could be a list item or a nav item — disambiguated by parent in tree builder
|
||||
if len(args) >= 2 and ("/" in args[1] or ":" in args[1]):
|
||||
# Nav item: item "Label" "/dest.mu" [active]
|
||||
label = args[0]
|
||||
dest = args[1]
|
||||
active = "active" in args[2:] if len(args) > 2 else False
|
||||
return _NavItemNode("item", label, dest, active, line_num)
|
||||
content = args[0] if args else ""
|
||||
return ListItem(content=content, source_line=line_num)
|
||||
|
||||
elif keyword == "separator":
|
||||
return _NavItemNode("separator", "", "", False, line_num)
|
||||
|
||||
# Style modifiers (applied to parent)
|
||||
elif keyword == "align":
|
||||
val = args[0].lower() if args else "left"
|
||||
@@ -427,6 +437,15 @@ def _parse_line(keyword: str, args: list[str], line_num: int) -> IRNode:
|
||||
content = " ".join([keyword] + args)
|
||||
return Text(content=content, source_line=line_num)
|
||||
|
||||
elif keyword == "hnav":
|
||||
nav_style = args[0] if args else "bar"
|
||||
return HNav(nav_style=nav_style, source_line=line_num)
|
||||
|
||||
elif keyword == "vnav":
|
||||
nav_style = args[0] if args else "list"
|
||||
nav_width = int(args[1]) if len(args) > 1 and args[1].isdigit() else 0
|
||||
return VNav(nav_style=nav_style, nav_width=nav_width, source_line=line_num)
|
||||
|
||||
elif keyword == "image":
|
||||
path = args[0] if args else ""
|
||||
mode = args[1] if len(args) > 1 else "braille"
|
||||
@@ -477,6 +496,16 @@ def _parse_line(keyword: str, args: list[str], line_num: int) -> IRNode:
|
||||
return ComponentUse(comp_name=keyword, args=args, source_line=line_num)
|
||||
|
||||
|
||||
class _NavItemNode(IRNode):
|
||||
"""Temporary node — absorbed by parent HNav/VNav during tree building."""
|
||||
def __init__(self, kind: str, label: str, dest: str, active: bool, line_num: int):
|
||||
super().__init__(source_line=line_num)
|
||||
self.kind = kind
|
||||
self.label = label
|
||||
self.dest = dest
|
||||
self.active = active
|
||||
|
||||
|
||||
class _ThemeDirective(IRNode):
|
||||
"""Temporary node — sets theme_name on the Page during tree building."""
|
||||
def __init__(self, theme_name: str, line_num: int):
|
||||
@@ -840,6 +869,30 @@ def parse(source: str, components: dict[str, ComponentDef] | None = None) -> Pag
|
||||
break
|
||||
continue
|
||||
|
||||
# Nav items — absorbed by parent HNav/VNav
|
||||
if isinstance(node, _NavItemNode):
|
||||
for si in range(len(stack) - 1, -1, -1):
|
||||
parent = stack[si][1]
|
||||
if isinstance(parent, (HNav, VNav)):
|
||||
parent.items.append(NavItem(
|
||||
kind=node.kind, label=node.label,
|
||||
dest=node.dest, active=node.active))
|
||||
break
|
||||
continue
|
||||
|
||||
# Headings inside vnav become nav headings
|
||||
if isinstance(node, Heading):
|
||||
for si in range(len(stack) - 1, -1, -1):
|
||||
if isinstance(stack[si][1], VNav):
|
||||
stack[si][1].items.append(NavItem(
|
||||
kind="heading", label=node.text))
|
||||
break
|
||||
else:
|
||||
# Not inside a vnav — proceed as normal heading
|
||||
pass
|
||||
if any(isinstance(stack[si][1], VNav) for si in range(len(stack))):
|
||||
continue
|
||||
|
||||
# Use directive — load standard library components
|
||||
if isinstance(node, _UseDirective):
|
||||
lib_comps = _load_library(node.lib_path)
|
||||
|
||||
Reference in New Issue
Block a user