Coverage for src / lexigram / ui / molecules / virtual_scroll.py: 31%

32 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-10 04:11 +0800

1"""Virtual Scroll component for Lexigram Admin.""" 

2 

3from __future__ import annotations 

4 

5from typing import Any 

6 

7from lexigram.ui.core.base import Component, el 

8 

9 

10class VirtualScroll(Component): 

11 """Component for handling infinite scroll and virtualization. 

12 

13 This uses HTMX 'revealed' trigger to load next chunks of data. 

14 """ 

15 

16 def __init__( 

17 self, 

18 url: str, 

19 total_items: int | None = None, 

20 chunk_size: int = 50, 

21 target_id: str | None = None, 

22 placeholder: Any | None = None, 

23 **props, 

24 ): 

25 super().__init__(**props) 

26 self.url = url 

27 self.total_items = total_items 

28 self.chunk_size = chunk_size 

29 self.target_id = target_id 

30 self.placeholder = placeholder 

31 

32 def render(self) -> Any: 

33 return el( 

34 "div", 

35 self.children, 

36 id=self.target_id or "virtual-scroll-container", 

37 class_="virtual-scroll", 

38 **self.props, 

39 ) 

40 

41 

42def render_infinite_row( 

43 row_content: Any, 

44 next_url: str | None = None, 

45 trigger: str = "revealed", 

46 threshold: str = "0.5", 

47) -> Any: 

48 """Helper to render a row that triggers the next page load. 

49 

50 Args: 

51 row_content: The content of the current row 

52 next_url: URL to fetch the next page. If None, no trigger is added. 

53 trigger: HTMX trigger (default 'revealed') 

54 threshold: Intersection observer threshold 

55 """ 

56 if not next_url: 

57 return row_content 

58 

59 attrs = { 

60 "hx-get": next_url, 

61 "hx-trigger": f"{trigger} threshold:{threshold}", 

62 "hx-swap": "afterend", 

63 } 

64 

65 if hasattr(row_content, "attrs"): 

66 row_content.attrs.update(attrs) 

67 return row_content 

68 

69 return el("div", row_content, **attrs) 

70 

71 

72class InfiniteScrollTrigger(Component): 

73 """A component that triggers an HTMX request when scrolled into view.""" 

74 

75 def __init__( 

76 self, 

77 url: str, 

78 trigger: str = "revealed", 

79 target: str | None = None, 

80 swap: str = "afterend", 

81 select: str | None = None, 

82 **props, 

83 ): 

84 super().__init__(**props) 

85 self.url = url 

86 self.trigger = trigger 

87 self.target = target 

88 self.swap = swap 

89 self.select = select or "#table-content > *" 

90 

91 def render(self) -> Any: 

92 children = self.children or [ 

93 el( 

94 "div", 

95 el( 

96 "i", 

97 class_="fas fa-spinner fa-spin mr-2 text-primary", 

98 ), 

99 el("span", "Loading more...", class_="text-muted-foreground text-sm"), 

100 class_="flex items-center justify-center p-4 w-full", 

101 ), 

102 ] 

103 

104 return el( 

105 "div", 

106 *children, 

107 **{ 

108 "hx-get": self.url, 

109 "hx-trigger": self.trigger, 

110 "hx-target": self.target, 

111 "hx-swap": self.swap, 

112 "hx-select": self.select, 

113 **self.props, 

114 }, 

115 )