@effect-tui/react 0.6.0 → 0.6.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-tui/react",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "React bindings for @effect-tui/core",
5
5
  "type": "module",
6
6
  "files": [
@@ -83,7 +83,7 @@
83
83
  "prepublishOnly": "bun run typecheck && bun run build"
84
84
  },
85
85
  "dependencies": {
86
- "@effect-tui/core": "^0.6.0",
86
+ "@effect-tui/core": "^0.6.1",
87
87
  "@effect/platform": "^0.94.0",
88
88
  "@effect/platform-bun": "^0.87.0",
89
89
  "@effect/rpc": "^0.73.0",
@@ -1,4 +1,4 @@
1
- // ListView.tsx — Selection-aware scrolling list with scroll-into-view behavior
1
+ // ListView.tsx — Virtualized selection-aware scrolling list
2
2
  import type { ReactNode } from "react"
3
3
  import { useEffect, useRef } from "react"
4
4
  import { useScroll } from "../hooks/use-scroll.js"
@@ -20,12 +20,15 @@ export interface ListViewProps<T> {
20
20
  showScrollbar?: boolean
21
21
  /** Empty state content when items is empty */
22
22
  emptyContent?: ReactNode
23
+ /** Number of extra items to render above/below viewport for smooth scrolling (default: 3) */
24
+ overscan?: number
23
25
  }
24
26
 
25
27
  /**
26
- * A scrolling list view with selection and scroll-into-view behavior.
28
+ * A virtualized scrolling list view with selection and scroll-into-view behavior.
27
29
  *
28
30
  * Features:
31
+ * - **Virtualized**: Only renders visible items + overscan buffer for performance
29
32
  * - Automatically scrolls to keep selected item visible when selection changes
30
33
  * - Supports mouse wheel scrolling with macOS-style acceleration
31
34
  * - Shows a scroll bar when content overflows
@@ -68,8 +71,9 @@ export function ListView<T>({
68
71
  scrollPadding = 0,
69
72
  showScrollbar = true,
70
73
  emptyContent,
74
+ overscan = 3,
71
75
  }: ListViewProps<T>) {
72
- const { scrollProps, scrollToVisible } = useScroll({
76
+ const { state, scrollProps, scrollToVisible } = useScroll({
73
77
  enableKeyboard: false, // Parent handles keyboard for selection
74
78
  enableMouseWheel: true, // Free scroll with wheel
75
79
  })
@@ -98,14 +102,36 @@ export function ListView<T>({
98
102
  return <>{emptyContent}</>
99
103
  }
100
104
 
105
+ // Calculate visible range for virtualization
106
+ const totalHeight = items.length * itemHeight
107
+ const startIndex = Math.max(0, Math.floor(state.offset / itemHeight) - overscan)
108
+ const endIndex = Math.min(items.length, Math.ceil((state.offset + state.viewportSize) / itemHeight) + overscan)
109
+
110
+ // Calculate spacer heights for virtual scrolling
111
+ const topSpacerHeight = startIndex * itemHeight
112
+ const bottomSpacerHeight = Math.max(0, totalHeight - endIndex * itemHeight)
113
+
114
+ // Get visible slice of items
115
+ const visibleItems = items.slice(startIndex, endIndex)
116
+
101
117
  return (
102
118
  <scroll {...scrollProps} showScrollbar={showScrollbar}>
103
119
  <vstack>
104
- {items.map((item, index) => (
105
- <vstack key={keyExtractor(item, index)}>
106
- {renderItem(item, index, index === selectedIndex)}
107
- </vstack>
108
- ))}
120
+ {/* Virtual top spacer */}
121
+ {topSpacerHeight > 0 && <spacer height={topSpacerHeight} />}
122
+
123
+ {/* Render only visible items */}
124
+ {visibleItems.map((item, i) => {
125
+ const actualIndex = startIndex + i
126
+ return (
127
+ <vstack key={keyExtractor(item, actualIndex)}>
128
+ {renderItem(item, actualIndex, actualIndex === selectedIndex)}
129
+ </vstack>
130
+ )
131
+ })}
132
+
133
+ {/* Virtual bottom spacer */}
134
+ {bottomSpacerHeight > 0 && <spacer height={bottomSpacerHeight} />}
109
135
  </vstack>
110
136
  </scroll>
111
137
  )