lazy-render-virtual-scroll 1.0.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.
Files changed (73) hide show
  1. package/README.md +303 -0
  2. package/dist/cjs/adapters/react/LazyList.d.ts +12 -0
  3. package/dist/cjs/adapters/react/LazyList.d.ts.map +1 -0
  4. package/dist/cjs/adapters/react/useLazyList.d.ts +13 -0
  5. package/dist/cjs/adapters/react/useLazyList.d.ts.map +1 -0
  6. package/dist/cjs/core/Engine.d.ts +48 -0
  7. package/dist/cjs/core/Engine.d.ts.map +1 -0
  8. package/dist/cjs/core/PrefetchManager.d.ts +13 -0
  9. package/dist/cjs/core/PrefetchManager.d.ts.map +1 -0
  10. package/dist/cjs/core/RequestQueue.d.ts +23 -0
  11. package/dist/cjs/core/RequestQueue.d.ts.map +1 -0
  12. package/dist/cjs/core/WindowManager.d.ts +20 -0
  13. package/dist/cjs/core/WindowManager.d.ts.map +1 -0
  14. package/dist/cjs/core/types.d.ts +20 -0
  15. package/dist/cjs/core/types.d.ts.map +1 -0
  16. package/dist/cjs/index.d.ts +11 -0
  17. package/dist/cjs/index.d.ts.map +1 -0
  18. package/dist/cjs/index.js +435 -0
  19. package/dist/cjs/index.js.map +1 -0
  20. package/dist/cjs/platform/browser/ScrollObserver.d.ts +25 -0
  21. package/dist/cjs/platform/browser/ScrollObserver.d.ts.map +1 -0
  22. package/dist/cjs/utils/debounce.d.ts +5 -0
  23. package/dist/cjs/utils/debounce.d.ts.map +1 -0
  24. package/dist/cjs/utils/throttle.d.ts +5 -0
  25. package/dist/cjs/utils/throttle.d.ts.map +1 -0
  26. package/dist/esm/adapters/react/LazyList.d.ts +12 -0
  27. package/dist/esm/adapters/react/LazyList.d.ts.map +1 -0
  28. package/dist/esm/adapters/react/useLazyList.d.ts +13 -0
  29. package/dist/esm/adapters/react/useLazyList.d.ts.map +1 -0
  30. package/dist/esm/core/Engine.d.ts +48 -0
  31. package/dist/esm/core/Engine.d.ts.map +1 -0
  32. package/dist/esm/core/PrefetchManager.d.ts +13 -0
  33. package/dist/esm/core/PrefetchManager.d.ts.map +1 -0
  34. package/dist/esm/core/RequestQueue.d.ts +23 -0
  35. package/dist/esm/core/RequestQueue.d.ts.map +1 -0
  36. package/dist/esm/core/WindowManager.d.ts +20 -0
  37. package/dist/esm/core/WindowManager.d.ts.map +1 -0
  38. package/dist/esm/core/types.d.ts +20 -0
  39. package/dist/esm/core/types.d.ts.map +1 -0
  40. package/dist/esm/index.d.ts +11 -0
  41. package/dist/esm/index.d.ts.map +1 -0
  42. package/dist/esm/index.js +425 -0
  43. package/dist/esm/index.js.map +1 -0
  44. package/dist/esm/platform/browser/ScrollObserver.d.ts +25 -0
  45. package/dist/esm/platform/browser/ScrollObserver.d.ts.map +1 -0
  46. package/dist/esm/utils/debounce.d.ts +5 -0
  47. package/dist/esm/utils/debounce.d.ts.map +1 -0
  48. package/dist/esm/utils/throttle.d.ts +5 -0
  49. package/dist/esm/utils/throttle.d.ts.map +1 -0
  50. package/dist/index.d.ts +181 -0
  51. package/examples/chat-ui/Chat.jsx +158 -0
  52. package/examples/infinite-feed/Feed.jsx +97 -0
  53. package/examples/react-basic/App.jsx +64 -0
  54. package/package.json +55 -0
  55. package/rollup.config.js +39 -0
  56. package/src/adapters/react/LazyList.tsx +92 -0
  57. package/src/adapters/react/useLazyList.ts +87 -0
  58. package/src/core/Engine.ts +134 -0
  59. package/src/core/PrefetchManager.ts +22 -0
  60. package/src/core/RequestQueue.ts +69 -0
  61. package/src/core/WindowManager.ts +49 -0
  62. package/src/core/types.ts +24 -0
  63. package/src/index.ts +17 -0
  64. package/src/platform/browser/ScrollObserver.ts +86 -0
  65. package/src/utils/debounce.ts +19 -0
  66. package/src/utils/throttle.ts +19 -0
  67. package/test/engine.test.ts +136 -0
  68. package/test/prefetchManager.test.ts +99 -0
  69. package/test/reactAdapter.test.ts +26 -0
  70. package/test/requestQueue.test.ts +88 -0
  71. package/test/testRunner.ts +8 -0
  72. package/test/windowManager.test.ts +98 -0
  73. package/tsconfig.json +33 -0
@@ -0,0 +1,181 @@
1
+ import React from 'react';
2
+
3
+ interface EngineConfig {
4
+ itemHeight: number;
5
+ viewportHeight: number;
6
+ bufferSize?: number;
7
+ totalItems?: number;
8
+ }
9
+ interface VisibleRange {
10
+ start: number;
11
+ end: number;
12
+ }
13
+ interface FetchMoreCallback {
14
+ (): Promise<any>;
15
+ }
16
+ interface EngineState {
17
+ scrollTop: number;
18
+ visibleRange: VisibleRange;
19
+ loadedItems: number;
20
+ isLoading: boolean;
21
+ }
22
+
23
+ declare class Engine {
24
+ private config;
25
+ private windowManager;
26
+ private prefetchManager;
27
+ private requestQueue;
28
+ private state;
29
+ private fetchMoreCallback;
30
+ private totalItems;
31
+ constructor(config: EngineConfig);
32
+ /**
33
+ * Update scroll position and recalculate visible range
34
+ */
35
+ updateScrollPosition(scrollTop: number): void;
36
+ /**
37
+ * Get the current visible range
38
+ */
39
+ getVisibleRange(): VisibleRange;
40
+ /**
41
+ * Check if more items should be fetched
42
+ */
43
+ shouldFetchMore(): boolean;
44
+ /**
45
+ * Fetch more items
46
+ */
47
+ fetchMore(): Promise<void>;
48
+ /**
49
+ * Set the fetchMore callback function
50
+ */
51
+ setFetchMoreCallback(callback: FetchMoreCallback): void;
52
+ /**
53
+ * Update total items count
54
+ */
55
+ updateTotalItems(count: number): void;
56
+ /**
57
+ * Get current engine state
58
+ */
59
+ getState(): EngineState;
60
+ /**
61
+ * Update viewport dimensions
62
+ */
63
+ updateDimensions(viewportHeight: number, itemHeight: number): void;
64
+ /**
65
+ * Cleanup resources
66
+ */
67
+ cleanup(): void;
68
+ }
69
+
70
+ declare class WindowManager {
71
+ private itemHeight;
72
+ private viewportHeight;
73
+ private bufferSize;
74
+ constructor(itemHeight: number, viewportHeight: number, bufferSize?: number);
75
+ /**
76
+ * Calculate the visible range based on scroll position
77
+ */
78
+ calculateVisibleRange(scrollTop: number): VisibleRange;
79
+ /**
80
+ * Update viewport height if it changes
81
+ */
82
+ updateViewportHeight(height: number): void;
83
+ /**
84
+ * Update item height if it changes
85
+ */
86
+ updateItemHeight(height: number): void;
87
+ }
88
+
89
+ declare class PrefetchManager {
90
+ private bufferSize;
91
+ constructor(bufferSize?: number);
92
+ /**
93
+ * Determine if more items should be fetched based on visible range and loaded items
94
+ */
95
+ shouldPrefetch(visibleEnd: number, totalLoaded: number): boolean;
96
+ /**
97
+ * Update buffer size if it changes
98
+ */
99
+ updateBufferSize(size: number): void;
100
+ }
101
+
102
+ declare class RequestQueue {
103
+ private queue;
104
+ private processing;
105
+ private maxConcurrent;
106
+ constructor(maxConcurrent?: number);
107
+ /**
108
+ * Add a request to the queue
109
+ */
110
+ add(requestFn: () => Promise<any>): Promise<any>;
111
+ /**
112
+ * Process the queue
113
+ */
114
+ private processQueue;
115
+ /**
116
+ * Clear the queue
117
+ */
118
+ clear(): void;
119
+ /**
120
+ * Get the current queue length
121
+ */
122
+ getLength(): number;
123
+ }
124
+
125
+ declare class ScrollObserver {
126
+ private container;
127
+ private callback;
128
+ private options;
129
+ private observer;
130
+ private sentinelElement;
131
+ constructor(container: HTMLElement, callback: (scrollTop: number) => void, options?: Partial<IntersectionObserverInit>);
132
+ /**
133
+ * Start observing scroll events
134
+ */
135
+ observe(): void;
136
+ /**
137
+ * Handle scroll events
138
+ */
139
+ private onScroll;
140
+ /**
141
+ * Debounce function for scroll events
142
+ */
143
+ private debounce;
144
+ /**
145
+ * Disconnect observer and clean up
146
+ */
147
+ disconnect(): void;
148
+ }
149
+
150
+ interface LazyListConfig extends EngineConfig {
151
+ fetchMore: FetchMoreCallback;
152
+ }
153
+ declare const useLazyList: (config: LazyListConfig) => {
154
+ visibleRange: VisibleRange;
155
+ loadedItems: any[];
156
+ isLoading: boolean;
157
+ setContainerRef: (element: HTMLElement | null) => (() => void) | undefined;
158
+ refresh: () => void;
159
+ };
160
+
161
+ interface LazyListProps extends EngineConfig {
162
+ fetchMore: FetchMoreCallback;
163
+ renderItem: (item: any, index: number) => React.ReactNode;
164
+ items: any[];
165
+ className?: string;
166
+ style?: React.CSSProperties;
167
+ }
168
+ declare const LazyList: React.ForwardRefExoticComponent<LazyListProps & React.RefAttributes<HTMLDivElement>>;
169
+
170
+ /**
171
+ * Debounce function to limit the rate at which a function is called
172
+ */
173
+ declare function debounce<T extends (...args: any[]) => any>(func: T, wait: number): (...args: Parameters<T>) => void;
174
+
175
+ /**
176
+ * Throttle function to limit the rate at which a function is called
177
+ */
178
+ declare function throttle<T extends (...args: any[]) => any>(func: T, limit: number): (...args: Parameters<T>) => void;
179
+
180
+ export { Engine, LazyList, PrefetchManager, RequestQueue, ScrollObserver, WindowManager, debounce, throttle, useLazyList };
181
+ export type { EngineConfig, EngineState, FetchMoreCallback, VisibleRange };
@@ -0,0 +1,158 @@
1
+ import React, { useState, useRef } from 'react';
2
+ import { LazyList, useLazyList } from 'lazy-render';
3
+
4
+ const ChatUI = () => {
5
+ const [messages, setMessages] = useState([]);
6
+ const [inputText, setInputText] = useState('');
7
+ const [userId] = useState('user_' + Math.random().toString(36).substr(2, 9));
8
+ const messagesEndRef = useRef(null);
9
+
10
+ // Initialize with some messages
11
+ React.useEffect(() => {
12
+ const initialMessages = Array.from({ length: 100 }, (_, i) => ({
13
+ id: i,
14
+ text: `Initial message ${i} - Lorem ipsum dolor sit amet, consectetur adipiscing elit.`,
15
+ sender: i % 3 === 0 ? 'other' : 'self',
16
+ timestamp: new Date(Date.now() - (100 - i) * 60000).toLocaleTimeString(),
17
+ avatar: `https://i.pravatar.cc/32?img=${i % 5 + 1}`
18
+ }));
19
+ setMessages(initialMessages);
20
+ }, []);
21
+
22
+ const sendMessage = () => {
23
+ if (inputText.trim() === '') return;
24
+
25
+ const newMessage = {
26
+ id: messages.length,
27
+ text: inputText,
28
+ sender: 'self',
29
+ timestamp: new Date().toLocaleTimeString(),
30
+ avatar: `https://i.pravatar.cc/32?img=6`
31
+ };
32
+
33
+ setMessages(prev => [...prev, newMessage]);
34
+ setInputText('');
35
+ };
36
+
37
+ const handleKeyPress = (e) => {
38
+ if (e.key === 'Enter' && !e.shiftKey) {
39
+ e.preventDefault();
40
+ sendMessage();
41
+ }
42
+ };
43
+
44
+ // Custom hook usage for advanced control
45
+ const {
46
+ visibleRange,
47
+ setContainerRef,
48
+ scrollToIndex
49
+ } = useLazyList({
50
+ itemHeight: 80, // Average message height
51
+ viewportHeight: 500,
52
+ bufferSize: 5,
53
+ overscan: 3,
54
+ fetchMore: async () => {
55
+ // In a real app, this would fetch older messages
56
+ return [];
57
+ }
58
+ });
59
+
60
+ const renderMessage = (msg, index) => (
61
+ <div
62
+ key={msg.id}
63
+ style={{
64
+ minHeight: '60px',
65
+ borderBottom: '1px solid #eee',
66
+ padding: '12px 16px',
67
+ display: 'flex',
68
+ alignItems: 'flex-start',
69
+ backgroundColor: msg.sender === 'self' ? '#e3f2fd' : '#fff'
70
+ }}
71
+ >
72
+ <img
73
+ src={msg.avatar}
74
+ alt="Avatar"
75
+ style={{
76
+ width: '32px',
77
+ height: '32px',
78
+ borderRadius: '50%',
79
+ marginRight: '12px',
80
+ flexShrink: 0
81
+ }}
82
+ />
83
+ <div style={{ flex: 1 }}>
84
+ <div style={{ fontWeight: 'bold', fontSize: '12px', marginBottom: '4px' }}>
85
+ {msg.sender === 'self' ? 'You' : 'Other User'} • {msg.timestamp}
86
+ </div>
87
+ <div style={{ fontSize: '14px' }}>
88
+ {msg.text}
89
+ </div>
90
+ </div>
91
+ </div>
92
+ );
93
+
94
+ return (
95
+ <div style={{ width: '100%', maxWidth: '600px', margin: '0 auto', height: '100vh', display: 'flex', flexDirection: 'column' }}>
96
+ <h1 style={{ textAlign: 'center', margin: '10px 0' }}>Chat UI Example</h1>
97
+
98
+ {/* Messages container */}
99
+ <div
100
+ ref={setContainerRef}
101
+ style={{
102
+ flex: 1,
103
+ overflowY: 'auto',
104
+ border: '1px solid #ddd',
105
+ borderRadius: '8px',
106
+ marginBottom: '10px'
107
+ }}
108
+ >
109
+ <div style={{ height: `${visibleRange.start * 80}px` }} />
110
+
111
+ {messages
112
+ .slice(visibleRange.start, visibleRange.end)
113
+ .map((msg, idx) => renderMessage(msg, visibleRange.start + idx))}
114
+
115
+ <div style={{ height: `${Math.max(0, (messages.length - visibleRange.end) * 80)}px` }} />
116
+ </div>
117
+
118
+ {/* Input area */}
119
+ <div style={{ display: 'flex', gap: '8px', padding: '8px' }}>
120
+ <textarea
121
+ value={inputText}
122
+ onChange={(e) => setInputText(e.target.value)}
123
+ onKeyPress={handleKeyPress}
124
+ placeholder="Type a message..."
125
+ style={{
126
+ flex: 1,
127
+ padding: '10px',
128
+ border: '1px solid #ddd',
129
+ borderRadius: '4px',
130
+ resize: 'none',
131
+ minHeight: '40px',
132
+ maxHeight: '100px'
133
+ }}
134
+ />
135
+ <button
136
+ onClick={sendMessage}
137
+ disabled={!inputText.trim()}
138
+ style={{
139
+ padding: '10px 16px',
140
+ backgroundColor: inputText.trim() ? '#2196f3' : '#ccc',
141
+ color: 'white',
142
+ border: 'none',
143
+ borderRadius: '4px',
144
+ cursor: inputText.trim() ? 'pointer' : 'not-allowed'
145
+ }}
146
+ >
147
+ Send
148
+ </button>
149
+ </div>
150
+
151
+ <div style={{ fontSize: '12px', textAlign: 'center', color: '#666' }}>
152
+ Showing {visibleRange.start}-{visibleRange.end} of {messages.length} messages
153
+ </div>
154
+ </div>
155
+ );
156
+ };
157
+
158
+ export default ChatUI;
@@ -0,0 +1,97 @@
1
+ import React, { useState } from 'react';
2
+ import { LazyList } from 'lazy-render';
3
+
4
+ const InfiniteFeed = () => {
5
+ const [posts, setPosts] = useState([]);
6
+ const [page, setPage] = useState(1);
7
+ const [hasMore, setHasMore] = useState(true);
8
+ const [loading, setLoading] = useState(false);
9
+
10
+ const fetchMorePosts = async () => {
11
+ if (loading || !hasMore) return;
12
+
13
+ setLoading(true);
14
+
15
+ // Simulate API call
16
+ try {
17
+ await new Promise(resolve => setTimeout(resolve, 800)); // Simulate network delay
18
+
19
+ // Generate new posts
20
+ const newPosts = Array.from({ length: 10 }, (_, i) => ({
21
+ id: (page * 10) + i,
22
+ title: `Post Title ${page * 10 + i}`,
23
+ content: `This is the content for post number ${page * 10 + i}. It contains some interesting information.`,
24
+ author: `Author ${(page * 10) + i % 5}`,
25
+ timestamp: new Date(Date.now() - Math.random() * 10000000000).toISOString(),
26
+ likes: Math.floor(Math.random() * 100),
27
+ comments: Math.floor(Math.random() * 50)
28
+ }));
29
+
30
+ setPosts(prev => [...prev, ...newPosts]);
31
+ setPage(prev => prev + 1);
32
+
33
+ // Stop after 100 posts for demo purposes
34
+ if (page >= 10) {
35
+ setHasMore(false);
36
+ }
37
+ } catch (error) {
38
+ console.error('Error fetching posts:', error);
39
+ } finally {
40
+ setLoading(false);
41
+ }
42
+
43
+ return newPosts;
44
+ };
45
+
46
+ const renderPost = (post, index) => (
47
+ <div
48
+ key={post.id}
49
+ style={{
50
+ height: '120px',
51
+ borderBottom: '1px solid #eee',
52
+ padding: '16px',
53
+ backgroundColor: '#fafafa'
54
+ }}
55
+ >
56
+ <h3>{post.title}</h3>
57
+ <p style={{ fontSize: '14px', color: '#666', margin: '4px 0' }}>
58
+ by {post.author} • {post.timestamp.substring(0, 10)}
59
+ </p>
60
+ <p style={{ margin: '8px 0' }}>{post.content}</p>
61
+ <div style={{ fontSize: '12px', color: '#888' }}>
62
+ {post.likes} likes • {post.comments} comments
63
+ </div>
64
+ </div>
65
+ );
66
+
67
+ return (
68
+ <div style={{ width: '100%', maxWidth: '600px', margin: '0 auto' }}>
69
+ <h1>Infinite Feed Example</h1>
70
+ <p>{posts.length} posts loaded</p>
71
+
72
+ <LazyList
73
+ items={posts}
74
+ itemHeight={120}
75
+ viewportHeight={500}
76
+ fetchMore={fetchMorePosts}
77
+ renderItem={renderPost}
78
+ bufferSize={3}
79
+ overscan={2}
80
+ />
81
+
82
+ {loading && (
83
+ <div style={{ textAlign: 'center', padding: '20px', color: '#666' }}>
84
+ Loading more posts...
85
+ </div>
86
+ )}
87
+
88
+ {!hasMore && (
89
+ <div style={{ textAlign: 'center', padding: '20px', color: '#666' }}>
90
+ You've reached the end!
91
+ </div>
92
+ )}
93
+ </div>
94
+ );
95
+ };
96
+
97
+ export default InfiniteFeed;
@@ -0,0 +1,64 @@
1
+ import React, { useState } from 'react';
2
+ import { LazyList } from 'lazy-render';
3
+
4
+ const App = () => {
5
+ // Generate 10000 sample items
6
+ const [items] = useState(() =>
7
+ Array.from({ length: 10000 }, (_, i) => ({
8
+ id: i,
9
+ text: `Item ${i}`,
10
+ timestamp: new Date(Date.now() - Math.random() * 10000000000).toISOString()
11
+ }))
12
+ );
13
+
14
+ const [loadedItems, setLoadedItems] = useState(items.slice(0, 100)); // Initially load 100 items
15
+ const [hasMore, setHasMore] = useState(true);
16
+
17
+ const fetchMore = async () => {
18
+ if (loadedItems.length >= items.length) {
19
+ setHasMore(false);
20
+ return [];
21
+ }
22
+
23
+ // Simulate API delay
24
+ await new Promise(resolve => setTimeout(resolve, 500));
25
+
26
+ const nextBatch = items.slice(loadedItems.length, loadedItems.length + 50);
27
+ setLoadedItems(prev => [...prev, ...nextBatch]);
28
+ return nextBatch;
29
+ };
30
+
31
+ const renderItem = (item, index) => (
32
+ <div
33
+ key={item.id}
34
+ style={{
35
+ height: '60px',
36
+ borderBottom: '1px solid #eee',
37
+ display: 'flex',
38
+ alignItems: 'center',
39
+ paddingLeft: '16px'
40
+ }}
41
+ >
42
+ {item.text} - {item.timestamp.substring(0, 10)}
43
+ </div>
44
+ );
45
+
46
+ return (
47
+ <div style={{ width: '100%', height: '100vh' }}>
48
+ <h1>Basic Lazy Render Example</h1>
49
+ <p>Loading {loadedItems.length} of {items.length} items</p>
50
+
51
+ <LazyList
52
+ items={loadedItems}
53
+ itemHeight={60}
54
+ viewportHeight={400}
55
+ fetchMore={fetchMore}
56
+ renderItem={renderItem}
57
+ bufferSize={5}
58
+ overscan={2}
59
+ />
60
+ </div>
61
+ );
62
+ };
63
+
64
+ export default App;
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "lazy-render-virtual-scroll",
3
+ "version": "1.0.1",
4
+ "description": "A framework-agnostic virtual scrolling and lazy rendering solution",
5
+ "main": "dist/cjs/index.js",
6
+ "module": "dist/esm/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "type": "module",
9
+ "scripts": {
10
+ "build": "rollup -c",
11
+ "dev": "rollup -c -w",
12
+ "test": "npx tsx test/testRunner.ts",
13
+ "test:verbose": "npx tsx test/testRunner.ts",
14
+ "lint": "eslint src --ext .ts,.tsx",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "keywords": [
18
+ "virtual-scrolling",
19
+ "lazy-rendering",
20
+ "performance",
21
+ "react",
22
+ "vue",
23
+ "angular",
24
+ "scrolling",
25
+ "infinite-scroll",
26
+ "pagination"
27
+ ],
28
+ "author": "Sannu",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/sannuk79/lezzyrender.git"
33
+ },
34
+ "bugs": {
35
+ "url": "https://github.com/sannuk79/lezzyrender/issues"
36
+ },
37
+ "homepage": "https://github.com/sannuk79/lezzyrender#readme",
38
+ "peerDependencies": {
39
+ "react": "^18.3.1",
40
+ "react-dom": "^18.3.1"
41
+ },
42
+ "devDependencies": {
43
+ "@rollup/plugin-node-resolve": "^15.0.0",
44
+ "@rollup/plugin-typescript": "^11.0.0",
45
+ "@types/react": "^18.3.28",
46
+ "@types/react-dom": "^18.3.7",
47
+ "rollup": "^3.0.0",
48
+ "rollup-plugin-dts": "^6.0.0",
49
+ "tslib": "^2.4.0",
50
+ "typescript": "^5.0.0"
51
+ },
52
+ "dependencies": {
53
+ "tsx": "^4.0.0"
54
+ }
55
+ }
@@ -0,0 +1,39 @@
1
+ import resolve from '@rollup/plugin-node-resolve';
2
+ import typescript from '@rollup/plugin-typescript';
3
+ import dts from 'rollup-plugin-dts';
4
+ import fs from 'fs';
5
+
6
+ const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
7
+
8
+ export default [
9
+ {
10
+ input: 'src/index.ts',
11
+ output: [
12
+ {
13
+ file: packageJson.main,
14
+ format: 'cjs',
15
+ sourcemap: true
16
+ },
17
+ {
18
+ file: packageJson.module,
19
+ format: 'esm',
20
+ sourcemap: true
21
+ }
22
+ ],
23
+ plugins: [
24
+ resolve({
25
+ browser: true,
26
+ }),
27
+ typescript({
28
+ tsconfig: './tsconfig.json',
29
+ jsx: 'react' // Use classic JSX transform
30
+ })
31
+ ],
32
+ external: ['react', 'react-dom']
33
+ },
34
+ {
35
+ input: 'src/index.ts',
36
+ output: [{ file: 'dist/index.d.ts', format: 'es' }],
37
+ plugins: [dts()],
38
+ }
39
+ ];
@@ -0,0 +1,92 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { useLazyList } from './useLazyList';
3
+ import { EngineConfig, FetchMoreCallback } from '../../core/types';
4
+
5
+ interface LazyListProps extends EngineConfig {
6
+ fetchMore: FetchMoreCallback;
7
+ renderItem: (item: any, index: number) => React.ReactNode;
8
+ items: any[];
9
+ className?: string;
10
+ style?: React.CSSProperties;
11
+ }
12
+
13
+ export const LazyList = forwardRef<HTMLDivElement, LazyListProps>((props, ref) => {
14
+ const {
15
+ fetchMore,
16
+ renderItem,
17
+ items,
18
+ itemHeight,
19
+ viewportHeight,
20
+ bufferSize,
21
+ className = '',
22
+ style = {},
23
+ ...rest
24
+ } = props;
25
+
26
+ const { visibleRange, setContainerRef, isLoading } = useLazyList({
27
+ fetchMore,
28
+ itemHeight,
29
+ viewportHeight,
30
+ bufferSize,
31
+ ...rest
32
+ });
33
+
34
+ // Calculate container height to simulate infinite scroll
35
+ const containerHeight = items.length * itemHeight;
36
+ const visibleItems = items.slice(visibleRange.start, visibleRange.end);
37
+
38
+ // Calculate top padding to maintain scroll position
39
+ const paddingTop = visibleRange.start * itemHeight;
40
+
41
+ return (
42
+ <div
43
+ ref={(el) => {
44
+ setContainerRef(el);
45
+ if (ref) {
46
+ if (typeof ref === 'function') {
47
+ ref(el);
48
+ } else {
49
+ ref.current = el;
50
+ }
51
+ }
52
+ }}
53
+ className={`lazy-list ${className}`}
54
+ style={{
55
+ height: `${viewportHeight}px`,
56
+ overflowY: 'auto',
57
+ ...style
58
+ }}
59
+ {...rest}
60
+ >
61
+ {/* Top padding to maintain scroll position */}
62
+ <div style={{ height: `${paddingTop}px` }} />
63
+
64
+ {/* Visible items */}
65
+ {visibleItems.map((item, index) => (
66
+ <div
67
+ key={visibleRange.start + index}
68
+ style={{ height: `${itemHeight}px` }}
69
+ className="lazy-item"
70
+ >
71
+ {renderItem(item, visibleRange.start + index)}
72
+ </div>
73
+ ))}
74
+
75
+ {/* Bottom padding */}
76
+ <div
77
+ style={{
78
+ height: `${Math.max(0, containerHeight - (visibleRange.end * itemHeight))}px`
79
+ }}
80
+ />
81
+
82
+ {/* Loading indicator */}
83
+ {isLoading && (
84
+ <div className="lazy-loading">
85
+ Loading more items...
86
+ </div>
87
+ )}
88
+ </div>
89
+ );
90
+ });
91
+
92
+ LazyList.displayName = 'LazyList';